chore(fleet): sync operator memory, hatch menu dialogs, and watchdog alerts

This commit is contained in:
operator
2026-10-07 00:25:51 +00:00
parent 0065d11e97
commit 34b0ef9fe2
38 changed files with 2205 additions and 444 deletions
+581 -4
View File
@@ -204,11 +204,78 @@ case "$cmd" in
args=$(python3 -c "import json, sys; print(json.dumps({'agent': sys.argv[1], 'target': sys.argv[2], 'limit': int(sys.argv[3])}))" "$AGENT" "$target" "$limit")
call_exec "dm.read" "$args"
;;
log)
limit=""
log_agent=""
while [ $# -gt 0 ]; do
case "$1" in
--agent) log_agent="$2"; shift 2 ;;
*) if [ -z "$limit" ]; then limit="$1"; fi; shift ;;
esac
done
limit="${limit:-20}"
args=$(python3 -c "import json,sys; lim=int(sys.argv[1]); ag=sys.argv[2]; print(json.dumps({'limit':lim,**({'agent':ag} if ag else {})}))" "$limit" "$log_agent")
call_exec "dm.log" "$args"
;;
ack)
to=""
from_agent=""
sidechat=""
allow_main=""
ref_id=""
while [ $# -gt 0 ]; do
case "$1" in
--to) to="$2"; shift 2 ;;
--sender|--from) from_agent="$2"; shift 2 ;;
--sidechat) sidechat="$2"; shift 2 ;;
--allow-main-chat) allow_main="1"; shift ;;
*) if [ -z "$ref_id" ]; then ref_id="$1"; fi; shift ;;
esac
done
ref_id="${ref_id:?usage: box dm ack <id> --to <agent> --sender <agent> [--sidechat <name>] [--allow-main-chat]}"
from_agent="${from_agent:-$AGENT}"
args=$(python3 -c "
import json, sys
ref, to, sender, sc, main = sys.argv[1:6]
args = {'id': ref, 'to': to, 'sender': sender}
if sc:
args['sidechat'] = sc
if main:
args['allow_main_chat'] = True
print(json.dumps(args))
" "$ref_id" "$to" "$from_agent" "$sidechat" "$allow_main")
call_exec "dm.ack" "$args"
;;
*)
echo "Usage: box dm send|read ..."
echo "Usage: box dm send|read|log|ack ..."
;;
esac
;;
notify)
target_agent="${1:?usage: box notify <agent> [--sidechat <name>] [--sender <agent>] <message...>}"
shift
sidechat=""
sender=""
while [ $# -gt 0 ]; do
case "$1" in
--sidechat) sidechat="$2"; shift 2 ;;
--sender|--from) sender="$2"; shift 2 ;;
*) break ;;
esac
done
msg="${*:?usage: box notify <agent> [--sidechat <name>] [--sender <agent>] <message...>}"
args=$(python3 -c "
import json, sys
agent, message, sc, sender = sys.argv[1:5]
args = {'agent': agent, 'message': message}
if sc:
args['sidechat'] = sc
if sender:
args['sender'] = sender
print(json.dumps(args))
" "$target_agent" "$msg" "$sidechat" "$sender")
call_exec "notify.send" "$args"
;;
thread)
sub="${1:-list}"
shift || true
@@ -262,6 +329,70 @@ case "$cmd" in
args=$(python3 -c "import json, sys; print(json.dumps({'job': sys.argv[1]}))" "$name")
call_exec "cron.run" "$args"
;;
put)
name="${1:?usage: box cron put <name> '<json-definition>'}"
json_def="${2:?usage: box cron put <name> '<json-definition>'}"
args=$(python3 -c "
import json, sys
try:
definition = json.loads(sys.argv[2])
except Exception as e:
sys.stderr.write('invalid job JSON: %s\n' % e)
sys.exit(2)
print(json.dumps({'name': sys.argv[1], 'definition': definition}))
" "$name" "$json_def")
call_exec "job.put" "$args"
;;
trigger)
name="${1:?usage: box cron trigger <name>}"
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
call_exec "job.trigger" "$args"
;;
chain)
from_job="${1:?usage: box cron chain <from> <to> [--on-failure]}"
shift || true
on_failure=""
to_job=""
while [ $# -gt 0 ]; do
case "$1" in
--on-failure) on_failure="1"; shift ;;
*) if [ -z "$to_job" ]; then to_job="$1"; fi; shift ;;
esac
done
to_job="${to_job:?usage: box cron chain <from> <to> [--on-failure]}"
args=$(python3 -c "
import json, sys
frm, to, onfail = sys.argv[1:4]
args = {'from': frm, 'to': to}
if onfail:
args['on_failure'] = True
print(json.dumps(args))
" "$from_job" "$to_job" "$on_failure")
call_exec "job.chain" "$args"
;;
next)
job_id=""
success=""
while [ $# -gt 0 ]; do
case "$1" in
--success) success="1"; shift ;;
--fail) success="0"; shift ;;
*) if [ -z "$job_id" ]; then job_id="$1"; fi; shift ;;
esac
done
job_id="${job_id:?usage: box cron next <job-id> [--success|--fail]}"
args=$(python3 -c "
import json, sys
jid, success = sys.argv[1:3]
args = {'job_id': jid}
if success == '1':
args['success'] = True
elif success == '0':
args['success'] = False
print(json.dumps(args))
" "$job_id" "$success")
call_exec "job.next" "$args"
;;
timer-create)
name="${1:?usage: box cron timer-create <name>}"
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
@@ -273,7 +404,7 @@ case "$cmd" in
call_exec "cron.timer_start" "$args"
;;
*)
echo "Usage: box cron runs|status|view|run|timer-create|timer-start ..."
echo "Usage: box cron runs|status|view|run|put|trigger|chain|next|timer-create|timer-start ..."
;;
esac
;;
@@ -291,6 +422,16 @@ case "$cmd" in
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
call_exec "cron.timer_start" "$args"
;;
stop)
name="${1:?usage: box timer stop <name>}"
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
call_exec "cron.timer_stop" "$args"
;;
disable)
name="${1:?usage: box timer disable <name>}"
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
call_exec "cron.timer_disable" "$args"
;;
status|view|list)
name="${1:-heartbeat}"
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
@@ -304,7 +445,7 @@ case "$cmd" in
call_exec "followup.create" "$args"
;;
*)
echo "Usage: box timer create|start|status <name> OR box timer in <minutes> <prompt>"
echo "Usage: box timer create|start|stop|enable|disable|status <name> OR box timer in <minutes> <prompt>"
;;
esac
;;
@@ -353,6 +494,125 @@ case "$cmd" in
;;
esac
;;
loop)
sub="${1:?usage: box loop remediate|resolve ...}"
shift || true
case "$sub" in
remediate)
dry=""
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) dry="1"; shift ;;
*) break ;;
esac
done
if [ -n "$dry" ]; then
args='{"dry_run": true}'
else
args='{}'
fi
call_exec "loop.remediate" "$args"
;;
resolve)
dm_id="${1:?usage: box loop resolve <dm_id> [note...]}"
shift || true
note="$*"
args=$(python3 -c "
import json, sys
dm_id, note = sys.argv[1:3]
args = {'dm_id': dm_id}
if note:
args['note'] = note
print(json.dumps(args))
" "$dm_id" "$note")
call_exec "loop.resolve" "$args"
;;
*)
echo "Usage: box loop remediate [--dry-run] OR box loop resolve <dm_id> [note...]"
;;
esac
;;
strat)
sub="${1:?usage: box strat set|reset ...}"
shift || true
case "$sub" in
set)
stype="${1:?usage: box strat set <type> [options]}"
shift || true
subtype=""
agent=""
track=""
priority=""
timeout_s=""
nudges=""
escalate=""
while [ $# -gt 0 ]; do
case "$1" in
--subtype) subtype="$2"; shift 2 ;;
--agent) agent="$2"; shift 2 ;;
--track) track="$2"; shift 2 ;;
--priority) priority="$2"; shift 2 ;;
--timeout) timeout_s="$2"; shift 2 ;;
--nudges) nudges="$2"; shift 2 ;;
--escalate) escalate="$2"; shift 2 ;;
*) break ;;
esac
done
args=$(python3 -c "
import json, sys
stype, subtype, agent, track, prio, timeout_s, nudges, esc = sys.argv[1:9]
args = {'type': stype}
if subtype:
args['subtype'] = subtype
if agent:
args['agent'] = agent
if track.lower() == 'true':
args['track'] = True
elif track.lower() == 'false':
args['track'] = False
elif track:
sys.stderr.write('track must be true|false\n')
sys.exit(2)
if prio:
args['priority'] = prio
if timeout_s:
args['timeout_s'] = int(timeout_s)
if nudges:
args['nudges'] = int(nudges)
if esc:
args['escalate'] = esc
print(json.dumps(args))
" "$stype" "$subtype" "$agent" "$track" "$priority" "$timeout_s" "$nudges" "$escalate")
call_exec "strat.set" "$args"
;;
reset)
stype="${1:?usage: box strat reset <type> [subtype] [--agent <agent>]}"
shift || true
subtype=""
agent=""
while [ $# -gt 0 ]; do
case "$1" in
--agent) agent="$2"; shift 2 ;;
*) if [ -z "$subtype" ]; then subtype="$1"; fi; shift ;;
esac
done
args=$(python3 -c "
import json, sys
stype, subtype, agent = sys.argv[1:4]
args = {'type': stype}
if subtype:
args['subtype'] = subtype
if agent:
args['agent'] = agent
print(json.dumps(args))
" "$stype" "$subtype" "$agent")
call_exec "strat.reset" "$args"
;;
*)
echo "Usage: box strat set <type> [options] OR box strat reset <type> [subtype] [--agent <agent>]"
;;
esac
;;
vars)
sub="${1:-list}"
shift || true
@@ -371,8 +631,26 @@ case "$cmd" in
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1], 'value': sys.argv[2]}))" "$name" "$val")
call_exec "vars.set" "$args"
;;
reset)
name="${1:?usage: box vars reset <name>}"
args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name")
call_exec "vars.reset" "$args"
;;
rollback)
name="${1:?usage: box vars rollback <name> [revision]}"
rev="${2:-}"
args=$(python3 -c "
import json, sys
name, rev = sys.argv[1:3]
args = {'name': name}
if rev:
args['revision'] = int(rev) if rev.isdigit() else rev
print(json.dumps(args))
" "$name" "$rev")
call_exec "vars.rollback" "$args"
;;
*)
echo "Usage: box vars list|get|set ..."
echo "Usage: box vars list|get|set|reset|rollback ..."
;;
esac
;;
@@ -430,6 +708,272 @@ case "$cmd" in
;;
esac
;;
git)
sub="${1:-status}"
shift || true
case "$sub" in
status)
call_exec "git.status" "{}"
;;
diff)
stat=""
path=""
while [ $# -gt 0 ]; do
case "$1" in
--stat) stat="1"; shift ;;
--path) path="$2"; shift 2 ;;
*) if [ -z "$path" ]; then path="$1"; fi; shift ;;
esac
done
args=$(python3 -c "
import json, sys
stat, path = sys.argv[1:3]
args = {}
if stat:
args['stat'] = True
if path:
args['path'] = path
print(json.dumps(args))
" "$stat" "$path")
call_exec "git.diff" "$args"
;;
log)
limit=""
path=""
while [ $# -gt 0 ]; do
case "$1" in
--limit) limit="$2"; shift 2 ;;
--path) path="$2"; shift 2 ;;
*) if [ -z "$limit" ]; then limit="$1"; fi; shift ;;
esac
done
limit="${limit:-10}"
args=$(python3 -c "
import json, sys
limit, path = sys.argv[1:3]
args = {'limit': int(limit)}
if path:
args['path'] = path
print(json.dumps(args))
" "$limit" "$path")
call_exec "git.log" "$args"
;;
*)
echo "Usage: box git status|diff|log ..."
;;
esac
;;
tests)
sub="${1:-run}"
shift || true
case "$sub" in
run)
test_mod=""
filt=""
while [ $# -gt 0 ]; do
case "$1" in
--filter) filt="$2"; shift 2 ;;
*) if [ -z "$test_mod" ]; then test_mod="$1"; fi; shift ;;
esac
done
args=$(python3 -c "
import json, sys
mod, filt = sys.argv[1:3]
args = {}
if mod:
args['test'] = mod
if filt:
args['filter'] = filt
print(json.dumps(args))
" "$test_mod" "$filt")
call_exec "tests.run" "$args"
;;
*)
echo "Usage: box tests run [tests.<module>] [--filter <pattern>]"
;;
esac
;;
approvals)
sub="${1:-check}"
shift || true
case "$sub" in
check)
node="${1:-}"
if [ -n "$node" ]; then
args=$(python3 -c "import json, sys; print(json.dumps({'node': sys.argv[1]}))" "$node")
else
args="{}"
fi
call_exec "approval.check" "$args"
;;
allow)
node=""
message=""
main_chat=""
while [ $# -gt 0 ]; do
case "$1" in
--message) message="$2"; shift 2 ;;
--allow-main-chat) main_chat="1"; shift ;;
*) if [ -z "$node" ]; then node="$1"; fi; shift ;;
esac
done
node="${node:?usage: box approvals allow <node> --message <text> [--allow-main-chat]}"
[ -n "$message" ] || { echo "usage: box approvals allow <node> --message <text> [--allow-main-chat]" >&2; exit 2; }
args=$(python3 -c "
import json, sys
node, message, main = sys.argv[1:4]
args = {'node': node, 'message': message}
if main:
args['allow_main_chat'] = True
print(json.dumps(args))
" "$node" "$message" "$main_chat")
call_exec "approval.allow" "$args"
;;
deny)
node=""
message=""
main_chat=""
while [ $# -gt 0 ]; do
case "$1" in
--message) message="$2"; shift 2 ;;
--allow-main-chat) main_chat="1"; shift 2 ;;
*) if [ -z "$node" ]; then node="$1"; fi; shift ;;
esac
done
node="${node:?usage: box approvals deny <node> --message <text> [--allow-main-chat]}"
[ -n "$message" ] || { echo "usage: box approvals deny <node> --message <text> [--allow-main-chat]" >&2; exit 2; }
args=$(python3 -c "
import json, sys
node, message, main = sys.argv[1:4]
args = {'node': node, 'message': message}
if main:
args['allow_main_chat'] = True
print(json.dumps(args))
" "$node" "$message" "$main_chat")
call_exec "approval.deny" "$args"
;;
auto)
node="${1:-}"
if [ -n "$node" ]; then
args=$(python3 -c "import json, sys; print(json.dumps({'node': sys.argv[1]}))" "$node")
else
args="{}"
fi
call_exec "approval.auto" "$args"
;;
*)
echo "Usage: box approvals check|allow|deny|auto ..."
;;
esac
;;
md)
sub="${1:-audit}"
shift || true
case "$sub" in
audit)
args=$(python3 -c "
import json, sys
accts = [a for a in sys.argv[1:] if a]
print(json.dumps({'accounts': accts} if accts else {}))
" "$@")
call_exec "md.audit" "$args"
;;
list)
account="${1:?usage: box md list <account> [path]}"
path="${2:-}"
args=$(python3 -c "import json, sys; print(json.dumps({'account': sys.argv[1], 'path': sys.argv[2]}))" "$account" "$path")
call_exec "md.list" "$args"
;;
read)
account="${1:?usage: box md read <account> <filename>}"
filename="${2:?usage: box md read <account> <filename>}"
args=$(python3 -c "import json, sys; print(json.dumps({'account': sys.argv[1], 'filename': sys.argv[2]}))" "$account" "$filename")
call_exec "md.read" "$args"
;;
diff)
account="${1:?usage: box md diff <account> <filename>}"
filename="${2:?usage: box md diff <account> <filename>}"
args=$(python3 -c "import json, sys; print(json.dumps({'account': sys.argv[1], 'filename': sys.argv[2]}))" "$account" "$filename")
call_exec "md.diff" "$args"
;;
pull)
account="${1:?usage: box md pull <account> <filename>}"
filename="${2:?usage: box md pull <account> <filename>}"
args=$(python3 -c "import json, sys; print(json.dumps({'account': sys.argv[1], 'filename': sys.argv[2]}))" "$account" "$filename")
call_exec "md.pull" "$args"
;;
inject-drive)
account="${1:?usage: box md inject-drive <account>}"
args=$(python3 -c "import json, sys; print(json.dumps({'account': sys.argv[1]}))" "$account")
call_exec "md.inject_drive" "$args"
;;
sync-all)
call_exec "md.sync_all" "{}"
;;
amend)
filename="${1:?usage: box md amend <filename> (--content <text>|--file <path>) [--author <name>] [--reason <why>]}"
shift || true
content=""; content_src=""; author="operator"; reason=""
while [ $# -gt 0 ]; do
case "$1" in
--content) content="$2"; content_src="arg"; shift 2 ;;
--file) content="$2"; content_src="file"; shift 2 ;;
--author) author="$2"; shift 2 ;;
--reason) reason="$2"; shift 2 ;;
*) echo "usage: box md amend <filename> (--content <text>|--file <path>) [--author <name>] [--reason <why>]" >&2; exit 2 ;;
esac
done
[ -n "$content_src" ] || { echo "usage: box md amend <filename> (--content <text>|--file <path>) [--author <name>] [--reason <why>]" >&2; exit 2; }
args=$(python3 -c "
import json, sys
fn, src, val, author, reason = sys.argv[1:6]
content = open(val, encoding='utf-8').read() if src == 'file' else val
args = {'filename': fn, 'content': content, 'author': author}
if reason:
args['reason'] = reason
print(json.dumps(args))
" "$filename" "$content_src" "$content" "$author" "$reason")
call_exec "md.amend" "$args"
;;
append)
filename="${1:?usage: box md append <filename> (--content <text>|--file <path>) [--author <name>] [--section <header>]}"
shift || true
text=""; text_src=""; author="operator"; section=""
while [ $# -gt 0 ]; do
case "$1" in
--content) text="$2"; text_src="arg"; shift 2 ;;
--file) text="$2"; text_src="file"; shift 2 ;;
--author) author="$2"; shift 2 ;;
--section) section="$2"; shift 2 ;;
*) echo "usage: box md append <filename> (--content <text>|--file <path>) [--author <name>] [--section <header>]" >&2; exit 2 ;;
esac
done
[ -n "$text_src" ] || { echo "usage: box md append <filename> (--content <text>|--file <path>) [--author <name>] [--section <header>]" >&2; exit 2; }
args=$(python3 -c "
import json, sys
fn, src, val, author, section = sys.argv[1:6]
text = open(val, encoding='utf-8').read() if src == 'file' else val
args = {'filename': fn, 'text': text, 'author': author}
if section:
args['section'] = section
print(json.dumps(args))
" "$filename" "$text_src" "$text" "$author" "$section")
call_exec "md.append" "$args"
;;
*)
echo "Usage: box md audit|list|read|diff|pull|inject-drive|sync-all|amend|append ..."
;;
esac
;;
unread)
target_agent="${1:-}"
if [ -n "$target_agent" ]; then
args=$(python3 -c "import json, sys; print(json.dumps({'agent': sys.argv[1]}))" "$target_agent")
else
args="{}"
fi
call_exec "fleet.unread" "$args"
;;
health|fleet-status)
call_exec "health.check" "{}"
;;
@@ -464,21 +1008,54 @@ Usage:
box deploy pipeline <name>
box dm send --to <agent> [--target <target>] <message>
box dm read [<target=main>] [<limit=10>]
box dm log [<limit=20>] [--agent <agent>]
box dm ack <id> --to <agent> --sender <agent> [--sidechat <name>]
box notify <agent> [--sidechat <name>] [--sender <agent>] <message...>
box thread list [<agent>]
box thread view <thread_id> [<limit=15>]
box cron runs
box cron status [<name=heartbeat>]
box cron view <name>
box cron run <name>
box cron put <name> '<json-definition>'
box cron trigger <name>
box cron chain <from> <to> [--on-failure]
box cron next <job-id> [--success|--fail]
box timer stop <name>
box timer disable <name>
box vars list
box vars get <name>
box vars set <name> <value>
box vars reset <name>
box vars rollback <name> [revision]
box strat set <type> [--subtype S] [--agent A] [--track b] [--priority p] [--timeout N] [--nudges N] [--escalate E]
box strat reset <type> [subtype] [--agent <agent>]
box loop remediate [--dry-run]
box loop resolve <dm_id> [note...]
box files read <path> [lines=100]
box files write <path> <content>
box web fetch <url>
box service status <unit>
box service restart <unit>
box git status
box git diff [--stat] [--path <path>]
box git log [<limit=10>] [--path <path>]
box tests run [tests.<module>] [--filter <pattern>]
box md audit [accounts...]
box md list <account> [path]
box md read <account> <filename>
box md diff <account> <filename>
box md pull <account> <filename>
box md inject-drive <account>
box md sync-all
box md amend <filename> (--content <text>|--file <path>) [--author <name>] [--reason <why>]
box md append <filename> (--content <text>|--file <path>) [--author <name>] [--section <header>]
box approvals check [node]
box approvals allow <node> --message <text> [--allow-main-chat]
box approvals deny <node> --message <text> [--allow-main-chat]
box approvals auto [node]
box health
box unread [<agent>]
box ping
box ops