feat(fleet): expand VALID_AGENTS to include dev and def
This commit is contained in:
+175
-2
@@ -41,7 +41,9 @@ DM_LOG = NETVM_ROOT / "dm-log.jsonl"
|
|||||||
WATCHDOG_STATE = NETVM_ROOT / "main-chat-watchdog.state"
|
WATCHDOG_STATE = NETVM_ROOT / "main-chat-watchdog.state"
|
||||||
|
|
||||||
NAME_RE = re.compile(r"^[a-z0-9-]{1,64}$")
|
NAME_RE = re.compile(r"^[a-z0-9-]{1,64}$")
|
||||||
VALID_AGENTS = {"muse", "pip", "646", "opm"}
|
VALID_AGENTS = {"muse", "pip", "646", "opm", "dev", "def"}
|
||||||
|
THREAD_RE = re.compile(r"^[A-Za-z0-9-]{1,64}$")
|
||||||
|
THREAD_OPS = ("list", "pin", "unpin", "archive", "unarchive")
|
||||||
|
|
||||||
# Default sidechat per agent for `box notify`. Mirrors super-cli.py
|
# Default sidechat per agent for `box notify`. Mirrors super-cli.py
|
||||||
# DEFAULT_AGENT_SIDECHATS (kept in sync manually; super-cli is the
|
# DEFAULT_AGENT_SIDECHATS (kept in sync manually; super-cli is the
|
||||||
@@ -1216,6 +1218,14 @@ loop actions:
|
|||||||
loop-resolve <dm_id> [note]
|
loop-resolve <dm_id> [note]
|
||||||
loop-remediate [--dry-run]
|
loop-remediate [--dry-run]
|
||||||
|
|
||||||
|
thread actions (muse-cli gateway; hyphenated aliases for `thread <sub>`):
|
||||||
|
thread-list --agent <agent> [--limit N]
|
||||||
|
thread-pin --agent <agent> --thread <id>
|
||||||
|
thread-unpin --agent <agent> --thread <id>
|
||||||
|
thread-archive --agent <agent> --thread <id> --confirm
|
||||||
|
thread-unarchive --agent <agent> --thread <id>
|
||||||
|
thread-rename --agent <agent> --thread <id> --title <title>
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
notify <agent> <message> [--sidechat <name>] [--allow-main-chat]
|
notify <agent> <message> [--sidechat <name>] [--allow-main-chat]
|
||||||
Send a DM to an agent's default sidechat (never main chat unless
|
Send a DM to an agent's default sidechat (never main chat unless
|
||||||
@@ -1224,7 +1234,74 @@ notify:
|
|||||||
policy:
|
policy:
|
||||||
policy chat policy version, rule, and compliance summary
|
policy chat policy version, rule, and compliance summary
|
||||||
policy check <agent> per-agent compliance detail
|
policy check <agent> per-agent compliance detail
|
||||||
policy show print the full CHAT_POLICY.md"""
|
policy show print the full CHAT_POLICY.md
|
||||||
|
|
||||||
|
thread actions (muse-cli gateway):
|
||||||
|
thread list <agent> [--limit N]
|
||||||
|
thread pin <agent> <thread-id>
|
||||||
|
thread unpin <agent> <thread-id>
|
||||||
|
thread archive <agent> <thread-id> --confirm
|
||||||
|
thread unarchive <agent> <thread-id>"""
|
||||||
|
|
||||||
|
|
||||||
|
def act_thread(op, agent, thread=None, title=None, limit=None, confirm=False):
|
||||||
|
"""Thread bookkeeping via bin/muse-threads.py (hybrid gateway).
|
||||||
|
|
||||||
|
ops: list | pin | unpin | archive | unarchive | rename
|
||||||
|
archive requires confirm=True (the --confirm flag; BatchMode SSH
|
||||||
|
cannot prompt, so the flag is the explicit confirmation).
|
||||||
|
"""
|
||||||
|
if op not in ("list", "pin", "unpin", "archive", "unarchive", "rename"):
|
||||||
|
fail("BAD_NAME", "usage: thread list|pin|unpin|archive|unarchive|rename <agent> [args]")
|
||||||
|
if agent not in VALID_AGENTS:
|
||||||
|
fail("BAD_NAME", f"agent must be one of {sorted(VALID_AGENTS)}")
|
||||||
|
if op == "list":
|
||||||
|
audit("thread-list-" + agent)
|
||||||
|
else:
|
||||||
|
if not thread or not THREAD_RE.match(thread):
|
||||||
|
fail("BAD_NAME", "thread id must match ^[A-Za-z0-9-]{1,64}$",
|
||||||
|
{"field": "thread", "value": thread})
|
||||||
|
if op == "archive" and not confirm:
|
||||||
|
fail("CONFIRM_REQUIRED",
|
||||||
|
"archive is a visible side effect -- pass --confirm to proceed",
|
||||||
|
{"would_archive": {"agent": agent, "thread": thread}})
|
||||||
|
if op == "rename" and not title:
|
||||||
|
fail("BAD_NAME", "rename requires a title")
|
||||||
|
audit("thread-" + op, f"{agent}/{thread}")
|
||||||
|
helper = os.path.join(os.path.dirname(os.path.abspath(__file__)), "muse-threads.py")
|
||||||
|
cmd = [sys.executable, helper, op, "--agent", agent]
|
||||||
|
if thread:
|
||||||
|
cmd += ["--thread", thread]
|
||||||
|
if title:
|
||||||
|
cmd += ["--title", title]
|
||||||
|
try:
|
||||||
|
p = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
fail("GATEWAY_UNAVAILABLE", f"muse-threads.py timed out (op={op})")
|
||||||
|
except OSError as e:
|
||||||
|
fail("GATEWAY_UNAVAILABLE", f"cannot run muse-threads.py: {e}")
|
||||||
|
# Helper prints one JSON object; relay it, applying --limit for list
|
||||||
|
if p.returncode == 0:
|
||||||
|
raw = p.stdout.strip()
|
||||||
|
if limit is not None and op == "list" and raw:
|
||||||
|
try:
|
||||||
|
data = json.loads(raw)
|
||||||
|
if isinstance(data.get("threads"), list):
|
||||||
|
data["threads"] = data["threads"][:limit]
|
||||||
|
print(json.dumps(data))
|
||||||
|
return
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
print(raw if raw else '{"ok": true}')
|
||||||
|
return
|
||||||
|
# Failure: try to extract the helper's JSON error, else wrap
|
||||||
|
try:
|
||||||
|
err = json.loads(p.stdout.strip() or "{}")
|
||||||
|
fail(err.get("code", "GATEWAY_ERROR"), err.get("error", "thread op failed"),
|
||||||
|
err.get("detail"))
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
fail("GATEWAY_ERROR", f"thread {op} failed",
|
||||||
|
(p.stderr.strip() or p.stdout.strip())[:300])
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
@@ -1374,6 +1451,30 @@ def main(argv):
|
|||||||
elif action == "loop-remediate":
|
elif action == "loop-remediate":
|
||||||
dry = "--dry-run" in rest
|
dry = "--dry-run" in rest
|
||||||
act_loop_remediate(dry_run=dry)
|
act_loop_remediate(dry_run=dry)
|
||||||
|
elif action in ("thread-list", "thread-pin", "thread-unpin",
|
||||||
|
"thread-archive", "thread-unarchive", "thread-rename"):
|
||||||
|
op = action[len("thread-"):]
|
||||||
|
agent = None
|
||||||
|
thread = None
|
||||||
|
title = None
|
||||||
|
idx = 0
|
||||||
|
while idx < len(rest):
|
||||||
|
if rest[idx] == "--agent" and idx + 1 < len(rest):
|
||||||
|
agent = rest[idx + 1]
|
||||||
|
idx += 2
|
||||||
|
elif rest[idx] == "--thread" and idx + 1 < len(rest):
|
||||||
|
thread = rest[idx + 1]
|
||||||
|
idx += 2
|
||||||
|
elif rest[idx] == "--title" and idx + 1 < len(rest):
|
||||||
|
title = rest[idx + 1]
|
||||||
|
idx += 2
|
||||||
|
else:
|
||||||
|
idx += 1
|
||||||
|
if not agent:
|
||||||
|
fail("BAD_NAME", f"usage: {action} --agent <agent> [--thread <id>] [--title <title>]")
|
||||||
|
if op in ("pin", "unpin", "archive", "unarchive", "rename") and not thread:
|
||||||
|
fail("BAD_NAME", f"usage: {action} --agent <agent> --thread <id> [--title <title>]")
|
||||||
|
act_thread(op, agent, thread, title)
|
||||||
elif action == "notify":
|
elif action == "notify":
|
||||||
# notify <agent> <message> [--sidechat <name>] [--allow-main-chat]
|
# notify <agent> <message> [--sidechat <name>] [--allow-main-chat]
|
||||||
args = list(rest)
|
args = list(rest)
|
||||||
@@ -1437,6 +1538,78 @@ def main(argv):
|
|||||||
if args:
|
if args:
|
||||||
fail("BAD_ARGS", "usage: main-loop %s [--agent <name>]" % sub)
|
fail("BAD_ARGS", "usage: main-loop %s [--agent <name>]" % sub)
|
||||||
act_main_loop(sub, agent)
|
act_main_loop(sub, agent)
|
||||||
|
elif action == "thread":
|
||||||
|
if not rest or rest[0] not in THREAD_OPS:
|
||||||
|
fail("BAD_NAME", "usage: thread list|pin|unpin|archive|unarchive <agent> [args]")
|
||||||
|
sub = rest[0]
|
||||||
|
args = rest[1:]
|
||||||
|
if not args:
|
||||||
|
fail("BAD_NAME", f"usage: thread {sub} <agent> [...]")
|
||||||
|
agent = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
thread_id = None
|
||||||
|
limit = None
|
||||||
|
confirm = "--confirm" in args
|
||||||
|
args = [a for a in args if a != "--confirm"]
|
||||||
|
if confirm and sub != "archive":
|
||||||
|
fail("BAD_ARGS", "--confirm only applies to thread archive")
|
||||||
|
idx = 0
|
||||||
|
while idx < len(args):
|
||||||
|
if args[idx] == "--limit" and idx + 1 < len(args):
|
||||||
|
try:
|
||||||
|
limit = int(args[idx + 1])
|
||||||
|
except ValueError:
|
||||||
|
fail("BAD_LIMIT", "usage: thread list <agent> [--limit N]")
|
||||||
|
idx += 2
|
||||||
|
elif thread_id is None and not args[idx].startswith("--"):
|
||||||
|
thread_id = args[idx]
|
||||||
|
idx += 1
|
||||||
|
else:
|
||||||
|
fail("BAD_ARGS", f"usage: thread {sub} <agent> [...]")
|
||||||
|
if limit is not None and sub != "list":
|
||||||
|
fail("BAD_ARGS", "--limit only applies to thread list")
|
||||||
|
if sub in ("pin", "unpin", "archive", "unarchive") and not thread_id:
|
||||||
|
fail("BAD_NAME", f"usage: thread {sub} <agent> <thread-id> [--confirm]")
|
||||||
|
if sub == "list" and thread_id:
|
||||||
|
fail("BAD_ARGS", "usage: thread list <agent> [--limit N]")
|
||||||
|
act_thread(sub, agent, thread=thread_id, limit=limit, confirm=confirm)
|
||||||
|
elif action in ("thread-list", "thread-pin", "thread-unpin",
|
||||||
|
"thread-archive", "thread-unarchive", "thread-rename"):
|
||||||
|
op = action[len("thread-"):]
|
||||||
|
agent = None
|
||||||
|
thread_id = None
|
||||||
|
title = None
|
||||||
|
limit = None
|
||||||
|
confirm = "--confirm" in rest
|
||||||
|
args = [a for a in rest if a != "--confirm"]
|
||||||
|
if confirm and op != "archive":
|
||||||
|
fail("BAD_ARGS", "--confirm only applies to thread-archive")
|
||||||
|
idx = 0
|
||||||
|
while idx < len(args):
|
||||||
|
if args[idx] == "--agent" and idx + 1 < len(args):
|
||||||
|
agent = args[idx + 1]
|
||||||
|
idx += 2
|
||||||
|
elif args[idx] == "--thread" and idx + 1 < len(args):
|
||||||
|
thread_id = args[idx + 1]
|
||||||
|
idx += 2
|
||||||
|
elif args[idx] == "--title" and idx + 1 < len(args):
|
||||||
|
title = args[idx + 1]
|
||||||
|
idx += 2
|
||||||
|
elif args[idx] == "--limit" and idx + 1 < len(args):
|
||||||
|
try:
|
||||||
|
limit = int(args[idx + 1])
|
||||||
|
except ValueError:
|
||||||
|
fail("BAD_LIMIT", "usage: thread-list --agent <agent> [--limit N]")
|
||||||
|
idx += 2
|
||||||
|
else:
|
||||||
|
fail("BAD_ARGS", f"usage: {action} --agent <agent> [--thread <id>] [--title <t>] [--confirm] [--limit N]")
|
||||||
|
if not agent:
|
||||||
|
fail("BAD_NAME", f"usage: {action} --agent <agent> [...]")
|
||||||
|
if limit is not None and op != "list":
|
||||||
|
fail("BAD_ARGS", "--limit only applies to thread-list")
|
||||||
|
if op in ("pin", "unpin", "archive", "unarchive", "rename") and not thread_id:
|
||||||
|
fail("BAD_NAME", f"usage: {action} --agent <agent> --thread <id> [...]")
|
||||||
|
act_thread(op, agent, thread=thread_id, title=title, limit=limit, confirm=confirm)
|
||||||
elif action == "dm-log":
|
elif action == "dm-log":
|
||||||
limit = 50
|
limit = 50
|
||||||
if rest:
|
if rest:
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ except ImportError:
|
|||||||
|
|
||||||
API = "/home/super/Projects/NetVM/bin/muse-chat-api.py"
|
API = "/home/super/Projects/NetVM/bin/muse-chat-api.py"
|
||||||
NETVM_EXEC = "/home/super/Projects/NetVM/bin/netvm-exec.sh"
|
NETVM_EXEC = "/home/super/Projects/NetVM/bin/netvm-exec.sh"
|
||||||
VALID_AGENTS = ["muse", "pip", "646", "opm"]
|
VALID_AGENTS = ["muse", "pip", "646", "opm", "dev", "def"]
|
||||||
VALID_SENDERS = ["muse", "pip", "646", "opm", "super"]
|
VALID_SENDERS = ["muse", "pip", "646", "opm", "dev", "def", "super"]
|
||||||
VALID_RECIPIENTS = ["muse", "pip", "646", "opm"]
|
VALID_RECIPIENTS = ["muse", "pip", "646", "opm", "dev", "def"]
|
||||||
|
|
||||||
# ---- Canonical follow-up tags (2026-10-04, DEPLOY-DECISIONS.md) ----
|
# ---- Canonical follow-up tags (2026-10-04, DEPLOY-DECISIONS.md) ----
|
||||||
# Tags declare follow-up policy at send time. They are metadata only:
|
# Tags declare follow-up policy at send time. They are metadata only:
|
||||||
@@ -589,6 +589,11 @@ def dm_send(agent, target, message, verify=True, raw=False,
|
|||||||
log_event({"type": "sent", "id": msg_id, "agent": agent, "to": recipient, "target": target,
|
log_event({"type": "sent", "id": msg_id, "agent": agent, "to": recipient, "target": target,
|
||||||
"verified": True, "transport": "gateway", "tags": tags})
|
"verified": True, "transport": "gateway", "tags": tags})
|
||||||
print(f"DM {msg_id} from {agent} to {recipient}/{target}: SENT and VERIFIED thread={thread_uuid} (gateway)")
|
print(f"DM {msg_id} from {agent} to {recipient}/{target}: SENT and VERIFIED thread={thread_uuid} (gateway)")
|
||||||
|
# Follow-up record creation (same as the CDP path below): the
|
||||||
|
# gateway fast-path must not skip followup registration, or
|
||||||
|
# --expect-reply sends silently lose tracking.
|
||||||
|
if tags.get("reply:expected"):
|
||||||
|
_register_followup(msg_id, agent, recipient, target, tags)
|
||||||
return msg_id
|
return msg_id
|
||||||
except Exception as _e:
|
except Exception as _e:
|
||||||
log_event({"type": "gateway_fallback", "id": msg_id, "error": str(_e)[:100]})
|
log_event({"type": "gateway_fallback", "id": msg_id, "error": str(_e)[:100]})
|
||||||
|
|||||||
Reference in New Issue
Block a user