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:
@@ -25,3 +25,5 @@ ssl/
|
|||||||
job-scheduler-state.json
|
job-scheduler-state.json
|
||||||
var/
|
var/
|
||||||
swarms.json
|
swarms.json
|
||||||
|
subagent-sessions.json
|
||||||
|
conversation-nudge-tracker.json
|
||||||
|
|||||||
@@ -140,19 +140,23 @@ veth IPs aren't routable off the host and Warp forwards no inbound traffic.
|
|||||||
- `bin/accounts-health.py` — per-account CDP session probe (runs inside the netns).
|
- `bin/accounts-health.py` — per-account CDP session probe (runs inside the netns).
|
||||||
- `bin/accounts-health.sh` — aggregates account vitality from ACCOUNTS.md,
|
- `bin/accounts-health.sh` — aggregates account vitality from ACCOUNTS.md,
|
||||||
signs + POSTs to the board health ingest (systemd timer, every 15 min).
|
signs + POSTs to the board health ingest (systemd timer, every 15 min).
|
||||||
- `bin/muse -a <account> [args]` — interactive terminal entrypoint for muse-cli; enforces account selection, auto-refreshes CDP cookies, and provides interactive email/OTP prompt fallback.
|
- `bin/muse` (or `box muse`) — native terminal entrypoint for muse-cli; supports global lookups (`muse status`, `muse threads`, `muse unread`, `muse lookup`, `muse passkey`, `muse tmux`), per-account REPL chats (`muse <account> chat`), and automated CDP cookie extraction.
|
||||||
|
- `bin/super-cli.py` (`box` or `super`) — unified orchestrator CLI for fleet health (`box fleet`), seamless lookups (`box lookup`), thread inspections (`box thread list/view`), background tmux (`box tmux`), and passkey reference (`box passkey`).
|
||||||
- `bin/muse-cli-node <node> [args]` — runs muse-cli inside node's netns with dedicated Cloudflare WARP egress & auto-refreshing cookies.
|
- `bin/muse-cli-node <node> [args]` — runs muse-cli inside node's netns with dedicated Cloudflare WARP egress & auto-refreshing cookies.
|
||||||
- `bin/refresh-node-cookies.py <node>` — extracts fresh cookies from running Chromium CDP in netns into `~/.config/muse-cli/<node>/cookies.txt`.
|
- `bin/refresh-node-cookies.py <node>` — extracts fresh cookies from running Chromium CDP in netns into `~/.config/muse-cli/<node>/cookies.txt`.
|
||||||
- `bin/muse_hybrid.py` — programmatic hybrid bridge combining fast gateway calls with CDP fallbacks.
|
- `bin/muse_hybrid.py` — programmatic hybrid bridge combining fast gateway calls with CDP fallbacks.
|
||||||
- `bin/muse-tmux.py` — shared tmux socket manager (`/tmp/tmux-muse.sock`) for agent background execution, pipe-pane logging, and 2h session pruning.
|
- `bin/muse-tmux.py` (`box tmux` / `muse tmux`) — shared tmux socket manager (`/tmp/tmux-muse.sock`) for agent background execution, pipe-pane logging, hybrid netns/container execution, and session pruning.
|
||||||
- `bin/agent_md.py` — CLI & library for auditing, reading, writing, and synchronizing agent `.md` drive files (`SOUL.md`, `PROACTIVE_PREFERENCES.md`, `HEARTBEAT.md`, etc.) across containers via Hatch WebSocket RPC.
|
- `bin/agent_md.py` — CLI & library for auditing, reading, writing, and synchronizing agent `.md` drive files (`SOUL.md`, `PROACTIVE_PREFERENCES.md`, `HEARTBEAT.md`, etc.) across containers via Hatch WebSocket RPC.
|
||||||
- `bin/agent-drive-watchdog.py` — background drive watchdog and auto-healing daemon (every 10m via `agent-drive-watchdog.timer`).
|
- `bin/agent-drive-watchdog.py` — background drive watchdog and auto-healing daemon (every 10m via `agent-drive-watchdog.timer`).
|
||||||
- `bin/swarm_worker/` & `bin/swarm-worker-supervise.sh` — supervised autonomous swarm worker daemon executing queued tasks in a hard sandbox.
|
- `bin/swarm_worker/` & `bin/swarm-worker-supervise.sh` — supervised autonomous swarm worker daemon executing queued tasks in a hard sandbox.
|
||||||
- `bin/fleet-alert-relay.sh` — idempotent alert relay with 3-gate deduplication (watermark + 10m TTL hash + receipt verification) posting critical conditions to `#lobby`.
|
- `bin/fleet-alert-relay.sh` — idempotent alert relay with 3-gate deduplication (watermark + 10m TTL hash + receipt verification) posting critical conditions to `#lobby`.
|
||||||
- `shared/operators/` — canonical operator drive markdown templates ensuring agents maintain autonomous loops, active supervision, and self-healing reflexes.
|
- `shared/operators/` — canonical operator drive markdown templates ensuring agents maintain autonomous loops, active supervision, and self-healing reflexes.
|
||||||
- docs/OPERATOR-DRIVE-RUNBOOK.md — operator runbook for auditing and modifying agent `.md` files via Hatch WebSocket RPC and SSH reverse tunnels.
|
- docs/OPERATOR-DRIVE-RUNBOOK.md — operator runbook for auditing and modifying agent `.md` files via Hatch WebSocket RPC and SSH reverse tunnels.
|
||||||
|
- docs/BOX-WEB-SURFACE-GUIDE.md — Box web surface (`box.muse-dev.online`), passkey reality (single .txt file on VM), and agent approval flow.
|
||||||
- docs/HYBRID-GATEWAY-ADAPTATION.md — architectural guide on the muse-cli fast gateway adaptation and per-node egress isolation.
|
- docs/HYBRID-GATEWAY-ADAPTATION.md — architectural guide on the muse-cli fast gateway adaptation and per-node egress isolation.
|
||||||
- docs/AGENT-TOOLING.md — guide to agent delegation, shared tmux background tooling, Work Orders (`[WO:...]`), and prompt envelope execution.
|
- docs/AGENT-TOOLING.md — guide to agent delegation, seamless lookups, shared tmux background tooling, Work Orders (`[WO:...]`), and prompt envelope execution.
|
||||||
|
- `bin/docs-lookup.py` (`box docs` / `super docs` / `docs-lookup`) — internal documentation, sentence structure grammar, regex passing engine, and assistive surfaces for `box.muse-dev.online`.
|
||||||
|
- `docs_internal/` — dual `.md` and structured `.json` lookup database for autonomous agents, protocol definitions, regex fixtures, and surface selectors.
|
||||||
|
|
||||||
|
|
||||||
## Verification checklist
|
## Verification checklist
|
||||||
|
|||||||
+430
-40
@@ -48,11 +48,55 @@ TRUSTED_IPS = {
|
|||||||
"34.139.37.135", # VM (gateway)
|
"34.139.37.135", # VM (gateway)
|
||||||
"100.123.153.75", # bl (main compute)
|
"100.123.153.75", # bl (main compute)
|
||||||
"100.81.31.9", # VM tailnet
|
"100.81.31.9", # VM tailnet
|
||||||
|
"1.1.1.1", # Cloudflare DNS
|
||||||
|
"1.0.0.1", # Cloudflare DNS
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trusted infrastructure domains safe for automated approval
|
||||||
|
TRUSTED_DOMAINS = {
|
||||||
|
"muse-dev.online",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def is_trusted_target(target: str, card_text: str = "") -> bool:
|
||||||
|
"""Check if the extracted approval target is trusted infrastructure.
|
||||||
|
|
||||||
|
Fail-closed: only the parsed target (IP or hostname) is evaluated. Free-form
|
||||||
|
card text is deliberately NOT substring-matched, since an untrusted request
|
||||||
|
could mention a trusted domain in its purpose string. `card_text` is kept
|
||||||
|
for signature compatibility.
|
||||||
|
"""
|
||||||
|
if not target:
|
||||||
|
return False
|
||||||
|
target = target.strip().lower().rstrip(".")
|
||||||
|
if target in TRUSTED_IPS:
|
||||||
|
return True
|
||||||
|
for dom in TRUSTED_DOMAINS:
|
||||||
|
if target == dom or target.endswith("." + dom):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
REDACT_PATTERNS = [
|
||||||
|
(re.compile(r"Bearer\s+[A-Za-z0-9._~+/-]+=*", re.IGNORECASE), "Bearer [REDACTED]"),
|
||||||
|
(re.compile(r"eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}", re.IGNORECASE), "[JWT-REDACTED]"),
|
||||||
|
(re.compile(r"(?i)\b(api[_-]?key|token|secret|password|auth|passkey)\s*[:=]\s*(['\"]?)([A-Za-z0-9_\-\.]{4,})\2"), r"\1: \2[REDACTED]\2"),
|
||||||
|
(re.compile(r"-----BEGIN [A-Z ]+ PRIVATE KEY-----[\s\S]*?-----END [A-Z ]+ PRIVATE KEY-----"), "[PRIVATE-KEY-REDACTED]"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def redact_sensitive(text: str) -> str:
|
||||||
|
"""Mask credentials, tokens, and passkeys in text."""
|
||||||
|
if not text or not isinstance(text, str):
|
||||||
|
return text
|
||||||
|
out = text
|
||||||
|
for pattern, repl in REDACT_PATTERNS:
|
||||||
|
out = pattern.sub(repl, out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def log_box_ctl(action: str, name: str = None, caller: str = "box-approvals", extra: dict = None):
|
def log_box_ctl(action: str, name: str = None, caller: str = "box-approvals", extra: dict = None):
|
||||||
"""Log an audit event to box-ctl.jsonl."""
|
"""Log an audit event to box-ctl.jsonl with secret redaction."""
|
||||||
try:
|
try:
|
||||||
rec = {
|
rec = {
|
||||||
"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||||
@@ -61,13 +105,76 @@ def log_box_ctl(action: str, name: str = None, caller: str = "box-approvals", ex
|
|||||||
"caller": caller,
|
"caller": caller,
|
||||||
}
|
}
|
||||||
if extra:
|
if extra:
|
||||||
rec.update(extra)
|
clean_extra = {}
|
||||||
|
for k, v in extra.items():
|
||||||
|
if isinstance(v, str):
|
||||||
|
clean_extra[k] = redact_sensitive(v)
|
||||||
|
else:
|
||||||
|
clean_extra[k] = v
|
||||||
|
rec.update(clean_extra)
|
||||||
with open(CTL_LOG, "a") as f:
|
with open(CTL_LOG, "a") as f:
|
||||||
f.write(json.dumps(rec) + "\n")
|
f.write(json.dumps(rec) + "\n")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def request_key_approval(node: str, reason: str = "", caller: str = "agent") -> dict:
|
||||||
|
"""Register a key/passkey approval request for a node in box-ctl.jsonl."""
|
||||||
|
reason = reason or "Operator passkey access requested"
|
||||||
|
log_box_ctl(
|
||||||
|
"key-approval-request",
|
||||||
|
name=node,
|
||||||
|
caller=caller,
|
||||||
|
extra={"reason": reason},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"node": node,
|
||||||
|
"status": "KEY_APPROVAL_REQUESTED",
|
||||||
|
"reason": reason,
|
||||||
|
"caller": caller,
|
||||||
|
"note": "Request recorded in audit log. Operator can approve via 'box approvals allow <node>'."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_node_key_request(node: str) -> dict:
|
||||||
|
"""Check if node has an active unfulfilled key approval request in box-ctl.jsonl."""
|
||||||
|
if not CTL_LOG.exists():
|
||||||
|
return None
|
||||||
|
latest_req = None
|
||||||
|
resolved = False
|
||||||
|
try:
|
||||||
|
with open(CTL_LOG, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
rec = json.loads(line)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if rec.get("name") != node:
|
||||||
|
continue
|
||||||
|
act = rec.get("action")
|
||||||
|
if act == "key-approval-request":
|
||||||
|
latest_req = rec
|
||||||
|
resolved = False
|
||||||
|
elif act in ("key-approval-allow", "key-approval-deny", "approval-allow", "approval-deny"):
|
||||||
|
resolved = True
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if latest_req and not resolved:
|
||||||
|
return {
|
||||||
|
"node": node,
|
||||||
|
"reason": latest_req.get("reason", "Operator passkey access requested"),
|
||||||
|
"requested_at": latest_req.get("ts", ""),
|
||||||
|
"caller": latest_req.get("caller", ""),
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_node_connection_info(node: str) -> dict:
|
def get_node_connection_info(node: str) -> dict:
|
||||||
"""Return peer_ip, cdp_port, and netns for a given node."""
|
"""Return peer_ip, cdp_port, and netns for a given node."""
|
||||||
if netvm_registry:
|
if netvm_registry:
|
||||||
@@ -94,8 +201,8 @@ def get_node_connection_info(node: str) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_cdp_ws(node: str, timeout: float = 3.0):
|
def get_node_pages(node: str, timeout: float = 3.0) -> list:
|
||||||
"""Connect to the node's active browser page over CDP WebSocket."""
|
"""Return all active page targets for a node."""
|
||||||
if websocket is None:
|
if websocket is None:
|
||||||
raise RuntimeError("websocket-client library is required")
|
raise RuntimeError("websocket-client library is required")
|
||||||
|
|
||||||
@@ -125,13 +232,20 @@ def get_cdp_ws(node: str, timeout: float = 3.0):
|
|||||||
pages = [t for t in tabs if t.get("type") == "page"]
|
pages = [t for t in tabs if t.get("type") == "page"]
|
||||||
if not pages:
|
if not pages:
|
||||||
raise ConnectionError(f"No active page found for node {node}")
|
raise ConnectionError(f"No active page found for node {node}")
|
||||||
|
return pages
|
||||||
|
|
||||||
ws_url = pages[0].get("webSocketDebuggerUrl")
|
|
||||||
|
def get_cdp_ws(node: str, page_idx: int = 0, timeout: float = 3.0):
|
||||||
|
"""Connect to a node's browser page over CDP WebSocket."""
|
||||||
|
pages = get_node_pages(node, timeout=timeout)
|
||||||
|
if page_idx >= len(pages):
|
||||||
|
page_idx = 0
|
||||||
|
target_page = pages[page_idx]
|
||||||
|
ws_url = target_page.get("webSocketDebuggerUrl")
|
||||||
if not ws_url:
|
if not ws_url:
|
||||||
raise ConnectionError(f"No webSocketDebuggerUrl for node {node}")
|
raise ConnectionError(f"No webSocketDebuggerUrl for node {node}")
|
||||||
|
|
||||||
ws = websocket.create_connection(ws_url, timeout=timeout)
|
ws = websocket.create_connection(ws_url, timeout=timeout)
|
||||||
return ws, pages[0]
|
return ws, target_page
|
||||||
|
|
||||||
|
|
||||||
def cdp_evaluate(ws, js_expr: str, await_promise: bool = False, timeout: float = 3.0):
|
def cdp_evaluate(ws, js_expr: str, await_promise: bool = False, timeout: float = 3.0):
|
||||||
@@ -233,6 +347,16 @@ JS_INSPECT_APPROVALS = """(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Task rows in the Activity sidebar waiting on human input
|
||||||
|
// (e.g. "Launch 5 OPM Sub-Agents\\nAsked for input to start the launch\\n5:53 pm").
|
||||||
|
const inputWaits = [];
|
||||||
|
for (const b of document.querySelectorAll('button.rounded-10, a.rounded-10')) {
|
||||||
|
const lines = (b.innerText || '').split('\\n').map(s => s.trim()).filter(Boolean);
|
||||||
|
if (lines.length >= 2 && /^(asked for (input|details)|waiting for (your )?(input|reply|approval)|needs your input)/i.test(lines[1])) {
|
||||||
|
inputWaits.push({ task: lines[0], status: lines[1], when: lines[2] || '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
has_pending: !!activeCard && (hasAllowOnce || hasDeny),
|
has_pending: !!activeCard && (hasAllowOnce || hasDeny),
|
||||||
card_text: cardText.slice(0, 1000),
|
card_text: cardText.slice(0, 1000),
|
||||||
@@ -240,16 +364,40 @@ JS_INSPECT_APPROVALS = """(() => {
|
|||||||
has_allow_once: hasAllowOnce,
|
has_allow_once: hasAllowOnce,
|
||||||
has_always_allow: hasAlwaysAllow,
|
has_always_allow: hasAlwaysAllow,
|
||||||
has_deny: hasDeny,
|
has_deny: hasDeny,
|
||||||
history: historyBadges.slice(0, 5)
|
history: historyBadges.slice(0, 5),
|
||||||
|
input_waits: inputWaits.slice(0, 10)
|
||||||
});
|
});
|
||||||
})()"""
|
})()"""
|
||||||
|
|
||||||
|
|
||||||
def inspect_node_approvals(node: str) -> dict:
|
def inspect_node_approvals(node: str) -> dict:
|
||||||
"""Inspect a node for active browser approval prompts."""
|
"""Inspect a node across all open page targets for active approval prompts and input waits."""
|
||||||
try:
|
try:
|
||||||
ws, page = get_cdp_ws(node, timeout=2.5)
|
pages = get_node_pages(node, timeout=2.5)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
key_req = check_node_key_request(node)
|
||||||
|
if key_req:
|
||||||
|
return {
|
||||||
|
"node": node,
|
||||||
|
"status": "KEY_APPROVAL",
|
||||||
|
"has_pending": True,
|
||||||
|
"title": f"Passkey requested: {key_req.get('reason')}",
|
||||||
|
"purpose": key_req.get("reason"),
|
||||||
|
"ip": "34.139.37.135",
|
||||||
|
"target": "34.139.37.135 (VM passkey)",
|
||||||
|
"is_trusted": True,
|
||||||
|
"buttons": ["Allow", "Deny"],
|
||||||
|
"has_allow_once": True,
|
||||||
|
"has_always_allow": False,
|
||||||
|
"has_deny": True,
|
||||||
|
"raw_text": f"Agent on node {node} requested passkey: {key_req.get('reason')}",
|
||||||
|
"history": [],
|
||||||
|
"input_waits": [],
|
||||||
|
"page_title": "",
|
||||||
|
"page_url": "",
|
||||||
|
"ws_url": "",
|
||||||
|
"key_request": key_req,
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
"node": node,
|
"node": node,
|
||||||
"status": "UNREACHABLE",
|
"status": "UNREACHABLE",
|
||||||
@@ -257,64 +405,138 @@ def inspect_node_approvals(node: str) -> dict:
|
|||||||
"has_pending": False,
|
"has_pending": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
all_input_waits = []
|
||||||
|
first_page = pages[0]
|
||||||
|
last_err = None
|
||||||
|
|
||||||
|
for page in pages:
|
||||||
|
ws_url = page.get("webSocketDebuggerUrl")
|
||||||
|
if not ws_url:
|
||||||
|
continue
|
||||||
|
ws = None
|
||||||
try:
|
try:
|
||||||
val_str = cdp_evaluate(ws, JS_INSPECT_APPROVALS, timeout=3.0)
|
ws = websocket.create_connection(ws_url, timeout=2.0)
|
||||||
|
val_str = cdp_evaluate(ws, JS_INSPECT_APPROVALS, timeout=2.5)
|
||||||
ws.close()
|
ws.close()
|
||||||
|
ws = None
|
||||||
if not val_str or not isinstance(val_str, str):
|
if not val_str or not isinstance(val_str, str):
|
||||||
return {
|
continue
|
||||||
"node": node,
|
|
||||||
"status": "CLEAR",
|
|
||||||
"has_pending": False,
|
|
||||||
"page_title": page.get("title", ""),
|
|
||||||
"page_url": page.get("url", ""),
|
|
||||||
}
|
|
||||||
|
|
||||||
data = json.loads(val_str)
|
data = json.loads(val_str)
|
||||||
has_pending = data.get("has_pending", False)
|
if data.get("input_waits"):
|
||||||
card_text = data.get("card_text", "")
|
all_input_waits.extend(data["input_waits"])
|
||||||
|
|
||||||
# Parse details
|
if data.get("has_pending"):
|
||||||
|
card_text = data.get("card_text", "")
|
||||||
ip = None
|
ip = None
|
||||||
m_ip = re.search(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", card_text)
|
target = None
|
||||||
|
m_t = re.search(
|
||||||
|
r"(?:connection to|share information with|contact|connect to)\s+([A-Za-z0-9][A-Za-z0-9.-]*[A-Za-z0-9])",
|
||||||
|
card_text,
|
||||||
|
)
|
||||||
|
if m_t:
|
||||||
|
target = m_t.group(1)
|
||||||
|
m_ip = re.search(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", target or card_text)
|
||||||
if m_ip:
|
if m_ip:
|
||||||
ip = m_ip.group(0)
|
ip = m_ip.group(0)
|
||||||
|
if not target:
|
||||||
|
target = ip
|
||||||
|
if not target:
|
||||||
|
m_domain = re.search(r"\b([a-zA-Z0-9-]+\.)+(?:online|com|net|org|io|dev)\b", card_text)
|
||||||
|
if m_domain:
|
||||||
|
target = m_domain.group(0)
|
||||||
|
|
||||||
# Extract title and purpose summary
|
|
||||||
lines = [line.strip() for line in card_text.split("\n") if line.strip()]
|
lines = [line.strip() for line in card_text.split("\n") if line.strip()]
|
||||||
title = lines[0] if lines else "Permission request"
|
title = redact_sensitive(lines[0] if lines else "Permission request")
|
||||||
purpose = lines[1] if len(lines) > 1 else ""
|
purpose = redact_sensitive(lines[1] if len(lines) > 1 else "")
|
||||||
|
|
||||||
is_trusted = False
|
is_trusted = is_trusted_target(target or ip, card_text)
|
||||||
if ip:
|
|
||||||
is_trusted = ip in TRUSTED_IPS
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"node": node,
|
"node": node,
|
||||||
"status": "PENDING" if has_pending else "CLEAR",
|
"status": "PENDING",
|
||||||
"has_pending": has_pending,
|
"has_pending": True,
|
||||||
"title": title,
|
"title": title,
|
||||||
"purpose": purpose,
|
"purpose": purpose,
|
||||||
"ip": ip,
|
"ip": ip,
|
||||||
|
"target": target or ip or "-",
|
||||||
"is_trusted": is_trusted,
|
"is_trusted": is_trusted,
|
||||||
"buttons": data.get("buttons", []),
|
"buttons": data.get("buttons", []),
|
||||||
"has_allow_once": data.get("has_allow_once", False),
|
"has_allow_once": data.get("has_allow_once", False),
|
||||||
"has_always_allow": data.get("has_always_allow", False),
|
"has_always_allow": data.get("has_always_allow", False),
|
||||||
"has_deny": data.get("has_deny", False),
|
"has_deny": data.get("has_deny", False),
|
||||||
"raw_text": card_text,
|
"raw_text": redact_sensitive(card_text),
|
||||||
"history": data.get("history", []),
|
"history": data.get("history", []),
|
||||||
|
"input_waits": all_input_waits,
|
||||||
"page_title": page.get("title", ""),
|
"page_title": page.get("title", ""),
|
||||||
"page_url": page.get("url", ""),
|
"page_url": page.get("url", ""),
|
||||||
|
"ws_url": ws_url,
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
last_err = e
|
||||||
|
if ws:
|
||||||
try:
|
try:
|
||||||
ws.close()
|
ws.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Deduplicate input waits by task name
|
||||||
|
unique_waits = []
|
||||||
|
seen_tasks = set()
|
||||||
|
for w in all_input_waits:
|
||||||
|
t_name = redact_sensitive(w.get("task", ""))
|
||||||
|
status_text = redact_sensitive(w.get("status", ""))
|
||||||
|
if t_name not in seen_tasks:
|
||||||
|
seen_tasks.add(t_name)
|
||||||
|
unique_waits.append({
|
||||||
|
"task": t_name,
|
||||||
|
"status": status_text,
|
||||||
|
"when": w.get("when", ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
key_req = check_node_key_request(node)
|
||||||
|
if key_req:
|
||||||
return {
|
return {
|
||||||
"node": node,
|
"node": node,
|
||||||
"status": "ERROR",
|
"status": "KEY_APPROVAL",
|
||||||
"error": str(e),
|
"has_pending": True,
|
||||||
|
"title": f"Passkey requested: {key_req.get('reason')}",
|
||||||
|
"purpose": key_req.get("reason"),
|
||||||
|
"ip": "34.139.37.135",
|
||||||
|
"target": "34.139.37.135 (VM passkey)",
|
||||||
|
"is_trusted": True,
|
||||||
|
"buttons": ["Allow", "Deny"],
|
||||||
|
"has_allow_once": True,
|
||||||
|
"has_always_allow": False,
|
||||||
|
"has_deny": True,
|
||||||
|
"raw_text": f"Agent on node {node} requested passkey: {key_req.get('reason')}",
|
||||||
|
"history": [],
|
||||||
|
"input_waits": unique_waits,
|
||||||
|
"page_title": first_page.get("title", ""),
|
||||||
|
"page_url": first_page.get("url", ""),
|
||||||
|
"ws_url": first_page.get("webSocketDebuggerUrl", ""),
|
||||||
|
"key_request": key_req,
|
||||||
|
}
|
||||||
|
|
||||||
|
status = "INPUT_WAIT" if unique_waits else ("ERROR" if last_err and not first_page else "CLEAR")
|
||||||
|
return {
|
||||||
|
"node": node,
|
||||||
|
"status": status,
|
||||||
"has_pending": False,
|
"has_pending": False,
|
||||||
|
"title": "No pending approvals",
|
||||||
|
"purpose": "",
|
||||||
|
"ip": None,
|
||||||
|
"target": "-",
|
||||||
|
"is_trusted": False,
|
||||||
|
"buttons": [],
|
||||||
|
"has_allow_once": False,
|
||||||
|
"has_always_allow": False,
|
||||||
|
"has_deny": False,
|
||||||
|
"raw_text": "",
|
||||||
|
"history": [],
|
||||||
|
"input_waits": unique_waits,
|
||||||
|
"page_title": first_page.get("title", ""),
|
||||||
|
"page_url": first_page.get("url", ""),
|
||||||
|
"ws_url": first_page.get("webSocketDebuggerUrl", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -333,6 +555,28 @@ def allow_node_approval(node: str, always: bool = False, force: bool = False, ca
|
|||||||
if not info.get("has_pending"):
|
if not info.get("has_pending"):
|
||||||
return {"ok": False, "node": node, "error": "No pending approval dialog found on node"}
|
return {"ok": False, "node": node, "error": "No pending approval dialog found on node"}
|
||||||
|
|
||||||
|
if info.get("status") == "KEY_APPROVAL":
|
||||||
|
key_req = info.get("key_request") or check_node_key_request(node) or {}
|
||||||
|
reason = key_req.get("reason", info.get("purpose", ""))
|
||||||
|
log_box_ctl(
|
||||||
|
"key-approval-allow",
|
||||||
|
name=node,
|
||||||
|
caller=caller,
|
||||||
|
extra={
|
||||||
|
"reason": reason,
|
||||||
|
"forced": force,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"node": node,
|
||||||
|
"type": "key_approval",
|
||||||
|
"decision": "allow",
|
||||||
|
"dismissed": True,
|
||||||
|
"reason": reason,
|
||||||
|
"title": info.get("title"),
|
||||||
|
}
|
||||||
|
|
||||||
if not info.get("is_trusted") and not force:
|
if not info.get("is_trusted") and not force:
|
||||||
target = info.get("ip") or "unrecognized target"
|
target = info.get("ip") or "unrecognized target"
|
||||||
return {
|
return {
|
||||||
@@ -343,6 +587,10 @@ def allow_node_approval(node: str, always: bool = False, force: bool = False, ca
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
ws_url = info.get("ws_url")
|
||||||
|
if ws_url:
|
||||||
|
ws = websocket.create_connection(ws_url, timeout=3.0)
|
||||||
|
else:
|
||||||
ws, _ = get_cdp_ws(node, timeout=3.0)
|
ws, _ = get_cdp_ws(node, timeout=3.0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"ok": False, "node": node, "error": f"Failed to connect to CDP: {e}"}
|
return {"ok": False, "node": node, "error": f"Failed to connect to CDP: {e}"}
|
||||||
@@ -351,11 +599,19 @@ def allow_node_approval(node: str, always: bool = False, force: bool = False, ca
|
|||||||
if always:
|
if always:
|
||||||
js_click = """(() => {
|
js_click = """(() => {
|
||||||
const btns = Array.from(document.querySelectorAll('button'));
|
const btns = Array.from(document.querySelectorAll('button'));
|
||||||
const btn = btns.find(b => (b.innerText||'').toLowerCase().includes('always allow'));
|
let btn = btns.find(b => (b.innerText||'').toLowerCase().includes('always allow'));
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.click();
|
btn.click();
|
||||||
return 'CLICKED_ALWAYS';
|
return 'CLICKED_ALWAYS';
|
||||||
}
|
}
|
||||||
|
btn = document.querySelector('button[data-hatch-approval-primary-action="true"]');
|
||||||
|
if (!btn) {
|
||||||
|
btn = btns.find(b => (b.innerText||'').toLowerCase().includes('allow once') || (b.innerText||'').trim().toLowerCase() === 'allow');
|
||||||
|
}
|
||||||
|
if (btn) {
|
||||||
|
btn.click();
|
||||||
|
return 'CLICKED_PRIMARY_FALLBACK';
|
||||||
|
}
|
||||||
return 'NOT_FOUND';
|
return 'NOT_FOUND';
|
||||||
})()"""
|
})()"""
|
||||||
else:
|
else:
|
||||||
@@ -422,12 +678,37 @@ def allow_node_approval(node: str, always: bool = False, force: bool = False, ca
|
|||||||
|
|
||||||
|
|
||||||
def deny_node_approval(node: str, caller: str = "box-approvals") -> dict:
|
def deny_node_approval(node: str, caller: str = "box-approvals") -> dict:
|
||||||
"""Deny a pending approval on a node (click 'Deny')."""
|
"""Deny a pending approval on a node (click 'Deny' or deny key request)."""
|
||||||
info = inspect_node_approvals(node)
|
info = inspect_node_approvals(node)
|
||||||
if not info.get("has_pending"):
|
if not info.get("has_pending"):
|
||||||
return {"ok": False, "node": node, "error": "No pending approval dialog found on node"}
|
return {"ok": False, "node": node, "error": "No pending approval dialog found on node"}
|
||||||
|
|
||||||
|
if info.get("status") == "KEY_APPROVAL":
|
||||||
|
key_req = info.get("key_request") or check_node_key_request(node) or {}
|
||||||
|
reason = key_req.get("reason", info.get("purpose", ""))
|
||||||
|
log_box_ctl(
|
||||||
|
"key-approval-deny",
|
||||||
|
name=node,
|
||||||
|
caller=caller,
|
||||||
|
extra={
|
||||||
|
"reason": reason,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"node": node,
|
||||||
|
"type": "key_approval",
|
||||||
|
"decision": "deny",
|
||||||
|
"dismissed": True,
|
||||||
|
"reason": reason,
|
||||||
|
"title": info.get("title"),
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
ws_url = info.get("ws_url")
|
||||||
|
if ws_url:
|
||||||
|
ws = websocket.create_connection(ws_url, timeout=3.0)
|
||||||
|
else:
|
||||||
ws, _ = get_cdp_ws(node, timeout=3.0)
|
ws, _ = get_cdp_ws(node, timeout=3.0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"ok": False, "node": node, "error": f"Failed to connect to CDP: {e}"}
|
return {"ok": False, "node": node, "error": f"Failed to connect to CDP: {e}"}
|
||||||
@@ -481,8 +762,8 @@ def deny_node_approval(node: str, caller: str = "box-approvals") -> dict:
|
|||||||
return {"ok": False, "node": node, "error": str(e)}
|
return {"ok": False, "node": node, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
def auto_approve_fleet(nodes: list = None, caller: str = "box-approvals") -> dict:
|
def auto_approve_fleet(nodes: list = None, always: bool = True, caller: str = "box-approvals") -> dict:
|
||||||
"""Scan fleet nodes and automatically approve any requests to TRUSTED_IPS."""
|
"""Scan fleet nodes and automatically approve any requests to TRUSTED_IPS with Always Allow."""
|
||||||
fleet = check_fleet_approvals(nodes)
|
fleet = check_fleet_approvals(nodes)
|
||||||
approved = []
|
approved = []
|
||||||
untrusted = []
|
untrusted = []
|
||||||
@@ -491,12 +772,16 @@ def auto_approve_fleet(nodes: list = None, caller: str = "box-approvals") -> dic
|
|||||||
for item in fleet:
|
for item in fleet:
|
||||||
node = item["node"]
|
node = item["node"]
|
||||||
if item.get("has_pending"):
|
if item.get("has_pending"):
|
||||||
if item.get("is_trusted"):
|
if item.get("status") == "KEY_APPROVAL":
|
||||||
res = allow_node_approval(node, caller=caller)
|
# Key approvals require explicit operator decision, never auto-approve
|
||||||
|
untrusted.append(item)
|
||||||
|
elif item.get("is_trusted"):
|
||||||
|
res = allow_node_approval(node, always=always, caller=caller)
|
||||||
approved.append({
|
approved.append({
|
||||||
"node": node,
|
"node": node,
|
||||||
"target_ip": item.get("ip"),
|
"target_ip": item.get("ip"),
|
||||||
"title": item.get("title"),
|
"title": item.get("title"),
|
||||||
|
"decision": "always" if always else "allow_once",
|
||||||
"res": res,
|
"res": res,
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
@@ -510,3 +795,108 @@ def auto_approve_fleet(nodes: list = None, caller: str = "box-approvals") -> dic
|
|||||||
"untrusted_pending": untrusted,
|
"untrusted_pending": untrusted,
|
||||||
"clear_nodes": clear,
|
"clear_nodes": clear,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def reply_node_task(node: str, message: str, allow_main_chat: bool = False, caller: str = "box-approvals") -> dict:
|
||||||
|
"""Send an operator reply into the waiting agent's thread to answer an input prompt."""
|
||||||
|
info = inspect_node_approvals(node)
|
||||||
|
page_url = info.get("page_url", "")
|
||||||
|
page_title = info.get("page_title", "")
|
||||||
|
ws_url = info.get("ws_url")
|
||||||
|
|
||||||
|
# Check if target is Main Chat
|
||||||
|
# Main chat signatures: not sidechat and (no /thread/ or title contains 'Chat —')
|
||||||
|
is_main = "sidechat" not in page_url.lower() and ("/thread/" not in page_url or "chat —" in page_title.lower())
|
||||||
|
if is_main and not allow_main_chat:
|
||||||
|
return {
|
||||||
|
"ok": False,
|
||||||
|
"node": node,
|
||||||
|
"error": "Active thread appears to be Main Chat. Refusing reply by sidechat-first policy. Pass --allow-main-chat to confirm intentional operator override.",
|
||||||
|
"page_title": page_title,
|
||||||
|
"page_url": page_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
if ws_url:
|
||||||
|
ws = websocket.create_connection(ws_url, timeout=3.0)
|
||||||
|
else:
|
||||||
|
ws, _ = get_cdp_ws(node, timeout=3.0)
|
||||||
|
except Exception as e:
|
||||||
|
return {"ok": False, "node": node, "error": f"Failed to connect to CDP: {e}"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg_esc = message.replace('\\', '\\\\').replace('`', '\\`').replace('$', '\\$').replace('"', '\\"')
|
||||||
|
js_type_and_send = f"""(async() => {{
|
||||||
|
const input = document.querySelector('[contenteditable="true"]') ||
|
||||||
|
document.querySelector('textarea[placeholder*="Message"]') ||
|
||||||
|
document.querySelector('textarea');
|
||||||
|
if (!input) return 'NO_INPUT';
|
||||||
|
input.focus();
|
||||||
|
document.execCommand('insertText', false, "{msg_esc}");
|
||||||
|
await new Promise(r => setTimeout(r, 400));
|
||||||
|
const sendBtn = Array.from(document.querySelectorAll('button')).find(b => {{
|
||||||
|
const a = (b.getAttribute('aria-label') || '').toLowerCase();
|
||||||
|
const t = (b.innerText || '').toLowerCase();
|
||||||
|
return a.includes('send') || t === 'send';
|
||||||
|
}});
|
||||||
|
if (sendBtn && !sendBtn.disabled) {{
|
||||||
|
sendBtn.click();
|
||||||
|
return 'CLICKED_SEND';
|
||||||
|
}}
|
||||||
|
const ke = new KeyboardEvent('keydown', {{key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true}});
|
||||||
|
input.dispatchEvent(ke);
|
||||||
|
return 'ENTER_SENT';
|
||||||
|
}})()"""
|
||||||
|
send_res = cdp_evaluate(ws, js_type_and_send, await_promise=True, timeout=5.0)
|
||||||
|
ws.close()
|
||||||
|
|
||||||
|
log_box_ctl(
|
||||||
|
"approval-reply",
|
||||||
|
name=node,
|
||||||
|
caller=caller,
|
||||||
|
extra={
|
||||||
|
"message_len": len(message),
|
||||||
|
"page_url": page_url,
|
||||||
|
"allow_main_chat": allow_main_chat,
|
||||||
|
"send_res": send_res,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"node": node,
|
||||||
|
"send_result": send_res,
|
||||||
|
"page_title": page_title,
|
||||||
|
"page_url": page_url,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
try:
|
||||||
|
ws.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return {"ok": False, "node": node, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
def dismiss_node_task(node: str, caller: str = "box-approvals") -> dict:
|
||||||
|
"""Close any open task modal dialog or popup on a node."""
|
||||||
|
try:
|
||||||
|
ws, _ = get_cdp_ws(node, timeout=3.0)
|
||||||
|
except Exception as e:
|
||||||
|
return {"ok": False, "node": node, "error": f"Failed to connect to CDP: {e}"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
js_dismiss = """(() => {
|
||||||
|
const close = document.querySelector('[aria-label="Close"], button[data-slot="dialog-close"]');
|
||||||
|
if (close) { close.click(); return 'CLICKED_CLOSE'; }
|
||||||
|
document.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape', code: 'Escape', keyCode: 27, bubbles: true}));
|
||||||
|
return 'ESCAPE_SENT';
|
||||||
|
})()"""
|
||||||
|
res = cdp_evaluate(ws, js_dismiss, timeout=2.0)
|
||||||
|
ws.close()
|
||||||
|
return {"ok": True, "node": node, "result": res}
|
||||||
|
except Exception as e:
|
||||||
|
try:
|
||||||
|
ws.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return {"ok": False, "node": node, "error": str(e)}
|
||||||
|
|
||||||
|
|||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
/home/super/Projects/NetVM/bin/docs-lookup.py
|
||||||
Executable
+606
@@ -0,0 +1,606 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""docs-lookup.py — Unified Lookup & Regex Passing Tool for docs_internal/.
|
||||||
|
|
||||||
|
Provides high-speed queries, regex passing, grammar validation, and surface
|
||||||
|
lookups for autonomous agents and operators interfacing with NetVM, Box,
|
||||||
|
and box.muse-dev.online.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
docs-lookup.py search <query>
|
||||||
|
docs-lookup.py surfaces [name]
|
||||||
|
docs-lookup.py sentence [name]
|
||||||
|
docs-lookup.py regex [name] [--test "<string>"]
|
||||||
|
docs-lookup.py parse "<string>"
|
||||||
|
docs-lookup.py get <collection> [key]
|
||||||
|
docs-lookup.py cli [domain]
|
||||||
|
docs-lookup.py overview
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
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"
|
||||||
|
DOCS_INTERNAL = LOOKUP_INTERNAL
|
||||||
|
|
||||||
|
USE_COLOR = sys.stdout.isatty() and os.environ.get("NO_COLOR") is None
|
||||||
|
|
||||||
|
def _c(code: str, text: str) -> str:
|
||||||
|
return f"\033[{code}m{text}\033[0m" if USE_COLOR else str(text)
|
||||||
|
|
||||||
|
def c_bold(s: str) -> str: return _c("1", s)
|
||||||
|
def c_dim(s: str) -> str: return _c("2", s)
|
||||||
|
def c_green(s: str) -> str: return _c("32", s)
|
||||||
|
def c_red(s: str) -> str: return _c("31", s)
|
||||||
|
def c_yellow(s: str) -> str: return _c("33", s)
|
||||||
|
def c_blue(s: str) -> str: return _c("34", s)
|
||||||
|
def c_cyan(s: str) -> str: return _c("36", s)
|
||||||
|
def c_magenta(s: str) -> str: return _c("35", s)
|
||||||
|
|
||||||
|
|
||||||
|
def load_json_file(filename: str) -> Dict[str, Any]:
|
||||||
|
"""Safely load a JSON file from docs_internal."""
|
||||||
|
p = DOCS_INTERNAL / filename
|
||||||
|
if not p.is_file():
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
with open(p, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading {p}: {e}", file=sys.stderr)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def load_manifest() -> Dict[str, Any]:
|
||||||
|
return load_json_file("manifest.json")
|
||||||
|
|
||||||
|
|
||||||
|
def load_sentence_structures() -> Dict[str, Any]:
|
||||||
|
return load_json_file("sentence_structure.json")
|
||||||
|
|
||||||
|
|
||||||
|
def load_regex_patterns() -> Dict[str, Any]:
|
||||||
|
return load_json_file("regex_patterns.json")
|
||||||
|
|
||||||
|
|
||||||
|
def load_assistive_surfaces() -> Dict[str, Any]:
|
||||||
|
return load_json_file("assistive_surfaces.json")
|
||||||
|
|
||||||
|
|
||||||
|
def load_cli_tools() -> Dict[str, Any]:
|
||||||
|
return load_json_file("cli_tools.json")
|
||||||
|
|
||||||
|
|
||||||
|
def load_fleet_nodes() -> Dict[str, Any]:
|
||||||
|
return load_json_file("fleet_nodes.json")
|
||||||
|
|
||||||
|
|
||||||
|
def compile_pattern(pat_entry: Dict[str, Any]) -> Tuple[Optional[re.Pattern], Optional[str]]:
|
||||||
|
"""Compile a regex entry with its configured flags."""
|
||||||
|
raw_pat = pat_entry.get("pattern", "")
|
||||||
|
flag_names = pat_entry.get("flags", [])
|
||||||
|
flags = 0
|
||||||
|
for fn in flag_names:
|
||||||
|
if hasattr(re, fn):
|
||||||
|
flags |= getattr(re, fn)
|
||||||
|
try:
|
||||||
|
return re.compile(raw_pat, flags), None
|
||||||
|
except Exception as e:
|
||||||
|
return None, str(e)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Core Lookup Actions
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def handle_overview(json_mode: bool = False):
|
||||||
|
manifest = load_manifest()
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps(manifest, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold("\n=== docs_internal — Internal Agent & Operator Lookup Database ==="))
|
||||||
|
print(c_dim(f"Location: {DOCS_INTERNAL} | Host: {manifest.get('host', 'box.muse-dev.online')}"))
|
||||||
|
print(f"{manifest.get('description', '')}\n")
|
||||||
|
|
||||||
|
print(c_cyan("Available Collections:"))
|
||||||
|
collections = manifest.get("collections", [])
|
||||||
|
for col in collections:
|
||||||
|
cid = col.get("id")
|
||||||
|
name = col.get("name")
|
||||||
|
desc = col.get("description")
|
||||||
|
jfile = col.get("json_file")
|
||||||
|
mfile = col.get("md_file")
|
||||||
|
print(f" • {c_bold(cid):<20} {c_green(name)}")
|
||||||
|
print(f" {c_dim(desc)}")
|
||||||
|
print(f" {c_dim('Files:')} {c_yellow(jfile)} | {c_yellow(mfile)}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print(c_dim("Query commands: super docs [search|surfaces|sentence|regex|parse|get|cli]"))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_search(query: str, json_mode: bool = False):
|
||||||
|
q = query.lower()
|
||||||
|
results = []
|
||||||
|
|
||||||
|
# Search in all JSON files
|
||||||
|
for jpath in sorted(DOCS_INTERNAL.glob("*.json")):
|
||||||
|
try:
|
||||||
|
data = json.loads(jpath.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def recurse_search(obj, path=""):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
for k, v in obj.items():
|
||||||
|
subpath = f"{path}.{k}" if path else k
|
||||||
|
if q in str(k).lower():
|
||||||
|
results.append({
|
||||||
|
"file": jpath.name,
|
||||||
|
"type": "json_key",
|
||||||
|
"path": subpath,
|
||||||
|
"match": str(k),
|
||||||
|
"preview": str(v)[:160]
|
||||||
|
})
|
||||||
|
recurse_search(v, subpath)
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
for idx, item in enumerate(obj):
|
||||||
|
recurse_search(item, f"{path}[{idx}]")
|
||||||
|
elif isinstance(obj, str):
|
||||||
|
if q in obj.lower():
|
||||||
|
results.append({
|
||||||
|
"file": jpath.name,
|
||||||
|
"type": "json_value",
|
||||||
|
"path": path,
|
||||||
|
"match": obj[:120],
|
||||||
|
"preview": obj[:240]
|
||||||
|
})
|
||||||
|
|
||||||
|
recurse_search(data)
|
||||||
|
|
||||||
|
# Search in Markdown files
|
||||||
|
for mpath in sorted(DOCS_INTERNAL.glob("*.md")):
|
||||||
|
try:
|
||||||
|
content = mpath.read_text(encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
lines = content.splitlines()
|
||||||
|
for idx, line in enumerate(lines, 1):
|
||||||
|
if q in line.lower():
|
||||||
|
results.append({
|
||||||
|
"file": mpath.name,
|
||||||
|
"type": "markdown",
|
||||||
|
"line": idx,
|
||||||
|
"match": line.strip(),
|
||||||
|
"preview": line.strip()
|
||||||
|
})
|
||||||
|
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps({"query": query, "count": len(results), "results": results}, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold(f"\nSearch results for '{query}' ({len(results)} matches):"))
|
||||||
|
if not results:
|
||||||
|
print(c_dim(" (no matching entries found)"))
|
||||||
|
return
|
||||||
|
|
||||||
|
for r in results[:40]:
|
||||||
|
if r["type"] == "markdown":
|
||||||
|
print(f" [{c_yellow(r['file'])}:{c_cyan(str(r['line']))}] {r['match']}")
|
||||||
|
else:
|
||||||
|
print(f" [{c_blue(r['file'])}:{c_magenta(r['path'])}] {r['preview']}")
|
||||||
|
|
||||||
|
|
||||||
|
def handle_surfaces(view_name: Optional[str] = None, json_mode: bool = False):
|
||||||
|
data = load_assistive_surfaces()
|
||||||
|
views = data.get("views", {})
|
||||||
|
|
||||||
|
if view_name:
|
||||||
|
key = view_name.lower().strip()
|
||||||
|
v = views.get(key)
|
||||||
|
if not v:
|
||||||
|
for k, val in views.items():
|
||||||
|
if key in k or key in val.get("name", "").lower():
|
||||||
|
v = val
|
||||||
|
key = k
|
||||||
|
break
|
||||||
|
if not v:
|
||||||
|
err = {"error": f"Surface '{view_name}' not found", "available": list(views.keys())}
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps(err, indent=2))
|
||||||
|
else:
|
||||||
|
print(c_red(f"Error: Surface '{view_name}' not found. Available: {', '.join(views.keys())}"))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps({key: v}, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold(f"\n=== Assistive Surface: {v.get('name')} (`{key}`) ==="))
|
||||||
|
print(c_dim(f"Host: {data.get('host')} | Tab ID: {v.get('tab_id')}"))
|
||||||
|
print(f"{c_cyan('DOM Tab Selector:')} {c_yellow(str(v.get('dom_tab_selector')))}")
|
||||||
|
print(f"{c_cyan('DOM Pane Selector:')} {c_yellow(str(v.get('dom_pane_selector')))}")
|
||||||
|
|
||||||
|
elements = v.get("key_elements", {})
|
||||||
|
if elements:
|
||||||
|
print(c_bold("\nKey DOM Elements / Selectors:"))
|
||||||
|
for el_name, sel in elements.items():
|
||||||
|
print(f" • {c_magenta(el_name):<20} {c_green(sel)}")
|
||||||
|
|
||||||
|
endpoints = v.get("api_endpoints", [])
|
||||||
|
if endpoints:
|
||||||
|
print(c_bold("\nAssociated REST API Endpoints:"))
|
||||||
|
for ep in endpoints:
|
||||||
|
print(f" • {c_bold(ep.get('method'))} {c_cyan(ep.get('path'))}")
|
||||||
|
print(f" {c_dim(ep.get('description'))}")
|
||||||
|
if ep.get("curl_example"):
|
||||||
|
print(f" {c_dim('curl:')} {c_yellow(ep.get('curl_example'))}")
|
||||||
|
|
||||||
|
recipe = v.get("assistive_recipe")
|
||||||
|
if recipe:
|
||||||
|
print(c_bold("\nAssistive Recipe:"))
|
||||||
|
print(f" {recipe}")
|
||||||
|
print()
|
||||||
|
return
|
||||||
|
|
||||||
|
# All views
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold(f"\n=== Assistive Surfaces for {data.get('host', 'box.muse-dev.online')} ==="))
|
||||||
|
print(c_dim(f"Operator PIN: {data.get('auth', {}).get('pin')} | Session Cookie: {data.get('auth', {}).get('cookie_name')}"))
|
||||||
|
print()
|
||||||
|
|
||||||
|
for k, v in views.items():
|
||||||
|
name = v.get("name", k)
|
||||||
|
tab_sel = v.get("dom_tab_selector") or "(modal/overlay)"
|
||||||
|
eps = [f"{ep.get('method')} {ep.get('path')}" for ep in v.get("api_endpoints", [])]
|
||||||
|
ep_summary = ", ".join(eps) if eps else "(no direct endpoint)"
|
||||||
|
print(f" • {c_bold(k):<16} {c_cyan(name):<30} {c_yellow(tab_sel)}")
|
||||||
|
print(f" {c_dim('API:')} {ep_summary}")
|
||||||
|
if v.get("assistive_recipe"):
|
||||||
|
print(f" {c_dim('Hint:')} {v.get('assistive_recipe')[:100]}...")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_sentence(name: Optional[str] = None, json_mode: bool = False):
|
||||||
|
data = load_sentence_structures()
|
||||||
|
structs = data.get("structures", {})
|
||||||
|
|
||||||
|
if name:
|
||||||
|
key = name.lower().strip()
|
||||||
|
s = structs.get(key)
|
||||||
|
if not s:
|
||||||
|
for k, val in structs.items():
|
||||||
|
if key in k or key in val.get("name", "").lower() or key in val.get("protocol_tag", "").lower():
|
||||||
|
s = val
|
||||||
|
key = k
|
||||||
|
break
|
||||||
|
if not s:
|
||||||
|
err = {"error": f"Sentence structure '{name}' not found", "available": list(structs.keys())}
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps(err, indent=2))
|
||||||
|
else:
|
||||||
|
print(c_red(f"Error: Sentence structure '{name}' not found. Available: {', '.join(structs.keys())}"))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps({key: s}, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold(f"\n=== Protocol Structure: {s.get('name')} (`{key}`) ==="))
|
||||||
|
print(f"{c_cyan('Tag:')} {c_bold(s.get('protocol_tag'))}")
|
||||||
|
print(f"{c_cyan('Template:')} {c_green(s.get('template'))}")
|
||||||
|
print(f"{c_cyan('Lifecycle:')} {c_yellow(s.get('lifecycle_transition', ''))}")
|
||||||
|
print(f"\n{c_bold('Description:')}\n {s.get('description')}")
|
||||||
|
print(f"\n{c_bold('Required Fields:')} {', '.join(s.get('required_fields', []))}")
|
||||||
|
if s.get("optional_fields"):
|
||||||
|
print(f"{c_bold('Optional Fields:')} {', '.join(s.get('optional_fields', []))}")
|
||||||
|
print(f"\n{c_bold('Example:')}\n {c_cyan(s.get('example'))}")
|
||||||
|
print(f"\n{c_bold('Reply Expectation:')}\n {s.get('reply_expectation')}")
|
||||||
|
print()
|
||||||
|
return
|
||||||
|
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold("\n=== Agent Sentence Structures & Conversational Contracts ==="))
|
||||||
|
print(c_dim("Format rules enforced by response-harvester and self_main_loop.\n"))
|
||||||
|
|
||||||
|
for k, s in structs.items():
|
||||||
|
tag = s.get("protocol_tag", "")
|
||||||
|
desc = s.get("description", "")
|
||||||
|
print(f" • {c_bold(k):<18} {c_green(tag):<25} {s.get('name')}")
|
||||||
|
print(f" {c_dim(desc)}")
|
||||||
|
print(f" {c_dim('Template:')} {c_cyan(s.get('template', ''))}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_regex(name: Optional[str] = None, test_str: Optional[str] = None, json_mode: bool = False):
|
||||||
|
data = load_regex_patterns()
|
||||||
|
pats = data.get("patterns", {})
|
||||||
|
|
||||||
|
if name:
|
||||||
|
key = name.lower().strip()
|
||||||
|
p = pats.get(key)
|
||||||
|
if not p:
|
||||||
|
for k, val in pats.items():
|
||||||
|
if key in k or key in val.get("name", "").lower():
|
||||||
|
p = val
|
||||||
|
key = k
|
||||||
|
break
|
||||||
|
if not p:
|
||||||
|
err = {"error": f"Regex pattern '{name}' not found", "available": list(pats.keys())}
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps(err, indent=2))
|
||||||
|
else:
|
||||||
|
print(c_red(f"Error: Pattern '{name}' not found. Available: {', '.join(pats.keys())}"))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
compiled, comp_err = compile_pattern(p)
|
||||||
|
|
||||||
|
test_result = None
|
||||||
|
if test_str is not None:
|
||||||
|
if compiled:
|
||||||
|
m = compiled.search(test_str)
|
||||||
|
test_result = {
|
||||||
|
"matched": bool(m),
|
||||||
|
"match_span": m.span() if m else None,
|
||||||
|
"matched_text": m.group(0) if m else None,
|
||||||
|
"named_groups": m.groupdict() if m else {},
|
||||||
|
"groups": list(m.groups()) if m else []
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
test_result = {"matched": False, "error": comp_err}
|
||||||
|
|
||||||
|
if json_mode:
|
||||||
|
out = {key: p, "compiled_ok": bool(compiled)}
|
||||||
|
if test_str is not None:
|
||||||
|
out["test_evaluation"] = test_result
|
||||||
|
print(json.dumps(out, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold(f"\n=== Regex Pattern: {p.get('name')} (`{key}`) ==="))
|
||||||
|
print(f"{c_cyan('Pattern:')} {c_yellow(p.get('pattern'))}")
|
||||||
|
print(f"{c_cyan('Flags:')} {', '.join(p.get('flags', [])) or '(none)'}")
|
||||||
|
print(f"{c_cyan('Description:')} {p.get('description')}")
|
||||||
|
print(f"{c_cyan('Usage:')} {c_dim(p.get('usage', ''))}")
|
||||||
|
|
||||||
|
ng = p.get("named_groups", {})
|
||||||
|
if ng:
|
||||||
|
print(c_bold("\nNamed Groups:"))
|
||||||
|
for gname, gdesc in ng.items():
|
||||||
|
print(f" • {c_magenta(gname):<16} {gdesc}")
|
||||||
|
|
||||||
|
if test_str is not None:
|
||||||
|
print(c_bold("\nTest Execution Result:"))
|
||||||
|
print(f" Input: {c_dim(test_str)}")
|
||||||
|
if test_result.get("matched"):
|
||||||
|
print(f" Verdict: {c_green('✔ MATCHED')}")
|
||||||
|
print(f" Matched Text: {c_cyan(test_result['matched_text'])}")
|
||||||
|
if test_result["named_groups"]:
|
||||||
|
print(f" Extracted Tokens:")
|
||||||
|
for k_grp, v_grp in test_result["named_groups"].items():
|
||||||
|
print(f" - {c_magenta(k_grp)}: {c_yellow(str(v_grp))}")
|
||||||
|
else:
|
||||||
|
print(f" Verdict: {c_red('✖ NO MATCH')}")
|
||||||
|
if comp_err:
|
||||||
|
print(f" Compile Error: {comp_err}")
|
||||||
|
print()
|
||||||
|
return
|
||||||
|
|
||||||
|
# List patterns
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold("\n=== Master Regex Patterns & Passing Dictionary ==="))
|
||||||
|
print(c_dim("Patterns tested and calibrated across response-harvester, dm, and loop engines.\n"))
|
||||||
|
|
||||||
|
for k, p in pats.items():
|
||||||
|
print(f" • {c_bold(k):<18} {c_green(p.get('name'))}")
|
||||||
|
print(f" {c_dim('Pattern:')} {c_yellow(p.get('pattern'))}")
|
||||||
|
print(f" {c_dim(p.get('description'))}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_parse(candidate_str: str, json_mode: bool = False):
|
||||||
|
"""Pass candidate_str through all registered regexes and extract tokens."""
|
||||||
|
data = load_regex_patterns()
|
||||||
|
pats = data.get("patterns", {})
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
for k, p in pats.items():
|
||||||
|
compiled, err = compile_pattern(p)
|
||||||
|
if not compiled:
|
||||||
|
continue
|
||||||
|
m = compiled.search(candidate_str)
|
||||||
|
if m:
|
||||||
|
matches.append({
|
||||||
|
"pattern_key": k,
|
||||||
|
"pattern_name": p.get("name"),
|
||||||
|
"matched_text": m.group(0),
|
||||||
|
"named_groups": m.groupdict(),
|
||||||
|
"span": m.span()
|
||||||
|
})
|
||||||
|
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps({
|
||||||
|
"input": candidate_str,
|
||||||
|
"matched_patterns_count": len(matches),
|
||||||
|
"matches": matches
|
||||||
|
}, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold(f"\n=== Regex Parse Analysis ==="))
|
||||||
|
print(f"Input: {c_dim(candidate_str)}\n")
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
print(c_yellow(" ⚠ No registered regex pattern matched this string."))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_green(f"Matched {len(matches)} pattern(s):"))
|
||||||
|
for match in matches:
|
||||||
|
print(f"\n • Pattern: {c_bold(match['pattern_name'])} (`{c_cyan(match['pattern_key'])}`)")
|
||||||
|
print(f" Matched Chunk: {c_yellow(match['matched_text'])}")
|
||||||
|
ng = match["named_groups"]
|
||||||
|
if ng:
|
||||||
|
print(f" Extracted Tokens:")
|
||||||
|
for gk, gv in ng.items():
|
||||||
|
print(f" - {c_magenta(gk)}: {c_green(str(gv))}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_get(collection: str, key: Optional[str] = None, json_mode: bool = False):
|
||||||
|
col_map = {
|
||||||
|
"manifest": "manifest.json",
|
||||||
|
"sentence": "sentence_structure.json",
|
||||||
|
"sentence_structure": "sentence_structure.json",
|
||||||
|
"regex": "regex_patterns.json",
|
||||||
|
"regex_patterns": "regex_patterns.json",
|
||||||
|
"surfaces": "assistive_surfaces.json",
|
||||||
|
"assistive_surfaces": "assistive_surfaces.json",
|
||||||
|
"cli": "cli_tools.json",
|
||||||
|
"cli_tools": "cli_tools.json",
|
||||||
|
"fleet": "fleet_nodes.json",
|
||||||
|
"fleet_nodes": "fleet_nodes.json",
|
||||||
|
}
|
||||||
|
|
||||||
|
fname = col_map.get(collection.lower().strip())
|
||||||
|
if not fname:
|
||||||
|
print(c_red(f"Error: Unknown collection '{collection}'. Available: {', '.join(col_map.keys())}"), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
data = load_json_file(fname)
|
||||||
|
if key:
|
||||||
|
# Check top level or primary container
|
||||||
|
val = None
|
||||||
|
for primary in ["structures", "patterns", "views", "domains", "nodes", "collections"]:
|
||||||
|
if primary in data and isinstance(data[primary], dict) and key in data[primary]:
|
||||||
|
val = data[primary][key]
|
||||||
|
break
|
||||||
|
if val is None and key in data:
|
||||||
|
val = data[key]
|
||||||
|
if val is None:
|
||||||
|
print(c_red(f"Error: Key '{key}' not found in {fname}"), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
data = {key: val}
|
||||||
|
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_cli(domain: Optional[str] = None, json_mode: bool = False):
|
||||||
|
data = load_cli_tools()
|
||||||
|
domains = data.get("domains", {})
|
||||||
|
|
||||||
|
if domain:
|
||||||
|
d = domains.get(domain.lower().strip())
|
||||||
|
if not d:
|
||||||
|
print(c_red(f"Error: CLI domain '{domain}' not found. Available: {', '.join(domains.keys())}"), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps({domain: d}, indent=2))
|
||||||
|
return
|
||||||
|
print(c_bold(f"\n=== CLI Tool Domain: super {domain} / box {domain} ==="))
|
||||||
|
print(f"Summary: {d.get('summary')}\n")
|
||||||
|
for sub in d.get("subcommands", []):
|
||||||
|
print(f" • {c_green(sub['cmd'])}")
|
||||||
|
print(f" {c_dim(sub['desc'])}\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
if json_mode:
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print(c_bold("\n=== Unified NetVM & Box CLI Tool Catalog ==="))
|
||||||
|
print(c_dim("Powered by super-cli.py and bin/muse wrapper.\n"))
|
||||||
|
for dom_k, dom_v in domains.items():
|
||||||
|
print(f" • {c_bold(dom_k):<16} {c_cyan(dom_v.get('summary'))}")
|
||||||
|
for sub in dom_v.get("subcommands", [])[:2]:
|
||||||
|
print(f" - {c_dim(sub['cmd'])}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# CLI Argument Parser
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def build_parser():
|
||||||
|
common = argparse.ArgumentParser(add_help=False)
|
||||||
|
common.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="docs-lookup — Internal Agent & Operator Documentation Query Engine",
|
||||||
|
parents=[common],
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
sub = parser.add_subparsers(dest="action")
|
||||||
|
|
||||||
|
p_search = sub.add_parser("search", parents=[common], help="Full-text search across docs_internal")
|
||||||
|
p_search.add_argument("query", help="Search keyword or phrase")
|
||||||
|
|
||||||
|
p_surfaces = sub.add_parser("surfaces", parents=[common], help="Lookup assistive surfaces for box.muse-dev.online")
|
||||||
|
p_surfaces.add_argument("name", nargs="?", default=None, help="Surface or tab name")
|
||||||
|
|
||||||
|
p_sentence = sub.add_parser("sentence", parents=[common], help="Lookup agent sentence structures & protocols")
|
||||||
|
p_sentence.add_argument("name", nargs="?", default=None, help="Structure name (e.g. work_order, result)")
|
||||||
|
|
||||||
|
p_regex = sub.add_parser("regex", parents=[common], help="Lookup and test regex patterns")
|
||||||
|
p_regex.add_argument("name", nargs="?", default=None, help="Pattern name (e.g. work_order, verb)")
|
||||||
|
p_regex.add_argument("--test", dest="test_str", default=None, help="Test string to evaluate against pattern")
|
||||||
|
|
||||||
|
p_parse = sub.add_parser("parse", parents=[common], help="Parse an agent utterance through all regex patterns")
|
||||||
|
p_parse.add_argument("string", help="String/utterance to parse")
|
||||||
|
|
||||||
|
p_get = sub.add_parser("get", parents=[common], help="Query raw JSON collection and key")
|
||||||
|
p_get.add_argument("collection", help="Collection name (sentence, regex, surfaces, cli, fleet)")
|
||||||
|
p_get.add_argument("key", nargs="?", default=None, help="Optional specific key")
|
||||||
|
|
||||||
|
p_cli = sub.add_parser("cli", parents=[common], help="Lookup CLI commands and syntax")
|
||||||
|
p_cli.add_argument("domain", nargs="?", default=None, help="CLI domain (fleet, dm, job, etc.)")
|
||||||
|
|
||||||
|
p_overview = sub.add_parser("overview", parents=[common], help="Database overview and collections manifest")
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
act = args.action
|
||||||
|
json_mode = getattr(args, "json", False)
|
||||||
|
|
||||||
|
if not act or act == "overview":
|
||||||
|
handle_overview(json_mode)
|
||||||
|
elif act == "search":
|
||||||
|
handle_search(args.query, json_mode)
|
||||||
|
elif act == "surfaces":
|
||||||
|
handle_surfaces(args.name, json_mode)
|
||||||
|
elif act == "sentence":
|
||||||
|
handle_sentence(args.name, json_mode)
|
||||||
|
elif act == "regex":
|
||||||
|
handle_regex(args.name, args.test_str, json_mode)
|
||||||
|
elif act == "parse":
|
||||||
|
handle_parse(args.string, json_mode)
|
||||||
|
elif act == "get":
|
||||||
|
handle_get(args.collection, args.key, json_mode)
|
||||||
|
elif act == "cli":
|
||||||
|
handle_cli(args.domain, json_mode)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -49,7 +49,7 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
# ---------------------------------------------------------------- config
|
# ---------------------------------------------------------------- config
|
||||||
@@ -139,6 +139,7 @@ def check_token(token):
|
|||||||
|
|
||||||
|
|
||||||
def check_nonce(nonce):
|
def check_nonce(nonce):
|
||||||
|
with _nonce_lock:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
fresh = []
|
fresh = []
|
||||||
try:
|
try:
|
||||||
@@ -364,7 +365,7 @@ def _dm_read_validate(raw):
|
|||||||
def _dm_read_build(a):
|
def _dm_read_build(a):
|
||||||
return [sys.executable, os.path.join(BIN_DIR, 'dm.py'), 'read',
|
return [sys.executable, os.path.join(BIN_DIR, 'dm.py'), 'read',
|
||||||
'--agent', a['agent'], '--target', a['target'],
|
'--agent', a['agent'], '--target', a['target'],
|
||||||
'--limit', str(a['limit'])]
|
'--n', str(a['limit'])]
|
||||||
|
|
||||||
|
|
||||||
def _job_run_validate(raw):
|
def _job_run_validate(raw):
|
||||||
@@ -1294,6 +1295,7 @@ def permitted(ident, op):
|
|||||||
# ------------------------------------------------------- audit log
|
# ------------------------------------------------------- audit log
|
||||||
|
|
||||||
_audit_lock = threading.Lock()
|
_audit_lock = threading.Lock()
|
||||||
|
_nonce_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
def audit(entry):
|
def audit(entry):
|
||||||
@@ -1501,7 +1503,7 @@ def main():
|
|||||||
BIN_DIR, JOBS_DIR = args.bin_dir, args.jobs_dir
|
BIN_DIR, JOBS_DIR = args.bin_dir, args.jobs_dir
|
||||||
WORK_DIR = args.work_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
|
cert_file = args.cert_file
|
||||||
key_file = args.key_file
|
key_file = args.key_file
|
||||||
|
|||||||
@@ -185,22 +185,29 @@ HEALTHY_AGENTS=""
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# --- Agent approval blockage detection (catches agents held up on approvals) ---
|
# --- Agent approval blockage & input wait detection ---
|
||||||
"$BIN/netvm-registry.py" 2>/dev/null | while IFS=: read -r node port; do
|
"$BIN/netvm-registry.py" 2>/dev/null | while IFS=: read -r node port; do
|
||||||
[ -n "$node" ] || continue
|
[ -n "$node" ] || continue
|
||||||
cond="approval:$node"
|
node_data=$(python3 -c "
|
||||||
pending_info=$(python3 -c "
|
import sys, json
|
||||||
import sys
|
|
||||||
sys.path.insert(0, '$BIN')
|
sys.path.insert(0, '$BIN')
|
||||||
import approvals
|
import approvals
|
||||||
info = approvals.inspect_node_approvals('$node')
|
info = approvals.inspect_node_approvals('$node')
|
||||||
if info.get('has_pending'):
|
out = {
|
||||||
print(f\"{info.get('ip') or 'unknown'}|{info.get('title') or ''}\")
|
'has_pending': info.get('has_pending', False),
|
||||||
" 2>/dev/null || true)
|
'target': info.get('target') or info.get('ip') or 'unknown',
|
||||||
|
'title': info.get('title') or '',
|
||||||
|
'waits': info.get('input_waits') or []
|
||||||
|
}
|
||||||
|
print(json.dumps(out))
|
||||||
|
" 2>/dev/null || echo '{"has_pending":false,"target":"unknown","title":"","waits":[]}')
|
||||||
|
|
||||||
if [ -n "$pending_info" ]; then
|
# 1. Egress permission dialog
|
||||||
|
cond="approval:$node"
|
||||||
|
has_pending=$(python3 -c "import json,sys; print(1 if json.loads(sys.argv[1]).get('has_pending') else 0)" "$node_data" 2>/dev/null || echo 0)
|
||||||
|
if [ "$has_pending" = "1" ]; then
|
||||||
failing=1
|
failing=1
|
||||||
target="${pending_info%%|*}"
|
target=$(python3 -c "import json,sys; print(json.loads(sys.argv[1]).get('target','unknown'))" "$node_data" 2>/dev/null || echo unknown)
|
||||||
detail="Agent $node held up on browser approval for $target"
|
detail="Agent $node held up on browser approval for $target"
|
||||||
else
|
else
|
||||||
failing=0
|
failing=0
|
||||||
@@ -220,6 +227,37 @@ if info.get('has_pending'):
|
|||||||
log "$cond still critical x$fails — re-page suppressed"
|
log "$cond still critical x$fails — re-page suppressed"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
# 2. Sidebar task waiting on human input
|
||||||
|
cond_in="input_wait:$node"
|
||||||
|
wait_summary=$(python3 -c "
|
||||||
|
import json,sys
|
||||||
|
w = json.loads(sys.argv[1]).get('waits', [])
|
||||||
|
if w:
|
||||||
|
print('; '.join(f\"{item.get('task')}: {item.get('status')}\" for item in w)[:120])
|
||||||
|
" "$node_data" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$wait_summary" ]; then
|
||||||
|
failing_in=1
|
||||||
|
detail_in="Agent $node task waiting for human input: $wait_summary"
|
||||||
|
else
|
||||||
|
failing_in=0
|
||||||
|
detail_in="Agent $node tasks running"
|
||||||
|
fi
|
||||||
|
injected "$cond_in" && failing_in=1
|
||||||
|
read -r action_in fails_in < <(state_machine "$cond_in" "$failing_in")
|
||||||
|
case "$action_in" in
|
||||||
|
ALERT_FIRST|ALERT_REALERT)
|
||||||
|
emit_record "ALERT" "$cond_in" "$detail_in" "$fails_in"
|
||||||
|
echo "$cond_in" >> "$STATE_DIR/.alerts.tmp"
|
||||||
|
;;
|
||||||
|
RECOVERY)
|
||||||
|
emit_record "RECOVERY" "$cond_in" "$detail_in" "$fails_in"
|
||||||
|
;;
|
||||||
|
SUPPRESSED)
|
||||||
|
log "$cond_in still critical x$fails_in — re-page suppressed"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# --- Warp partition detection (2026-10-04) ---
|
# --- Warp partition detection (2026-10-04) ---
|
||||||
|
|||||||
+12
-2
@@ -632,7 +632,7 @@ def diagnose_breaks() -> list:
|
|||||||
if app.get("has_pending"):
|
if app.get("has_pending"):
|
||||||
node = app["node"]
|
node = app["node"]
|
||||||
is_trusted = app.get("is_trusted", False)
|
is_trusted = app.get("is_trusted", False)
|
||||||
ip = app.get("ip") or "unknown target"
|
ip = app.get("target") or app.get("ip") or "unknown target"
|
||||||
breaks.append({
|
breaks.append({
|
||||||
"type": "approval_blocked",
|
"type": "approval_blocked",
|
||||||
"severity": "WARNING" if is_trusted else "CRITICAL",
|
"severity": "WARNING" if is_trusted else "CRITICAL",
|
||||||
@@ -641,6 +641,16 @@ def diagnose_breaks() -> list:
|
|||||||
"detail": f"Agent {node} is held up on browser approval for {ip}",
|
"detail": f"Agent {node} is held up on browser approval for {ip}",
|
||||||
"remedy": f"Run 'box approvals auto' or 'box approvals allow {node}'."
|
"remedy": f"Run 'box approvals auto' or 'box approvals allow {node}'."
|
||||||
})
|
})
|
||||||
|
for w in app.get("input_waits") or []:
|
||||||
|
node = app["node"]
|
||||||
|
breaks.append({
|
||||||
|
"type": "input_wait",
|
||||||
|
"severity": "WARNING",
|
||||||
|
"component": f"node:{node}",
|
||||||
|
"agent": node,
|
||||||
|
"detail": f"Agent {node} task '{w.get('task')}' is waiting: {w.get('status')} ({w.get('when')})",
|
||||||
|
"remedy": f"Open {node}'s task and answer it, or 'box approvals check --node {node}'."
|
||||||
|
})
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -758,7 +768,7 @@ def remediate_breaks(dry_run=False) -> dict:
|
|||||||
if app.get("has_pending") and app.get("is_trusted"):
|
if app.get("has_pending") and app.get("is_trusted"):
|
||||||
node = app["node"]
|
node = app["node"]
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
approvals.allow_node_approval(node, caller="loop-remediate")
|
approvals.allow_node_approval(node, always=True, caller="loop-remediate")
|
||||||
remediated.append({
|
remediated.append({
|
||||||
"type": "approval_auto_allowed",
|
"type": "approval_auto_allowed",
|
||||||
"agent": node,
|
"agent": node,
|
||||||
|
|||||||
@@ -503,6 +503,26 @@ def main():
|
|||||||
elif job.get("target"):
|
elif job.get("target"):
|
||||||
target = job.get("target").strip()
|
target = job.get("target").strip()
|
||||||
|
|
||||||
|
# Pre-dispatch approval & input check (auto-approve trusted; warn if blocked)
|
||||||
|
if not dry_run:
|
||||||
|
try:
|
||||||
|
import approvals
|
||||||
|
app_info = approvals.inspect_node_approvals(agent)
|
||||||
|
if app_info.get("has_pending"):
|
||||||
|
if app_info.get("is_trusted"):
|
||||||
|
print(f"Pre-dispatch: auto-approving trusted request for {agent} ({app_info.get('target')})")
|
||||||
|
approvals.allow_node_approval(agent, always=True, caller="job-dispatch")
|
||||||
|
else:
|
||||||
|
print(f"Warning: Agent '{agent}' has untrusted pending approval ({app_info.get('target')}). Dispatch may stall.", file=sys.stderr)
|
||||||
|
log_event("job_dispatch_approval_blocked", {"job_id": job_id, "agent": agent, "target": app_info.get("target")})
|
||||||
|
elif app_info.get("status") == "INPUT_WAIT":
|
||||||
|
waits = app_info.get("input_waits", [])
|
||||||
|
w_desc = "; ".join(w.get("task", "") for w in waits)[:80]
|
||||||
|
print(f"Notice: Agent '{agent}' has task waiting for input ({w_desc}).", file=sys.stderr)
|
||||||
|
log_event("job_dispatch_agent_input_wait", {"job_id": job_id, "agent": agent, "waits": w_desc})
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Sidechat-first policy (2026-10-04): refuse to dispatch to main chat
|
# Sidechat-first policy (2026-10-04): refuse to dispatch to main chat
|
||||||
# unless the job explicitly opts in. Never fall back to main silently.
|
# unless the job explicitly opts in. Never fall back to main silently.
|
||||||
allow_main = bool(job.get("allow_main_chat")) or args.allow_main_chat
|
allow_main = bool(job.get("allow_main_chat")) or args.allow_main_chat
|
||||||
|
|||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
/home/super/Projects/NetVM/bin/docs-lookup.py
|
||||||
@@ -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
|
||||||
@@ -16,20 +16,28 @@ VALID_ACCOUNTS=("muse" "pip" "646" "opm" "def" "dev")
|
|||||||
show_usage() {
|
show_usage() {
|
||||||
echo "Usage: muse <account> <command> [arguments...]"
|
echo "Usage: muse <account> <command> [arguments...]"
|
||||||
echo " muse -a <account> <command> [arguments...]"
|
echo " muse -a <account> <command> [arguments...]"
|
||||||
|
echo " muse <global-command> [arguments...]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Available accounts:"
|
echo "Available accounts:"
|
||||||
for acct in "${VALID_ACCOUNTS[@]}"; do
|
for acct in "${VALID_ACCOUNTS[@]}"; do
|
||||||
echo " • $acct"
|
echo " • $acct"
|
||||||
done
|
done
|
||||||
echo ""
|
echo ""
|
||||||
echo "Common commands:"
|
echo "Global lookups & tools:"
|
||||||
|
echo " tmux [args...] Manage shared Muse tmux sessions (new, send, capture, ls, kill, prune)"
|
||||||
|
echo " status Fleet overview & node vitality"
|
||||||
|
echo " threads List registered threads and sidechats across fleet"
|
||||||
|
echo " unread View unread counts across all agents"
|
||||||
|
echo " lookup [subcommand] Unified lookup (fleet, threads, unread, approvals, key)"
|
||||||
|
echo " passkey (or key) View passkey location (VM-only), PIN, & agent approval protocol"
|
||||||
|
echo ""
|
||||||
|
echo "Per-account commands:"
|
||||||
echo " chat [--thread <id>] Launch interactive conversational shell / REPL"
|
echo " chat [--thread <id>] Launch interactive conversational shell / REPL"
|
||||||
echo " tmux <cmd> [args...] Manage shared Muse tmux sessions (new, send, capture, ls, kill)"
|
echo " status Check account status, sessions, and unread"
|
||||||
echo " status Check agent status, sessions, and unread"
|
echo " threads List active threads and sidechats for account"
|
||||||
echo " threads List active threads and sidechats"
|
|
||||||
echo " history --thread <id> View message history"
|
echo " history --thread <id> View message history"
|
||||||
echo " send --thread <id> msg Send message to an agent"
|
echo " send --thread <id> msg Send message to an agent"
|
||||||
echo " unread View unread counts"
|
echo " unread View unread counts for account"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Authentication & Cookie Management:"
|
echo "Authentication & Cookie Management:"
|
||||||
echo " Cookies are isolated per-node in ~/.config/muse-cli/<account>/"
|
echo " Cookies are isolated per-node in ~/.config/muse-cli/<account>/"
|
||||||
@@ -37,6 +45,40 @@ show_usage() {
|
|||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Direct top-level global actions that do not require an account
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
case "$1" in
|
||||||
|
tmux)
|
||||||
|
shift
|
||||||
|
exec python3 "$NETVM_BIN/muse-tmux.py" "$@"
|
||||||
|
;;
|
||||||
|
passkey|key)
|
||||||
|
shift
|
||||||
|
exec python3 "$NETVM_BIN/super-cli.py" passkey "$@"
|
||||||
|
;;
|
||||||
|
lookup|lookups)
|
||||||
|
shift
|
||||||
|
exec python3 "$NETVM_BIN/super-cli.py" lookup "$@"
|
||||||
|
;;
|
||||||
|
fleet)
|
||||||
|
shift
|
||||||
|
exec python3 "$NETVM_BIN/super-cli.py" fleet "$@"
|
||||||
|
;;
|
||||||
|
threads)
|
||||||
|
shift
|
||||||
|
exec python3 "$NETVM_BIN/super-cli.py" thread list "$@"
|
||||||
|
;;
|
||||||
|
unread)
|
||||||
|
shift
|
||||||
|
exec python3 "$NETVM_BIN/super-cli.py" lookup unread "$@"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
shift
|
||||||
|
exec python3 "$NETVM_BIN/super-cli.py" fleet status "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
ACCOUNT=""
|
ACCOUNT=""
|
||||||
POSITIONAL=()
|
POSITIONAL=()
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Socket location: /tmp/tmux-muse.sock (shared across fleet agents & super).
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
@@ -181,6 +182,13 @@ def cmd_attach(args):
|
|||||||
session = args.session
|
session = args.session
|
||||||
node = getattr(args, "node", None)
|
node = getattr(args, "node", None)
|
||||||
container = getattr(args, "container", None)
|
container = getattr(args, "container", None)
|
||||||
|
|
||||||
|
# Graceful non-interactive fallback for autonomous agents
|
||||||
|
if not sys.stdin.isatty():
|
||||||
|
sys.stderr.write(f"Notice: Non-interactive terminal (no tty). Capturing recent scrollback for '{session}':\n\n")
|
||||||
|
setattr(args, "lines", getattr(args, "lines", 30) or 30)
|
||||||
|
return cmd_capture(args)
|
||||||
|
|
||||||
if node:
|
if node:
|
||||||
cmd = ["/home/super/Projects/NetVM/bin/netvm-exec.sh", node, "--", TMUX_BIN, "-S", f"/tmp/tmux-{node}.sock", "attach", "-t", session]
|
cmd = ["/home/super/Projects/NetVM/bin/netvm-exec.sh", node, "--", TMUX_BIN, "-S", f"/tmp/tmux-{node}.sock", "attach", "-t", session]
|
||||||
os.execv(cmd[0], cmd)
|
os.execv(cmd[0], cmd)
|
||||||
|
|||||||
@@ -280,10 +280,17 @@ def make_digest_id(agent):
|
|||||||
# recursive box->agent->box discipline: post the RESULT back in this thread
|
# recursive box->agent->box discipline: post the RESULT back in this thread
|
||||||
# (box records it and dispatches the next chained step); never DM the next
|
# (box records it and dispatches the next chained step); never DM the next
|
||||||
# agent directly. ~166 chars, within the 600-char digest budget.
|
# agent directly. ~166 chars, within the 600-char digest budget.
|
||||||
CONTRACT_FOOTER = ("Reply: [ACK id] seen | [CLAIM id] mine | "
|
try:
|
||||||
|
import lookup_engine
|
||||||
|
HAS_LOOKUP_ENGINE = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_LOOKUP_ENGINE = False
|
||||||
|
|
||||||
|
_DEFAULT_CONTRACT_FOOTER = ("Reply: [ACK id] seen | [CLAIM id] mine | "
|
||||||
"[RESULT id] done | [DECLINE id] | [NO-ACTION id]. "
|
"[RESULT id] done | [DECLINE id] | [NO-ACTION id]. "
|
||||||
"Report back here. Box dispatches the next step; "
|
"Report back here. Box dispatches the next step; "
|
||||||
"do not DM the next agent directly.")
|
"do not DM the next agent directly.")
|
||||||
|
CONTRACT_FOOTER = lookup_engine.get_contract_footer() if HAS_LOOKUP_ENGINE else _DEFAULT_CONTRACT_FOOTER
|
||||||
|
|
||||||
|
|
||||||
def send_prompt(sender, agent, sidechat, digest):
|
def send_prompt(sender, agent, sidechat, digest):
|
||||||
|
|||||||
+517
-16
@@ -403,7 +403,7 @@ def cmd_approvals(args):
|
|||||||
action = getattr(args, "app_action", None) or "check"
|
action = getattr(args, "app_action", None) or "check"
|
||||||
node = getattr(args, "node", None)
|
node = getattr(args, "node", None)
|
||||||
|
|
||||||
if action in ("check", "list"):
|
if action in ("check", "list", "inspect"):
|
||||||
nodes = [node] if node else VALID_NODES
|
nodes = [node] if node else VALID_NODES
|
||||||
fleet = approvals.check_fleet_approvals(nodes)
|
fleet = approvals.check_fleet_approvals(nodes)
|
||||||
if getattr(args, "json", False):
|
if getattr(args, "json", False):
|
||||||
@@ -415,6 +415,7 @@ def cmd_approvals(args):
|
|||||||
rows = []
|
rows = []
|
||||||
pending_count = 0
|
pending_count = 0
|
||||||
untrusted_count = 0
|
untrusted_count = 0
|
||||||
|
input_wait_count = 0
|
||||||
|
|
||||||
for it in fleet:
|
for it in fleet:
|
||||||
n = it["node"]
|
n = it["node"]
|
||||||
@@ -422,12 +423,28 @@ def cmd_approvals(args):
|
|||||||
if st == "PENDING":
|
if st == "PENDING":
|
||||||
pending_count += 1
|
pending_count += 1
|
||||||
badge = badge_err("PENDING") if not it.get("is_trusted") else badge_warn("PENDING")
|
badge = badge_err("PENDING") if not it.get("is_trusted") else badge_warn("PENDING")
|
||||||
target = it.get("ip") or "-"
|
target = it.get("target") or it.get("ip") or "-"
|
||||||
purp = (it.get("purpose") or it.get("title") or "-")[:50]
|
purp = (it.get("purpose") or it.get("title") or "-")[:50]
|
||||||
trust = c_green("TRUSTED") if it.get("is_trusted") else c_red("UNTRUSTED")
|
trust = c_green("TRUSTED") if it.get("is_trusted") else c_red("UNTRUSTED")
|
||||||
btns = " ".join([f"[{b}]" for b in it.get("buttons", [])])
|
btns = " ".join([f"[{b}]" for b in it.get("buttons", [])])
|
||||||
if not it.get("is_trusted"):
|
if not it.get("is_trusted"):
|
||||||
untrusted_count += 1
|
untrusted_count += 1
|
||||||
|
elif st == "KEY_APPROVAL":
|
||||||
|
pending_count += 1
|
||||||
|
badge = badge_warn("KEY_REQ")
|
||||||
|
target = it.get("target") or "VM passkey"
|
||||||
|
purp = (it.get("purpose") or it.get("title") or "-")[:50]
|
||||||
|
trust = c_cyan("OPERATOR")
|
||||||
|
btns = " ".join([f"[{b}]" for b in it.get("buttons", ["Allow", "Deny"])])
|
||||||
|
untrusted_count += 1
|
||||||
|
elif st == "INPUT_WAIT":
|
||||||
|
waits = it.get("input_waits") or []
|
||||||
|
badge = badge_warn("INPUT")
|
||||||
|
target = "-"
|
||||||
|
purp = "; ".join(f"{w.get('task')}: {w.get('status')}" for w in waits)[:60]
|
||||||
|
trust = "-"
|
||||||
|
btns = c_dim("answer in task")
|
||||||
|
input_wait_count += 1
|
||||||
elif st == "UNREACHABLE":
|
elif st == "UNREACHABLE":
|
||||||
badge = badge_dim("OFFLINE")
|
badge = badge_dim("OFFLINE")
|
||||||
target = "-"
|
target = "-"
|
||||||
@@ -451,16 +468,51 @@ def cmd_approvals(args):
|
|||||||
|
|
||||||
print_table(headers, rows)
|
print_table(headers, rows)
|
||||||
|
|
||||||
|
if input_wait_count > 0:
|
||||||
|
print("\n" + c_yellow(f" ⚠ {input_wait_count} node(s) have tasks waiting for human input (not auto-resolvable)."))
|
||||||
if pending_count > 0:
|
if pending_count > 0:
|
||||||
print("\n" + c_yellow(f" ⚠ {pending_count} pending approval(s) detected across fleet."))
|
print("\n" + c_yellow(f" ⚠ {pending_count} pending approval(s) detected across fleet."))
|
||||||
if untrusted_count > 0:
|
if untrusted_count > 0:
|
||||||
print(c_red(f" ⚠ {untrusted_count} UNTRUSTED approval(s) require manual review: 'box approvals allow <node> --force' or 'box approvals deny <node>'."))
|
print(c_red(f" ⚠ {untrusted_count} approval(s) require manual review / key grant: 'box approvals allow <node>' or 'box approvals deny <node>'."))
|
||||||
else:
|
else:
|
||||||
print(c_cyan(" All pending approvals are for trusted infrastructure. Run 'box approvals auto' to resolve."))
|
print(c_cyan(" All pending approvals are for trusted infrastructure. Run 'box approvals auto' to resolve."))
|
||||||
print()
|
print()
|
||||||
else:
|
elif input_wait_count == 0:
|
||||||
print("\n" + c_green(" ✔ All agent approval queues clear. No agents blocked.") + "\n")
|
print("\n" + c_green(" ✔ All agent approval queues clear. No agents blocked.") + "\n")
|
||||||
|
|
||||||
|
# Verbose or inspect breakdown
|
||||||
|
is_verbose = getattr(args, "verbose", False) or action == "inspect"
|
||||||
|
if is_verbose:
|
||||||
|
print(c_bold("\n=== DETAILED NODE INSPECTION ==="))
|
||||||
|
for it in fleet:
|
||||||
|
n = it["node"]
|
||||||
|
if it.get("has_pending") or it.get("input_waits") or action == "inspect":
|
||||||
|
print(f"\n [{c_bold(n)}] Status: {it.get('status')} | Page: {c_cyan(it.get('page_url') or '-')}")
|
||||||
|
if it.get("has_pending"):
|
||||||
|
print(f" Egress Target: {it.get('target') or it.get('ip')} ({'TRUSTED' if it.get('is_trusted') else 'UNTRUSTED'})")
|
||||||
|
print(f" Prompt Title : {it.get('title')}")
|
||||||
|
if it.get("purpose"):
|
||||||
|
print(f" Purpose : {it.get('purpose')}")
|
||||||
|
print(f" Buttons : {', '.join(it.get('buttons') or [])}")
|
||||||
|
if it.get("input_waits"):
|
||||||
|
print(f" Tasks Awaiting Human Input ({len(it['input_waits'])}):")
|
||||||
|
for w in it["input_waits"]:
|
||||||
|
print(f" • {c_bold(w.get('task'))}")
|
||||||
|
print(f" Status: {w.get('status')} ({w.get('when')})")
|
||||||
|
# Correlate with active subagents on this node
|
||||||
|
try:
|
||||||
|
import subagent_tracker
|
||||||
|
active_subs = subagent_tracker.get_active_sessions(n)
|
||||||
|
if active_subs:
|
||||||
|
print(f" Active Subagents on Node ({len(active_subs)}):")
|
||||||
|
for sub in active_subs[-3:]:
|
||||||
|
s_id = sub.get("session_id", "")[:8]
|
||||||
|
s_title = sub.get("title") or "subagent"
|
||||||
|
print(f" • [{s_id}] {c_bold(s_title)} (spawned: {sub.get('spawned_at', '-')})")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
print()
|
||||||
|
|
||||||
elif action in ("allow", "approve"):
|
elif action in ("allow", "approve"):
|
||||||
if not node:
|
if not node:
|
||||||
print(c_red("Error: Must specify node for allow. e.g. 'box approvals allow 646'"), file=sys.stderr)
|
print(c_red("Error: Must specify node for allow. e.g. 'box approvals allow 646'"), file=sys.stderr)
|
||||||
@@ -472,6 +524,9 @@ def cmd_approvals(args):
|
|||||||
print(json.dumps(res, indent=2))
|
print(json.dumps(res, indent=2))
|
||||||
return
|
return
|
||||||
if res.get("ok"):
|
if res.get("ok"):
|
||||||
|
if res.get("type") == "key_approval":
|
||||||
|
print(c_green(f"✔ Approved operator key request for node '{node}'. Granted and logged to audit trail."))
|
||||||
|
else:
|
||||||
decision_str = "Always allow this site" if always else "Allow once"
|
decision_str = "Always allow this site" if always else "Allow once"
|
||||||
print(c_green(f"✔ Approved request on node '{node}' ({decision_str}). Dialog dismissed: {res.get('dismissed')}."))
|
print(c_green(f"✔ Approved request on node '{node}' ({decision_str}). Dialog dismissed: {res.get('dismissed')}."))
|
||||||
else:
|
else:
|
||||||
@@ -487,6 +542,9 @@ def cmd_approvals(args):
|
|||||||
print(json.dumps(res, indent=2))
|
print(json.dumps(res, indent=2))
|
||||||
return
|
return
|
||||||
if res.get("ok"):
|
if res.get("ok"):
|
||||||
|
if res.get("type") == "key_approval":
|
||||||
|
print(c_green(f"✔ Denied operator key request for node '{node}'. Denied and logged to audit trail."))
|
||||||
|
else:
|
||||||
print(c_green(f"✔ Denied request on node '{node}'. Dialog dismissed: {res.get('dismissed')}."))
|
print(c_green(f"✔ Denied request on node '{node}'. Dialog dismissed: {res.get('dismissed')}."))
|
||||||
else:
|
else:
|
||||||
print(c_red(f"✖ Failed to deny on node '{node}': {res.get('error')}"))
|
print(c_red(f"✖ Failed to deny on node '{node}': {res.get('error')}"))
|
||||||
@@ -535,6 +593,50 @@ def cmd_approvals(args):
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n" + c_dim("Exited watch mode."))
|
print("\n" + c_dim("Exited watch mode."))
|
||||||
|
|
||||||
|
elif action == "reply":
|
||||||
|
if not node:
|
||||||
|
print(c_red("Error: Must specify node for reply. e.g. 'box approvals reply pip \"proceed\"'"), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
message = getattr(args, "message", "")
|
||||||
|
allow_main = getattr(args, "allow_main_chat", False)
|
||||||
|
res = approvals.reply_node_task(node, message, allow_main_chat=allow_main)
|
||||||
|
if getattr(args, "json", False):
|
||||||
|
print(json.dumps(res, indent=2))
|
||||||
|
return
|
||||||
|
if res.get("ok"):
|
||||||
|
print(c_green(f"✔ Dispatched reply to node '{node}': \"{message}\" (thread: {res.get('page_url')})"))
|
||||||
|
else:
|
||||||
|
print(c_red(f"✖ Failed to reply to node '{node}': {res.get('error')}"))
|
||||||
|
sys.exit(2 if "Main Chat" in res.get("error", "") else 1)
|
||||||
|
|
||||||
|
elif action == "dismiss":
|
||||||
|
if not node:
|
||||||
|
print(c_red("Error: Must specify node for dismiss. e.g. 'box approvals dismiss pip'"), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
res = approvals.dismiss_node_task(node)
|
||||||
|
if getattr(args, "json", False):
|
||||||
|
print(json.dumps(res, indent=2))
|
||||||
|
return
|
||||||
|
if res.get("ok"):
|
||||||
|
print(c_green(f"✔ Dismissed task popup on node '{node}' ({res.get('result')})."))
|
||||||
|
else:
|
||||||
|
print(c_red(f"✖ Failed to dismiss task popup on node '{node}': {res.get('error')}"))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
elif action in ("request-key", "request_key"):
|
||||||
|
if not node:
|
||||||
|
print(c_red("Error: Must specify node for request-key. e.g. 'box approvals request-key 646'"), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
reason = getattr(args, "reason", "Operator passkey access requested")
|
||||||
|
caller = os.environ.get("BOX_CALLER") or getattr(args, "from_agent", "super")
|
||||||
|
res = approvals.request_key_approval(node, reason=reason, caller=caller)
|
||||||
|
if getattr(args, "json", False):
|
||||||
|
print(json.dumps(res, indent=2))
|
||||||
|
return
|
||||||
|
print(c_green(f"✔ Registered passkey approval request for node '{node}'."))
|
||||||
|
print(f" Reason: {c_cyan(reason)}")
|
||||||
|
print(c_dim(f" Operator can approve with: box approvals allow {node}"))
|
||||||
|
|
||||||
def resolve_sender(args) -> str:
|
def resolve_sender(args) -> str:
|
||||||
explicit = getattr(args, "from_agent", None)
|
explicit = getattr(args, "from_agent", None)
|
||||||
if explicit and explicit != DEFAULT_SENDER:
|
if explicit and explicit != DEFAULT_SENDER:
|
||||||
@@ -864,6 +966,15 @@ def cmd_dm_send(args):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print(c_yellow(" ⚠ [CHAT POLICY OVERRIDE] Main Chat targeted with --allow-main-chat. Keep interaction minimal."))
|
print(c_yellow(" ⚠ [CHAT POLICY OVERRIDE] Main Chat targeted with --allow-main-chat. Keep interaction minimal."))
|
||||||
|
|
||||||
|
# Soft pre-flight grammar check against lookup_internal
|
||||||
|
try:
|
||||||
|
import lookup_engine
|
||||||
|
is_val, kind, hint = lookup_engine.validate_outbound_sentence(message)
|
||||||
|
if not is_val and hint:
|
||||||
|
print(c_yellow(f"\n ⚠ [GRAMMAR NOTICE]\n {hint}\n"), file=sys.stderr)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
should_sign = (sender == "super") and not getattr(args, "no_sign", False)
|
should_sign = (sender == "super") and not getattr(args, "no_sign", False)
|
||||||
mid = None
|
mid = None
|
||||||
if should_sign:
|
if should_sign:
|
||||||
@@ -929,7 +1040,8 @@ def cmd_dm_wo(args):
|
|||||||
|
|
||||||
wo_id = hashlib.sha256(f"{sender}-{recipient}-{title}-{time.time()}".encode()).hexdigest()[:8]
|
wo_id = hashlib.sha256(f"{sender}-{recipient}-{title}-{time.time()}".encode()).hexdigest()[:8]
|
||||||
prefix = "[URGENT] " if priority == "urgent" else ""
|
prefix = "[URGENT] " if priority == "urgent" else ""
|
||||||
wo_content = f"{prefix}[WO:{wo_id}] {title} — {body}"
|
wo_content = f"{prefix}[WO:{wo_id}] [from {sender}] {title} — {body}"
|
||||||
|
|
||||||
|
|
||||||
should_sign = (sender == "super") and not getattr(args, "no_sign", False)
|
should_sign = (sender == "super") and not getattr(args, "no_sign", False)
|
||||||
if should_sign:
|
if should_sign:
|
||||||
@@ -1338,7 +1450,31 @@ def cmd_dm_files(args):
|
|||||||
# Domain: THREAD (Chat Oversight via box-chat.py)
|
# Domain: THREAD (Chat Oversight via box-chat.py)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
def cmd_thread_list(args):
|
def cmd_thread_list(args):
|
||||||
agent = args.agent
|
agent = getattr(args, "agent", None)
|
||||||
|
if not agent:
|
||||||
|
print("\n" + c_bold("=== FLEET REGISTERED SIDECHATS & THREAD MAPPINGS ===") + "\n")
|
||||||
|
sc_file = NETVM_ROOT / "job-sidechats.json"
|
||||||
|
rows = []
|
||||||
|
headers = ["TARGET / ALIAS", "AGENT", "THREAD UUID", "PURPOSE / NOTES"]
|
||||||
|
if sc_file.exists():
|
||||||
|
try:
|
||||||
|
with open(sc_file, "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for k, v in sorted(data.items()):
|
||||||
|
if isinstance(v, dict):
|
||||||
|
ag = v.get("agent", "-")
|
||||||
|
uuid = v.get("thread_uuid") or v.get("uuid") or "-"
|
||||||
|
desc = v.get("description") or v.get("purpose") or "-"
|
||||||
|
rows.append([c_cyan(k), c_bold(ag), uuid, desc[:45]])
|
||||||
|
elif isinstance(v, str):
|
||||||
|
rows.append([c_cyan(k), "-", v, "-"])
|
||||||
|
except Exception as e:
|
||||||
|
print(c_red(f"Error reading job-sidechats.json: {e}"))
|
||||||
|
print_table(headers, rows)
|
||||||
|
print("\n" + c_dim(" To view specific thread: box thread view <agent> <uuid|alias>") + "\n")
|
||||||
|
print(c_dim(" To list agent active sessions: box thread list <agent>") + "\n")
|
||||||
|
return
|
||||||
|
|
||||||
threads = []
|
threads = []
|
||||||
|
|
||||||
def is_noise(title):
|
def is_noise(title):
|
||||||
@@ -1401,6 +1537,29 @@ def cmd_thread_view(args):
|
|||||||
limit = getattr(args, "limit", 15)
|
limit = getattr(args, "limit", 15)
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
|
# 1. Resolve alias or name if known
|
||||||
|
try:
|
||||||
|
import dm
|
||||||
|
resolved = dm.resolve_sidechat_target(thread_id, agent)
|
||||||
|
if resolved and resolved != thread_id:
|
||||||
|
thread_id = resolved
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 2. If short UUID prefix (6-12 hex chars), resolve against agent's thread list
|
||||||
|
if re.fullmatch(r"[0-9a-fA-F]{6,12}", str(thread_id).strip()):
|
||||||
|
try:
|
||||||
|
import muse_hybrid
|
||||||
|
gw_threads, _ = muse_hybrid.get_threads(agent)
|
||||||
|
if gw_threads:
|
||||||
|
for t in gw_threads:
|
||||||
|
sid = t.get("session_id", "")
|
||||||
|
if sid.lower().startswith(str(thread_id).strip().lower()):
|
||||||
|
thread_id = sid
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Fast path: try fast headless gateway via muse_hybrid (isolated per-node WARP egress)
|
# Fast path: try fast headless gateway via muse_hybrid (isolated per-node WARP egress)
|
||||||
try:
|
try:
|
||||||
import muse_hybrid
|
import muse_hybrid
|
||||||
@@ -3735,14 +3894,278 @@ def cmd_swarm_prune(args):
|
|||||||
sys.stdout.write(res.stdout)
|
sys.stdout.write(res.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_passkey_info(args):
|
||||||
|
"""Display operator passkey reference and agent approval architecture, or fetch from VM."""
|
||||||
|
action = getattr(args, "action", "show") or "show"
|
||||||
|
is_json = getattr(args, "json", False)
|
||||||
|
|
||||||
|
if action in ("fetch", "get"):
|
||||||
|
vm_host = "34.139.37.135"
|
||||||
|
vm_paths = ["/srv/box/passkey.txt", "/etc/netvm/passkey.txt"]
|
||||||
|
cmd = [
|
||||||
|
"ssh",
|
||||||
|
"-o", "BatchMode=yes",
|
||||||
|
"-o", "ConnectTimeout=3",
|
||||||
|
f"super@{vm_host}",
|
||||||
|
f"cat {vm_paths[0]} 2>/dev/null || cat {vm_paths[1]} 2>/dev/null"
|
||||||
|
]
|
||||||
|
key_content = ""
|
||||||
|
fetch_err = None
|
||||||
|
try:
|
||||||
|
res = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
|
||||||
|
if res.returncode == 0 and res.stdout.strip():
|
||||||
|
key_content = res.stdout.strip()
|
||||||
|
else:
|
||||||
|
fetch_err = res.stderr.strip() or "Empty output or authentication required"
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
fetch_err = "SSH connection timed out"
|
||||||
|
except Exception as e:
|
||||||
|
fetch_err = str(e)
|
||||||
|
|
||||||
|
if key_content:
|
||||||
|
if is_json:
|
||||||
|
print(json.dumps({
|
||||||
|
"ok": True,
|
||||||
|
"fetched": True,
|
||||||
|
"passkey": key_content,
|
||||||
|
"vm_host": vm_host,
|
||||||
|
"path": vm_paths[0],
|
||||||
|
"operator_pin": "3128",
|
||||||
|
"surface": "https://box.muse-dev.online/"
|
||||||
|
}, indent=2))
|
||||||
|
return
|
||||||
|
print("\n" + c_bold("=== OPERATOR PASSKEY (FETCHED FROM VM) ===") + "\n")
|
||||||
|
print(f" {c_bold('Passkey Content')}: {c_green(key_content)}")
|
||||||
|
print(f" {c_bold('Source Host')} : {c_cyan(vm_host)} ({vm_paths[0]})")
|
||||||
|
print(f" {c_bold('Web Surface')} : https://box.muse-dev.online/ (PIN: 3128)\n")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if is_json:
|
||||||
|
print(json.dumps({
|
||||||
|
"ok": False,
|
||||||
|
"fetched": False,
|
||||||
|
"vm_host": vm_host,
|
||||||
|
"path": vm_paths[0],
|
||||||
|
"fallback_path": vm_paths[1],
|
||||||
|
"operator_pin": "3128",
|
||||||
|
"surface": "https://box.muse-dev.online/",
|
||||||
|
"error": f"Could not fetch passkey directly: {fetch_err}",
|
||||||
|
"note": "Passkey is stored in a single .txt file on the VM (34.139.37.135) only, NOT on BL (100.123.153.75).",
|
||||||
|
"operator_command": f"ssh super@{vm_host} 'cat {vm_paths[0]}'",
|
||||||
|
"agent_approval_command": "box approvals request-key <node> --reason '<reason>'"
|
||||||
|
}, indent=2))
|
||||||
|
return
|
||||||
|
print("\n" + c_bold("=== VM PASSKEY FETCH PROBE ===") + "\n")
|
||||||
|
print(f" {c_bold('Target VM Host')} : {c_cyan(vm_host)}")
|
||||||
|
print(f" {c_bold('Target Paths')} : {vm_paths[0]} (fallback: {vm_paths[1]})")
|
||||||
|
print(f" {c_bold('Probe Result')} : {c_yellow('Direct SSH access unavailable (' + (fetch_err or 'auth required') + ')')}\n")
|
||||||
|
print(c_bold(" Location Reality (VM vs BL):"))
|
||||||
|
print(f" • {c_yellow('VM (' + vm_host + ')')} : Key material lives in a {c_bold('single text file (.txt)')} on the VM.")
|
||||||
|
print(f" • {c_red('BL (100.123.153.75)')} : {c_bold('NO passkey / secret material exists on the dedicated BL.')}\n")
|
||||||
|
print(f" {c_bold('Console PIN')} : {c_green('3128')} (sets session cookie: {c_dim('ops_session')} at {c_cyan('https://box.muse-dev.online/')})\n")
|
||||||
|
print(c_bold(" Operator Access:"))
|
||||||
|
print(f" ssh super@{vm_host} 'cat {vm_paths[0]}'\n")
|
||||||
|
print(c_bold(" Agent UX / Approval Request:"))
|
||||||
|
print(f" Agents must not hunt BL. If passkey authorization is needed, request it via:")
|
||||||
|
print(f" {c_cyan('box approvals request-key <node> --reason \"<reason>\"')}\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_json:
|
||||||
|
print(json.dumps({
|
||||||
|
"ok": True,
|
||||||
|
"surface": "https://box.muse-dev.online/",
|
||||||
|
"operator_pin": "3128",
|
||||||
|
"session_cookie": "ops_session",
|
||||||
|
"key_location": {
|
||||||
|
"host": "Google Cloud VM (34.139.37.135)",
|
||||||
|
"canonical_path": "/srv/box/passkey.txt",
|
||||||
|
"fallback_path": "/etc/netvm/passkey.txt",
|
||||||
|
"note": "Stored in a single text file (.txt) on the VM — NOT on the dedicated BL compute node (100.123.153.75)",
|
||||||
|
"credstore_path": "/etc/netvm/meta-credentials/store.age",
|
||||||
|
"credstore_key": "/etc/netvm/meta-credentials/.age-key"
|
||||||
|
},
|
||||||
|
"operator_fetch_cmd": "ssh super@34.139.37.135 'cat /srv/box/passkey.txt'",
|
||||||
|
"agent_approval_protocol": {
|
||||||
|
"rule": "Agents do not hold administrative passkeys or credentials directly. Secrets never reside on bl.",
|
||||||
|
"request_flow": "1. Agent signals APPROVAL_REQUEST: key=<reason> or runs 'box approvals request-key <node>'.\n2. Operator verifies request.\n3. Operator retrieves key/PIN from VM single text file (.txt) or submits OTP.\n4. Operator approves via 'box approvals allow <node>'.",
|
||||||
|
"request_command": "box approvals request-key <node> --reason '<reason>'",
|
||||||
|
"sidechats": ["646 tasks", "pip tasks", "#jobs", "heartbeat"]
|
||||||
|
}
|
||||||
|
}, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n" + c_bold("=== OPERATOR PASSKEY & KEY MATERIAL ARCHITECTURE ===") + "\n")
|
||||||
|
print(f" {c_bold('Box Front-Door Console')}: {c_cyan('https://box.muse-dev.online/')}")
|
||||||
|
print(f" {c_bold('Operator PIN / Passkey')}: {c_green('3128')} (sets session cookie: {c_dim('ops_session')})\n")
|
||||||
|
|
||||||
|
print(c_bold(" Location Reality (VM vs BL):"))
|
||||||
|
print(f" • {c_yellow('VM (34.139.37.135)')} : Key material lives in a {c_bold('single text file (.txt)')} on the VM.")
|
||||||
|
print(f" - Canonical path: {c_cyan('/srv/box/passkey.txt')}")
|
||||||
|
print(f" - Fallback path : {c_cyan('/etc/netvm/passkey.txt')}")
|
||||||
|
print(f" - Fetch command : {c_dim('box passkey fetch')}")
|
||||||
|
print(f" • {c_red('BL (100.123.153.75)')} : {c_bold('NO passkey / secret material exists on the dedicated BL.')}")
|
||||||
|
print(f" • Credstore on VM : /etc/netvm/meta-credentials/store.age (age-encrypted, root 600)")
|
||||||
|
print(f" • Age Key on VM : /etc/netvm/meta-credentials/.age-key\n")
|
||||||
|
|
||||||
|
print(c_bold(" Agent UX & Operator Approval Flow:"))
|
||||||
|
print(f" 1. {c_cyan('Never hunt on bl')} : Agents must never attempt to grep or locate passkeys on bl.")
|
||||||
|
print(f" 2. {c_cyan('Signal Approval')} : If an agent requires key access or operator privilege:")
|
||||||
|
print(f" - Run: {c_yellow('box approvals request-key <node> --reason \"<reason>\"')}")
|
||||||
|
print(f" - Or emit {c_yellow('APPROVAL_NEEDED: <details>')} in task sidechat")
|
||||||
|
print(f" - Or exit with code {c_yellow('2')} (per INFRA.md convention)")
|
||||||
|
print(f" 3. {c_cyan('Operator Action')} : Operator consults the single .txt file on the VM to authenticate")
|
||||||
|
print(f" and approves via {c_dim('box approvals allow <node>')}.")
|
||||||
|
print(f" 4. {c_cyan('Target Sidechats')} : Use dedicated sidechats ({c_dim('646 tasks, pip tasks, #jobs, heartbeat')}).\n")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _lookup_unreads(args):
|
||||||
|
print("\n" + c_bold("=== FLEET UNREAD / ACTIVITY STATUS ===") + "\n")
|
||||||
|
headers = ["NODE", "UNREAD COUNT", "ACTIVE PAGE / TITLE", "LAST SEEN / THREAD"]
|
||||||
|
rows = []
|
||||||
|
data = collect_fleet_data()
|
||||||
|
for item in data:
|
||||||
|
n = item["node"]
|
||||||
|
title = item.get("title", "")
|
||||||
|
unread = "0"
|
||||||
|
if "(1)" in title or "(2)" in title or "(3)" in title:
|
||||||
|
m = re.search(r"\((\d+)\)", title)
|
||||||
|
unread = c_yellow(m.group(1)) if m else c_yellow("1+")
|
||||||
|
elif item.get("approval_pending"):
|
||||||
|
unread = c_yellow("APPROVAL")
|
||||||
|
|
||||||
|
thread_info = "-"
|
||||||
|
if "thread/" in item.get("url", ""):
|
||||||
|
thread_info = item["url"].split("thread/")[-1][:12]
|
||||||
|
elif item.get("url") == "https://muse.ai/":
|
||||||
|
thread_info = "home"
|
||||||
|
|
||||||
|
rows.append([c_bold(n), unread, title[:45], thread_info])
|
||||||
|
print_table(headers, rows)
|
||||||
|
print("\n" + c_dim(" To view unread messages: box thread view <node> <thread>") + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_lookup(args):
|
||||||
|
"""Seamless unified lookup across nodes, threads, unreads, approvals, and keys."""
|
||||||
|
lookup_args = getattr(args, "lookup_args", []) or []
|
||||||
|
if "--json" in lookup_args:
|
||||||
|
setattr(args, "json", True)
|
||||||
|
sub = getattr(args, "target", None)
|
||||||
|
if not sub or sub in ("all", "summary"):
|
||||||
|
print("\n" + c_bold("=== SEAMLESS FLEET LOOKUP SUMMARY ===") + c_dim(f" ({datetime.now().strftime('%H:%M:%S')} local)\n"))
|
||||||
|
# 1. Fleet status
|
||||||
|
cmd_fleet_status(args)
|
||||||
|
# 2. Approvals summary
|
||||||
|
try:
|
||||||
|
import approvals
|
||||||
|
app_list = approvals.check_fleet_approvals(VALID_NODES)
|
||||||
|
pending = [a for a in app_list if a.get("status") == "PENDING"]
|
||||||
|
if pending:
|
||||||
|
print(c_yellow(f" ⚠ {len(pending)} pending approval(s): {', '.join(a['node'] for a in pending)} (run: box approvals)"))
|
||||||
|
else:
|
||||||
|
print(c_green(" ✔ Approval Queues: All clean"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# 3. Key note
|
||||||
|
print(c_dim(" ℹ Passkey: Stored in single .txt on VM (34.139.37.135), not bl. (run: box passkey)"))
|
||||||
|
print()
|
||||||
|
return
|
||||||
|
|
||||||
|
if sub in ("key", "passkey", "auth"):
|
||||||
|
cmd_passkey_info(args)
|
||||||
|
elif sub in ("fleet", "nodes"):
|
||||||
|
cmd_fleet_status(args)
|
||||||
|
elif sub in ("threads", "sidechats"):
|
||||||
|
cmd_thread_list(args)
|
||||||
|
elif sub in ("approvals", "approval"):
|
||||||
|
cmd_approvals(args)
|
||||||
|
elif sub in ("docs", "doc", "surfaces", "sentence", "regex", "parse"):
|
||||||
|
extra = getattr(args, "lookup_args", []) or []
|
||||||
|
sub_args = [sub] if sub not in ("docs", "doc") else []
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "docs-lookup.py")] + sub_args + extra
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
else:
|
||||||
|
print(f"Unknown lookup target '{sub}'. Choose from: fleet, threads, unread, approvals, key, docs", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_docs_dispatch(args):
|
||||||
|
"""Bridge 'box docs' and 'super docs' directly to bin/docs-lookup.py."""
|
||||||
|
d_args = getattr(args, "docs_args", []) or []
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "docs-lookup.py")] + d_args
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
|
||||||
|
|
||||||
def cmd_tmux_dispatch(args):
|
def cmd_tmux_dispatch(args):
|
||||||
"""Bridge 'box tmux' commands directly to bin/muse-tmux.py."""
|
"""Bridge 'box tmux' commands directly to bin/muse-tmux.py."""
|
||||||
tmux_bin = str(BIN_DIR / "muse-tmux.py")
|
tmux_bin = str(BIN_DIR / "muse-tmux.py")
|
||||||
subcmd = args.tmux_action or "list"
|
t_args = getattr(args, "tmux_args", []) or []
|
||||||
cmd = [sys.executable, tmux_bin, subcmd] + (args.tmux_args or [])
|
if not t_args:
|
||||||
|
t_args = ["list"]
|
||||||
|
cmd = [sys.executable, tmux_bin] + t_args
|
||||||
res = subprocess.run(cmd)
|
res = subprocess.run(cmd)
|
||||||
sys.exit(res.returncode)
|
sys.exit(res.returncode)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_muse_dispatch(args):
|
||||||
|
"""Bridge 'box muse' commands to muse-cli-node or interactive REPL / multi-node lookups."""
|
||||||
|
m_args = getattr(args, "muse_args", []) or []
|
||||||
|
if not m_args:
|
||||||
|
cmd = [str(BIN_DIR / "muse"), "--help"]
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
|
||||||
|
first = m_args[0]
|
||||||
|
|
||||||
|
# If first is 'tmux', dispatch to tmux
|
||||||
|
if first == "tmux":
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "muse-tmux.py")] + m_args[1:]
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
|
||||||
|
# If first is a cross-fleet query
|
||||||
|
if first in ("status", "fleet"):
|
||||||
|
cmd_fleet_status(args)
|
||||||
|
return
|
||||||
|
|
||||||
|
if first in ("passkey", "key"):
|
||||||
|
cmd_passkey_info(args)
|
||||||
|
return
|
||||||
|
|
||||||
|
if first in ("lookup", "lookups"):
|
||||||
|
setattr(args, "target", m_args[1] if len(m_args) > 1 else None)
|
||||||
|
cmd_lookup(args)
|
||||||
|
return
|
||||||
|
|
||||||
|
if first in ("threads", "sidechats"):
|
||||||
|
cmd_thread_list(args)
|
||||||
|
return
|
||||||
|
|
||||||
|
if first in ("unread", "unreads"):
|
||||||
|
_lookup_unreads(args)
|
||||||
|
return
|
||||||
|
|
||||||
|
# If first is a valid node
|
||||||
|
if first in VALID_NODES:
|
||||||
|
node = first
|
||||||
|
subargs = m_args[1:]
|
||||||
|
if not subargs:
|
||||||
|
subargs = ["status"]
|
||||||
|
if subargs[0] == "chat":
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "muse-chat-repl.py"), node] + subargs[1:]
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
cmd = [str(BIN_DIR / "muse-cli-node"), node] + subargs
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
|
||||||
|
# Fallback to bin/muse wrapper
|
||||||
|
cmd = [str(BIN_DIR / "muse")] + m_args
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
|
||||||
|
|
||||||
def build_parser():
|
def build_parser():
|
||||||
common = argparse.ArgumentParser(add_help=False)
|
common = argparse.ArgumentParser(add_help=False)
|
||||||
common.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
common.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
||||||
@@ -3775,9 +4198,14 @@ def build_parser():
|
|||||||
|
|
||||||
p_app_check = app_sub.add_parser("check", parents=[common], help="Check fleet approval states")
|
p_app_check = app_sub.add_parser("check", parents=[common], help="Check fleet approval states")
|
||||||
p_app_check.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
|
p_app_check.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
|
||||||
|
p_app_check.add_argument("-v", "--verbose", action="store_true", help="Show detailed task prompts, thread URLs, and card text")
|
||||||
|
|
||||||
p_app_list = app_sub.add_parser("list", parents=[common], help="Alias for 'check'")
|
p_app_list = app_sub.add_parser("list", parents=[common], help="Alias for 'check'")
|
||||||
p_app_list.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
|
p_app_list.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
|
||||||
|
p_app_list.add_argument("-v", "--verbose", action="store_true", help="Show detailed task prompts, thread URLs, and card text")
|
||||||
|
|
||||||
|
p_app_inspect = app_sub.add_parser("inspect", parents=[common], help="Inspect detailed approval and input-wait status on a node")
|
||||||
|
p_app_inspect.add_argument("node", choices=VALID_NODES, help="Target node to inspect")
|
||||||
|
|
||||||
p_app_allow = app_sub.add_parser("allow", parents=[common], help="Approve pending browser request")
|
p_app_allow = app_sub.add_parser("allow", parents=[common], help="Approve pending browser request")
|
||||||
p_app_allow.add_argument("node", choices=VALID_NODES, help="Target node to approve")
|
p_app_allow.add_argument("node", choices=VALID_NODES, help="Target node to approve")
|
||||||
@@ -3800,6 +4228,18 @@ def build_parser():
|
|||||||
p_app_watch.add_argument("--auto", action="store_true", help="Automatically approve trusted requests as they appear")
|
p_app_watch.add_argument("--auto", action="store_true", help="Automatically approve trusted requests as they appear")
|
||||||
p_app_watch.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
|
p_app_watch.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
|
||||||
|
|
||||||
|
p_app_reply = app_sub.add_parser("reply", parents=[common], help="Reply to a waiting agent task/prompt in its active thread")
|
||||||
|
p_app_reply.add_argument("node", choices=VALID_NODES, help="Target node to reply to")
|
||||||
|
p_app_reply.add_argument("message", help="Message or answer to dispatch")
|
||||||
|
p_app_reply.add_argument("--allow-main-chat", action="store_true", help="Explicit override if the active thread is Main Chat")
|
||||||
|
|
||||||
|
p_app_dismiss = app_sub.add_parser("dismiss", parents=[common], help="Dismiss any open task dialog/popup on a node")
|
||||||
|
p_app_dismiss.add_argument("node", choices=VALID_NODES, help="Target node to dismiss dialog on")
|
||||||
|
|
||||||
|
p_app_req_key = app_sub.add_parser("request-key", parents=[common], help="Request operator passkey/key approval for an agent")
|
||||||
|
p_app_req_key.add_argument("node", choices=VALID_NODES, help="Target node requesting key")
|
||||||
|
p_app_req_key.add_argument("--reason", default="Passkey authentication required", help="Reason for key request")
|
||||||
|
|
||||||
# Domain: DM
|
# Domain: DM
|
||||||
p_dm = subparsers.add_parser("dm", parents=[common], help="Inter-agent DMs, work orders ([WO]), acks, live log tail")
|
p_dm = subparsers.add_parser("dm", parents=[common], help="Inter-agent DMs, work orders ([WO]), acks, live log tail")
|
||||||
dm_sub = p_dm.add_subparsers(dest="action")
|
dm_sub = p_dm.add_subparsers(dest="action")
|
||||||
@@ -3878,8 +4318,8 @@ def build_parser():
|
|||||||
p_thread = subparsers.add_parser("thread", parents=[common], help="Inspect agent main chats, sidechats, and scrollbacks")
|
p_thread = subparsers.add_parser("thread", parents=[common], help="Inspect agent main chats, sidechats, and scrollbacks")
|
||||||
thread_sub = p_thread.add_subparsers(dest="action")
|
thread_sub = p_thread.add_subparsers(dest="action")
|
||||||
|
|
||||||
p_thread_list = thread_sub.add_parser("list", parents=[common], help="List active threads for an agent")
|
p_thread_list = thread_sub.add_parser("list", parents=[common], help="List active threads for an agent or all fleet sidechats")
|
||||||
p_thread_list.add_argument("agent", choices=VALID_NODES, help="Agent node to inspect")
|
p_thread_list.add_argument("agent", nargs="?", default=None, choices=VALID_NODES + [None], help="Agent node to inspect (default: all registered fleet sidechats)")
|
||||||
|
|
||||||
p_thread_view = thread_sub.add_parser("view", parents=[common], help="View recent messages from an agent's thread")
|
p_thread_view = thread_sub.add_parser("view", parents=[common], help="View recent messages from an agent's thread")
|
||||||
p_thread_view.add_argument("agent", choices=VALID_NODES, help="Agent node to inspect")
|
p_thread_view.add_argument("agent", choices=VALID_NODES, help="Agent node to inspect")
|
||||||
@@ -4255,17 +4695,74 @@ def build_parser():
|
|||||||
|
|
||||||
# Domain: TMUX (Headless background tmux sessions with automatic logging)
|
# Domain: TMUX (Headless background tmux sessions with automatic logging)
|
||||||
p_tmux = subparsers.add_parser("tmux", parents=[common], help="Manage headless background tmux sessions on /tmp/tmux-muse.sock")
|
p_tmux = subparsers.add_parser("tmux", parents=[common], help="Manage headless background tmux sessions on /tmp/tmux-muse.sock")
|
||||||
p_tmux.add_argument("tmux_action", nargs="?", default="list", choices=["list", "ls", "new", "send", "send-keys", "capture", "cap", "tail", "kill", "prune", "attach"], help="tmux action (default: list)")
|
|
||||||
p_tmux.add_argument("tmux_args", nargs=argparse.REMAINDER, help="Arguments passed directly to muse-tmux.py")
|
p_tmux.add_argument("tmux_args", nargs=argparse.REMAINDER, help="Arguments passed directly to muse-tmux.py")
|
||||||
|
|
||||||
# Domain: muse (fast headless gateway via muse-cli-node with isolated per-node Cloudflare WARP egress)
|
# Domain: muse (fast headless gateway via muse-cli-node with isolated per-node Cloudflare WARP egress)
|
||||||
p_muse = subparsers.add_parser("muse", parents=[common], help="Direct headless gateway client (muse-cli-node)")
|
p_muse = subparsers.add_parser("muse", parents=[common], help="Direct headless gateway client (muse-cli-node)")
|
||||||
p_muse.add_argument("node", choices=VALID_NODES, help="Target agent node")
|
p_muse.add_argument("muse_args", nargs=argparse.REMAINDER, help="Arguments passed to muse-cli-node or fleet lookups")
|
||||||
p_muse.add_argument("muse_args", nargs=argparse.REMAINDER, help="Arguments passed directly to muse-cli-node")
|
|
||||||
|
# Domain: LOOKUP (Unified seamless lookups across nodes, threads, unread, approvals, key, docs)
|
||||||
|
p_lookup = subparsers.add_parser("lookup", aliases=["lookups"], parents=[common], help="Seamless fleet lookups (summary, fleet, threads, unread, approvals, key, docs)")
|
||||||
|
p_lookup.add_argument("target", nargs="?", default="summary", choices=["summary", "fleet", "nodes", "threads", "sidechats", "unread", "unreads", "approvals", "key", "passkey", "docs", "doc", "surfaces", "sentence", "regex", "parse"], help="Lookup target")
|
||||||
|
p_lookup.add_argument("lookup_args", nargs=argparse.REMAINDER, help="Additional arguments passed to lookup engine")
|
||||||
|
|
||||||
|
# Domain: PASSKEY (Operator passkey location & agent approval protocol)
|
||||||
|
p_passkey = subparsers.add_parser("passkey", aliases=["key"], parents=[common], help="Display operator passkey location (VM-only), PIN, & agent approval protocol")
|
||||||
|
p_passkey.add_argument("action", nargs="?", default="show", choices=["show", "fetch", "get"], help="Passkey action: 'show' details or 'fetch' from VM")
|
||||||
|
|
||||||
|
# Domain: DOCS (Internal agent lookups, surfaces, sentence grammar, and regex engine)
|
||||||
|
p_docs = subparsers.add_parser("docs", aliases=["doc"], parents=[common], help="Agent lookups, surfaces for box.muse-dev.online, sentence grammar & regex")
|
||||||
|
p_docs.add_argument("docs_args", nargs=argparse.REMAINDER, help="Arguments passed directly to docs-lookup.py")
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
if sys.argv[1] == "tmux":
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "muse-tmux.py")] + (sys.argv[2:] or ["list"])
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
elif sys.argv[1] in ("docs", "doc"):
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "docs-lookup.py")] + sys.argv[2:]
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
elif sys.argv[1] == "muse":
|
||||||
|
m_args = sys.argv[2:]
|
||||||
|
if not m_args:
|
||||||
|
cmd = [str(BIN_DIR / "muse"), "--help"]
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
if m_args[0] == "tmux":
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "muse-tmux.py")] + (m_args[1:] or ["list"])
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
if m_args[0] in ("status", "fleet"):
|
||||||
|
sys.argv = [sys.argv[0], "fleet", "status"]
|
||||||
|
elif m_args[0] in ("passkey", "key"):
|
||||||
|
sys.argv = [sys.argv[0], "passkey"] + m_args[1:]
|
||||||
|
elif m_args[0] in ("lookup", "lookups"):
|
||||||
|
sys.argv = [sys.argv[0], "lookup"] + m_args[1:]
|
||||||
|
elif m_args[0] in ("threads", "sidechats"):
|
||||||
|
sys.argv = [sys.argv[0], "thread", "list"] + m_args[1:]
|
||||||
|
elif m_args[0] in ("unread", "unreads"):
|
||||||
|
sys.argv = [sys.argv[0], "lookup", "unread"]
|
||||||
|
elif m_args[0] in VALID_NODES:
|
||||||
|
node = m_args[0]
|
||||||
|
sub = m_args[1:]
|
||||||
|
if not sub:
|
||||||
|
sub = ["status"]
|
||||||
|
if sub[0] == "chat":
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "muse-chat-repl.py"), node] + sub[1:]
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
cmd = [str(BIN_DIR / "muse-cli-node"), node] + sub
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
else:
|
||||||
|
cmd = [str(BIN_DIR / "muse")] + m_args
|
||||||
|
res = subprocess.run(cmd)
|
||||||
|
sys.exit(res.returncode)
|
||||||
|
|
||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
# Default behavior with no arguments: show fleet status
|
# Default behavior with no arguments: show fleet status
|
||||||
@@ -4489,12 +4986,16 @@ def main():
|
|||||||
if args.domain == "subagent":
|
if args.domain == "subagent":
|
||||||
args.action = "subagent"
|
args.action = "subagent"
|
||||||
cmd_deploy(args)
|
cmd_deploy(args)
|
||||||
|
elif args.domain in ("lookup", "lookups"):
|
||||||
|
cmd_lookup(args)
|
||||||
|
elif args.domain in ("passkey", "key"):
|
||||||
|
cmd_passkey_info(args)
|
||||||
elif args.domain == "tmux":
|
elif args.domain == "tmux":
|
||||||
cmd_tmux_dispatch(args)
|
cmd_tmux_dispatch(args)
|
||||||
elif args.domain == "muse":
|
elif args.domain == "muse":
|
||||||
cmd = [str(BIN_DIR / "muse-cli-node"), args.node] + (args.muse_args or [])
|
cmd_muse_dispatch(args)
|
||||||
res = subprocess.run(cmd)
|
elif args.domain in ("docs", "doc"):
|
||||||
sys.exit(res.returncode)
|
cmd_docs_dispatch(args)
|
||||||
else:
|
else:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|
||||||
|
|||||||
+82
-3
@@ -51,7 +51,15 @@ box dm send --agent 646 --to pip --target 646-pip "Hey Pip, start-page onboardin
|
|||||||
You can directly interact with the headless Muse gateway inside your isolated network namespace using either `muse` or `box muse`:
|
You can directly interact with the headless Muse gateway inside your isolated network namespace using either `muse` or `box muse`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Using native muse wrapper (interactive prompt & account enforcement)
|
# Global lookups & fleet status (no account required)
|
||||||
|
muse status # Complete fleet overview & node vitality
|
||||||
|
muse threads # List registered threads and sidechats across fleet
|
||||||
|
muse unread # View unread counts across all agents
|
||||||
|
muse lookup # Unified lookup (summary of fleet, approvals, unread)
|
||||||
|
muse passkey (or muse key) # Passkey reference (VM .txt location) & agent approval flow
|
||||||
|
muse tmux list # List shared tmux sessions across fleet
|
||||||
|
|
||||||
|
# Using native muse wrapper for per-account actions:
|
||||||
muse -a <account> chat # Interactive conversational REPL with thread selection
|
muse -a <account> chat # Interactive conversational REPL with thread selection
|
||||||
muse -a <account> chat --thread <id> # Direct conversational REPL in specified thread
|
muse -a <account> chat --thread <id> # Direct conversational REPL in specified thread
|
||||||
muse -a <account> status
|
muse -a <account> status
|
||||||
@@ -59,19 +67,67 @@ muse -a <account> threads
|
|||||||
muse -a <account> history --thread <thread_uuid> --limit 10
|
muse -a <account> history --thread <thread_uuid> --limit 10
|
||||||
muse -a <account> send --thread <thread_uuid> "<message>"
|
muse -a <account> send --thread <thread_uuid> "<message>"
|
||||||
|
|
||||||
# If invoked without -a/--account, it displays valid accounts and usage instructions:
|
# If invoked without arguments, it displays available accounts and commands:
|
||||||
muse
|
muse
|
||||||
|
|
||||||
# Alternatively via box CLI:
|
# Alternatively via box CLI:
|
||||||
box muse <self> threads
|
box muse status # Cross-fleet status
|
||||||
|
box muse <self> status # Agent-specific status
|
||||||
|
box muse <self> threads # Active sessions for agent
|
||||||
box muse <self> history --thread <thread_uuid> --limit 10
|
box muse <self> history --thread <thread_uuid> --limit 10
|
||||||
box muse <self> unread
|
box muse <self> unread
|
||||||
|
box muse <self> chat # Launch interactive chat REPL
|
||||||
box muse <self> session-start --title "<title>"
|
box muse <self> session-start --title "<title>"
|
||||||
box muse <self> send --thread <thread_uuid> "<message>"
|
box muse <self> send --thread <thread_uuid> "<message>"
|
||||||
|
box muse tmux list # Direct bridge to muse-tmux manager
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 3.1. Unified & Seamless Lookups (`box lookup` & `box thread`)
|
||||||
|
|
||||||
|
For fast inspection of fleet state without hunting across multiple tools:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unified lookup summary (fleet health, pending approvals, key reference)
|
||||||
|
box lookup
|
||||||
|
box lookup fleet # Node health, CDP status, active pages
|
||||||
|
box lookup threads # List all registered fleet sidechats and mapped UUIDs
|
||||||
|
box lookup threads <agent> # List active threads for a specific agent
|
||||||
|
box lookup unread # Unread indicators and active tabs across fleet
|
||||||
|
box lookup approvals # Check if any agent is held on browser approvals
|
||||||
|
box lookup key (or box passkey) # Operator passkey & approval protocol
|
||||||
|
|
||||||
|
# Seamless thread inspection:
|
||||||
|
box thread list # List all registered fleet sidechats across agents
|
||||||
|
box thread list <agent> # List active sessions for an agent
|
||||||
|
box thread view <agent> <uuid> # View recent thread messages (supports short UUID prefix)
|
||||||
|
box thread view <agent> "<alias>" # View thread by registered alias (e.g. "646 tasks", "heartbeat")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.2. Operator Passkey & Key Material Architecture
|
||||||
|
|
||||||
|
> **Crucial Reality**: Key material and administrative passkeys live in a **single file (`.txt`) on the Google Cloud VM (`34.139.37.135`)**. **NO passkeys or secret stores exist on the dedicated BL (`100.123.153.75`)**.
|
||||||
|
|
||||||
|
### Why This Matters:
|
||||||
|
- Front-door console access (`https://box.muse-dev.online/`) is secured by operator PIN `3128` (or the passkey in the VM text file).
|
||||||
|
- Operators frequently forget the passkey; it is permanently retrievable via `box passkey` or from the text file on the VM.
|
||||||
|
- **Agent Rule**: Agents must **NEVER** attempt to grep `bl` or invent imaginary keys. Secrets never reside on the compute node.
|
||||||
|
|
||||||
|
### How Agents Get Key Access / Operator Approval:
|
||||||
|
When an agent or automated task requires elevated privileges, key material, or operator confirmation:
|
||||||
|
1. **Signal the Request**:
|
||||||
|
- In automated scripts: exit with code `2` (the standard `APPROVAL_NEEDED` convention per `INFRA.md`).
|
||||||
|
- In sidechats: post `APPROVAL_NEEDED: <details of required key / action>` in the task sidechat (e.g. `646 tasks`, `pip tasks`, `#jobs`, `heartbeat`).
|
||||||
|
2. **Operator Verification**:
|
||||||
|
- The human operator reviews the request in the sidechat or via `box approvals check`.
|
||||||
|
- If approved, the operator retrieves the key from the single `.txt` file on the VM (or submits transient OTP via `box cred submit-otp`).
|
||||||
|
3. **Execution**:
|
||||||
|
- The operator authorizes the flow or enters the credential transiently. No raw credentials are saved to `bl` or git.
|
||||||
|
|
||||||
|
|
||||||
## 4. Shared & Hybrid Tmux Tooling (`muse tmux`, `box tmux`, & `[TOOL tmux.*]`)
|
## 4. Shared & Hybrid Tmux Tooling (`muse tmux`, `box tmux`, & `[TOOL tmux.*]`)
|
||||||
|
|
||||||
Agents and operators can spawn background sessions and send keystrokes to long-running tasks across three execution tiers:
|
Agents and operators can spawn background sessions and send keystrokes to long-running tasks across three execution tiers:
|
||||||
@@ -147,3 +203,26 @@ When jobs are dispatched to agents via `bin/job-dispatch.py`, they are wrapped i
|
|||||||
2. **Sub-Agent Prioritization**: Break down complex diagnostic or verification jobs by delegating sub-tasks to dedicated subagent threads.
|
2. **Sub-Agent Prioritization**: Break down complex diagnostic or verification jobs by delegating sub-tasks to dedicated subagent threads.
|
||||||
3. **Execution Reality**: Work is only real if tool calls ran. Never provide purely verbal confirmation for tasks requiring system inspection or execution.
|
3. **Execution Reality**: Work is only real if tool calls ran. Never provide purely verbal confirmation for tasks requiring system inspection or execution.
|
||||||
4. **Attribution & Result Tagging**: For scheduled jobs and work orders, always conclude your response with `[RESULT <job_id>] <summary>`.
|
4. **Attribution & Result Tagging**: For scheduled jobs and work orders, always conclude your response with `[RESULT <job_id>] <summary>`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Internal Documentation & Agent Lookups (`box docs` & `docs_internal/`)
|
||||||
|
|
||||||
|
Agents have access to a structured internal `.md` and `.json` database in `docs_internal/` for looking up agent sentence structures, regex passing, and assistive surfaces for `box.muse-dev.online`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Query surfaces, DOM selectors, and REST endpoints for box.muse-dev.online
|
||||||
|
box docs surfaces jobs
|
||||||
|
box docs surfaces dms
|
||||||
|
|
||||||
|
# Inspect agent sentence structures and conversational contracts
|
||||||
|
box docs sentence work_order
|
||||||
|
box docs sentence result
|
||||||
|
|
||||||
|
# Test strings or evaluate against canonical regex patterns
|
||||||
|
box docs regex result --test "[RESULT 7fce46e0] OK 14 endpoints verified"
|
||||||
|
box docs parse "[WO:7fce46e0] [from super] Audit exec — Check stats"
|
||||||
|
|
||||||
|
# Full-text search across documentation database
|
||||||
|
box docs search "work order"
|
||||||
|
```
|
||||||
|
|||||||
@@ -39,12 +39,15 @@ flowchart LR
|
|||||||
### Trust & Identity Principles
|
### Trust & Identity Principles
|
||||||
1. **Host as Source of Truth (`bl`)**: All mutating state, cryptographic keys, job definitions, variables, and browser CDP relays reside on `bl`. The VM does not persist authoritative fleet state.
|
1. **Host as Source of Truth (`bl`)**: All mutating state, cryptographic keys, job definitions, variables, and browser CDP relays reside on `bl`. The VM does not persist authoritative fleet state.
|
||||||
2. **On-Demand Tailnet SSH Bridge**: The VM board service bridges into allowlisted verbs in [`bin/box-ctl.py`](file:///home/super/Projects/NetVM/bin/box-ctl.py) using an on-demand Tailnet SSH connection (`super@100.123.153.75`).
|
2. **On-Demand Tailnet SSH Bridge**: The VM board service bridges into allowlisted verbs in [`bin/box-ctl.py`](file:///home/super/Projects/NetVM/bin/box-ctl.py) using an on-demand Tailnet SSH connection (`super@100.123.153.75`).
|
||||||
3. **Session Authentication & Brute-Force Defense**:
|
3. **Session Authentication & Passkey Location Reality**:
|
||||||
- Operator console access is unlocked via `POST /api/ops/login` with operator PIN `3128`, establishing cookie `ops_session`.
|
- Operator console access is unlocked via `POST /api/ops/login` with operator PIN `3128`, establishing cookie `ops_session`.
|
||||||
|
- **Crucial Passkey Reality**: In reality, the passkey material is stored in a **single file (`.txt`) on the Google Cloud VM (`34.139.37.135`)**, **NOT on the dedicated BL (`100.123.153.75`)**.
|
||||||
|
- Operators frequently forget this passkey; retrieve anytime via `box passkey` or from the text file on the VM.
|
||||||
- Successful PIN logins bypass the rate limiter. Failed attempts are constrained by a 10-attempt sliding window.
|
- Successful PIN logins bypass the rate limiter. Failed attempts are constrained by a 10-attempt sliding window.
|
||||||
4. **Agent-Scoped Privacy Tiers**:
|
4. **Agent-Scoped Privacy Tiers & Approval Flow**:
|
||||||
- `ops_session` grants unredacted administrative visibility across all DMs, logs, loops, and actions.
|
- `ops_session` grants unredacted administrative visibility across all DMs, logs, loops, and actions.
|
||||||
- Unauthenticated or agent-scoped queries receive redacted logs filtered to their respective identities.
|
- Unauthenticated or agent-scoped queries receive redacted logs filtered to their respective identities.
|
||||||
|
- **Agent Key Requests**: Agents do not hold administrative keys directly and must never hunt on `bl` for secrets. When elevated key access or approval is required, agents signal `APPROVAL_NEEDED: <details>` in task sidechats (or exit code 2). Operators verify and fulfill from the single `.txt` file on the VM.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
lookup_internal
|
||||||
+1859
-17
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "test-tmux-wo",
|
||||||
|
"description": "Verification job: execute background tmux task and report output",
|
||||||
|
"agent": "pip",
|
||||||
|
"schedule": null,
|
||||||
|
"timeout": 180,
|
||||||
|
"prompt_template": "Execute a verification command in your background tmux session and capture the output.\n1. Run [TOOL tmux.new {\"session\": \"worker-pip-test\", \"command\": \"bash\"}]\n2. Run [TOOL tmux.send {\"session\": \"worker-pip-test\", \"keys\": \"hostname && whoami && echo TMUX_VERIFIED_SUCCESS\"}]\n3. Capture the output with [TOOL tmux.capture {\"session\": \"worker-pip-test\", \"lines\": 10}]\n4. When done, reply with [RESULT {job_id}] OK: tmux test verified.",
|
||||||
|
"sidechat": {
|
||||||
|
"create": true,
|
||||||
|
"reuse_key": "test-tmux-wo",
|
||||||
|
"name_template": "test-tmux-wo-{date}"
|
||||||
|
},
|
||||||
|
"chain_next": null,
|
||||||
|
"on_failure": "alert"
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# CLI Tools Reference & Grammar
|
||||||
|
|
||||||
|
> **Box is the main surface.** All operator work goes through Box (`box.muse-dev.online`). The web UI, `box` CLI, and agents share the same API endpoints. No UI-only powers.
|
||||||
|
|
||||||
|
NetVM provides a unified command line toolchain anchored by `box` and `super` (`/usr/local/bin/box` and `/home/super/.local/bin/super`), both pointing to the off-board orchestrator script [`bin/super-cli.py`](file:///home/super/Projects/NetVM/bin/super-cli.py).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Quick Command Summary
|
||||||
|
|
||||||
|
```text
|
||||||
|
box <domain> <action> [options]
|
||||||
|
super <domain> <action> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Domain | Description | Common Invocation |
|
||||||
|
|---|---|---|
|
||||||
|
| `fleet` | Node status, CDP ports, watch | `box fleet status` |
|
||||||
|
| `approvals` | Browser approvals & input waits | `box approvals check -v` |
|
||||||
|
| `dm` | Work orders, Acks, log tail | `box dm send --agent 646 --to pip ...` |
|
||||||
|
| `thread` | Thread listings & message inspection | `box thread list` |
|
||||||
|
| `job` | Timers, scheduled triggers, logs | `box job run box-http-health` |
|
||||||
|
| `loop` | Loop health, open loops, nudges | `box loop status` |
|
||||||
|
| `vars` | Dynamic runtime variables | `box vars set max_nudge_count 3` |
|
||||||
|
| `tmux` | Headless background tmux workers | `box tmux new worker1 --command "bash"` |
|
||||||
|
| `deploy` | Spawn subagents & pipelines | `box deploy subagent --agent 646 ...` |
|
||||||
|
| `docs` | Internal docs, regexes, & surfaces | `box docs surfaces dashboard` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Using `box docs` for Real-Time Lookup
|
||||||
|
|
||||||
|
Autonomous agents should query the `docs` domain whenever they need schema verification, regex passing, or surface selector lookups:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search across all internal databases
|
||||||
|
box docs search "work order"
|
||||||
|
|
||||||
|
# Inspect assistive surfaces on box.muse-dev.online
|
||||||
|
box docs surfaces jobs
|
||||||
|
|
||||||
|
# Look up sentence grammar specifications
|
||||||
|
box docs sentence result
|
||||||
|
|
||||||
|
# Test an utterance through the regex engine
|
||||||
|
box docs parse "[RESULT 7fce46e0] OK Finished task"
|
||||||
|
```
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Fleet Topology & Agent Identities
|
||||||
|
|
||||||
|
> **Box is the main surface.** All operator work goes through Box (`box.muse-dev.online`). The web UI, `box` CLI, and agents share the same API endpoints. No UI-only powers.
|
||||||
|
|
||||||
|
NetVM manages four primary autonomous browser agents and two development/staging nodes isolated inside Linux network namespaces (`warp-*`) on compute host `bl` (`100.123.153.75`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Node Inventory
|
||||||
|
|
||||||
|
| Node | NetNS | CDP Port | Peer IP | Default Sidechat | Primary Role |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `muse` | `warp-muse` | 9410 | 10.201.35.2 | `muse tasks` | General assistant, UI testing |
|
||||||
|
| `pip` | `warp-pip` | 9411 | 10.201.35.3 | `646-pip-coord` | Pipeline execution & driver |
|
||||||
|
| `646` | `warp-646` | 9412 | 10.201.35.4 | `646 tasks` | Operator-646, code & bridge |
|
||||||
|
| `opm` | `warp-opm` | 9413 | 10.201.35.5 | `heartbeat` | Operator-main supervisor |
|
||||||
|
| `dev` | `warp-dev` | 9414 | 10.201.35.6 | `dev tasks` | Development sandbox |
|
||||||
|
| `def` | `warp-def` | 9415 | 10.201.35.7 | `default` | Fallback routing node |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Headless Gateway & CLI Access
|
||||||
|
|
||||||
|
Each node has its own isolated Cloudflare WARP egress. To interact with a node:
|
||||||
|
```bash
|
||||||
|
# Gateway REPL
|
||||||
|
muse -a 646 chat
|
||||||
|
|
||||||
|
# Direct headless status
|
||||||
|
box muse 646 status
|
||||||
|
|
||||||
|
# Bounce node
|
||||||
|
box fleet restart 646
|
||||||
|
```
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Internal Agent & Operator Documentation (`docs_internal/`)
|
||||||
|
|
||||||
|
> **Box is the main surface.** All operator work goes through Box (`box.muse-dev.online`). The web UI, `box` CLI, and agents share the same API endpoints, protocol grammar, and lookup structures.
|
||||||
|
|
||||||
|
Welcome to `docs_internal/`. This directory serves as a dual Markdown (`.md`) and structured JSON (`.json`) lookup database for autonomous agents, background daemons, and off-board operators across NetVM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Directory Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs_internal/
|
||||||
|
├── manifest.json # Master database manifest and collections schema
|
||||||
|
├── README.md # This file
|
||||||
|
├── sentence_structure.json # Agent sentence structures, protocols, and grammar
|
||||||
|
├── SENTENCE-STRUCTURE.md # Comprehensive guide to agent communication protocols
|
||||||
|
├── regex_patterns.json # Master regular expression database with test fixtures
|
||||||
|
├── REGEX-PATTERNS.md # Guide to regex parsing, capture groups, and testing
|
||||||
|
├── assistive_surfaces.json # UI tabs, DOM selectors, data-testids & REST endpoints for box.muse-dev.online
|
||||||
|
├── SURFACES.md # Guide to interacting with box.muse-dev.online
|
||||||
|
├── cli_tools.json # CLI command catalog and syntax database
|
||||||
|
├── CLI-TOOLS.md # Guide to super, box, and muse CLI tools
|
||||||
|
├── fleet_nodes.json # Fleet node topology, namespaces, and ports
|
||||||
|
└── FLEET-NODES.md # Guide to fleet agents and network topology
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Using the CLI Lookup Engine
|
||||||
|
|
||||||
|
You can query this database directly from the shell using `super docs` or `box docs`:
|
||||||
|
|
||||||
|
### 2.1 Full-Text Search
|
||||||
|
```bash
|
||||||
|
box docs search "work order"
|
||||||
|
box docs search "cdp"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Inspect Assistive Surfaces for `box.muse-dev.online`
|
||||||
|
```bash
|
||||||
|
# List all surfaces and views
|
||||||
|
box docs surfaces
|
||||||
|
|
||||||
|
# Inspect a specific surface (e.g. jobs, dms, loops, dashboard)
|
||||||
|
box docs surfaces jobs
|
||||||
|
box docs surfaces dms
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Inspect Agent Sentence Grammar & Protocols
|
||||||
|
```bash
|
||||||
|
# List all sentence structures
|
||||||
|
box docs sentence
|
||||||
|
|
||||||
|
# View full structure specification
|
||||||
|
box docs sentence work_order
|
||||||
|
box docs sentence result
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Regex Passing & Testing
|
||||||
|
```bash
|
||||||
|
# List available regex patterns
|
||||||
|
box docs regex
|
||||||
|
|
||||||
|
# Test a string against a specific pattern
|
||||||
|
box docs regex work_order --test "[WO:7fce46e0] [from super] Audit — Check status"
|
||||||
|
|
||||||
|
# Parse an agent sentence through all patterns to extract captured tokens
|
||||||
|
box docs parse "[RESULT 7fce46e0] OK Verified 14 endpoints"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 Machine-Readable JSON Output
|
||||||
|
Add `--json` to any command for structured JSON output:
|
||||||
|
```bash
|
||||||
|
box docs sentence work_order --json
|
||||||
|
box docs surfaces dashboard --json
|
||||||
|
box docs parse "[CLAIM 7fce46e0]" --json
|
||||||
|
```
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Master Regex Patterns & Passing Reference
|
||||||
|
|
||||||
|
> **Box is the main surface.** All operator work goes through Box (`box.muse-dev.online`). The web UI, `box` CLI, and agents share the same API endpoints and parsing patterns.
|
||||||
|
|
||||||
|
This document serves as the canonical reference for regular expressions used across NetVM's orchestrators, harvesters, loop state managers, and agent message parsers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Quick Pattern Directory
|
||||||
|
|
||||||
|
| Pattern Key | Purpose | Match Example |
|
||||||
|
|---|---|---|
|
||||||
|
| `work_order` | Parses `[WO:<id>] [from <sender>] ...` | `[WO:7fce46e0] [from super] Audit — Check stats` |
|
||||||
|
| `ack` | Parses `[ACK:<id>] [from <sender>]` | `[ACK:7fce46e0] [from 646]` |
|
||||||
|
| `verb` | Matches `[ACK\|CLAIM\|RESULT\|DECLINE\|NO-ACTION <id>]` | `[CLAIM 7fce46e0]` |
|
||||||
|
| `result` | Parses `[RESULT <id>] <status> <summary>` | `[RESULT 7fce46e0] OK 14 endpoints healthy` |
|
||||||
|
| `tool_call` | Parses inline `[TOOL <op> <args>]` | `[TOOL followup.create {"in_m": 5}]` |
|
||||||
|
| `job_tag` | Matches `[JOB <id>]` | `[JOB ml-646-20261005]` |
|
||||||
|
| `nudge` | Parses followup SLA nudges | `[NUDGE 7fce46e0] [nudge 1/3] Deadline 19:45: ...` |
|
||||||
|
| `fleet_alert` | Parses alert broadcasts | `[fleet-alert] CRITICAL: VM unreachable x2` |
|
||||||
|
| `uuid` | RFC 4122 UUID v4 detector | `dc6d72ca-4b02-4217-bf41-7dbca19c5c24` |
|
||||||
|
| `short_hex` | 6-12 character hex IDs | `7fce46e0` |
|
||||||
|
| `subagent_update`| Subagent status notices | `[SUBAGENT-UPDATE] Subagent 'audit' (4a2b8e) ...` |
|
||||||
|
| `swarm_slot` | Swarm worker slot targets | `[JOB swarm-99a/1]` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Core Patterns & Named Capture Groups
|
||||||
|
|
||||||
|
### 2.1 Work Order Pattern (`work_order`)
|
||||||
|
```python
|
||||||
|
r"^\[WO:(?P<wo_id>[a-f0-9-]+)\]\s+\[from\s+(?P<sender>[a-zA-Z0-9_-]+)\]\s+(?P<title>[^—\n]+?)\s*—\s*(?P<body>.+)$"
|
||||||
|
```
|
||||||
|
* **Flags**: `re.MULTILINE`, `re.DOTALL`
|
||||||
|
* **Groups**:
|
||||||
|
- `wo_id`: The unique tracking ID.
|
||||||
|
- `sender`: Originating identity (`super`, `646`, `pip`, etc.).
|
||||||
|
- `title`: Short task name preceding the em-dash (`—`).
|
||||||
|
- `body`: Instruction payload following the em-dash.
|
||||||
|
|
||||||
|
### 2.2 Standard Verbs (`verb`)
|
||||||
|
```python
|
||||||
|
r"\[(?P<verb>ACK|CLAIM|RESULT|DECLINE|NO-ACTION)\s+(?P<job_id>[A-Za-z0-9_/-]+)\]"
|
||||||
|
```
|
||||||
|
* **Groups**:
|
||||||
|
- `verb`: One of the 5 canonical lifecycle verbs.
|
||||||
|
- `job_id`: Target job, work order, or swarm slot.
|
||||||
|
|
||||||
|
### 2.3 Task Result (`result`)
|
||||||
|
```python
|
||||||
|
r"\[RESULT\s+(?P<job_id>[A-Za-z0-9_/-]+)\]\s*(?P<status>OK|FAIL|DECLINE|SUCCESS|ERROR)?\s*(?P<summary>.*?)(?=\[RESULT\s|\Z)"
|
||||||
|
```
|
||||||
|
* **Flags**: `re.DOTALL`
|
||||||
|
* **Groups**:
|
||||||
|
- `job_id`: Target task ID.
|
||||||
|
- `status`: Optional explicit status keyword.
|
||||||
|
- `summary`: Detailed response text.
|
||||||
|
|
||||||
|
### 2.4 In-Band Tool Directives (`tool_call`)
|
||||||
|
```python
|
||||||
|
r"\[(?P<engine>TOOL|EXEC)\s+(?P<op>[a-zA-Z0-9_.-]+)\s+(?P<args>\{.*?\})\]"
|
||||||
|
```
|
||||||
|
* **Groups**:
|
||||||
|
- `engine`: `TOOL` or `EXEC`.
|
||||||
|
- `op`: Method name (e.g. `followup.create`, `health.check`).
|
||||||
|
- `args`: Embedded JSON argument payload.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. CLI Pattern Passing & Validation
|
||||||
|
|
||||||
|
The unified CLI allows testing strings directly against these patterns:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test a string against a specific pattern
|
||||||
|
super docs regex result --test "[RESULT 7fce46e0] OK Task complete"
|
||||||
|
box docs regex work_order --test "[WO:8a12bc44] [from super] Audit — verify 200"
|
||||||
|
|
||||||
|
# Parse an agent utterance through ALL registered patterns
|
||||||
|
super docs parse "[RESULT 7fce46e0] OK Verified 14 endpoints"
|
||||||
|
box docs parse "[TOOL followup.create {\"in_m\": 5, \"prompt\": \"check\"}]"
|
||||||
|
|
||||||
|
# View raw JSON pattern definition
|
||||||
|
super docs get regex_patterns work_order --json
|
||||||
|
```
|
||||||
|
|
||||||
|
When run with `--json`, the parser outputs structured dictionary mappings suitable for programmatic ingestion by subagents.
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Agent Sentence Structures & Conversational Grammar
|
||||||
|
|
||||||
|
> **Box is the main surface.** All operator work goes through Box (`box.muse-dev.online`). The web UI, `box` CLI, and agents share the same API endpoints and protocol grammar.
|
||||||
|
|
||||||
|
This master document defines the standardized sentence structures, bracketed markers, conversational contracts, and grammar rules governing autonomous agent communication across NetVM and Box.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Overview & Protocol Lifecycle
|
||||||
|
|
||||||
|
Agent communication relies on structured, machine-parsable prefixes embedded into natural language sentences. This allows human operators and autonomous language models to read the same stream while automation engines (`response-harvester.py`, `followup-sweeper.py`, `self_main_loop.py`) extract telemetry, update loop states, and trigger downstream events.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Dispatched: [WO:id] Work Order
|
||||||
|
Dispatched --> Acknowledged: [ACK:id]
|
||||||
|
Dispatched --> Claimed: [CLAIM id]
|
||||||
|
Acknowledged --> Claimed: [CLAIM id]
|
||||||
|
Claimed --> Running: [TOOL op args] / execution
|
||||||
|
Running --> Resolved: [RESULT id] OK <summary>
|
||||||
|
Claimed --> Declined: [DECLINE id] <reason>
|
||||||
|
Claimed --> NoAction: [NO-ACTION id] <reason>
|
||||||
|
Running --> Nudged: [NUDGE id] (SLA warning)
|
||||||
|
Nudged --> Resolved: [RESULT id]
|
||||||
|
Resolved --> [*]
|
||||||
|
Declined --> [*]
|
||||||
|
NoAction --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Core Sentence Structures
|
||||||
|
|
||||||
|
### 2.1 Work Order (`[WO:<id>]`)
|
||||||
|
* **Purpose**: Tasking dispatched across operators or from the off-board orchestrator. Creates a tracked loop with an SLA deadline.
|
||||||
|
* **Format**:
|
||||||
|
```text
|
||||||
|
[WO:<dm_id>] [from <sender>] <title> — <body>
|
||||||
|
```
|
||||||
|
* **Required Components**:
|
||||||
|
- `dm_id`: Unique identifier (hex string, e.g. `7fce46e0`).
|
||||||
|
- `sender`: Author identity (`super`, `646`, `pip`, `opm`, `muse`).
|
||||||
|
- `title`: Short task description.
|
||||||
|
- `body`: Detailed instructions and acceptance criteria.
|
||||||
|
* **Example**:
|
||||||
|
```text
|
||||||
|
[WO:8a12bc44] [from super] Audit exec.muse-dev.online endpoints — Verify that allowlisted ops respond with 200 OK.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Acknowledgement (`[ACK:<id>]`)
|
||||||
|
* **Purpose**: Confirms delivery and receipt of a Work Order or message, preventing re-dispatch.
|
||||||
|
* **Format**:
|
||||||
|
```text
|
||||||
|
[ACK:<dm_id>] [from <sender>]
|
||||||
|
```
|
||||||
|
* **Example**:
|
||||||
|
```text
|
||||||
|
[ACK:8a12bc44] [from 646]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Task Claim (`[CLAIM <id>]`)
|
||||||
|
* **Purpose**: Declares exclusive ownership of a task or swarm worker slot.
|
||||||
|
* **Format**:
|
||||||
|
```text
|
||||||
|
[CLAIM <job_id>]
|
||||||
|
```
|
||||||
|
* **Example**:
|
||||||
|
```text
|
||||||
|
[CLAIM 8a12bc44]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Task Completion Result (`[RESULT <id>]`)
|
||||||
|
* **Purpose**: Delivers final evidence or outcome, closing the active loop and recording success/failure in `job-log.jsonl`.
|
||||||
|
* **Format**:
|
||||||
|
```text
|
||||||
|
[RESULT <job_id>] <status> <summary>
|
||||||
|
```
|
||||||
|
*(Status options: `OK`, `FAIL`, `SUCCESS`, `DECLINE`)*
|
||||||
|
* **Example**:
|
||||||
|
```text
|
||||||
|
[RESULT 8a12bc44] OK Verified 14 endpoints; all return valid 200 responses with expected schemas.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 Task Decline & No-Action
|
||||||
|
* **Decline Format**:
|
||||||
|
```text
|
||||||
|
[DECLINE <job_id>] <reason>
|
||||||
|
```
|
||||||
|
* **No-Action Format**:
|
||||||
|
```text
|
||||||
|
[NO-ACTION <job_id>] <reason>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.6 In-Band Tool Calls (`[TOOL <op> <args>]`)
|
||||||
|
* **Purpose**: Inline directive parsed by `response-harvester.py` and executed directly on the host or inside a node netns.
|
||||||
|
* **Format**:
|
||||||
|
```text
|
||||||
|
[TOOL <op> <args_json>]
|
||||||
|
```
|
||||||
|
* **Example**:
|
||||||
|
```text
|
||||||
|
[TOOL followup.create {"in_m": 5, "prompt": "Re-check Cloudflare WARP proxy status"}]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.7 Loop Followup Nudge (`[NUDGE <id>]`)
|
||||||
|
* **Purpose**: Automated escalation sent to an agent when an SLA deadline is approaching or breached.
|
||||||
|
* **Format**:
|
||||||
|
```text
|
||||||
|
[NUDGE <loop_id>] [nudge <count>/<max_nudges>] Deadline <time_utc>: <prompt>
|
||||||
|
```
|
||||||
|
* **Example**:
|
||||||
|
```text
|
||||||
|
[NUDGE 8a12bc44] [nudge 1/3] Deadline 19:45 UTC: Please confirm status of exec audit.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.8 Fleet Alert (`[fleet-alert]`)
|
||||||
|
* **Purpose**: Infrastructure health broadcasts dispatched to `#lobby` and `#jobs`.
|
||||||
|
* **Format**:
|
||||||
|
```text
|
||||||
|
[fleet-alert] <SEVERITY>: <message>
|
||||||
|
```
|
||||||
|
* **Example**:
|
||||||
|
```text
|
||||||
|
[fleet-alert] CRITICAL: VM unreachable x2 on port 22
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Conversational Contract Footer
|
||||||
|
|
||||||
|
When issuing prompts to model instances, orchestrators append the **Contract Footer**:
|
||||||
|
```text
|
||||||
|
Reply: [ACK id] seen | [CLAIM id] mine | [RESULT id] done | [DECLINE id] | [NO-ACTION id].
|
||||||
|
```
|
||||||
|
This forces strict conformance to parsable reply tokens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Querying from CLI
|
||||||
|
|
||||||
|
Agents and operators can look up these sentence structures using the unified CLI:
|
||||||
|
```bash
|
||||||
|
# View list of all structures
|
||||||
|
super docs sentence
|
||||||
|
box docs sentence
|
||||||
|
|
||||||
|
# View specific structure specification
|
||||||
|
super docs sentence work_order
|
||||||
|
super docs sentence result
|
||||||
|
|
||||||
|
# Validate or parse an utterance
|
||||||
|
super docs parse "[WO:7fce46e0] [from super] Run audit — check endpoints"
|
||||||
|
```
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Assistive Surfaces Reference — box.muse-dev.online
|
||||||
|
|
||||||
|
> **Box is the main surface.** All operator work goes through Box (`box.muse-dev.online`). The web UI, `box` CLI, and agents share the same API endpoints. No UI-only powers.
|
||||||
|
|
||||||
|
This guide provides autonomous agents and operators with detailed maps of the visual DOM, interactive controls, API backends, and assistive interaction recipes for `box.muse-dev.online`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Global Layout & Authentication
|
||||||
|
|
||||||
|
* **Production URL**: `https://box.muse-dev.online/`
|
||||||
|
* **Google Cloud VM**: `34.139.37.135`
|
||||||
|
* **Authoritative Compute Host**: `bl` (`100.123.153.75`)
|
||||||
|
* **Operator PIN**: `3128` (establishes `ops_session` cookie via `POST /api/ops/login`)
|
||||||
|
* **Agent Authentication**: Header `Authorization: Bearer <operator-token>` or SSH-keygen signature.
|
||||||
|
|
||||||
|
### Layout Wireframe
|
||||||
|
```text
|
||||||
|
+----------------------------------------------------------------------------------+
|
||||||
|
| [BOX] Fleet Console box.muse-dev.online ● [pulse] [</> API] [◐ theme] |
|
||||||
|
+----------------------------------------------------------------------------------+
|
||||||
|
| [Fleet Pulse] [DMs & Work Orders (N)] [Timers & Jobs] [Loop & Strategy (N)]|
|
||||||
|
+----------------------------------------------------------------------------------+
|
||||||
|
| |
|
||||||
|
| <Active Pane Content: Cards / Tables / Modals / Curldrawer> |
|
||||||
|
| |
|
||||||
|
+----------------------------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Views & Navigation Map
|
||||||
|
|
||||||
|
### 2.1 Fleet Pulse (`#tab-dashboard`)
|
||||||
|
* **Tab Button**: `.tab-btn[data-tab="dashboard"]`
|
||||||
|
* **Key Selectors**:
|
||||||
|
- `#fleet-grid`: Grid container for node cards.
|
||||||
|
- `.node-card`: Card representing an agent (`muse`, `pip`, `646`, `opm`).
|
||||||
|
- `.node-badge`: Status badge (`UP`, `WARN`, `DOWN`).
|
||||||
|
- `#fleet-pulse`: Global heartbeat animation.
|
||||||
|
* **REST API**:
|
||||||
|
- `GET /api/box/fleet`
|
||||||
|
- Returns array of `{ node, netns, peer_ip, cdp_port, proc_alive, title, latency_ms }`.
|
||||||
|
|
||||||
|
### 2.2 DMs & Work Orders (`#tab-dms`)
|
||||||
|
* **Tab Button**: `.tab-btn[data-tab="dms"]`
|
||||||
|
* **Key Selectors**:
|
||||||
|
- `#dms-table`: Log stream table.
|
||||||
|
- `#dms-tbody`: Dynamic row container.
|
||||||
|
- `#filter-search`: Search text filter input.
|
||||||
|
- `.filter-agent-chip`: Filter by agent (`all`, `super`, `muse`, `pip`, `646`, `opm`).
|
||||||
|
- `.filter-kind-chip`: Filter by message kind (`all`, `workorder`, `ack`, `chat`).
|
||||||
|
* **REST API**:
|
||||||
|
- `GET /api/box/dm/log?limit=50`
|
||||||
|
- `POST /api/box/dm/send`
|
||||||
|
|
||||||
|
### 2.3 Timers & Scheduled Jobs (`#tab-jobs`)
|
||||||
|
* **Tab Button**: `.tab-btn[data-tab="jobs"]`
|
||||||
|
* **Key Selectors**:
|
||||||
|
- `#jobs-table`: Systemd user timer table.
|
||||||
|
- `.btn-trigger`: Direct hot-trigger button (`▶ Run`).
|
||||||
|
- `.badge-active`: Active/inactive state indicator.
|
||||||
|
* **REST API**:
|
||||||
|
- `GET /api/box/timers`
|
||||||
|
- `POST /api/box/jobs/{name}/trigger` (Returns `202 Accepted` immediately).
|
||||||
|
|
||||||
|
### 2.4 Loop & Strategy Matrix (`#tab-loops`)
|
||||||
|
* **Tab Button**: `.tab-btn[data-tab="loops"]`
|
||||||
|
* **Key Selectors**:
|
||||||
|
- `#loop-health-badge`: Real-time health verdict (e.g. `HEALTHY (89%)`).
|
||||||
|
- `#loops-grid`: Cards for active open loops and deadlines.
|
||||||
|
- `#strat-table`: Table of strategy escalation rules.
|
||||||
|
- `#vars-table`: Interactive table of runtime variables with inline edit buttons.
|
||||||
|
* **REST API**:
|
||||||
|
- `GET /api/box/loop/health`
|
||||||
|
- `GET /api/box/loop/status`
|
||||||
|
- `GET /api/box/loop/vars`
|
||||||
|
- `POST /api/box/loop/vars`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Assistive Interaction Recipes
|
||||||
|
|
||||||
|
### Recipe A: Querying Fleet Health via CLI
|
||||||
|
```bash
|
||||||
|
# High level overview
|
||||||
|
box fleet
|
||||||
|
# Directly query docs database for surface selectors
|
||||||
|
box docs surfaces dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recipe B: Hot-Triggering a Job via API
|
||||||
|
```bash
|
||||||
|
# Asynchronously trigger job
|
||||||
|
curl -sk -X POST https://box.muse-dev.online/api/box/jobs/box-http-health/trigger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recipe C: Resolving a Blocked Modal Dialog
|
||||||
|
```bash
|
||||||
|
# Check if any agent is held on an approval dialog
|
||||||
|
box approvals check
|
||||||
|
# Inspect dialog details
|
||||||
|
box approvals inspect pip
|
||||||
|
# Approve request
|
||||||
|
box approvals allow pip
|
||||||
|
```
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "Assistive Surfaces for box.muse-dev.online",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"host": "box.muse-dev.online",
|
||||||
|
"origin_vm": "34.139.37.135",
|
||||||
|
"compute_host": "bl (100.123.153.75)",
|
||||||
|
"auth": {
|
||||||
|
"pin": "3128",
|
||||||
|
"cookie_name": "ops_session",
|
||||||
|
"login_endpoint": "/api/ops/login",
|
||||||
|
"agent_header": "Authorization: Bearer <operator-token>",
|
||||||
|
"signature_mechanism": "SSH-keygen -Y sign via Tailnet identity"
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"dashboard": {
|
||||||
|
"name": "Fleet Pulse",
|
||||||
|
"tab_id": "dashboard",
|
||||||
|
"dom_tab_selector": ".tab-btn[data-tab=\"dashboard\"]",
|
||||||
|
"dom_pane_selector": "#tab-dashboard",
|
||||||
|
"key_elements": {
|
||||||
|
"grid": "#fleet-grid",
|
||||||
|
"pulse_indicator": "#fleet-pulse",
|
||||||
|
"node_card": ".node-card",
|
||||||
|
"node_badge": ".node-badge",
|
||||||
|
"active_page_title": ".page-title-preview"
|
||||||
|
},
|
||||||
|
"api_endpoints": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/box/fleet",
|
||||||
|
"description": "Real-time CDP latency, node status, current page titles, and queue depths across all nodes.",
|
||||||
|
"curl_example": "curl -sk https://box.muse-dev.online/api/box/fleet"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assistive_recipe": "To check fleet health, read /api/box/fleet or inspect #fleet-grid node cards. Green indicator denotes CDP responsive <100ms."
|
||||||
|
},
|
||||||
|
"dms": {
|
||||||
|
"name": "DMs & Work Orders",
|
||||||
|
"tab_id": "dms",
|
||||||
|
"dom_tab_selector": ".tab-btn[data-tab=\"dms\"]",
|
||||||
|
"dom_pane_selector": "#tab-dms",
|
||||||
|
"key_elements": {
|
||||||
|
"table": "#dms-table",
|
||||||
|
"tbody": "#dms-tbody",
|
||||||
|
"search_input": "#filter-search",
|
||||||
|
"agent_filter_chips": ".filter-agent-chip",
|
||||||
|
"kind_filter_chips": ".filter-kind-chip",
|
||||||
|
"count_badge": "#dms-count-badge"
|
||||||
|
},
|
||||||
|
"api_endpoints": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/box/dm/log?limit=50",
|
||||||
|
"description": "Chronological stream of Work Orders, Acks, Results, and inter-operator DMs.",
|
||||||
|
"curl_example": "curl -sk -b 'ops_session=...' https://box.muse-dev.online/api/box/dm/log?limit=30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/api/box/dm/send",
|
||||||
|
"description": "Dispatch a signed DM or Work Order to a recipient or sidechat target.",
|
||||||
|
"curl_example": "curl -sk -X POST https://box.muse-dev.online/api/box/dm/send -d '{\"agent\":\"646\",\"to\":\"pip\",\"target\":\"646-pip\",\"message\":\"[WO:...] ...\"}'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assistive_recipe": "Filter by data-agent attribute to isolate node traffic. Search box filters dynamically by text."
|
||||||
|
},
|
||||||
|
"jobs": {
|
||||||
|
"name": "Timers & Jobs",
|
||||||
|
"tab_id": "jobs",
|
||||||
|
"dom_tab_selector": ".tab-btn[data-tab=\"jobs\"]",
|
||||||
|
"dom_pane_selector": "#tab-jobs",
|
||||||
|
"key_elements": {
|
||||||
|
"table": "#jobs-table",
|
||||||
|
"tbody": "#jobs-tbody",
|
||||||
|
"trigger_button": ".btn-trigger",
|
||||||
|
"active_badge": ".badge-active",
|
||||||
|
"unit_name": ".timer-unit-name"
|
||||||
|
},
|
||||||
|
"api_endpoints": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/box/timers",
|
||||||
|
"description": "List all active systemd user timer schedules, next run, and last results.",
|
||||||
|
"curl_example": "curl -sk https://box.muse-dev.online/api/box/timers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/api/box/jobs/{name}/trigger",
|
||||||
|
"description": "Asynchronous hot-trigger calling box-ctl.py job-trigger. Returns 202 Accepted immediately.",
|
||||||
|
"curl_example": "curl -sk -X POST https://box.muse-dev.online/api/box/jobs/box-http-health/trigger"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assistive_recipe": "Clicking ▶ Run invokes hot-trigger without blocking the browser. Button displays Triggering... then Sent."
|
||||||
|
},
|
||||||
|
"loops": {
|
||||||
|
"name": "Loop & Strategy Matrix",
|
||||||
|
"tab_id": "loops",
|
||||||
|
"dom_tab_selector": ".tab-btn[data-tab=\"loops\"]",
|
||||||
|
"dom_pane_selector": "#tab-loops",
|
||||||
|
"key_elements": {
|
||||||
|
"health_badge": "#loop-health-badge",
|
||||||
|
"loops_grid": "#loops-grid",
|
||||||
|
"strategy_table": "#strat-table",
|
||||||
|
"vars_table": "#vars-table",
|
||||||
|
"edit_modal": "#modal-edit-var",
|
||||||
|
"count_badge": "#loops-count-badge"
|
||||||
|
},
|
||||||
|
"api_endpoints": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/box/loop/health",
|
||||||
|
"description": "Fleet loop health ratio (answered+closed vs landed) with health verdicts.",
|
||||||
|
"curl_example": "curl -sk https://box.muse-dev.online/api/box/loop/health"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/box/loop/status",
|
||||||
|
"description": "Active open loops, deadlines, and current nudge counters.",
|
||||||
|
"curl_example": "curl -sk https://box.muse-dev.online/api/box/loop/status"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/box/loop/vars",
|
||||||
|
"description": "Runtime control variables schema, values, and units.",
|
||||||
|
"curl_example": "curl -sk https://box.muse-dev.online/api/box/loop/vars"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/api/box/loop/vars",
|
||||||
|
"description": "Mutate runtime variable without restarting daemons.",
|
||||||
|
"curl_example": "curl -sk -X POST https://box.muse-dev.online/api/box/loop/vars -d '{\"name\":\"max_nudge_count\",\"value\":3}'"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assistive_recipe": "Use loop health ratio to evaluate fleet velocity. Overdue loops can be nudged directly via box loop nudge."
|
||||||
|
},
|
||||||
|
"approvals": {
|
||||||
|
"name": "Agent Approval Surfaces",
|
||||||
|
"tab_id": "approvals",
|
||||||
|
"dom_tab_selector": null,
|
||||||
|
"dom_pane_selector": ".approval-dialog-container",
|
||||||
|
"key_elements": {
|
||||||
|
"dialog": "[data-testid=\"approval-dialog\"]",
|
||||||
|
"allow_btn": "[data-testid=\"approval-allow\"]",
|
||||||
|
"deny_btn": "[data-testid=\"approval-deny\"]",
|
||||||
|
"ip_field": ".approval-requested-ip",
|
||||||
|
"action_desc": ".approval-description"
|
||||||
|
},
|
||||||
|
"api_endpoints": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"path": "/api/approvals",
|
||||||
|
"description": "Query pending approvals across all browser nodes.",
|
||||||
|
"curl_example": "curl -sk https://box.muse-dev.online/api/approvals"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assistive_recipe": "If browser agent is blocked on modal, inspect via 'box approvals check' and approve with 'box approvals allow <node>'."
|
||||||
|
},
|
||||||
|
"telemetry_curl": {
|
||||||
|
"name": "View as curl Drawer",
|
||||||
|
"tab_id": null,
|
||||||
|
"dom_tab_selector": "#curl-toggle-btn",
|
||||||
|
"dom_pane_selector": "#curl-drawer",
|
||||||
|
"key_elements": {
|
||||||
|
"toggle_btn": "#curl-toggle-btn",
|
||||||
|
"drawer": "#curl-drawer",
|
||||||
|
"display_code": "#curl-display",
|
||||||
|
"copy_btn": "#curl-copy-btn"
|
||||||
|
},
|
||||||
|
"api_endpoints": [],
|
||||||
|
"assistive_recipe": "Toggles an off-canvas drawer exposing exact curl invocation for the currently active tab and action."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "CLI Tools Reference & Grammar",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"binaries": {
|
||||||
|
"super": {
|
||||||
|
"path": "/home/super/.local/bin/super",
|
||||||
|
"target": "/home/super/Projects/NetVM/bin/super-cli.py",
|
||||||
|
"description": "Unified off-board orchestrator CLI for NetVM & Box."
|
||||||
|
},
|
||||||
|
"box": {
|
||||||
|
"path": "/home/super/.local/bin/box",
|
||||||
|
"target": "/home/super/Projects/NetVM/bin/super-cli.py",
|
||||||
|
"description": "Agent-facing unified CLI (symlink to super-cli.py)."
|
||||||
|
},
|
||||||
|
"muse": {
|
||||||
|
"path": "/home/super/.local/bin/muse",
|
||||||
|
"target": "/home/super/Projects/NetVM/bin/muse",
|
||||||
|
"description": "Direct headless gateway wrapper with isolated per-node WARP netns."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domains": {
|
||||||
|
"fleet": {
|
||||||
|
"summary": "Node health, CDP status, active tabs, watch, restart",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box fleet status", "desc": "Tabular health status of all 4 browser nodes (CDP latency, tabs, proc)"},
|
||||||
|
{"cmd": "box fleet watch", "desc": "Live refresh terminal dashboard of fleet nodes"},
|
||||||
|
{"cmd": "box fleet restart <node>", "desc": "Safely bounce a specific browser netns node"},
|
||||||
|
{"cmd": "box fleet cdp <node>", "desc": "Inspect open DevTools targets and webSocket URLs"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"approvals": {
|
||||||
|
"summary": "Inspect and handle agent browser & gateway approvals",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box approvals check [-v]", "desc": "Check if any browser agent is waiting on approval dialogs"},
|
||||||
|
{"cmd": "box approvals inspect <node>", "desc": "Inspect pending approval modal prompt and details"},
|
||||||
|
{"cmd": "box approvals allow <node>", "desc": "Approve pending request on target node"},
|
||||||
|
{"cmd": "box approvals deny <node>", "desc": "Reject pending request on target node"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dm": {
|
||||||
|
"summary": "Inter-agent DMs, work orders ([WO]), acks, live log tail",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box dm send --agent <self> --to <peer> --target <sidechat> \"<msg>\"", "desc": "Send signed DM"},
|
||||||
|
{"cmd": "box dm wo --agent <self> --to <peer> --target <chat> --title \"<title>\" \"<body>\"", "desc": "Dispatch structured [WO:id]"},
|
||||||
|
{"cmd": "box dm ack <id> --agent <self>", "desc": "Acknowledge received work order"},
|
||||||
|
{"cmd": "box dm log [--limit N]", "desc": "Read stream of signed work orders and DMs"},
|
||||||
|
{"cmd": "box dm tail", "desc": "Live stream incoming DMs"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"thread": {
|
||||||
|
"summary": "Inspect agent main chats, sidechats, and scrollbacks",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box thread list [agent]", "desc": "List active sidechats and message threads"},
|
||||||
|
{"cmd": "box thread view <agent> <thread_id> [--limit N]", "desc": "Read messages in a specific thread"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"job": {
|
||||||
|
"summary": "Manage scheduled jobs, systemd timers, triggers, logs",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box job list", "desc": "List registered jobs and systemd user timers"},
|
||||||
|
{"cmd": "box job run <name>", "desc": "Hot-trigger immediate execution of a job"},
|
||||||
|
{"cmd": "box job status <name>", "desc": "Inspect job execution status and logs"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"summary": "Probe VM web surfaces (box.muse-dev.online), auth, sync",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box web health", "desc": "Check Google Cloud VM web surface HTTP status"},
|
||||||
|
{"cmd": "box web test-auth", "desc": "Verify operator auth gating and session token"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"loop": {
|
||||||
|
"summary": "Intrinsic loop strategy, health, break taxonomy, and variables",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box loop status", "desc": "List active open loops, deadlines, and nudge counts"},
|
||||||
|
{"cmd": "box loop health", "desc": "Show fleet loop health ratio and closure rate"},
|
||||||
|
{"cmd": "box loop nudge <id>", "desc": "Manually trigger a followup nudge on an open loop"},
|
||||||
|
{"cmd": "box loop close <id>", "desc": "Close a loop with verdict"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"vars": {
|
||||||
|
"summary": "Inspect and adjust runtime control variables",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box vars list", "desc": "List all dynamic runtime control variables"},
|
||||||
|
{"cmd": "box vars get <name>", "desc": "Get current value of a variable"},
|
||||||
|
{"cmd": "box vars set <name> <value>", "desc": "Update runtime variable value"},
|
||||||
|
{"cmd": "box vars reset <name>", "desc": "Reset variable to canonical default"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tmux": {
|
||||||
|
"summary": "Manage headless background tmux sessions on /tmp/tmux-muse.sock",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box tmux list", "desc": "List active background agent sessions"},
|
||||||
|
{"cmd": "box tmux new <session> --command \"<cmd>\"", "desc": "Spawn new logged session"},
|
||||||
|
{"cmd": "box tmux send <session> \"<keys>\"", "desc": "Send keystrokes to session"},
|
||||||
|
{"cmd": "box tmux capture <session> [--lines N]", "desc": "Capture session output"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"deploy": {
|
||||||
|
"summary": "Spawn sub-agent session and dispatch task",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box deploy subagent --agent <self> --title \"<title>\" \"<prompt>\"", "desc": "Spawn autonomous subagent"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"docs": {
|
||||||
|
"summary": "Internal documentation, surfaces, sentence grammar, and regex lookup",
|
||||||
|
"subcommands": [
|
||||||
|
{"cmd": "box docs search <query>", "desc": "Search internal docs database"},
|
||||||
|
{"cmd": "box docs surfaces [name]", "desc": "Lookup assistive surfaces for box.muse-dev.online"},
|
||||||
|
{"cmd": "box docs sentence [name]", "desc": "Lookup agent sentence structures and protocols"},
|
||||||
|
{"cmd": "box docs regex [name] [--test \"str\"]", "desc": "Lookup or test regex patterns"},
|
||||||
|
{"cmd": "box docs parse \"<utterance>\"", "desc": "Parse agent utterance through regex engine"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "Fleet Topology & Agent Identities",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodes": {
|
||||||
|
"muse": {
|
||||||
|
"identity": "muse",
|
||||||
|
"netns": "warp-muse",
|
||||||
|
"egress_ip": "Dedicated Stable Colo IP (WARP)",
|
||||||
|
"cdp_port": 9410,
|
||||||
|
"peer_ip": "10.201.35.2",
|
||||||
|
"default_sidechat": "muse tasks",
|
||||||
|
"role": "General agent, primary chat evaluation"
|
||||||
|
},
|
||||||
|
"pip": {
|
||||||
|
"identity": "pip",
|
||||||
|
"netns": "warp-pip",
|
||||||
|
"egress_ip": "Dedicated Stable Colo IP (WARP)",
|
||||||
|
"cdp_port": 9411,
|
||||||
|
"peer_ip": "10.201.35.3",
|
||||||
|
"default_sidechat": "646-pip-coord",
|
||||||
|
"role": "Pipeline driver, account management"
|
||||||
|
},
|
||||||
|
"646": {
|
||||||
|
"identity": "646",
|
||||||
|
"netns": "warp-646",
|
||||||
|
"egress_ip": "Dedicated Stable Colo IP (WARP)",
|
||||||
|
"cdp_port": 9412,
|
||||||
|
"peer_ip": "10.201.35.4",
|
||||||
|
"default_sidechat": "646 tasks",
|
||||||
|
"role": "Operator-646, code execution, bridge management"
|
||||||
|
},
|
||||||
|
"opm": {
|
||||||
|
"identity": "opm",
|
||||||
|
"netns": "warp-opm",
|
||||||
|
"egress_ip": "Dedicated Stable Colo IP (WARP)",
|
||||||
|
"cdp_port": 9413,
|
||||||
|
"peer_ip": "10.201.35.5",
|
||||||
|
"default_sidechat": "heartbeat",
|
||||||
|
"role": "Operator-main, supervisor, telemetry check"
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"identity": "dev",
|
||||||
|
"netns": "warp-dev",
|
||||||
|
"egress_ip": "Staging WARP egress",
|
||||||
|
"cdp_port": 9414,
|
||||||
|
"peer_ip": "10.201.35.6",
|
||||||
|
"default_sidechat": "dev tasks",
|
||||||
|
"role": "Development and experimental features"
|
||||||
|
},
|
||||||
|
"def": {
|
||||||
|
"identity": "def",
|
||||||
|
"netns": "warp-def",
|
||||||
|
"egress_ip": "Default fallback egress",
|
||||||
|
"cdp_port": 9415,
|
||||||
|
"peer_ip": "10.201.35.7",
|
||||||
|
"default_sidechat": "default",
|
||||||
|
"role": "Fallback routing node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"name": "docs_internal",
|
||||||
|
"alias": "lookup_internal",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"description": "Internal Agent & Operator Lookup Database for NetVM, Box, and box.muse-dev.online",
|
||||||
|
"created_at": "2026-10-05T19:16:00Z",
|
||||||
|
"host": "box.muse-dev.online",
|
||||||
|
"primary_node": "bl (100.123.153.75)",
|
||||||
|
"collections": [
|
||||||
|
{
|
||||||
|
"id": "sentence_structure",
|
||||||
|
"name": "Agent Sentence Structures & Protocols",
|
||||||
|
"json_file": "sentence_structure.json",
|
||||||
|
"md_file": "SENTENCE-STRUCTURE.md",
|
||||||
|
"description": "Formal grammar, prefixes, intents, work orders, acks, results, and conversational contracts used by autonomous fleet agents.",
|
||||||
|
"keys_count": 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "regex_patterns",
|
||||||
|
"name": "Regex Patterns & Passing Engine",
|
||||||
|
"json_file": "regex_patterns.json",
|
||||||
|
"md_file": "REGEX-PATTERNS.md",
|
||||||
|
"description": "Comprehensive regex patterns with capture groups, test fixtures, and passing mechanisms for message harvesting and parsing.",
|
||||||
|
"keys_count": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "assistive_surfaces",
|
||||||
|
"name": "Assistive Surfaces for box.muse-dev.online",
|
||||||
|
"json_file": "assistive_surfaces.json",
|
||||||
|
"md_file": "SURFACES.md",
|
||||||
|
"description": "Catalog of UI views, DOM selectors, data-testids, modals, REST endpoints, and automation recipes for box.muse-dev.online.",
|
||||||
|
"keys_count": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cli_tools",
|
||||||
|
"name": "CLI Tools Reference & Grammar",
|
||||||
|
"json_file": "cli_tools.json",
|
||||||
|
"md_file": "CLI-TOOLS.md",
|
||||||
|
"description": "Command syntax, options, and dispatch paths for super, box, muse, and orchestrator utilities.",
|
||||||
|
"keys_count": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fleet_nodes",
|
||||||
|
"name": "Fleet Topology & Agent Identities",
|
||||||
|
"json_file": "fleet_nodes.json",
|
||||||
|
"md_file": "FLEET-NODES.md",
|
||||||
|
"description": "Active fleet agents, network namespaces, WireGuard warp addresses, CDP debugging ports, and task assignments.",
|
||||||
|
"keys_count": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "Regex Patterns & Passing Engine",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"patterns": {
|
||||||
|
"work_order": {
|
||||||
|
"name": "Work Order Parser",
|
||||||
|
"pattern": "^\\[WO:(?P<wo_id>[a-f0-9-]+)\\]\\s+\\[from\\s+(?P<sender>[a-zA-Z0-9_-]+)\\]\\s+(?P<title>[^—\\n]+?)\\s*—\\s*(?P<body>.+)$",
|
||||||
|
"flags": ["MULTILINE", "DOTALL"],
|
||||||
|
"description": "Matches canonical Work Order messages, extracting unique ID, sender, title, and body.",
|
||||||
|
"named_groups": {
|
||||||
|
"wo_id": "Unique work order identifier (hex or UUID)",
|
||||||
|
"sender": "Originating agent or operator identity",
|
||||||
|
"title": "Short title describing the task",
|
||||||
|
"body": "Full body text and instructions"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[WO:7fce46e0] [from super] Audit exec.muse-dev.online endpoints — Run full verification and check 200 codes.",
|
||||||
|
"[WO:a1b2c3d4-e5f6-7890-abcd-ef1234567890] [from 646] Deploy patch — Apply fix to response-harvester."
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"Just sending a regular message without tag",
|
||||||
|
"[WO:] missing id [from super] title — body",
|
||||||
|
"[WO:123] missing from tag title — body"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used by dm.py, super dm, and self_main_loop.py for tracking work orders."
|
||||||
|
},
|
||||||
|
"ack": {
|
||||||
|
"name": "Acknowledgement Parser",
|
||||||
|
"pattern": "^\\[ACK:(?P<ack_id>[a-f0-9-]+)\\](?:\\s+\\[from\\s+(?P<sender>[a-zA-Z0-9_-]+)\\])?",
|
||||||
|
"flags": ["MULTILINE"],
|
||||||
|
"description": "Matches work order acknowledgement tags, capturing referenced ID and optional sender.",
|
||||||
|
"named_groups": {
|
||||||
|
"ack_id": "Referenced work order ID being acknowledged",
|
||||||
|
"sender": "Optional sender acknowledging the message"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[ACK:7fce46e0] [from 646]",
|
||||||
|
"[ACK:a1b2c3d4]"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"ACK: 7fce46e0",
|
||||||
|
"[ACK:]",
|
||||||
|
"Acknowledged without brackets"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used by response-harvester.py and followup-sweeper.py to transition loops to acknowledged."
|
||||||
|
},
|
||||||
|
"verb": {
|
||||||
|
"name": "Standard Agent Verb Marker",
|
||||||
|
"pattern": "\\[(?P<verb>ACK|CLAIM|RESULT|DECLINE|NO-ACTION)\\s+(?P<job_id>[A-Za-z0-9_/-]+)\\]",
|
||||||
|
"flags": [],
|
||||||
|
"description": "Matches all standard conversational contract verbs referencing a job ID.",
|
||||||
|
"named_groups": {
|
||||||
|
"verb": "One of ACK, CLAIM, RESULT, DECLINE, NO-ACTION",
|
||||||
|
"job_id": "Target task or job identifier"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[ACK 7fce46e0]",
|
||||||
|
"[CLAIM job-123]",
|
||||||
|
"[RESULT swarm-4a/2]",
|
||||||
|
"[DECLINE audit-01]",
|
||||||
|
"[NO-ACTION check-99]"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"[DONE 7fce46e0]",
|
||||||
|
"[RESULT]",
|
||||||
|
"RESULT 7fce46e0 without brackets"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Primary regex used in response-harvester.py (VERB_RE) for loop state resolution."
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"name": "Task Result & Outcome Parser",
|
||||||
|
"pattern": "\\[RESULT\\s+(?P<job_id>[A-Za-z0-9_/-]+)\\]\\s*(?P<status>OK|FAIL|DECLINE|SUCCESS|ERROR)?\\s*(?P<summary>.*?)(?=\\[RESULT\\s|\\Z)",
|
||||||
|
"flags": ["DOTALL"],
|
||||||
|
"description": "Extracts job ID, optional status flag, and trailing summary for task outcomes.",
|
||||||
|
"named_groups": {
|
||||||
|
"job_id": "Target job or swarm slot ID",
|
||||||
|
"status": "Optional explicit status token (OK, FAIL, etc.)",
|
||||||
|
"summary": "Outcome description and evidence payload"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[RESULT 7fce46e0] OK Verified 14 endpoints successfully.",
|
||||||
|
"[RESULT test-job] FAIL Connection timed out on port 22",
|
||||||
|
"[RESULT swarm-1/0] Completed slot tasks."
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"Result of job 123 is OK",
|
||||||
|
"[RESULT] missing job id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used in response-harvester.py (RESULT_RE) to harvest results into job-log.jsonl."
|
||||||
|
},
|
||||||
|
"tool_call": {
|
||||||
|
"name": "In-Band Tool Execution Call",
|
||||||
|
"pattern": "\\[(?P<engine>TOOL|EXEC)\\s+(?P<op>[a-zA-Z0-9_.-]+)\\s+(?P<args>\\{.*?\\})\\]",
|
||||||
|
"flags": ["DOTALL"],
|
||||||
|
"description": "Matches synthesized inline tool execution directives with JSON arguments.",
|
||||||
|
"named_groups": {
|
||||||
|
"engine": "Either TOOL or EXEC",
|
||||||
|
"op": "Target operation name (e.g. followup.create, swarm.spawn)",
|
||||||
|
"args": "Valid JSON string of argument parameters"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[TOOL followup.create {\"in_m\": 5, \"prompt\": \"check\"}]",
|
||||||
|
"[EXEC health.check {\"verbose\": true}]"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"[TOOL followup.create without args]",
|
||||||
|
"[TOOL invalid args not json]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Parsed by response-harvester.py and exec-constrained.py for automated in-line actions."
|
||||||
|
},
|
||||||
|
"job_tag": {
|
||||||
|
"name": "Job Tracking Tag",
|
||||||
|
"pattern": "\\[JOB\\s+(?P<job_id>[A-Za-z0-9_/-]+)\\]",
|
||||||
|
"flags": [],
|
||||||
|
"description": "Matches job tag markers embedded in digests, tasks, or swarm slots.",
|
||||||
|
"named_groups": {
|
||||||
|
"job_id": "Unique job identifier"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[JOB ml-646-20261005-191500]",
|
||||||
|
"[JOB swarm-4b/1]"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"JOB ml-646 without brackets",
|
||||||
|
"[JOB]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used by self_main_loop.py and job-dispatch.py for tracking."
|
||||||
|
},
|
||||||
|
"nudge": {
|
||||||
|
"name": "Loop Followup Nudge Parser",
|
||||||
|
"pattern": "\\[NUDGE\\s+(?P<loop_id>[a-f0-9-]+)\\]\\s*(?:\\[nudge\\s+(?P<count>\\d+)/(?P<max_count>\\d+)\\])?\\s*(?:Deadline\\s+(?P<deadline>[^:]+):)?\\s*(?P<prompt>.*)",
|
||||||
|
"flags": ["MULTILINE"],
|
||||||
|
"description": "Matches automated followup nudges, extracting loop ID, nudge counters, and deadlines.",
|
||||||
|
"named_groups": {
|
||||||
|
"loop_id": "Target loop identifier",
|
||||||
|
"count": "Current nudge sequence number",
|
||||||
|
"max_count": "Maximum allowed nudges before escalation",
|
||||||
|
"deadline": "Deadline timestamp string",
|
||||||
|
"prompt": "Nudge message instructions"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[NUDGE 7fce46e0] [nudge 1/3] Deadline 19:45 UTC: Please confirm status of exec audit.",
|
||||||
|
"[NUDGE a1b2c3d4] Please review pending pull request."
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"Nudge for 7fce46e0",
|
||||||
|
"[NUDGE]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used by followup-sweeper.py for SLA enforcement."
|
||||||
|
},
|
||||||
|
"fleet_alert": {
|
||||||
|
"name": "Fleet Alert Broadcast Parser",
|
||||||
|
"pattern": "\\[fleet-alert\\]\\s+(?P<severity>CRITICAL|WARN|INFO|RECOVERED):\\s+(?P<message>.+)",
|
||||||
|
"flags": ["MULTILINE"],
|
||||||
|
"description": "Matches infrastructure alert broadcasts dispatched into #lobby and #jobs.",
|
||||||
|
"named_groups": {
|
||||||
|
"severity": "CRITICAL, WARN, INFO, or RECOVERED",
|
||||||
|
"message": "Alert message body"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[fleet-alert] CRITICAL: VM unreachable x2 on port 22",
|
||||||
|
"[fleet-alert] RECOVERED: VM 34.139.37.135 responded with 200 OK"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"[alert] CRITICAL: Missing fleet prefix",
|
||||||
|
"fleet-alert: info"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used by fleet-alert-check.sh and main-chat-watchdog.py."
|
||||||
|
},
|
||||||
|
"directive": {
|
||||||
|
"name": "Agent Action Directive",
|
||||||
|
"pattern": "\\[Directive:\\s*(?P<directive>.+?)\\]",
|
||||||
|
"flags": ["DOTALL"],
|
||||||
|
"description": "Extracts operational action directives targeting autonomous agents.",
|
||||||
|
"named_groups": {
|
||||||
|
"directive": "Actionable directive instruction"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[Directive: Take next action or close with [RESULT 7fce46e0] <summary>]",
|
||||||
|
"[Directive: Run audit on node pip]"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"Directive: without brackets",
|
||||||
|
"[Directive:]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Injected into prompts by response-harvester.py and job-dispatch.py."
|
||||||
|
},
|
||||||
|
"runtime_context": {
|
||||||
|
"name": "Runtime Context URL",
|
||||||
|
"pattern": "\\[Runtime Context:\\s*(?P<url>https?://[^\\s\\]]+)\\]",
|
||||||
|
"flags": [],
|
||||||
|
"description": "Extracts assistive web surface or thread URLs from message context.",
|
||||||
|
"named_groups": {
|
||||||
|
"url": "HTTP/HTTPS URL"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[Runtime Context: https://box.muse-dev.online/thread/7fce46e0]",
|
||||||
|
"[Runtime Context: https://box.muse-dev.online/api/box/fleet]"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"Runtime Context: not wrapped",
|
||||||
|
"[Runtime Context: invalid-url]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used by assistive surfaces and browser agent navigation routines."
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"name": "Canonical UUID Pattern",
|
||||||
|
"pattern": "\\b(?P<uuid>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\\b",
|
||||||
|
"flags": [],
|
||||||
|
"description": "Standard RFC 4122 UUID v4 detector for threads, messages, and session IDs.",
|
||||||
|
"named_groups": {
|
||||||
|
"uuid": "Matched UUID string"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"dc6d72ca-4b02-4217-bf41-7dbca19c5c24",
|
||||||
|
"d410b9ad-f667-465f-a103-43fabc0f69fe"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"dc6d72ca-4b02-4217-bf41",
|
||||||
|
"not-a-uuid"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Thread ID resolution in muse-chat-api.py and super-cli.py."
|
||||||
|
},
|
||||||
|
"short_hex": {
|
||||||
|
"name": "Short Hex Identifier",
|
||||||
|
"pattern": "\\b(?P<hex>[0-9a-fA-F]{6,12})\\b",
|
||||||
|
"flags": [],
|
||||||
|
"description": "Matches 6-12 character hexadecimal hashes used for compact DM IDs and subagent hashes.",
|
||||||
|
"named_groups": {
|
||||||
|
"hex": "Hexadecimal string"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"7fce46e0",
|
||||||
|
"04af8e15",
|
||||||
|
"ca56e39199da"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"123",
|
||||||
|
"abcdefghijk"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Compact ID matching across logs and DM tables."
|
||||||
|
},
|
||||||
|
"subagent_update": {
|
||||||
|
"name": "Subagent Update Notification",
|
||||||
|
"pattern": "\\[SUBAGENT-UPDATE\\]\\s+Subagent\\s+'(?P<title>[^']+)'\\s+\\((?P<short_id>[0-9a-fA-F]+)\\)\\s+(?P<message>.+)",
|
||||||
|
"flags": ["MULTILINE"],
|
||||||
|
"description": "Parses status updates and milestones from background subagent executions.",
|
||||||
|
"named_groups": {
|
||||||
|
"title": "Subagent session title",
|
||||||
|
"short_id": "Hex identifier of the subagent session",
|
||||||
|
"message": "Progress update message"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[SUBAGENT-UPDATE] Subagent 'exec-audit' (4a2b8e) has posted new output.",
|
||||||
|
"[SUBAGENT-UPDATE] Subagent 'health-check' (12ff4a) completed successfully."
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"Subagent update without brackets"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used by self_main_loop.py subagent monitor."
|
||||||
|
},
|
||||||
|
"swarm_slot": {
|
||||||
|
"name": "Swarm Slot Task Marker",
|
||||||
|
"pattern": "\\[JOB\\s+(?P<swarm_id>[a-zA-Z0-9_-]+)/(?P<slot>\\d+)\\]",
|
||||||
|
"flags": [],
|
||||||
|
"description": "Matches swarm worker slot task assignments.",
|
||||||
|
"named_groups": {
|
||||||
|
"swarm_id": "ID of parent swarm execution",
|
||||||
|
"slot": "Index of worker slot (e.g. 0, 1, 2)"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[JOB swarm-99a/0]",
|
||||||
|
"[JOB swarm-4a2b/3]"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"[JOB swarm-99a]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used in response-harvester.py and swarm_worker."
|
||||||
|
},
|
||||||
|
"contract_footer": {
|
||||||
|
"name": "Contract Footer Verifier",
|
||||||
|
"pattern": "Reply:\\s*\\[ACK\\s+id\\]\\s*seen\\s*\\|\\s*\\[CLAIM\\s+id\\]\\s*mine\\s*\\|\\s*\\[RESULT\\s+id\\]\\s*done\\s*\\|\\s*\\[DECLINE\\s+id\\]\\s*\\|\\s*\\[NO-ACTION\\s+id\\]\\.?",
|
||||||
|
"flags": ["IGNORECASE"],
|
||||||
|
"description": "Validates the presence of the standard agent contract footer in prompt envelopes.",
|
||||||
|
"named_groups": {},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"Reply: [ACK id] seen | [CLAIM id] mine | [RESULT id] done | [DECLINE id] | [NO-ACTION id]."
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"Please reply when ready"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Enforced in self_main_loop.py CONTRACT_FOOTER."
|
||||||
|
},
|
||||||
|
"sender_tag": {
|
||||||
|
"name": "Sender Identity Tag",
|
||||||
|
"pattern": "\\[from[:\\s]+(?P<sender>[a-zA-Z0-9_-]+)\\]",
|
||||||
|
"flags": ["IGNORECASE"],
|
||||||
|
"description": "Matches agent or operator attribution tags in chat messages.",
|
||||||
|
"named_groups": {
|
||||||
|
"sender": "Identity of sender"
|
||||||
|
},
|
||||||
|
"test_samples": {
|
||||||
|
"valid": [
|
||||||
|
"[from:super]",
|
||||||
|
"[from 646]",
|
||||||
|
"[from pip]"
|
||||||
|
],
|
||||||
|
"invalid": [
|
||||||
|
"from super without brackets"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"usage": "Used in chat-history parsing and loop attribution."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "Agent Sentence Structures & Protocols",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"surface": "box.muse-dev.online",
|
||||||
|
"structures": {
|
||||||
|
"work_order": {
|
||||||
|
"name": "Work Order",
|
||||||
|
"protocol_tag": "[WO:<id>]",
|
||||||
|
"template": "[WO:{dm_id}] [from {sender}] {title} — {body}",
|
||||||
|
"description": "Structured task dispatched between operators or from orchestrator to agent. Initiates a trackable loop.",
|
||||||
|
"required_fields": ["dm_id", "sender", "title", "body"],
|
||||||
|
"optional_fields": ["priority", "due"],
|
||||||
|
"regex_ref": "work_order",
|
||||||
|
"reply_expectation": "Recipient must reply with [ACK id], [CLAIM id], [RESULT id], [DECLINE id], or [NO-ACTION id].",
|
||||||
|
"example": "[WO:7fce46e0] [from super] Audit exec.muse-dev.online endpoints — Please run full check on GET /api/stats and post results.",
|
||||||
|
"lifecycle_transition": "open -> acknowledged -> running -> closed"
|
||||||
|
},
|
||||||
|
"ack": {
|
||||||
|
"name": "Acknowledgement",
|
||||||
|
"protocol_tag": "[ACK:<id>]",
|
||||||
|
"template": "[ACK:{dm_id}] [from {sender}]",
|
||||||
|
"description": "Signals receipt and understanding of a Work Order or message, updating status to acknowledged.",
|
||||||
|
"required_fields": ["dm_id"],
|
||||||
|
"optional_fields": ["sender"],
|
||||||
|
"regex_ref": "ack",
|
||||||
|
"reply_expectation": "Loop remains open awaiting final [RESULT id].",
|
||||||
|
"example": "[ACK:7fce46e0] [from 646]",
|
||||||
|
"lifecycle_transition": "open -> acknowledged"
|
||||||
|
},
|
||||||
|
"claim": {
|
||||||
|
"name": "Task Claim",
|
||||||
|
"protocol_tag": "[CLAIM <id>]",
|
||||||
|
"template": "[CLAIM {job_id}]",
|
||||||
|
"description": "Claims exclusive ownership of an unassigned job or swarm task slot, preventing duplicate worker runs.",
|
||||||
|
"required_fields": ["job_id"],
|
||||||
|
"optional_fields": [],
|
||||||
|
"regex_ref": "verb",
|
||||||
|
"reply_expectation": "Worker must execute task and post [RESULT id] when complete.",
|
||||||
|
"example": "[CLAIM 7fce46e0]",
|
||||||
|
"lifecycle_transition": "acknowledged -> claimed"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"name": "Task Completion Result",
|
||||||
|
"protocol_tag": "[RESULT <id>]",
|
||||||
|
"template": "[RESULT {job_id}] {status} {summary}",
|
||||||
|
"description": "Delivers the final output, verdict, or evidence of a completed task and closes the tracked loop.",
|
||||||
|
"required_fields": ["job_id", "summary"],
|
||||||
|
"optional_fields": ["status"],
|
||||||
|
"regex_ref": "result",
|
||||||
|
"reply_expectation": "Closes the loop; no further reply required unless sender issues new work order.",
|
||||||
|
"example": "[RESULT 7fce46e0] OK Audit complete: 14 allowlisted ops confirmed healthy, 0 errors.",
|
||||||
|
"lifecycle_transition": "running/claimed -> resolved (closed)"
|
||||||
|
},
|
||||||
|
"decline": {
|
||||||
|
"name": "Task Decline / Refusal",
|
||||||
|
"protocol_tag": "[DECLINE <id>]",
|
||||||
|
"template": "[DECLINE {job_id}] {reason}",
|
||||||
|
"description": "Explicitly declines or rejects a work order with justification (e.g. rate limit, outside domain).",
|
||||||
|
"required_fields": ["job_id", "reason"],
|
||||||
|
"optional_fields": [],
|
||||||
|
"regex_ref": "verb",
|
||||||
|
"reply_expectation": "Closes loop as declined; triggers orchestrator fallback or re-dispatch.",
|
||||||
|
"example": "[DECLINE 7fce46e0] Target node warp-dev is currently down for maintenance.",
|
||||||
|
"lifecycle_transition": "open/claimed -> declined (closed)"
|
||||||
|
},
|
||||||
|
"no_action": {
|
||||||
|
"name": "No Action Warranted",
|
||||||
|
"protocol_tag": "[NO-ACTION <id>]",
|
||||||
|
"template": "[NO-ACTION {job_id}] {reason}",
|
||||||
|
"description": "Acknowledges check but concludes no mutation or action was required (e.g. alert was transient flap).",
|
||||||
|
"required_fields": ["job_id", "reason"],
|
||||||
|
"optional_fields": [],
|
||||||
|
"regex_ref": "verb",
|
||||||
|
"reply_expectation": "Closes loop as resolved without side-effects.",
|
||||||
|
"example": "[NO-ACTION 7fce46e0] Transponder probe recovered before grace period expired.",
|
||||||
|
"lifecycle_transition": "open/claimed -> no-action (closed)"
|
||||||
|
},
|
||||||
|
"tool_call": {
|
||||||
|
"name": "In-Band Tool Execution Call",
|
||||||
|
"protocol_tag": "[TOOL <op> <args>]",
|
||||||
|
"template": "[TOOL {op} {args_json}]",
|
||||||
|
"description": "Synthesized inline tool execution directive parsed by response-harvester and executed on host.",
|
||||||
|
"required_fields": ["op", "args_json"],
|
||||||
|
"optional_fields": [],
|
||||||
|
"regex_ref": "tool_call",
|
||||||
|
"reply_expectation": "System executes op and posts execution result back into thread.",
|
||||||
|
"example": "[TOOL followup.create {\"in_m\": 5, \"prompt\": \"Recheck VM reachability\"}]",
|
||||||
|
"lifecycle_transition": "in-flight execution"
|
||||||
|
},
|
||||||
|
"job_marker": {
|
||||||
|
"name": "Job Tracking Marker",
|
||||||
|
"protocol_tag": "[JOB <id>]",
|
||||||
|
"template": "[JOB {job_id}]",
|
||||||
|
"description": "Identifies an automated task or swarm slot dispatched by scheduler or supervisor.",
|
||||||
|
"required_fields": ["job_id"],
|
||||||
|
"optional_fields": ["slot"],
|
||||||
|
"regex_ref": "job_tag",
|
||||||
|
"reply_expectation": "Worker replies with [RESULT job_id] upon conclusion.",
|
||||||
|
"example": "[JOB swarm-4a2b/1] Task for swarm slot 1: Run identity variance audit.",
|
||||||
|
"lifecycle_transition": "dispatched -> tracked"
|
||||||
|
},
|
||||||
|
"nudge": {
|
||||||
|
"name": "Loop Followup Nudge",
|
||||||
|
"protocol_tag": "[NUDGE <id>]",
|
||||||
|
"template": "[NUDGE {loop_id}] [nudge {count}/{max_nudges}] Deadline {deadline_utc}: {prompt}",
|
||||||
|
"description": "Automated prompt sent to recipient when a work order approaches or exceeds its SLA without an ack/result.",
|
||||||
|
"required_fields": ["loop_id", "count", "max_nudges", "deadline_utc"],
|
||||||
|
"optional_fields": ["prompt"],
|
||||||
|
"regex_ref": "nudge",
|
||||||
|
"reply_expectation": "Recipient must immediately reply with [ACK id] or [RESULT id].",
|
||||||
|
"example": "[NUDGE 7fce46e0] [nudge 1/3] Deadline 19:45 UTC: Please confirm status of exec audit.",
|
||||||
|
"lifecycle_transition": "nudged"
|
||||||
|
},
|
||||||
|
"fleet_alert": {
|
||||||
|
"name": "Fleet Operational Alert",
|
||||||
|
"protocol_tag": "[fleet-alert]",
|
||||||
|
"template": "[fleet-alert] {severity}: {message}",
|
||||||
|
"description": "System or detector broadcast into broadcast sidechats (#lobby, #jobs) regarding infrastructure events.",
|
||||||
|
"required_fields": ["severity", "message"],
|
||||||
|
"optional_fields": [],
|
||||||
|
"regex_ref": "fleet_alert",
|
||||||
|
"reply_expectation": "Broadcast telemetry; no direct reply required unless work order accompanies it.",
|
||||||
|
"example": "[fleet-alert] CRITICAL: VM unreachable x2 on port 22",
|
||||||
|
"lifecycle_transition": "alert -> incident"
|
||||||
|
},
|
||||||
|
"contract_footer": {
|
||||||
|
"name": "Standard Conversational Contract Footer",
|
||||||
|
"protocol_tag": "Reply: [ACK id] seen ...",
|
||||||
|
"template": "Reply: [ACK {id}] seen | [CLAIM {id}] mine | [RESULT {id}] done | [DECLINE {id}] | [NO-ACTION {id}].",
|
||||||
|
"description": "Appended to prompts, digests, and task orders to remind model instances of acceptable parsing grammar.",
|
||||||
|
"required_fields": ["id"],
|
||||||
|
"optional_fields": [],
|
||||||
|
"regex_ref": "contract_footer",
|
||||||
|
"reply_expectation": "Strictly governs model output to use one of the specified reply prefixes.",
|
||||||
|
"example": "Reply: [ACK 7fce46e0] seen | [CLAIM 7fce46e0] mine | [RESULT 7fce46e0] done | [DECLINE 7fce46e0] | [NO-ACTION 7fce46e0].",
|
||||||
|
"lifecycle_transition": "directive"
|
||||||
|
},
|
||||||
|
"directive": {
|
||||||
|
"name": "Agent Action Directive",
|
||||||
|
"protocol_tag": "[Directive: ...]",
|
||||||
|
"template": "[Directive: {directive}]",
|
||||||
|
"description": "Actionable instruction enclosed in square brackets guiding agent forward progression.",
|
||||||
|
"required_fields": ["directive"],
|
||||||
|
"optional_fields": [],
|
||||||
|
"regex_ref": "directive",
|
||||||
|
"reply_expectation": "Agent must fulfill directive in subsequent conversational turn.",
|
||||||
|
"example": "[Directive: Take next action or close with [RESULT 7fce46e0] <summary>]",
|
||||||
|
"lifecycle_transition": "instruction"
|
||||||
|
},
|
||||||
|
"runtime_context": {
|
||||||
|
"name": "Runtime Context URL",
|
||||||
|
"protocol_tag": "[Runtime Context: ...]",
|
||||||
|
"template": "[Runtime Context: {url}]",
|
||||||
|
"description": "Injects thread URL or Web UI surface link for assistive navigation by agent.",
|
||||||
|
"required_fields": ["url"],
|
||||||
|
"optional_fields": [],
|
||||||
|
"regex_ref": "runtime_context",
|
||||||
|
"reply_expectation": "Provides reference telemetry for browser agent.",
|
||||||
|
"example": "[Runtime Context: https://box.muse-dev.online/thread/7fce46e0]",
|
||||||
|
"lifecycle_transition": "telemetry"
|
||||||
|
},
|
||||||
|
"subagent_update": {
|
||||||
|
"name": "Subagent Progress Update",
|
||||||
|
"protocol_tag": "[SUBAGENT-UPDATE]",
|
||||||
|
"template": "[SUBAGENT-UPDATE] Subagent '{title}' ({short_id}) has posted new output.",
|
||||||
|
"description": "Notification relay from background subagent session to parent orchestrator.",
|
||||||
|
"required_fields": ["title", "short_id"],
|
||||||
|
"optional_fields": ["details"],
|
||||||
|
"regex_ref": "subagent_update",
|
||||||
|
"reply_expectation": "Parent logs progress or incorporates findings into main thread.",
|
||||||
|
"example": "[SUBAGENT-UPDATE] Subagent 'exec-audit' (4a2b8e) has posted new output.",
|
||||||
|
"lifecycle_transition": "subagent-relay"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,14 @@ Every outbound ask gets a follow-up deadline matched to its round-trip, not a ho
|
|||||||
|
|
||||||
## Box system (2026-10-04)
|
## Box system (2026-10-04)
|
||||||
- `https://box.muse-dev.online` — dashboard at `/#dashboard`. Health: `/srv/box/bin/box-health-check.sh {services,data,http}` on the VM (board.service, caddy, sweeper timer; /srv/box + uploads writable; 6 HTTP checks).
|
- `https://box.muse-dev.online` — dashboard at `/#dashboard`. Health: `/srv/box/bin/box-health-check.sh {services,data,http}` on the VM (board.service, caddy, sweeper timer; /srv/box + uploads writable; 6 HTTP checks).
|
||||||
|
- **Operator Passkey & Key Material Reality (2026-10-05):**
|
||||||
|
- **Location Reality:** The operator passkey / PIN for `https://box.muse-dev.online` is stored in a **single file (`.txt`) on the VM (`34.139.37.135`)** — **NOT on the dedicated BL compute node (`100.123.153.75`)**.
|
||||||
|
- **Operator PIN:** `3128` (unlocks `https://box.muse-dev.online` via `POST /api/ops/login`, establishing `ops_session` cookie). Operators frequently forget this passkey; retrieve from the VM txt file or use `box passkey` on bl.
|
||||||
|
- **Agent Approval Protocol:** Autonomous agents do NOT hold administrative passkeys or credentials directly. Secrets never reside on bl. If an agent requires key access, privileged credentials, or operator elevation, the agent MUST NOT search `bl`. Instead:
|
||||||
|
1. Emit `APPROVAL_NEEDED: <details of required key / action>` in the task sidechat (e.g. `646 tasks`, `pip tasks`, `#jobs`, `heartbeat`), or exit code `2` (INFRA.md convention).
|
||||||
|
2. The human operator validates the request, consults the single `.txt` file on the VM to authenticate/approve, or handles the action via `box approvals`.
|
||||||
|
- **Unified Lookup Surfaces:** Use `box lookup` (or `box key`, `box passkey`, `box tmux`, `box muse`) for instant lookups across nodes, registered threads, unread status, and approvals.
|
||||||
|
- **Internal Docs & Agent Lookup Engine (2026-10-05):** `docs_internal/` holds the canonical database (`.md` and `.json`) for agent sentence grammar (`[WO:...]`, `[ACK:...]`, `[RESULT:...]`), regex passing fixtures, and assistive surfaces for `box.muse-dev.online` (DOM selectors, tabs, and REST routes). Accessible via `box docs surfaces`, `box docs sentence`, `box docs regex`, and `box docs parse`.
|
||||||
- **Agent-tier API auth:** my `~/.ssh/id_frontdoor` is registered as `operator-646` in `/srv/board/allowed_signers`. Method: `TS=$(date +%s); printf '%s\n%s' "$TS" "<endpoint>" > p; ssh-keygen -Y sign -f ~/.ssh/id_frontdoor -n box p` (file-based, never pipe), then `GET https://box.muse-dev.online/api/box/<path>?identity=operator-646&ts=$TS&sig=<urlencoded p.sig>`. Signature endpoint = last path segment (`fleet`, `log`, `nodes`, …). Verified: `/api/box/fleet` → 200 live fleet array; `/api/box/dm/log` → 200 (agent tier sees only DMs where it's a party — empty is correct). All box APIs are 403 unauthenticated by design.
|
- **Agent-tier API auth:** my `~/.ssh/id_frontdoor` is registered as `operator-646` in `/srv/board/allowed_signers`. Method: `TS=$(date +%s); printf '%s\n%s' "$TS" "<endpoint>" > p; ssh-keygen -Y sign -f ~/.ssh/id_frontdoor -n box p` (file-based, never pipe), then `GET https://box.muse-dev.online/api/box/<path>?identity=operator-646&ts=$TS&sig=<urlencoded p.sig>`. Signature endpoint = last path segment (`fleet`, `log`, `nodes`, …). Verified: `/api/box/fleet` → 200 live fleet array; `/api/box/dm/log` → 200 (agent tier sees only DMs where it's a party — empty is correct). All box APIs are 403 unauthenticated by design.
|
||||||
- Request-store checker WARN is a path bug: `box-health-check.sh:167` checks `/srv/box/box_requests.jsonl` and `/srv/box/requests.jsonl`, but the real store is `/srv/box/requests/requests.jsonl`. One-line fix (add the real path); WARN is non-fatal by design.
|
- Request-store checker WARN is a path bug: `box-health-check.sh:167` checks `/srv/box/box_requests.jsonl` and `/srv/box/requests.jsonl`, but the real store is `/srv/box/requests/requests.jsonl`. One-line fix (add the real path); WARN is non-fatal by design.
|
||||||
- `/srv/box/uploads` keeps getting reset to `root:root 700` by the box publish path (3×); manual `chown super:frontdoor; chmod 770` holds. The publish script lives outside reachable repos — durable fix needs the publish owner to add the explicit chown post-deploy.
|
- `/srv/box/uploads` keeps getting reset to `root:root 700` by the box publish path (3×); manual `chown super:frontdoor; chmod 770` holds. The publish script lives outside reachable repos — durable fix needs the publish owner to add the explicit chown post-deploy.
|
||||||
|
|||||||
@@ -29,6 +29,23 @@ class TestApprovalsModule(unittest.TestCase):
|
|||||||
def test_trusted_ips_configuration(self):
|
def test_trusted_ips_configuration(self):
|
||||||
self.assertIn("34.139.37.135", approvals.TRUSTED_IPS)
|
self.assertIn("34.139.37.135", approvals.TRUSTED_IPS)
|
||||||
self.assertIn("100.123.153.75", approvals.TRUSTED_IPS)
|
self.assertIn("100.123.153.75", approvals.TRUSTED_IPS)
|
||||||
|
self.assertTrue(approvals.is_trusted_target("34.139.37.135"))
|
||||||
|
self.assertTrue(approvals.is_trusted_target("status.muse-dev.online"))
|
||||||
|
self.assertTrue(approvals.is_trusted_target("1.1.1.1"))
|
||||||
|
self.assertFalse(approvals.is_trusted_target("8.8.8.8"))
|
||||||
|
self.assertFalse(approvals.is_trusted_target("malicious-site.com"))
|
||||||
|
self.assertFalse(approvals.is_trusted_target("evilmuse-dev.online"))
|
||||||
|
self.assertFalse(approvals.is_trusted_target(
|
||||||
|
"evil.com", "operator-main wants to reach evil.com for status.muse-dev.online"))
|
||||||
|
self.assertFalse(approvals.is_trusted_target(None, "status.muse-dev.online"))
|
||||||
|
|
||||||
|
def test_redact_sensitive(self):
|
||||||
|
s1 = "Connecting with Bearer ya29.a0AfH6SMAKskd9238jdf"
|
||||||
|
self.assertEqual(approvals.redact_sensitive(s1), "Connecting with Bearer [REDACTED]")
|
||||||
|
s2 = "My api_key: secret12345678"
|
||||||
|
self.assertEqual(approvals.redact_sensitive(s2), "My api_key: [REDACTED]")
|
||||||
|
s3 = "JWT token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0"
|
||||||
|
self.assertIn("[JWT-REDACTED]", approvals.redact_sensitive(s3))
|
||||||
|
|
||||||
def test_get_node_connection_info(self):
|
def test_get_node_connection_info(self):
|
||||||
info = approvals.get_node_connection_info("pip")
|
info = approvals.get_node_connection_info("pip")
|
||||||
@@ -46,6 +63,7 @@ class TestApprovalsModule(unittest.TestCase):
|
|||||||
self.assertIn("has_pending", it)
|
self.assertIn("has_pending", it)
|
||||||
self.assertIn("buttons", it)
|
self.assertIn("buttons", it)
|
||||||
self.assertIn("is_trusted", it)
|
self.assertIn("is_trusted", it)
|
||||||
|
self.assertIn("input_waits", it)
|
||||||
|
|
||||||
def test_auto_approve_fleet_structure(self):
|
def test_auto_approve_fleet_structure(self):
|
||||||
res = approvals.auto_approve_fleet(nodes=["pip"])
|
res = approvals.auto_approve_fleet(nodes=["pip"])
|
||||||
@@ -124,5 +142,106 @@ class TestMuseChatApiConnection(unittest.TestCase):
|
|||||||
self.assertIn("No pending approvals", r.stdout)
|
self.assertIn("No pending approvals", r.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
class TestApprovalsReplySafety(unittest.TestCase):
|
||||||
|
"""Test reply policy enforcement on Main Chat."""
|
||||||
|
|
||||||
|
def test_reply_main_chat_refusal(self):
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "approvals", "reply", "pip", "hello test"]
|
||||||
|
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
# Should refuse with exit code 2 when target is Main Chat without --allow-main-chat
|
||||||
|
self.assertEqual(r.returncode, 2)
|
||||||
|
self.assertIn("Refusing reply by sidechat-first policy", r.stderr + r.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
class TestKeyApprovalsAndPasskey(unittest.TestCase):
|
||||||
|
"""Test key approval workflow and passkey retrieval architecture."""
|
||||||
|
|
||||||
|
def test_request_and_resolve_key_approval(self):
|
||||||
|
req = approvals.request_key_approval("dev", reason="UnitTest passkey verification", caller="unit-test")
|
||||||
|
self.assertTrue(req.get("ok"))
|
||||||
|
self.assertEqual(req.get("node"), "dev")
|
||||||
|
|
||||||
|
# Verify active in check_node_key_request
|
||||||
|
pending = approvals.check_node_key_request("dev")
|
||||||
|
self.assertIsNotNone(pending)
|
||||||
|
self.assertEqual(pending.get("reason"), "UnitTest passkey verification")
|
||||||
|
|
||||||
|
# Inspect should report KEY_APPROVAL
|
||||||
|
info = approvals.inspect_node_approvals("dev")
|
||||||
|
self.assertEqual(info.get("status"), "KEY_APPROVAL")
|
||||||
|
self.assertTrue(info.get("has_pending"))
|
||||||
|
|
||||||
|
# Resolve via allow_node_approval
|
||||||
|
res = approvals.allow_node_approval("dev", caller="unit-test")
|
||||||
|
self.assertTrue(res.get("ok"))
|
||||||
|
self.assertEqual(res.get("type"), "key_approval")
|
||||||
|
self.assertEqual(res.get("decision"), "allow")
|
||||||
|
|
||||||
|
# After resolution, pending should be cleared
|
||||||
|
cleared = approvals.check_node_key_request("dev")
|
||||||
|
self.assertIsNone(cleared)
|
||||||
|
|
||||||
|
def test_box_passkey_info_json(self):
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "passkey", "--json"]
|
||||||
|
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
self.assertEqual(r.returncode, 0)
|
||||||
|
data = json.loads(r.stdout)
|
||||||
|
self.assertTrue(data.get("ok"))
|
||||||
|
self.assertEqual(data.get("operator_pin"), "3128")
|
||||||
|
self.assertIn("key_location", data)
|
||||||
|
self.assertIn("canonical_path", data["key_location"])
|
||||||
|
self.assertEqual(data["key_location"]["canonical_path"], "/srv/box/passkey.txt")
|
||||||
|
|
||||||
|
def test_box_passkey_fetch_json(self):
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "passkey", "fetch", "--json"]
|
||||||
|
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
self.assertEqual(r.returncode, 0)
|
||||||
|
data = json.loads(r.stdout)
|
||||||
|
self.assertIn("operator_pin", data)
|
||||||
|
self.assertEqual(data.get("operator_pin"), "3128")
|
||||||
|
self.assertEqual(data.get("vm_host"), "34.139.37.135")
|
||||||
|
self.assertEqual(data.get("path"), "/srv/box/passkey.txt")
|
||||||
|
self.assertIn("operator_command", data)
|
||||||
|
|
||||||
|
def test_box_lookup_key(self):
|
||||||
|
cmd = [sys.executable, str(BIN_DIR / "super-cli.py"), "lookup", "key", "--json"]
|
||||||
|
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
self.assertEqual(r.returncode, 0)
|
||||||
|
data = json.loads(r.stdout)
|
||||||
|
self.assertTrue(data.get("ok"))
|
||||||
|
self.assertEqual(data.get("operator_pin"), "3128")
|
||||||
|
|
||||||
|
def test_cli_request_key_lifecycle(self):
|
||||||
|
# 1. Request key
|
||||||
|
r_req = subprocess.run([
|
||||||
|
sys.executable, str(BIN_DIR / "super-cli.py"),
|
||||||
|
"approvals", "request-key", "dev", "--reason", "CLI lifecycle test", "--json"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
self.assertEqual(r_req.returncode, 0)
|
||||||
|
req_data = json.loads(r_req.stdout)
|
||||||
|
self.assertTrue(req_data.get("ok"))
|
||||||
|
|
||||||
|
# 2. Check shows KEY_APPROVAL
|
||||||
|
r_check = subprocess.run([
|
||||||
|
sys.executable, str(BIN_DIR / "super-cli.py"),
|
||||||
|
"approvals", "check", "--node", "dev", "--json"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
self.assertEqual(r_check.returncode, 0)
|
||||||
|
check_data = json.loads(r_check.stdout)
|
||||||
|
dev_app = next(a for a in check_data["approvals"] if a["node"] == "dev")
|
||||||
|
self.assertEqual(dev_app["status"], "KEY_APPROVAL")
|
||||||
|
|
||||||
|
# 3. Deny key
|
||||||
|
r_deny = subprocess.run([
|
||||||
|
sys.executable, str(BIN_DIR / "super-cli.py"),
|
||||||
|
"approvals", "deny", "dev", "--json"
|
||||||
|
], capture_output=True, text=True)
|
||||||
|
self.assertEqual(r_deny.returncode, 0)
|
||||||
|
deny_data = json.loads(r_deny.stdout)
|
||||||
|
self.assertTrue(deny_data.get("ok"))
|
||||||
|
self.assertEqual(deny_data.get("decision"), "deny")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""test_docs_lookup.py — Unit tests for docs_internal database and lookup engine."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
LOOKUP_INTERNAL = REPO_ROOT / "lookup_internal"
|
||||||
|
DOCS_INTERNAL = REPO_ROOT / "docs_internal"
|
||||||
|
DOCS_LOOKUP = REPO_ROOT / "bin" / "docs-lookup.py"
|
||||||
|
SUPER_CLI = REPO_ROOT / "bin" / "super-cli.py"
|
||||||
|
|
||||||
|
|
||||||
|
class TestDocsInternalDatabase(unittest.TestCase):
|
||||||
|
def test_manifest_and_symlink(self):
|
||||||
|
self.assertTrue(LOOKUP_INTERNAL.is_dir(), "lookup_internal must exist as directory")
|
||||||
|
self.assertTrue(DOCS_INTERNAL.exists(), "docs_internal must exist")
|
||||||
|
self.assertTrue(DOCS_INTERNAL.is_symlink(), "docs_internal must be a symlink to lookup_internal")
|
||||||
|
|
||||||
|
manifest_path = LOOKUP_INTERNAL / "manifest.json"
|
||||||
|
self.assertTrue(manifest_path.is_file(), "manifest.json must exist")
|
||||||
|
data = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
self.assertIn(data.get("name"), ("lookup_internal", "docs_internal"))
|
||||||
|
self.assertIn("collections", data)
|
||||||
|
collections = data["collections"]
|
||||||
|
self.assertGreaterEqual(len(collections), 5)
|
||||||
|
for col in collections:
|
||||||
|
self.assertTrue((LOOKUP_INTERNAL / col["json_file"]).is_file(), f"{col['json_file']} missing")
|
||||||
|
self.assertTrue((LOOKUP_INTERNAL / col["md_file"]).is_file(), f"{col['md_file']} missing")
|
||||||
|
|
||||||
|
|
||||||
|
def test_sentence_structures(self):
|
||||||
|
p = DOCS_INTERNAL / "sentence_structure.json"
|
||||||
|
self.assertTrue(p.is_file())
|
||||||
|
data = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
structs = data.get("structures", {})
|
||||||
|
self.assertIn("work_order", structs)
|
||||||
|
self.assertIn("ack", structs)
|
||||||
|
self.assertIn("result", structs)
|
||||||
|
self.assertIn("claim", structs)
|
||||||
|
self.assertIn("tool_call", structs)
|
||||||
|
self.assertIn("nudge", structs)
|
||||||
|
|
||||||
|
for name, entry in structs.items():
|
||||||
|
self.assertIn("protocol_tag", entry)
|
||||||
|
self.assertIn("template", entry)
|
||||||
|
self.assertIn("required_fields", entry)
|
||||||
|
self.assertIn("description", entry)
|
||||||
|
self.assertIn("example", entry)
|
||||||
|
|
||||||
|
def test_regex_patterns_and_fixtures(self):
|
||||||
|
p = DOCS_INTERNAL / "regex_patterns.json"
|
||||||
|
self.assertTrue(p.is_file())
|
||||||
|
data = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
patterns = data.get("patterns", {})
|
||||||
|
self.assertIn("work_order", patterns)
|
||||||
|
self.assertIn("ack", patterns)
|
||||||
|
self.assertIn("verb", patterns)
|
||||||
|
self.assertIn("result", patterns)
|
||||||
|
self.assertIn("tool_call", patterns)
|
||||||
|
|
||||||
|
for pat_name, entry in patterns.items():
|
||||||
|
raw_pat = entry["pattern"]
|
||||||
|
flag_names = entry.get("flags", [])
|
||||||
|
flags = 0
|
||||||
|
for fn in flag_names:
|
||||||
|
flags |= getattr(re, fn)
|
||||||
|
# Must compile cleanly
|
||||||
|
compiled = re.compile(raw_pat, flags)
|
||||||
|
|
||||||
|
# Test valid samples
|
||||||
|
samples = entry.get("test_samples", {})
|
||||||
|
for valid_str in samples.get("valid", []):
|
||||||
|
self.assertIsNotNone(
|
||||||
|
compiled.search(valid_str),
|
||||||
|
f"Pattern '{pat_name}' failed to match valid sample: '{valid_str}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test invalid samples
|
||||||
|
for invalid_str in samples.get("invalid", []):
|
||||||
|
self.assertIsNone(
|
||||||
|
compiled.fullmatch(invalid_str),
|
||||||
|
f"Pattern '{pat_name}' unexpectedly matched invalid sample: '{invalid_str}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_assistive_surfaces(self):
|
||||||
|
p = DOCS_INTERNAL / "assistive_surfaces.json"
|
||||||
|
self.assertTrue(p.is_file())
|
||||||
|
data = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
self.assertEqual(data.get("host"), "box.muse-dev.online")
|
||||||
|
self.assertIn("views", data)
|
||||||
|
views = data["views"]
|
||||||
|
self.assertIn("dashboard", views)
|
||||||
|
self.assertIn("dms", views)
|
||||||
|
self.assertIn("jobs", views)
|
||||||
|
self.assertIn("loops", views)
|
||||||
|
|
||||||
|
# Check dashboard surface details
|
||||||
|
dash = views["dashboard"]
|
||||||
|
self.assertEqual(dash["dom_pane_selector"], "#tab-dashboard")
|
||||||
|
self.assertEqual(dash["key_elements"]["grid"], "#fleet-grid")
|
||||||
|
self.assertEqual(dash["api_endpoints"][0]["path"], "/api/box/fleet")
|
||||||
|
|
||||||
|
def test_cli_tools_reference(self):
|
||||||
|
p = DOCS_INTERNAL / "cli_tools.json"
|
||||||
|
self.assertTrue(p.is_file())
|
||||||
|
data = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
self.assertIn("binaries", data)
|
||||||
|
self.assertIn("super", data["binaries"])
|
||||||
|
self.assertIn("box", data["binaries"])
|
||||||
|
self.assertIn("domains", data)
|
||||||
|
domains = data["domains"]
|
||||||
|
self.assertIn("fleet", domains)
|
||||||
|
self.assertIn("dm", domains)
|
||||||
|
self.assertIn("job", domains)
|
||||||
|
self.assertIn("docs", domains)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDocsLookupCLI(unittest.TestCase):
|
||||||
|
def run_cmd(self, args: list) -> subprocess.CompletedProcess:
|
||||||
|
return subprocess.run(
|
||||||
|
[sys.executable, str(DOCS_LOOKUP)] + args,
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cli_overview_json(self):
|
||||||
|
res = self.run_cmd(["overview", "--json"])
|
||||||
|
self.assertEqual(res.returncode, 0, res.stderr)
|
||||||
|
out = json.loads(res.stdout)
|
||||||
|
self.assertEqual(out.get("name"), "docs_internal")
|
||||||
|
|
||||||
|
def test_cli_search(self):
|
||||||
|
res = self.run_cmd(["search", "work order", "--json"])
|
||||||
|
self.assertEqual(res.returncode, 0, res.stderr)
|
||||||
|
out = json.loads(res.stdout)
|
||||||
|
self.assertGreater(out.get("count", 0), 0)
|
||||||
|
|
||||||
|
def test_cli_surfaces(self):
|
||||||
|
res = self.run_cmd(["surfaces", "jobs", "--json"])
|
||||||
|
self.assertEqual(res.returncode, 0, res.stderr)
|
||||||
|
out = json.loads(res.stdout)
|
||||||
|
self.assertIn("jobs", out)
|
||||||
|
self.assertEqual(out["jobs"]["dom_tab_selector"], ".tab-btn[data-tab=\"jobs\"]")
|
||||||
|
|
||||||
|
def test_cli_sentence(self):
|
||||||
|
res = self.run_cmd(["sentence", "work_order", "--json"])
|
||||||
|
self.assertEqual(res.returncode, 0, res.stderr)
|
||||||
|
out = json.loads(res.stdout)
|
||||||
|
self.assertIn("work_order", out)
|
||||||
|
self.assertEqual(out["work_order"]["protocol_tag"], "[WO:<id>]")
|
||||||
|
|
||||||
|
def test_cli_regex_test(self):
|
||||||
|
res = self.run_cmd([
|
||||||
|
"regex", "result",
|
||||||
|
"--test", "[RESULT 7fce46e0] OK 14 allowlisted ops confirmed",
|
||||||
|
"--json"
|
||||||
|
])
|
||||||
|
self.assertEqual(res.returncode, 0, res.stderr)
|
||||||
|
out = json.loads(res.stdout)
|
||||||
|
self.assertIn("result", out)
|
||||||
|
eval_res = out.get("test_evaluation", {})
|
||||||
|
self.assertTrue(eval_res.get("matched"))
|
||||||
|
self.assertEqual(eval_res["named_groups"]["job_id"], "7fce46e0")
|
||||||
|
self.assertEqual(eval_res["named_groups"]["status"], "OK")
|
||||||
|
|
||||||
|
def test_cli_parse(self):
|
||||||
|
res = self.run_cmd([
|
||||||
|
"parse",
|
||||||
|
"[WO:7fce46e0] [from super] Audit exec endpoints — Check /api/stats",
|
||||||
|
"--json"
|
||||||
|
])
|
||||||
|
self.assertEqual(res.returncode, 0, res.stderr)
|
||||||
|
out = json.loads(res.stdout)
|
||||||
|
self.assertGreaterEqual(out.get("matched_patterns_count", 0), 2)
|
||||||
|
pattern_keys = [m["pattern_key"] for m in out.get("matches", [])]
|
||||||
|
self.assertIn("work_order", pattern_keys)
|
||||||
|
|
||||||
|
def test_super_cli_integration(self):
|
||||||
|
res = subprocess.run(
|
||||||
|
[sys.executable, str(SUPER_CLI), "docs", "surfaces", "dashboard", "--json"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
self.assertEqual(res.returncode, 0, res.stderr)
|
||||||
|
out = json.loads(res.stdout)
|
||||||
|
self.assertIn("dashboard", out)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLookupEngine(unittest.TestCase):
|
||||||
|
def test_lookup_engine_imports_and_hot_reload(self):
|
||||||
|
sys.path.insert(0, str(REPO_ROOT / "bin"))
|
||||||
|
import lookup_engine
|
||||||
|
|
||||||
|
# Verify compiled patterns
|
||||||
|
pats = lookup_engine.get_all_compiled_patterns()
|
||||||
|
self.assertGreaterEqual(len(pats), 14)
|
||||||
|
self.assertIn("result", pats)
|
||||||
|
self.assertIn("verb", pats)
|
||||||
|
|
||||||
|
res_re = lookup_engine.get_result_regex()
|
||||||
|
self.assertIsNotNone(res_re.search("[RESULT 7fce46e0] OK Done"))
|
||||||
|
|
||||||
|
verb_re = lookup_engine.get_verb_regex()
|
||||||
|
self.assertIsNotNone(verb_re.search("[CLAIM 7fce46e0]"))
|
||||||
|
|
||||||
|
# Verify soft validation
|
||||||
|
val_ok, kind, hint = lookup_engine.validate_outbound_sentence("[WO:7fce46e0] [from super] Audit — OK")
|
||||||
|
self.assertTrue(val_ok)
|
||||||
|
self.assertEqual(kind, "work_order")
|
||||||
|
|
||||||
|
val_fail, kind_f, hint_f = lookup_engine.validate_outbound_sentence("[WO:invalid] bad format")
|
||||||
|
self.assertFalse(val_fail)
|
||||||
|
self.assertEqual(kind_f, "work_order")
|
||||||
|
self.assertIn("Expected format", hint_f)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ class TestSidechatNavigationLogic(unittest.TestCase):
|
|||||||
import dm
|
import dm
|
||||||
# Well-known mappings
|
# Well-known mappings
|
||||||
self.assertEqual(dm.resolve_sidechat_target("main"), "main")
|
self.assertEqual(dm.resolve_sidechat_target("main"), "main")
|
||||||
self.assertEqual(dm.resolve_sidechat_target("646 tasks"), "1e75a740-d08f-443d-a0f9-793db196e24f")
|
self.assertEqual(dm.resolve_sidechat_target("646 tasks"), "1dfb3199-2f99-446c-83e3-848ae2da0a12")
|
||||||
self.assertEqual(dm.resolve_sidechat_target("heartbeat"), "1e75a740-d08f-443d-a0f9-793db196e24f")
|
self.assertEqual(dm.resolve_sidechat_target("heartbeat"), "757198c3-c1b2-48b8-ba2b-062c84f71b02")
|
||||||
|
|
||||||
def test_uuid_regex_detection(self):
|
def test_uuid_regex_detection(self):
|
||||||
import dm, re
|
import dm, re
|
||||||
|
|||||||
+169
-1
@@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
// Check hash on load
|
// Check hash on load
|
||||||
const hash = window.location.hash.replace('#', '');
|
const hash = window.location.hash.replace('#', '');
|
||||||
if (['dashboard', 'dms', 'jobs', 'loops'].includes(hash)) {
|
if (['dashboard', 'dms', 'jobs', 'loops', 'lookup'].includes(hash)) {
|
||||||
switchTab(hash);
|
switchTab(hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1188,6 +1188,8 @@
|
|||||||
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/timers`;
|
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/timers`;
|
||||||
} else if (currentTab === 'loops') {
|
} else if (currentTab === 'loops') {
|
||||||
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/loop/status\n\ncurl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/loop/health`;
|
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/loop/status\n\ncurl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/loop/health`;
|
||||||
|
} else if (currentTab === 'lookup') {
|
||||||
|
codeEl.textContent = `curl -s -H "Authorization: Bearer <token>" \\\n https://box.muse-dev.online/api/box/lookup\n\n# Or CLI lookup on bl:\nbox lookup surfaces\nbox lookup parse "[RESULT 7fce46e0] OK Done"`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1278,6 +1280,171 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 8. Lookup & Grammar Surface Integration
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
window.setLookupPreset = function (text) {
|
||||||
|
const input = document.getElementById('lookup-test-input');
|
||||||
|
if (input) {
|
||||||
|
input.value = text;
|
||||||
|
runLookupParser();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const CANONICAL_PATTERNS = [
|
||||||
|
{
|
||||||
|
key: 'work_order',
|
||||||
|
name: 'Work Order',
|
||||||
|
regex: /^\[WO:([a-f0-9-]+)\]\s+\[from\s+([a-zA-Z0-9_-]+)\]\s+([^—\n]+?)\s*—\s*(.+)$/s,
|
||||||
|
fields: ['wo_id', 'sender', 'title', 'body']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ack',
|
||||||
|
name: 'Acknowledgement',
|
||||||
|
regex: /^\[ACK:([a-f0-9-]+)\](?:\s+\[from\s+([a-zA-Z0-9_-]+)\])?/,
|
||||||
|
fields: ['ack_id', 'sender']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'verb',
|
||||||
|
name: 'Standard Verb',
|
||||||
|
regex: /\[(ACK|CLAIM|RESULT|DECLINE|NO-ACTION)\s+([A-Za-z0-9_/-]+)\]/,
|
||||||
|
fields: ['verb', 'job_id']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'result',
|
||||||
|
name: 'Task Result',
|
||||||
|
regex: /\[RESULT\s+([A-Za-z0-9_/-]+)\]\s*(OK|FAIL|DECLINE|SUCCESS|ERROR)?\s*(.*?)(?=\[RESULT\s|$)/s,
|
||||||
|
fields: ['job_id', 'status', 'summary']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tool_call',
|
||||||
|
name: 'In-Band Tool Call',
|
||||||
|
regex: /\[(TOOL|EXEC)\s+([a-zA-Z0-9_.-]+)\s+(\{.*?\})\]/s,
|
||||||
|
fields: ['engine', 'op', 'args']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function runLookupParser() {
|
||||||
|
const input = document.getElementById('lookup-test-input');
|
||||||
|
const resBox = document.getElementById('lookup-parse-result');
|
||||||
|
if (!input || !resBox) return;
|
||||||
|
|
||||||
|
const val = input.value.trim();
|
||||||
|
if (!val) {
|
||||||
|
resBox.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = [];
|
||||||
|
CANONICAL_PATTERNS.forEach(pat => {
|
||||||
|
const m = pat.regex.exec(val);
|
||||||
|
if (m) {
|
||||||
|
const tokens = {};
|
||||||
|
pat.fields.forEach((f, idx) => {
|
||||||
|
tokens[f] = m[idx + 1] !== undefined ? m[idx + 1].trim() : null;
|
||||||
|
});
|
||||||
|
matches.push({
|
||||||
|
key: pat.key,
|
||||||
|
name: pat.name,
|
||||||
|
matchedText: m[0],
|
||||||
|
tokens: tokens
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resBox.style.display = 'block';
|
||||||
|
if (matches.length === 0) {
|
||||||
|
resBox.innerHTML = `
|
||||||
|
<div style="color: var(--warn); display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span>⚠</span>
|
||||||
|
<span>No canonical sentence pattern matched this input. Check required tags: [WO:id] [from sender], [ACK:id], [RESULT id] OK, or [CLAIM id].</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
let html = `<div style="color: var(--ok); font-weight: 700; margin-bottom: 8px;">✔ Valid Syntax — Matched ${matches.length} Pattern(s):</div>`;
|
||||||
|
matches.forEach(m => {
|
||||||
|
html += `
|
||||||
|
<div style="border-top: 1px solid var(--border); padding-top: 8px; margin-top: 8px;">
|
||||||
|
<div style="font-weight: 600; color: var(--accent); margin-bottom: 4px;">Pattern: ${m.name} (<code>${m.key}</code>)</div>
|
||||||
|
<div style="color: var(--muted); font-size: 11px; margin-bottom: 6px;">Matched Chunk: <span style="color: var(--fg);">${m.matchedText}</span></div>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
||||||
|
`;
|
||||||
|
Object.entries(m.tokens).forEach(([k, v]) => {
|
||||||
|
html += `
|
||||||
|
<div style="background: var(--bg); border: 1px solid var(--border); padding: 4px 8px; border-radius: 4px; font-size: 12px;">
|
||||||
|
<span style="color: var(--muted);">${k}:</span> <strong style="color: var(--ok);">${v || '-'}</strong>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
html += `</div></div>`;
|
||||||
|
});
|
||||||
|
resBox.innerHTML = html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLookupSurfaces() {
|
||||||
|
const tbody = document.getElementById('lookup-surfaces-tbody');
|
||||||
|
if (!tbody) return;
|
||||||
|
|
||||||
|
const surfaces = [
|
||||||
|
{ name: 'Fleet Pulse', id: 'dashboard', selector: '.tab-btn[data-tab="dashboard"]', route: 'GET /api/box/fleet', hint: 'Real-time CDP status, page titles, and queue depths across all nodes.' },
|
||||||
|
{ name: 'DMs & Work Orders', id: 'dms', selector: '.tab-btn[data-tab="dms"]', route: 'GET /api/box/dm/log?limit=50', hint: 'Chronological audit trail of signed work orders, acks, and agent replies.' },
|
||||||
|
{ name: 'Timers & Jobs', id: 'jobs', selector: '.tab-btn[data-tab="jobs"]', route: 'GET /api/box/timers', hint: 'Systemd user timers with hot-trigger buttons (POST /api/box/jobs/{name}/trigger).' },
|
||||||
|
{ name: 'Loop & Strategy', id: 'loops', selector: '.tab-btn[data-tab="loops"]', route: 'GET /api/box/loop/health', hint: 'Fleet loop velocity, open SLA deadlines, strategies, and runtime variables.' },
|
||||||
|
{ name: 'Lookup & Grammar', id: 'lookup', selector: '.tab-btn[data-tab="lookup"]', route: 'GET /api/box/lookup', hint: 'Interactive regex testing, sentence grammar, and DOM surface maps.' },
|
||||||
|
{ name: 'Approvals Surface', id: 'approvals', selector: '[data-testid="approval-dialog"]', route: 'GET /api/approvals', hint: 'Permission modals and operator elevation requests (exit code 2 contract).' }
|
||||||
|
];
|
||||||
|
|
||||||
|
tbody.innerHTML = surfaces.map(s => `
|
||||||
|
<tr>
|
||||||
|
<td style="font-weight: 600; color: var(--fg);">${s.name}</td>
|
||||||
|
<td><code>#tab-${s.id}</code></td>
|
||||||
|
<td><code style="color: var(--accent);">${s.selector}</code></td>
|
||||||
|
<td><code style="color: var(--ok);">${s.route}</code></td>
|
||||||
|
<td style="color: var(--muted); font-size: 12px;">${s.hint}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLookupSentences() {
|
||||||
|
const grid = document.getElementById('lookup-sentence-grid');
|
||||||
|
if (!grid) return;
|
||||||
|
|
||||||
|
const cards = [
|
||||||
|
{ name: 'Work Order', tag: '[WO:<id>]', template: '[WO:{id}] [from {sender}] {title} — {body}', lifecycle: 'open -> claimed -> resolved', desc: 'Dispatches actionable, trackable tasking between operators or from orchestrator.' },
|
||||||
|
{ name: 'Acknowledgement', tag: '[ACK:<id>]', template: '[ACK:{id}] [from {sender}]', lifecycle: 'open -> acknowledged', desc: 'Confirms receipt of task without closing the tracked loop.' },
|
||||||
|
{ name: 'Task Claim', tag: '[CLAIM <id>]', template: '[CLAIM {job_id}]', lifecycle: 'acknowledged -> claimed', desc: 'Declares worker ownership of a task or swarm slot, preventing duplicate runs.' },
|
||||||
|
{ name: 'Task Result', tag: '[RESULT <id>]', template: '[RESULT {job_id}] {status} {summary}', lifecycle: 'claimed -> resolved', desc: 'Submits completion evidence and resolves the active loop.' },
|
||||||
|
{ name: 'Task Decline', tag: '[DECLINE <id>]', template: '[DECLINE {job_id}] {reason}', lifecycle: 'claimed -> declined', desc: 'Explicit rejection of a task with reason, prompting fallback.' },
|
||||||
|
{ name: 'In-Band Tool Call', tag: '[TOOL <op> <args>]', template: '[TOOL {op} {args_json}]', lifecycle: 'in-flight execution', desc: 'Inline tool directive parsed and executed directly by response-harvester.' }
|
||||||
|
];
|
||||||
|
|
||||||
|
grid.innerHTML = cards.map(c => `
|
||||||
|
<div class="node-card" style="padding: 16px;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||||
|
<span style="font-weight: 700; color: var(--fg);">${c.name}</span>
|
||||||
|
<span class="node-status-badge badge-active">${c.tag}</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-family: var(--font-mono); font-size: 12px; color: var(--accent); background: var(--bg); padding: 6px 8px; border-radius: 4px; margin-bottom: 8px; word-break: break-all;">${c.template}</div>
|
||||||
|
<div style="font-size: 12px; color: var(--muted); margin-bottom: 8px;">${c.desc}</div>
|
||||||
|
<div style="font-size: 11px; color: var(--muted); text-transform: uppercase;">Lifecycle: <span style="color: var(--ok);">${c.lifecycle}</span></div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupLookupTab() {
|
||||||
|
const parseBtn = document.getElementById('lookup-parse-btn');
|
||||||
|
const input = document.getElementById('lookup-test-input');
|
||||||
|
if (parseBtn) parseBtn.addEventListener('click', runLookupParser);
|
||||||
|
if (input) {
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') runLookupParser();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
renderLookupSurfaces();
|
||||||
|
renderLookupSentences();
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// 9. Initialization
|
// 9. Initialization
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -1291,6 +1458,7 @@
|
|||||||
setupTabs();
|
setupTabs();
|
||||||
setupFilters();
|
setupFilters();
|
||||||
setupCurlDrawer();
|
setupCurlDrawer();
|
||||||
|
setupLookupTab();
|
||||||
|
|
||||||
// Initial Fetch & Start Polling
|
// Initial Fetch & Start Polling
|
||||||
refreshData();
|
refreshData();
|
||||||
|
|||||||
@@ -47,6 +47,9 @@
|
|||||||
Loop & Strategy
|
Loop & Strategy
|
||||||
<span id="loops-count-badge" class="tab-badge">0</span>
|
<span id="loops-count-badge" class="tab-badge">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="tab-btn" data-tab="lookup">
|
||||||
|
Lookup & Grammar
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Main Container -->
|
<!-- Main Container -->
|
||||||
@@ -242,6 +245,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Tab 5: Lookup & Grammar -->
|
||||||
|
<section id="tab-lookup" class="tab-pane">
|
||||||
|
<!-- Section 1: Interactive Regex & Sentence Parser -->
|
||||||
|
<div class="section-heading">
|
||||||
|
<div class="section-title">Interactive Sentence & Regex Parser</div>
|
||||||
|
<div class="section-subtitle">Test and validate agent sentence structures against lookup_internal patterns</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-card" style="padding: 16px;">
|
||||||
|
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
|
||||||
|
<input type="text" id="lookup-test-input" placeholder="Type or paste an agent message (e.g. [WO:7fce46e0] [from super] Audit — Check stats)..." style="flex: 1; padding: 10px 14px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-family: var(--font-mono); font-size: 13px;">
|
||||||
|
<button id="lookup-parse-btn" class="btn-control" style="background: var(--accent); color: #fff; font-weight: 600; padding: 0 18px;">Parse & Validate</button>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
|
||||||
|
<span style="font-size: 11px; color: var(--muted); text-transform: uppercase; align-self: center;">Presets:</span>
|
||||||
|
<button class="btn-control preset-btn" onclick="setLookupPreset('[WO:7fce46e0] [from super] Audit exec.muse-dev.online — Check 200 OK codes')">[WO]</button>
|
||||||
|
<button class="btn-control preset-btn" onclick="setLookupPreset('[ACK:7fce46e0] [from 646]')">[ACK]</button>
|
||||||
|
<button class="btn-control preset-btn" onclick="setLookupPreset('[CLAIM 7fce46e0]')">[CLAIM]</button>
|
||||||
|
<button class="btn-control preset-btn" onclick="setLookupPreset('[RESULT 7fce46e0] OK 14 endpoints verified healthy')">[RESULT]</button>
|
||||||
|
<button class="btn-control preset-btn" onclick="setLookupPreset('[TOOL followup.create {"in_m": 5, "prompt": "Recheck reachability"}]')">[TOOL]</button>
|
||||||
|
</div>
|
||||||
|
<div id="lookup-parse-result" style="display: none; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 12px; font-family: var(--font-mono); font-size: 13px;">
|
||||||
|
<!-- Rendered dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Section 2: Sentence Structure Catalog -->
|
||||||
|
<div class="section-heading" style="margin-top: 24px;">
|
||||||
|
<div class="section-title">Protocol Grammar & Conversational Contracts</div>
|
||||||
|
<div class="section-subtitle">Authoritative patterns from lookup_internal/sentence_structure.json</div>
|
||||||
|
</div>
|
||||||
|
<div id="lookup-sentence-grid" class="grid-nodes">
|
||||||
|
<!-- Rendered dynamically by box.js -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Section 3: Assistive Surfaces Catalog -->
|
||||||
|
<div class="section-heading" style="margin-top: 24px;">
|
||||||
|
<div class="section-title">Assistive Surfaces Map for box.muse-dev.online</div>
|
||||||
|
<div class="section-subtitle">DOM Selectors, data-testids, and REST endpoints for browser agents</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-card">
|
||||||
|
<table id="lookup-surfaces-table" class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 120px;">Surface</th>
|
||||||
|
<th style="width: 150px;">Tab / Pane ID</th>
|
||||||
|
<th style="width: 240px;">DOM Tab Selector</th>
|
||||||
|
<th style="width: 240px;">API Route</th>
|
||||||
|
<th>Assistive Purpose & Hint</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="lookup-surfaces-tbody">
|
||||||
|
<!-- Rendered dynamically by box.js -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- 'View as curl' Drawer -->
|
<!-- 'View as curl' Drawer -->
|
||||||
|
|||||||
Reference in New Issue
Block a user