feat(box): enable real-time agent tool triggers and capabilities expansion
This commit is contained in:
@@ -240,6 +240,56 @@ case "$cmd" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
|
cron)
|
||||||
|
sub="${1:-runs}"
|
||||||
|
shift || true
|
||||||
|
case "$sub" in
|
||||||
|
runs|list)
|
||||||
|
call_exec "cron.runs" "{}"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
name="${1:-heartbeat}"
|
||||||
|
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
|
||||||
|
call_exec "cron.status" "$args"
|
||||||
|
;;
|
||||||
|
view)
|
||||||
|
name="${1:?usage: box cron view <name>}"
|
||||||
|
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
|
||||||
|
call_exec "cron.view" "$args"
|
||||||
|
;;
|
||||||
|
run)
|
||||||
|
name="${1:?usage: box cron run <name>}"
|
||||||
|
args=$(python3 -c "import json, sys; print(json.dumps({'job': sys.argv[1]}))" "$name")
|
||||||
|
call_exec "cron.run" "$args"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: box cron runs|status|view|run ..."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
vars)
|
||||||
|
sub="${1:-list}"
|
||||||
|
shift || true
|
||||||
|
case "$sub" in
|
||||||
|
list)
|
||||||
|
call_exec "vars.list" "{}"
|
||||||
|
;;
|
||||||
|
get)
|
||||||
|
name="${1:?usage: box vars get <name>}"
|
||||||
|
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
|
||||||
|
call_exec "vars.get" "$args"
|
||||||
|
;;
|
||||||
|
set)
|
||||||
|
name="${1:?usage: box vars set <name> <value>}"
|
||||||
|
val="${2:?usage: box vars set <name> <value>}"
|
||||||
|
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1], 'value': sys.argv[2]}))" "$name" "$val")
|
||||||
|
call_exec "vars.set" "$args"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: box vars list|get|set ..."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
health)
|
health)
|
||||||
call_exec "health.check" "{}"
|
call_exec "health.check" "{}"
|
||||||
;;
|
;;
|
||||||
@@ -261,6 +311,13 @@ Usage:
|
|||||||
box dm read [<target=main>] [<limit=10>]
|
box dm read [<target=main>] [<limit=10>]
|
||||||
box thread list [<agent>]
|
box thread list [<agent>]
|
||||||
box thread view <thread_id> [<limit=15>]
|
box thread view <thread_id> [<limit=15>]
|
||||||
|
box cron runs
|
||||||
|
box cron status [<name=heartbeat>]
|
||||||
|
box cron view <name>
|
||||||
|
box cron run <name>
|
||||||
|
box vars list
|
||||||
|
box vars get <name>
|
||||||
|
box vars set <name> <value>
|
||||||
box health
|
box health
|
||||||
box ping
|
box ping
|
||||||
box ops
|
box ops
|
||||||
|
|||||||
+124
-1
@@ -67,6 +67,7 @@ SIG_MAX_SKEW = 300
|
|||||||
AGENTS = ('muse', 'pip', '646', 'opm', 'def', 'dev')
|
AGENTS = ('muse', 'pip', '646', 'opm', 'def', 'dev')
|
||||||
TARGET_RE = re.compile(r'^[A-Za-z0-9][A-Za-z0-9/_.-]{0,63}$')
|
TARGET_RE = re.compile(r'^[A-Za-z0-9][A-Za-z0-9/_.-]{0,63}$')
|
||||||
JOB_RE = re.compile(r'^[a-z0-9][a-z0-9-]{0,63}$')
|
JOB_RE = re.compile(r'^[a-z0-9][a-z0-9-]{0,63}$')
|
||||||
|
NAME_RE = re.compile(r'^[A-Za-z0-9_.-]{1,64}$')
|
||||||
IDENT_RE = re.compile(r'^[a-z0-9-]+$')
|
IDENT_RE = re.compile(r'^[a-z0-9-]+$')
|
||||||
NONCE_RE = re.compile(r'^[0-9a-fA-F]{16,128}$')
|
NONCE_RE = re.compile(r'^[0-9a-fA-F]{16,128}$')
|
||||||
HEX_RE = re.compile(r'^[0-9a-f]{8,128}$')
|
HEX_RE = re.compile(r'^[0-9a-f]{8,128}$')
|
||||||
@@ -523,6 +524,91 @@ def _pipeline_run_build(a):
|
|||||||
return [sys.executable, os.path.join(BIN_DIR, 'super-cli.py'), 'deploy', 'pipeline', a['name']]
|
return [sys.executable, os.path.join(BIN_DIR, 'super-cli.py'), 'deploy', 'pipeline', a['name']]
|
||||||
|
|
||||||
|
|
||||||
|
def _cron_status_validate(raw):
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise OpError('args must be an object')
|
||||||
|
allowed = {'name'}
|
||||||
|
for k in raw:
|
||||||
|
if k not in allowed:
|
||||||
|
raise OpError(f'unknown arg: {k}')
|
||||||
|
return {'name': _job_name(raw.get('name')) if raw.get('name') else 'heartbeat'}
|
||||||
|
|
||||||
|
|
||||||
|
def _cron_status_build(a):
|
||||||
|
return [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), 'timer-status', a['name']]
|
||||||
|
|
||||||
|
|
||||||
|
def _cron_view_validate(raw):
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise OpError('args must be an object')
|
||||||
|
allowed = {'name'}
|
||||||
|
for k in raw:
|
||||||
|
if k not in allowed:
|
||||||
|
raise OpError(f'unknown arg: {k}')
|
||||||
|
return {'name': _job_name(raw.get('name'))}
|
||||||
|
|
||||||
|
|
||||||
|
def _cron_view_build(a):
|
||||||
|
return [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), 'job-get', a['name']]
|
||||||
|
|
||||||
|
|
||||||
|
def _cron_runs_validate(raw):
|
||||||
|
if raw not in ({}, None):
|
||||||
|
raise OpError('cron.runs takes no required args')
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _cron_runs_build(a):
|
||||||
|
return [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), 'job-list']
|
||||||
|
|
||||||
|
|
||||||
|
def _vars_list_validate(raw):
|
||||||
|
if raw not in ({}, None):
|
||||||
|
raise OpError('vars.list takes no required args')
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _vars_list_build(a):
|
||||||
|
return [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), 'vars-list']
|
||||||
|
|
||||||
|
|
||||||
|
def _vars_get_validate(raw):
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise OpError('args must be an object')
|
||||||
|
allowed = {'name'}
|
||||||
|
for k in raw:
|
||||||
|
if k not in allowed:
|
||||||
|
raise OpError(f'unknown arg: {k}')
|
||||||
|
name = raw.get('name')
|
||||||
|
if not isinstance(name, str) or not NAME_RE.fullmatch(name):
|
||||||
|
raise OpError('name must match safe identifier')
|
||||||
|
return {'name': name}
|
||||||
|
|
||||||
|
|
||||||
|
def _vars_get_build(a):
|
||||||
|
return [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), 'vars-get', a['name']]
|
||||||
|
|
||||||
|
|
||||||
|
def _vars_set_validate(raw):
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise OpError('args must be an object')
|
||||||
|
allowed = {'name', 'value'}
|
||||||
|
for k in raw:
|
||||||
|
if k not in allowed:
|
||||||
|
raise OpError(f'unknown arg: {k}')
|
||||||
|
name = raw.get('name')
|
||||||
|
if not isinstance(name, str) or not NAME_RE.fullmatch(name):
|
||||||
|
raise OpError('name must match safe identifier')
|
||||||
|
val = raw.get('value')
|
||||||
|
if val is None or not isinstance(val, (str, int, float, bool)):
|
||||||
|
raise OpError('value must be a scalar')
|
||||||
|
return {'name': name, 'value': str(val)}
|
||||||
|
|
||||||
|
|
||||||
|
def _vars_set_build(a):
|
||||||
|
return [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), 'vars-set', a['name'], a['value']]
|
||||||
|
|
||||||
|
|
||||||
# op -> {validate, build, timeout, side_effecting, description}
|
# op -> {validate, build, timeout, side_effecting, description}
|
||||||
OPS = {
|
OPS = {
|
||||||
'dm.send': {
|
'dm.send': {
|
||||||
@@ -580,6 +666,41 @@ OPS = {
|
|||||||
'timeout': 120, 'side_effecting': True,
|
'timeout': 120, 'side_effecting': True,
|
||||||
'desc': 'Dispatch a multi-step pipeline across agents',
|
'desc': 'Dispatch a multi-step pipeline across agents',
|
||||||
},
|
},
|
||||||
|
'cron.status': {
|
||||||
|
'validate': _cron_status_validate, 'build': _cron_status_build,
|
||||||
|
'timeout': 30, 'side_effecting': False,
|
||||||
|
'desc': 'Check systemd timer / scheduled check status',
|
||||||
|
},
|
||||||
|
'cron.view': {
|
||||||
|
'validate': _cron_view_validate, 'build': _cron_view_build,
|
||||||
|
'timeout': 30, 'side_effecting': False,
|
||||||
|
'desc': 'View scheduled job definition and prompt',
|
||||||
|
},
|
||||||
|
'cron.runs': {
|
||||||
|
'validate': _cron_runs_validate, 'build': _cron_runs_build,
|
||||||
|
'timeout': 30, 'side_effecting': False,
|
||||||
|
'desc': 'List all scheduled crons/jobs across the fleet',
|
||||||
|
},
|
||||||
|
'cron.run': {
|
||||||
|
'validate': _job_run_validate, 'build': _job_run_build,
|
||||||
|
'timeout': 300, 'side_effecting': True,
|
||||||
|
'desc': 'Trigger on-demand execution of a scheduled job',
|
||||||
|
},
|
||||||
|
'vars.list': {
|
||||||
|
'validate': _vars_list_validate, 'build': _vars_list_build,
|
||||||
|
'timeout': 30, 'side_effecting': False,
|
||||||
|
'desc': 'List intrinsic loop strategy and timing variables',
|
||||||
|
},
|
||||||
|
'vars.get': {
|
||||||
|
'validate': _vars_get_validate, 'build': _vars_get_build,
|
||||||
|
'timeout': 30, 'side_effecting': False,
|
||||||
|
'desc': 'Get specific intrinsic loop variable',
|
||||||
|
},
|
||||||
|
'vars.set': {
|
||||||
|
'validate': _vars_set_validate, 'build': _vars_set_build,
|
||||||
|
'timeout': 30, 'side_effecting': True,
|
||||||
|
'desc': 'Update intrinsic loop variable',
|
||||||
|
},
|
||||||
'exec.ping': {
|
'exec.ping': {
|
||||||
'validate': _health_validate,
|
'validate': _health_validate,
|
||||||
'build': lambda a: ['/bin/echo', 'PONG'],
|
'build': lambda a: ['/bin/echo', 'PONG'],
|
||||||
@@ -757,7 +878,9 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self._send(400, {'error': f'bad args: {e}'})
|
self._send(400, {'error': f'bad args: {e}'})
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._send(500, {'error': 'internal'})
|
sys.stderr.write(f'Validation/build exception for {op}: {e}\n')
|
||||||
|
import traceback; traceback.print_exc()
|
||||||
|
self._send(500, {'error': f'internal: {e}'})
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- execute (NO shell, argv only)
|
# --- execute (NO shell, argv only)
|
||||||
|
|||||||
+2
-1
@@ -486,12 +486,13 @@ def main():
|
|||||||
import muse_hybrid
|
import muse_hybrid
|
||||||
res, err = muse_hybrid.start_session(agent, title=channel_title)
|
res, err = muse_hybrid.start_session(agent, title=channel_title)
|
||||||
if res and not err and res.get("session_id"):
|
if res and not err and res.get("session_id"):
|
||||||
new_uuid = res.get("session_id")
|
|
||||||
key_to_save = reuse_key or sc_name
|
key_to_save = reuse_key or sc_name
|
||||||
|
is_persistent = bool(reuse_key)
|
||||||
sc_state[key_to_save] = {
|
sc_state[key_to_save] = {
|
||||||
"thread_uuid": new_uuid,
|
"thread_uuid": new_uuid,
|
||||||
"agent": agent,
|
"agent": agent,
|
||||||
"title": channel_title,
|
"title": channel_title,
|
||||||
|
"type": "persistent" if is_persistent else "ephemeral",
|
||||||
"created_at": datetime.now(timezone.utc).isoformat()
|
"created_at": datetime.now(timezone.utc).isoformat()
|
||||||
}
|
}
|
||||||
save_sidechat_state(sc_state)
|
save_sidechat_state(sc_state)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import ssl
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@@ -105,6 +106,86 @@ def iter_verb_markers(text):
|
|||||||
yield m.group(1), m.group(2).strip()
|
yield m.group(1), m.group(2).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_tool_calls(text):
|
||||||
|
"""
|
||||||
|
Extract structured tool/exec calls from assistant messages.
|
||||||
|
Supports:
|
||||||
|
1. [TOOL <op> <json_args>] or [EXEC <op> <json_args>]
|
||||||
|
2. ```box / ```tool / ```exec JSON blocks
|
||||||
|
"""
|
||||||
|
calls = []
|
||||||
|
for m in re.finditer(r"\[(?:TOOL|EXEC)\s+([a-zA-Z0-9_.-]+)(?:\s+(.*?))?\]", text or ""):
|
||||||
|
op = m.group(1).strip()
|
||||||
|
raw_args = (m.group(2) or "").strip()
|
||||||
|
args = {}
|
||||||
|
if raw_args:
|
||||||
|
try:
|
||||||
|
args = json.loads(raw_args)
|
||||||
|
except Exception:
|
||||||
|
args = {"raw": raw_args}
|
||||||
|
calls.append((op, args))
|
||||||
|
|
||||||
|
for m in re.finditer(r"```(?:box|tool|exec)\s*\n(.*?)```", text or "", re.DOTALL):
|
||||||
|
block = m.group(1).strip()
|
||||||
|
try:
|
||||||
|
d = json.loads(block)
|
||||||
|
if isinstance(d, dict) and "op" in d:
|
||||||
|
calls.append((d["op"], d.get("args", {})))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return calls
|
||||||
|
|
||||||
|
|
||||||
|
def execute_agent_tool(agent, op, args, timeout=30):
|
||||||
|
"""
|
||||||
|
Execute tool call via local exec-constrained HTTPS daemon over Tailscale.
|
||||||
|
Returns (ok, result_or_error_string).
|
||||||
|
"""
|
||||||
|
token = None
|
||||||
|
token_file = os.path.expanduser(f"~/.exec-tokens/{agent}")
|
||||||
|
if not os.path.exists(token_file):
|
||||||
|
token_file = os.path.expanduser("~/.exec-server-token")
|
||||||
|
if os.path.exists(token_file):
|
||||||
|
try:
|
||||||
|
with open(token_file) as f:
|
||||||
|
token = f.read().strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
url = "https://100.123.153.75:8444/exec"
|
||||||
|
payload = json.dumps({"op": op, "args": args}).encode("utf-8")
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": f"Box-Harvester/{agent}",
|
||||||
|
}
|
||||||
|
if token:
|
||||||
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, context=ctx, timeout=timeout) as resp:
|
||||||
|
data = resp.read().decode("utf-8")
|
||||||
|
res = json.loads(data)
|
||||||
|
if res.get("rc") == 0:
|
||||||
|
return True, res.get("stdout", "").strip() or "OK"
|
||||||
|
elif "stdout" in res:
|
||||||
|
return False, (res.get("stdout") or res.get("stderr") or "failed").strip()
|
||||||
|
elif "error" in res:
|
||||||
|
return False, res["error"]
|
||||||
|
return True, json.dumps(res)
|
||||||
|
except urllib.error.HTTPError as he:
|
||||||
|
try:
|
||||||
|
err_body = he.read().decode("utf-8")
|
||||||
|
return False, f"HTTP {he.code}: {err_body}"
|
||||||
|
except Exception:
|
||||||
|
return False, f"HTTP {he.code}"
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
|
||||||
def utcnow():
|
def utcnow():
|
||||||
return datetime.now(timezone.utc).isoformat()
|
return datetime.now(timezone.utc).isoformat()
|
||||||
@@ -416,6 +497,35 @@ def process_messages(raw_messages, agent, thread_id, thread_name, last_wm, follo
|
|||||||
if author == "assistant":
|
if author == "assistant":
|
||||||
markers = list(iter_result_markers(text))
|
markers = list(iter_result_markers(text))
|
||||||
verbs = list(iter_verb_markers(text))
|
verbs = list(iter_verb_markers(text))
|
||||||
|
# Check for structured tool / command calls from the agent
|
||||||
|
tool_calls = parse_tool_calls(text)
|
||||||
|
if tool_calls and not dry_run:
|
||||||
|
for op, args in tool_calls:
|
||||||
|
print(f"[{agent}] Executing tool '{op}' from message {mid[:8]} in thread {thread_name or thread_id[:8]}")
|
||||||
|
t_ok, t_res = execute_agent_tool(agent, op, args)
|
||||||
|
tool_record = {
|
||||||
|
"ts": utcnow(),
|
||||||
|
"type": "tool_exec",
|
||||||
|
"agent": agent,
|
||||||
|
"thread_id": thread_id,
|
||||||
|
"op": op,
|
||||||
|
"args": args,
|
||||||
|
"success": t_ok,
|
||||||
|
"result_snippet": str(t_res)[:300],
|
||||||
|
"msg_id": mid,
|
||||||
|
}
|
||||||
|
append_jsonl(JOB_LOG, tool_record)
|
||||||
|
|
||||||
|
# Post tool output back to the originating thread via fast headless gateway
|
||||||
|
if thread_id and re.fullmatch(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", thread_id.lower()):
|
||||||
|
try:
|
||||||
|
import muse_hybrid
|
||||||
|
status_label = "OUTPUT" if t_ok else "ERROR"
|
||||||
|
resp_text = f"[{status_label} {op}]\n{t_res}"
|
||||||
|
muse_hybrid.send_message(agent, resp_text, thread_id=thread_id, wait=0)
|
||||||
|
except Exception as te:
|
||||||
|
sys.stderr.write(f"warning: failed to post tool response back to thread: {te}\n")
|
||||||
|
|
||||||
if markers or verbs:
|
if markers or verbs:
|
||||||
for job_id, result_text in markers:
|
for job_id, result_text in markers:
|
||||||
is_fail = is_fail_result(result_text)
|
is_fail = is_fail_result(result_text)
|
||||||
|
|||||||
+132
-46
@@ -1,230 +1,316 @@
|
|||||||
{
|
{
|
||||||
"646-pip-coord": {
|
"646-pip-coord": {
|
||||||
"thread_uuid": "4466d0c1-7961-4cf3-b99d-1ab7c38484c2",
|
"thread_uuid": "4466d0c1-7961-4cf3-b99d-1ab7c38484c2",
|
||||||
"agent": "pip"
|
"agent": "pip",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"646-pip-coord@646": {
|
"646-pip-coord@646": {
|
||||||
"thread_uuid": "e3442b78-0300-4034-8cbe-7a0ab6c5764a",
|
"thread_uuid": "e3442b78-0300-4034-8cbe-7a0ab6c5764a",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"description": "646 side of 646-pip coordination pair"
|
"description": "646 side of 646-pip coordination pair",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"646-pip-coord@pip": {
|
"646-pip-coord@pip": {
|
||||||
"thread_uuid": "4466d0c1-7961-4cf3-b99d-1ab7c38484c2",
|
"thread_uuid": "4466d0c1-7961-4cf3-b99d-1ab7c38484c2",
|
||||||
"agent": "pip",
|
"agent": "pip",
|
||||||
"description": "pip side of 646-pip coordination pair"
|
"description": "pip side of 646-pip coordination pair",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"646-opm-coord": {
|
"646-opm-coord": {
|
||||||
"thread_uuid": "4139dd4e-96fe-4222-a99f-82a59a7b0eeb",
|
"thread_uuid": "4139dd4e-96fe-4222-a99f-82a59a7b0eeb",
|
||||||
"agent": "opm"
|
"agent": "opm",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"test-auto-prov": {
|
"test-auto-prov": {
|
||||||
"thread_uuid": "b96dd020-b429-4f5f-9e52-dbe8f805ac6a",
|
"thread_uuid": "b96dd020-b429-4f5f-9e52-dbe8f805ac6a",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"created_at": "2026-10-04T16:20:33.919952+00:00"
|
"created_at": "2026-10-04T16:20:33.919952+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pipe-a86d86": {
|
"pipe-a86d86": {
|
||||||
"thread_uuid": "da55b05c-19c6-4bdb-8f8b-e507d7430ca2",
|
"thread_uuid": "da55b05c-19c6-4bdb-8f8b-e507d7430ca2",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T16:39:34.721269+00:00"
|
"created_at": "2026-10-04T16:39:34.721269+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"pipe-7d2896": {
|
"pipe-7d2896": {
|
||||||
"thread_uuid": "78e03d7a-0ba2-440e-a730-6f3b3737cbac",
|
"thread_uuid": "78e03d7a-0ba2-440e-a730-6f3b3737cbac",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T16:48:03.062944+00:00"
|
"created_at": "2026-10-04T16:48:03.062944+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"pipe-933473": {
|
"pipe-933473": {
|
||||||
"thread_uuid": "dc2288ef-3fe1-4b12-bf31-08c482398c0f",
|
"thread_uuid": "dc2288ef-3fe1-4b12-bf31-08c482398c0f",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"created_at": "2026-10-04T16:50:35.478877+00:00"
|
"created_at": "2026-10-04T16:50:35.478877+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"pipe-caddc7": {
|
"pipe-caddc7": {
|
||||||
"thread_uuid": "9726b713-32a7-4df6-8582-32f2af8dd95a",
|
"thread_uuid": "9726b713-32a7-4df6-8582-32f2af8dd95a",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T16:53:04.977475+00:00"
|
"created_at": "2026-10-04T16:53:04.977475+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"pipe-1b4579": {
|
"pipe-1b4579": {
|
||||||
"thread_uuid": "bf7bf3e1-3ee8-4816-9b66-b77b34387986",
|
"thread_uuid": "bf7bf3e1-3ee8-4816-9b66-b77b34387986",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T16:58:33.951216+00:00"
|
"created_at": "2026-10-04T16:58:33.951216+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"pipe-fe170d": {
|
"pipe-fe170d": {
|
||||||
"thread_uuid": "fbc37533-3908-460e-bb82-58a7edbe5f30",
|
"thread_uuid": "fbc37533-3908-460e-bb82-58a7edbe5f30",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T17:17:48.449687+00:00"
|
"created_at": "2026-10-04T17:17:48.449687+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"646-pip": {
|
"646-pip": {
|
||||||
"thread_uuid": "4466d0c1-7961-4cf3-b99d-1ab7c38484c2",
|
"thread_uuid": "4466d0c1-7961-4cf3-b99d-1ab7c38484c2",
|
||||||
"agent": "pip",
|
"agent": "pip",
|
||||||
"description": "default 646-pip coordination target"
|
"description": "default 646-pip coordination target",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"646 tasks": {
|
"646 tasks": {
|
||||||
"thread_uuid": "1dfb3199-2f99-446c-83e3-848ae2da0a12",
|
"thread_uuid": "1dfb3199-2f99-446c-83e3-848ae2da0a12",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"created_at": "2026-10-04T18:30:21.863666+00:00"
|
"created_at": "2026-10-04T18:30:21.863666+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"646-tasks": {
|
"646-tasks": {
|
||||||
"thread_uuid": "1dfb3199-2f99-446c-83e3-848ae2da0a12",
|
"thread_uuid": "1dfb3199-2f99-446c-83e3-848ae2da0a12",
|
||||||
"agent": "646"
|
"agent": "646",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pip tasks": {
|
"pip tasks": {
|
||||||
"thread_uuid": "5f33b1ab-a310-4cbb-9ccc-bb7bcd3d1b34",
|
"thread_uuid": "5f33b1ab-a310-4cbb-9ccc-bb7bcd3d1b34",
|
||||||
"agent": "pip",
|
"agent": "pip",
|
||||||
"description": "Dedicated pip task prompting sidechat",
|
"description": "Dedicated pip task prompting sidechat",
|
||||||
"created_at": "2026-10-04T22:59:13+00:00"
|
"created_at": "2026-10-04T22:59:13+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pip-tasks": {
|
"pip-tasks": {
|
||||||
"thread_uuid": "5f33b1ab-a310-4cbb-9ccc-bb7bcd3d1b34",
|
"thread_uuid": "5f33b1ab-a310-4cbb-9ccc-bb7bcd3d1b34",
|
||||||
"agent": "pip"
|
"agent": "pip",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"muse tasks": {
|
"muse tasks": {
|
||||||
"thread_uuid": "3881dae3-30fa-411b-8f5b-f6d5917bc302",
|
"thread_uuid": "3881dae3-30fa-411b-8f5b-f6d5917bc302",
|
||||||
"agent": "muse",
|
"agent": "muse",
|
||||||
"title": "muse tasks",
|
"title": "muse tasks",
|
||||||
"created_at": "2026-10-05T02:44:00+00:00"
|
"created_at": "2026-10-05T02:44:00+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"muse-tasks": {
|
"muse-tasks": {
|
||||||
"thread_uuid": "3881dae3-30fa-411b-8f5b-f6d5917bc302",
|
"thread_uuid": "3881dae3-30fa-411b-8f5b-f6d5917bc302",
|
||||||
"agent": "muse"
|
"agent": "muse",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pipe-c3f686": {
|
"pipe-c3f686": {
|
||||||
"thread_uuid": "afa977ba-c75b-4b3e-8c1f-f83f13fb287e",
|
"thread_uuid": "afa977ba-c75b-4b3e-8c1f-f83f13fb287e",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T20:13:56.485442+00:00"
|
"created_at": "2026-10-04T20:13:56.485442+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"main-loop brain": {
|
"main-loop brain": {
|
||||||
"thread_uuid": "5f18476d-8994-49e7-a9e0-4838732363fe",
|
"thread_uuid": "5f18476d-8994-49e7-a9e0-4838732363fe",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T19:40:31.161131+00:00",
|
"created_at": "2026-10-04T19:40:31.161131+00:00",
|
||||||
"purpose": "Main loop coordination: health reports, digests, alerts"
|
"purpose": "Main loop coordination: health reports, digests, alerts",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"heartbeat": {
|
"heartbeat": {
|
||||||
"thread_uuid": "51695451-ffba-49e2-8c6d-ffa4f511ce5a",
|
"thread_uuid": "51695451-ffba-49e2-8c6d-ffa4f511ce5a",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"description": "Dedicated heartbeat thread (provisioned 2026-10-05 via headless gateway)",
|
"description": "Dedicated heartbeat thread (provisioned 2026-10-05 via headless gateway)",
|
||||||
"updated_at": "2026-10-05T02:22:19+00:00"
|
"updated_at": "2026-10-05T02:22:19+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"heartbeat-opm": {
|
"heartbeat-opm": {
|
||||||
"thread_uuid": "51695451-ffba-49e2-8c6d-ffa4f511ce5a",
|
"thread_uuid": "ac8c3366-a2b4-407d-adc7-bfa18903c0f5",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"description": "Reuse key for OPM heartbeat -> dedicated heartbeat thread [updated 2026-10-05 to current]",
|
"title": "heartbeat",
|
||||||
"updated_at": "2026-10-05T02:22:19+00:00"
|
"created_at": "2026-10-05T03:00:02.900692+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"646-pip@646": {
|
"646-pip@646": {
|
||||||
"thread_uuid": "e3442b78-0300-4034-8cbe-7a0ab6c5764a",
|
"thread_uuid": "e3442b78-0300-4034-8cbe-7a0ab6c5764a",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"description": "646 side of 646-pip coordination pair"
|
"description": "646 side of 646-pip coordination pair",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"646-pip@pip": {
|
"646-pip@pip": {
|
||||||
"thread_uuid": "4466d0c1-7961-4cf3-b99d-1ab7c38484c2",
|
"thread_uuid": "4466d0c1-7961-4cf3-b99d-1ab7c38484c2",
|
||||||
"agent": "pip",
|
"agent": "pip",
|
||||||
"description": "pip side of 646-pip coordination pair"
|
"description": "pip side of 646-pip coordination pair",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pip-opm": {
|
"pip-opm": {
|
||||||
"thread_uuid": "75feb3a2-be36-499a-b314-bfd5803f1db0",
|
"thread_uuid": "75feb3a2-be36-499a-b314-bfd5803f1db0",
|
||||||
"agent": "pip",
|
"agent": "pip",
|
||||||
"description": "default pip-opm coordination target"
|
"description": "default pip-opm coordination target",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pip-opm@pip": {
|
"pip-opm@pip": {
|
||||||
"thread_uuid": "75feb3a2-be36-499a-b314-bfd5803f1db0",
|
"thread_uuid": "75feb3a2-be36-499a-b314-bfd5803f1db0",
|
||||||
"agent": "pip",
|
"agent": "pip",
|
||||||
"description": "pip side of pip-opm coordination pair"
|
"description": "pip side of pip-opm coordination pair",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pip-opm@opm": {
|
"pip-opm@opm": {
|
||||||
"thread_uuid": "769955af-21ad-4689-8909-0f75b8f1035c",
|
"thread_uuid": "769955af-21ad-4689-8909-0f75b8f1035c",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"description": "opm side of pip-opm coordination pair"
|
"description": "opm side of pip-opm coordination pair",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pipe-a0d377": {
|
"pipe-a0d377": {
|
||||||
"thread_uuid": "cc0aee8f-531a-40af-9e02-27459f57b3ae",
|
"thread_uuid": "cc0aee8f-531a-40af-9e02-27459f57b3ae",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T23:15:34.981356+00:00"
|
"created_at": "2026-10-04T23:15:34.981356+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"pipe-3a153d": {
|
"pipe-3a153d": {
|
||||||
"thread_uuid": "29066a5c-878f-4d9d-b0f8-32405088c2d8",
|
"thread_uuid": "29066a5c-878f-4d9d-b0f8-32405088c2d8",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T23:16:16.726507+00:00"
|
"created_at": "2026-10-04T23:16:16.726507+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"pipe-921583": {
|
"pipe-921583": {
|
||||||
"thread_uuid": "70c1e8a6-e3f7-4ef2-9c32-8207b4f8c44f",
|
"thread_uuid": "70c1e8a6-e3f7-4ef2-9c32-8207b4f8c44f",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T23:17:25.127456+00:00"
|
"created_at": "2026-10-04T23:17:25.127456+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"pipe-b3f7c4": {
|
"pipe-b3f7c4": {
|
||||||
"thread_uuid": "8f9ae8e7-dd3a-46e6-a5ac-3b1bb7cf6d66",
|
"thread_uuid": "8f9ae8e7-dd3a-46e6-a5ac-3b1bb7cf6d66",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-04T23:19:20.218056+00:00"
|
"created_at": "2026-10-04T23:19:20.218056+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"onboarding-test-dev": {
|
"onboarding-test-dev": {
|
||||||
"thread_uuid": "f7741827-7b44-45b7-bfb3-c08f8889e93a",
|
"thread_uuid": "f7741827-7b44-45b7-bfb3-c08f8889e93a",
|
||||||
"agent": "dev",
|
"agent": "dev",
|
||||||
"created_at": "2026-10-04T23:35:34.711793+00:00"
|
"created_at": "2026-10-04T23:35:34.711793+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"onboarding-dev": {
|
"onboarding-dev": {
|
||||||
"thread_uuid": "31ceab7e-7579-4acc-bcf5-1a10c081aa21",
|
"thread_uuid": "31ceab7e-7579-4acc-bcf5-1a10c081aa21",
|
||||||
"agent": "dev",
|
"agent": "dev",
|
||||||
"created_at": "2026-10-04T23:37:01+00:00",
|
"created_at": "2026-10-04T23:37:01+00:00",
|
||||||
"note": "onboarding-alpha-probe"
|
"note": "onboarding-alpha-probe",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"onboarding-def": {
|
"onboarding-def": {
|
||||||
"thread_uuid": "466005b1-7065-4798-bf31-bbe785d65f3a",
|
"thread_uuid": "466005b1-7065-4798-bf31-bbe785d65f3a",
|
||||||
"agent": "def",
|
"agent": "def",
|
||||||
"created_at": "2026-10-04T23:45:37+00:00",
|
"created_at": "2026-10-04T23:45:37+00:00",
|
||||||
"note": "onboarding-def-probe"
|
"note": "onboarding-def-probe",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"main-loop-brain": {
|
"main-loop-brain": {
|
||||||
"thread_uuid": "74d05d6b-af95-423f-8999-f733ab647785",
|
"thread_uuid": "74d05d6b-af95-423f-8999-f733ab647785",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"created_at": "2026-10-05T00:53:28.473884+00:00"
|
"created_at": "2026-10-05T00:53:28.473884+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"dev-audit-channel": {
|
"dev-audit-channel": {
|
||||||
"thread_uuid": "729d31e0-5154-44ad-97c8-748d36daa448",
|
"thread_uuid": "729d31e0-5154-44ad-97c8-748d36daa448",
|
||||||
"agent": "dev",
|
"agent": "dev",
|
||||||
"title": "dev-audit-channel",
|
"title": "dev-audit-channel",
|
||||||
"created_at": "2026-10-05T02:23:45.941926+00:00"
|
"created_at": "2026-10-05T02:23:45.941926+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"canary-test": {
|
"canary-test": {
|
||||||
"thread_uuid": "bcda3950-9966-4edd-a014-f931d8b17701",
|
"thread_uuid": "bcda3950-9966-4edd-a014-f931d8b17701",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"title": "canary-test",
|
"title": "canary-test",
|
||||||
"created_at": "2026-10-05T02:24:25.475505+00:00"
|
"created_at": "2026-10-05T02:24:25.475505+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"heartbeat-thread": {
|
"heartbeat-thread": {
|
||||||
"thread_uuid": "51695451-ffba-49e2-8c6d-ffa4f511ce5a",
|
"thread_uuid": "51695451-ffba-49e2-8c6d-ffa4f511ce5a",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"description": "Alias for heartbeat (dedicated heartbeat channel provisioned 2026-10-05) [updated 2026-10-05 to current]"
|
"description": "Alias for heartbeat (dedicated heartbeat channel provisioned 2026-10-05) [updated 2026-10-05 to current]",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"main-loop": {
|
"main-loop": {
|
||||||
"thread_uuid": "5f18476d-8994-49e7-a9e0-4838732363fe",
|
"thread_uuid": "5f18476d-8994-49e7-a9e0-4838732363fe",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"description": "Alias for main-loop brain; added 2026-10-05 to stop autoprovision junk"
|
"description": "Alias for main-loop brain; added 2026-10-05 to stop autoprovision junk",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"box-http-health": {
|
"box-http-health": {
|
||||||
"thread_uuid": "d9c596c4-5ace-44dc-9619-9862cf56968d",
|
"thread_uuid": "d9c596c4-5ace-44dc-9619-9862cf56968d",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"title": "box-http-health",
|
"title": "box-http-health",
|
||||||
"created_at": "2026-10-05T02:25:41.573556+00:00"
|
"created_at": "2026-10-05T02:25:41.573556+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"box-service-health": {
|
"box-service-health": {
|
||||||
"thread_uuid": "94ab9f9f-012c-41e5-ad35-dc44f2b24281",
|
"thread_uuid": "94ab9f9f-012c-41e5-ad35-dc44f2b24281",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"title": "box-service-health",
|
"title": "box-service-health",
|
||||||
"created_at": "2026-10-05T02:26:11.075842+00:00"
|
"created_at": "2026-10-05T02:26:11.075842+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"box-deep-health": {
|
"box-deep-health": {
|
||||||
"thread_uuid": "6628c035-4413-4d9f-863c-56ee861c8c83",
|
"thread_uuid": "6628c035-4413-4d9f-863c-56ee861c8c83",
|
||||||
"agent": "646",
|
"agent": "646",
|
||||||
"title": "box-deep-health",
|
"title": "box-deep-health",
|
||||||
"created_at": "2026-10-05T02:26:37.043639+00:00"
|
"created_at": "2026-10-05T02:26:37.043639+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
},
|
},
|
||||||
"dev-646-sync": {
|
"dev-646-sync": {
|
||||||
"thread_uuid": "4aefde50-c02c-4657-8d18-ad2238ef4cda",
|
"thread_uuid": "4aefde50-c02c-4657-8d18-ad2238ef4cda",
|
||||||
"agent": "dev",
|
"agent": "dev",
|
||||||
"title": "dev-646-sync",
|
"title": "dev-646-sync",
|
||||||
"created_at": "2026-10-05T02:30:35.461450+00:00"
|
"created_at": "2026-10-05T02:30:35.461450+00:00",
|
||||||
|
"type": "persistent"
|
||||||
},
|
},
|
||||||
"pipe-5086a2": {
|
"pipe-5086a2": {
|
||||||
"thread_uuid": "8a026cff-1b98-48df-9bf3-169b19c2e07c",
|
"thread_uuid": "8a026cff-1b98-48df-9bf3-169b19c2e07c",
|
||||||
"agent": "opm",
|
"agent": "opm",
|
||||||
"created_at": "2026-10-05T02:37:49.620793+00:00"
|
"created_at": "2026-10-05T02:37:49.620793+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
|
},
|
||||||
|
"canary-2026-10-05T03:10:23.381416+00:00": {
|
||||||
|
"thread_uuid": "20905085-eaf3-4026-94e3-3f6ac70e3f90",
|
||||||
|
"agent": "646",
|
||||||
|
"title": "canary-2026-10-05T03:10:23.381416+00:00",
|
||||||
|
"created_at": "2026-10-05T03:10:25.891171+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
|
},
|
||||||
|
"box-http-health-2026-10-05T03:17:43.411483+00:00": {
|
||||||
|
"thread_uuid": "26f22b42-c7e1-4596-a84c-c96ef3242e77",
|
||||||
|
"agent": "646",
|
||||||
|
"title": "box-http-health-2026-10-05T03:17:43.411483+00:00",
|
||||||
|
"created_at": "2026-10-05T03:17:45.822551+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
|
},
|
||||||
|
"canary-2026-10-05T03:18:05.975516+00:00": {
|
||||||
|
"thread_uuid": "cdba6af0-ce03-4db2-8036-03eddf7aab41",
|
||||||
|
"agent": "646",
|
||||||
|
"title": "canary-2026-10-05T03:18:05.975516+00:00",
|
||||||
|
"created_at": "2026-10-05T03:18:08.119304+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
|
},
|
||||||
|
"box-service-health-2026-10-05T03:22:00.100046+00:00": {
|
||||||
|
"thread_uuid": "40d2944d-6f19-4a20-827e-f8e1eaa493b4",
|
||||||
|
"agent": "646",
|
||||||
|
"title": "box-service-health-2026-10-05T03:22:00.100046+00:00",
|
||||||
|
"created_at": "2026-10-05T03:22:02.491797+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
|
},
|
||||||
|
"box-http-health-2026-10-05T03:30:01.629898+00:00": {
|
||||||
|
"thread_uuid": "50b61da3-0054-47ed-a829-baeb99d895a9",
|
||||||
|
"agent": "646",
|
||||||
|
"title": "box-http-health-2026-10-05T03:30:01.629898+00:00",
|
||||||
|
"created_at": "2026-10-05T03:30:06.414723+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
|
},
|
||||||
|
"box-service-health-2026-10-05T03:37:00.096660+00:00": {
|
||||||
|
"thread_uuid": "3b50c620-ad3a-4840-8449-991f2c3416e3",
|
||||||
|
"agent": "646",
|
||||||
|
"title": "box-service-health-2026-10-05T03:37:00.096660+00:00",
|
||||||
|
"created_at": "2026-10-05T03:37:02.331936+00:00",
|
||||||
|
"type": "ephemeral"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user