feat(work): add box work read command to extract live active chat text and side chats
This commit is contained in:
@@ -343,6 +343,111 @@ def get_agent_menu(node, tab="all"):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JS_LIVE_SCREEN = """(() => {
|
||||||
|
const ps = Array.from(document.querySelectorAll('p')).map(p => (p.innerText || '').trim()).filter(Boolean);
|
||||||
|
const actionBtns = Array.from(document.querySelectorAll('div[data-message-id] button, [role="log"] button, div[role="status"] button')).map(b => (b.innerText || '').trim()).filter(Boolean);
|
||||||
|
const stopBtn = !!document.querySelector('[data-testid="hatch-composer-stop-button"]');
|
||||||
|
const typing = !!document.querySelector('[data-testid="hatch-chat-typing-indicator"]:not(:empty)');
|
||||||
|
const statusTextEl = document.querySelector('.group\\\\/status-avatar span, [class*="status-avatar"] span, span[class*="text-body-status"]');
|
||||||
|
const avatarStatus = statusTextEl ? (statusTextEl.innerText || '').trim() : '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: document.title,
|
||||||
|
url: window.location.href,
|
||||||
|
is_main_chat: window.location.href === 'https://muse.ai/' || window.location.href.endsWith('/thread/new'),
|
||||||
|
is_generating: stopBtn,
|
||||||
|
is_typing: typing,
|
||||||
|
avatar_status: avatarStatus,
|
||||||
|
recent_paragraphs: ps.slice(-8),
|
||||||
|
action_buttons: actionBtns.filter(t => /approve|confirm|proceed|resume|start|allow|review/i.test(t))
|
||||||
|
};
|
||||||
|
})()"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_agent_live_screen(node: str) -> dict:
|
||||||
|
"""Extracts live active chat text, thoughts, prompt blocks, and threads."""
|
||||||
|
if not is_running_on_bl():
|
||||||
|
try:
|
||||||
|
cmd = ["ssh", "-q", "-o", "ConnectTimeout=5", f"super@{REMOTE_HOST}",
|
||||||
|
f"python3 /home/super/Projects/NetVM/bin/agent-cognitive-probe.py read {node} --json"]
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
||||||
|
if proc.returncode == 0 and proc.stdout.strip():
|
||||||
|
return json.loads(proc.stdout.strip())
|
||||||
|
except Exception as e:
|
||||||
|
return {"node": node, "error": f"Remote delegation failed: {e}"}
|
||||||
|
|
||||||
|
screen = run_cdp_eval_inside_netns(node, JS_LIVE_SCREEN)
|
||||||
|
if not isinstance(screen, dict) or "error" in screen:
|
||||||
|
return {"node": node, "error": screen.get("error", "Failed to inspect screen") if isinstance(screen, dict) else str(screen)}
|
||||||
|
|
||||||
|
sidechats = []
|
||||||
|
try:
|
||||||
|
cli_cmd = ["/home/super/Projects/NetVM/bin/muse-cli-node", node, "threads"]
|
||||||
|
proc = subprocess.run(cli_cmd, capture_output=True, text=True, timeout=8)
|
||||||
|
if proc.returncode == 0 and proc.stdout.strip():
|
||||||
|
threads_data = json.loads(proc.stdout.strip())
|
||||||
|
if isinstance(threads_data, list):
|
||||||
|
sidechats = threads_data[:8]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return {
|
||||||
|
"node": node,
|
||||||
|
"screen": screen,
|
||||||
|
"sidechats": sidechats
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_read(args):
|
||||||
|
node = getattr(args, "agent", None) or getattr(args, "node", None)
|
||||||
|
data = get_agent_live_screen(node)
|
||||||
|
if getattr(args, "json", False):
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
if "error" in data:
|
||||||
|
print(f"\n❌ Error inspecting screen for {node}: {data['error']}\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
screen = data.get("screen", {})
|
||||||
|
sidechats = data.get("sidechats", [])
|
||||||
|
|
||||||
|
url = screen.get("url", "")
|
||||||
|
mode = "Main Chat" if screen.get("is_main_chat") else "Side Chat"
|
||||||
|
title = screen.get("title", "")
|
||||||
|
avatar = screen.get("avatar_status") or "Connected"
|
||||||
|
gen = "🧠 GENERATING" if screen.get("is_generating") else ("💭 TYPING" if screen.get("is_typing") else "🟢 SETTLED")
|
||||||
|
|
||||||
|
print(f"\n=== LIVE THOUGHT STREAM & ACTIVE CHAT: {node.upper()} ===")
|
||||||
|
print(f"Context: {mode} ({url})")
|
||||||
|
print(f"Title: {title}")
|
||||||
|
print(f"Status: {avatar} | State: {gen}")
|
||||||
|
|
||||||
|
paragraphs = screen.get("recent_paragraphs", [])
|
||||||
|
if paragraphs:
|
||||||
|
print("\n--- ACTIVE CONVERSATION & THOUGHT PARAGRAPHS ---")
|
||||||
|
for p in paragraphs:
|
||||||
|
print(f" • {p}\n")
|
||||||
|
else:
|
||||||
|
print("\n (No text paragraphs visible in current viewport)")
|
||||||
|
|
||||||
|
actions = screen.get("action_buttons", [])
|
||||||
|
if actions:
|
||||||
|
print("--- PENDING ACTION CARDS / APPROVAL BUTTONS ---")
|
||||||
|
for a in actions:
|
||||||
|
print(f" ⚠️ [PROMPT ACTION] {a}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if sidechats:
|
||||||
|
print("--- RECENT SIDE CHATS & TOPICS ---")
|
||||||
|
for sc in sidechats:
|
||||||
|
sid = sc.get("session_id", "")[:8]
|
||||||
|
stitle = sc.get("title") or "(Untitled sidechat)"
|
||||||
|
upd = sc.get("updated", "")
|
||||||
|
print(f" • [{sid}] {stitle} ({upd})")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
def format_cognitive_badge(status):
|
def format_cognitive_badge(status):
|
||||||
badges = {
|
badges = {
|
||||||
"IDLE": "🟢 IDLE",
|
"IDLE": "🟢 IDLE",
|
||||||
@@ -464,6 +569,10 @@ def main():
|
|||||||
p_lock.add_argument("--force", action="store_true", help="Bypass lock check")
|
p_lock.add_argument("--force", action="store_true", help="Bypass lock check")
|
||||||
p_lock.add_argument("--json", action="store_true", help="Output JSON")
|
p_lock.add_argument("--json", action="store_true", help="Output JSON")
|
||||||
|
|
||||||
|
p_read = sub.add_parser("read", help="Extract live active chat text, thought stream, and side chats")
|
||||||
|
p_read.add_argument("node", help="Agent name")
|
||||||
|
p_read.add_argument("--json", action="store_true", help="Output JSON")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if not args.command:
|
if not args.command:
|
||||||
# Default to status
|
# Default to status
|
||||||
@@ -476,6 +585,8 @@ def main():
|
|||||||
cmd_status(args)
|
cmd_status(args)
|
||||||
elif args.command == "menu":
|
elif args.command == "menu":
|
||||||
cmd_menu(args)
|
cmd_menu(args)
|
||||||
|
elif args.command == "read":
|
||||||
|
cmd_read(args)
|
||||||
elif args.command == "lock":
|
elif args.command == "lock":
|
||||||
cmd_lock(args)
|
cmd_lock(args)
|
||||||
|
|
||||||
|
|||||||
@@ -907,9 +907,16 @@ def cmd_loopback(args):
|
|||||||
if as_json:
|
if as_json:
|
||||||
print(json.dumps({"ok": True, "actions": actions}, indent=2))
|
print(json.dumps({"ok": True, "actions": actions}, indent=2))
|
||||||
|
|
||||||
|
def cmd_read(args):
|
||||||
|
if not acp:
|
||||||
|
print(c_red("Error: agent_cognitive_probe module not found."))
|
||||||
|
sys.exit(1)
|
||||||
|
acp.cmd_read(args)
|
||||||
|
|
||||||
WORK_COMMAND_EXAMPLES = {
|
WORK_COMMAND_EXAMPLES = {
|
||||||
"box work": [
|
"box work": [
|
||||||
"box work # View fleet workspace dashboard & signals",
|
"box work # View fleet workspace dashboard & signals",
|
||||||
|
"box work read <agent> # Extract live screen text, thoughts, and side chats",
|
||||||
"box work loopback [--dry-run] # Sweep open tickets with 3-tier loopback escalation",
|
"box work loopback [--dry-run] # Sweep open tickets with 3-tier loopback escalation",
|
||||||
"box work cognitive [agent...] # Live zero-click cognitive sensor probe across fleet",
|
"box work cognitive [agent...] # Live zero-click cognitive sensor probe across fleet",
|
||||||
"box work menu <agent> [tab] # Inspect agent profile menu (tasks, timers, approvals)",
|
"box work menu <agent> [tab] # Inspect agent profile menu (tasks, timers, approvals)",
|
||||||
@@ -920,6 +927,10 @@ WORK_COMMAND_EXAMPLES = {
|
|||||||
"box work merge <pr#> # Verify tests and merge PR to master",
|
"box work merge <pr#> # Verify tests and merge PR to master",
|
||||||
"box work chats --agent <name> # View live multi-agent chat feed",
|
"box work chats --agent <name> # View live multi-agent chat feed",
|
||||||
],
|
],
|
||||||
|
"box work read": [
|
||||||
|
"box work read pip # Read pip's current active screen & thought stream",
|
||||||
|
"box work read 646 # Read 646's active thread and side chats",
|
||||||
|
],
|
||||||
"box work loopback": [
|
"box work loopback": [
|
||||||
"box work loopback # Sweep active tickets and escalate inactivity",
|
"box work loopback # Sweep active tickets and escalate inactivity",
|
||||||
"box work loopback --dry-run # Dry-run inspect without sending pings or comments",
|
"box work loopback --dry-run # Dry-run inspect without sending pings or comments",
|
||||||
@@ -1081,6 +1092,10 @@ def main():
|
|||||||
p_loop.add_argument("--dry-run", action="store_true", help="Inspect without modifying tickets or sending DMs")
|
p_loop.add_argument("--dry-run", action="store_true", help="Inspect without modifying tickets or sending DMs")
|
||||||
p_loop.add_argument("--json", action="store_true", help="Output actions as JSON")
|
p_loop.add_argument("--json", action="store_true", help="Output actions as JSON")
|
||||||
|
|
||||||
|
p_read = sub.add_parser("read", help="Extract live screen text, active thoughts, and side chats for an agent")
|
||||||
|
p_read.add_argument("agent", help="Agent username (muse, pip, 646, opm, dev, def)")
|
||||||
|
p_read.add_argument("--json", action="store_true", help="Output JSON")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
action = args.work_action
|
action = args.work_action
|
||||||
|
|
||||||
@@ -1104,6 +1119,8 @@ def main():
|
|||||||
cmd_cognitive(args)
|
cmd_cognitive(args)
|
||||||
elif action == "loopback":
|
elif action == "loopback":
|
||||||
cmd_loopback(args)
|
cmd_loopback(args)
|
||||||
|
elif action == "read":
|
||||||
|
cmd_read(args)
|
||||||
else:
|
else:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|
||||||
|
|||||||
@@ -1495,6 +1495,8 @@ def cmd_work(args):
|
|||||||
box_work.cmd_cognitive(args)
|
box_work.cmd_cognitive(args)
|
||||||
elif action == "loopback":
|
elif action == "loopback":
|
||||||
box_work.cmd_loopback(args)
|
box_work.cmd_loopback(args)
|
||||||
|
elif action == "read":
|
||||||
|
box_work.cmd_read(args)
|
||||||
else:
|
else:
|
||||||
box_work.cmd_status(args)
|
box_work.cmd_status(args)
|
||||||
|
|
||||||
@@ -7018,6 +7020,8 @@ def build_parser():
|
|||||||
p_w_cog.add_argument("agents", nargs="*", help="Optional agent usernames")
|
p_w_cog.add_argument("agents", nargs="*", help="Optional agent usernames")
|
||||||
p_w_loop = work_sub.add_parser("loopback", parents=[common], help="Sweep active tickets with 3-tier loopback escalation")
|
p_w_loop = work_sub.add_parser("loopback", parents=[common], help="Sweep active tickets with 3-tier loopback escalation")
|
||||||
p_w_loop.add_argument("--dry-run", action="store_true", help="Inspect without modifying tickets or sending DMs")
|
p_w_loop.add_argument("--dry-run", action="store_true", help="Inspect without modifying tickets or sending DMs")
|
||||||
|
p_w_read = work_sub.add_parser("read", parents=[common], help="Extract live screen text, active thoughts, and side chats for an agent")
|
||||||
|
p_w_read.add_argument("agent", help="Agent username (muse, pip, 646, opm, dev, def)")
|
||||||
|
|
||||||
p_tasks = subparsers.add_parser("tasks", parents=[common], help="Agent work queue: pending/claimed/done files (distinct from scheduled jobs)")
|
p_tasks = subparsers.add_parser("tasks", parents=[common], help="Agent work queue: pending/claimed/done files (distinct from scheduled jobs)")
|
||||||
p_tasks.add_argument("--dir", default=None, help="Task queue dir (default: fleet/tasks)")
|
p_tasks.add_argument("--dir", default=None, help="Task queue dir (default: fleet/tasks)")
|
||||||
|
|||||||
Reference in New Issue
Block a user