fix(work): import hashlib and wire heal subparser into main CLI
This commit is contained in:
+142
-1
@@ -74,6 +74,7 @@ HEX_RE = re.compile(r'^[0-9a-f]{8,128}$')
|
||||
DM_ID_RE = re.compile(r'^[0-9a-fA-F]{6,64}$')
|
||||
TEST_MODULE_RE = re.compile(r'^tests\.[a-z0-9_]+$')
|
||||
JOB_DISPATCH_ID_RE = re.compile(r'^[a-z0-9][a-z0-9-]{0,63}-\d{8}-\d{6}-[a-f0-9]{8}$')
|
||||
FLOW_ID_RE = re.compile(r'^[a-zA-Z0-9_-]{1,64}$')
|
||||
STRAT_TYPES = frozenset({'wake', 'job', 'siphon', 'manual', 'health', 'heartbeat'})
|
||||
STRAT_PRIORITIES = frozenset({'routine', 'normal', 'important'})
|
||||
SUBTYPE_RE = re.compile(r'^[A-Za-z0-9_.-]{1,64}$')
|
||||
@@ -765,6 +766,120 @@ def _tmux_prune_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'muse-tmux.py'), 'prune', '--ttl', str(a['ttl'])]
|
||||
|
||||
|
||||
def _flow_id_name(val):
|
||||
if not isinstance(val, str) or not FLOW_ID_RE.fullmatch(val):
|
||||
raise OpError("flow_id must be 1-64 alphanumeric, dash, or underscore chars")
|
||||
return val
|
||||
|
||||
|
||||
def _flow_start_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError("args must be an object")
|
||||
allowed = {"flow_id", "command", "agent", "cwd"}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f"unknown arg: {k}")
|
||||
if not raw.get("flow_id"):
|
||||
raise OpError("flow_id is required")
|
||||
agent = raw.get("agent", "646")
|
||||
if agent and (not isinstance(agent, str) or agent not in AGENTS):
|
||||
agent = "646"
|
||||
return {
|
||||
"flow_id": _flow_id_name(raw["flow_id"]),
|
||||
"command": str(raw["command"]) if raw.get("command") else None,
|
||||
"agent": agent,
|
||||
"cwd": str(raw["cwd"]) if raw.get("cwd") else None,
|
||||
}
|
||||
|
||||
|
||||
def _flow_start_build(a):
|
||||
cmd = [sys.executable, os.path.join(BIN_DIR, "flow_engine.py"), "start", a["flow_id"], "--agent", a["agent"]]
|
||||
if a.get("command"):
|
||||
cmd.extend(["--command", a["command"]])
|
||||
if a.get("cwd"):
|
||||
cmd.extend(["--cwd", a["cwd"]])
|
||||
return cmd
|
||||
|
||||
|
||||
def _flow_read_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError("args must be an object")
|
||||
allowed = {"flow_id", "lines"}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f"unknown arg: {k}")
|
||||
if not raw.get("flow_id"):
|
||||
raise OpError("flow_id is required")
|
||||
lines = raw.get("lines", 40)
|
||||
try:
|
||||
lines = int(lines)
|
||||
if lines < 1 or lines > 200:
|
||||
lines = 40
|
||||
except Exception:
|
||||
lines = 40
|
||||
return {
|
||||
"flow_id": _flow_id_name(raw["flow_id"]),
|
||||
"lines": lines,
|
||||
}
|
||||
|
||||
|
||||
def _flow_read_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, "flow_engine.py"), "read", a["flow_id"], "--lines", str(a["lines"])]
|
||||
|
||||
|
||||
def _flow_send_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError("args must be an object")
|
||||
allowed = {"flow_id", "keys", "command", "no_enter"}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f"unknown arg: {k}")
|
||||
if not raw.get("flow_id"):
|
||||
raise OpError("flow_id is required")
|
||||
if "keys" not in raw:
|
||||
raise OpError("keys is required")
|
||||
return {
|
||||
"flow_id": _flow_id_name(raw["flow_id"]),
|
||||
"keys": str(raw["keys"]),
|
||||
"command": bool(raw.get("command", False)),
|
||||
"no_enter": bool(raw.get("no_enter", False)),
|
||||
}
|
||||
|
||||
|
||||
def _flow_send_build(a):
|
||||
cmd = [sys.executable, os.path.join(BIN_DIR, "flow_engine.py"), "send", a["flow_id"], a["keys"]]
|
||||
if a.get("command"):
|
||||
cmd.append("--command")
|
||||
if a.get("no_enter"):
|
||||
cmd.append("--no-enter")
|
||||
return cmd
|
||||
|
||||
|
||||
def _flow_list_validate(raw):
|
||||
return {}
|
||||
|
||||
|
||||
def _flow_list_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, "flow_engine.py"), "list"]
|
||||
|
||||
|
||||
def _flow_stop_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError("args must be an object")
|
||||
allowed = {"flow_id"}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f"unknown arg: {k}")
|
||||
if not raw.get("flow_id"):
|
||||
raise OpError("flow_id is required")
|
||||
return {"flow_id": _flow_id_name(raw["flow_id"])}
|
||||
|
||||
|
||||
def _flow_stop_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, "flow_engine.py"), "stop", a["flow_id"]]
|
||||
|
||||
|
||||
|
||||
def _vars_list_validate(raw):
|
||||
if raw not in ({}, None):
|
||||
raise OpError('vars.list takes no required args')
|
||||
@@ -2279,6 +2394,31 @@ OPS = {
|
||||
'timeout': 15, 'side_effecting': True,
|
||||
'desc': 'Reap stale unattached sessions inactive for >TTL (default 2h)',
|
||||
},
|
||||
'flow.start': {
|
||||
'validate': _flow_start_validate, 'build': _flow_start_build,
|
||||
'timeout': 15, 'side_effecting': True,
|
||||
'desc': 'Start an agentic workflow in a persistent tmux pane with output logging',
|
||||
},
|
||||
'flow.read': {
|
||||
'validate': _flow_read_validate, 'build': _flow_read_build,
|
||||
'timeout': 15, 'side_effecting': False,
|
||||
'desc': 'Read output delta and execution state (working/idle/waiting_prompt/finished) from a flow pane',
|
||||
},
|
||||
'flow.send': {
|
||||
'validate': _flow_send_validate, 'build': _flow_send_build,
|
||||
'timeout': 15, 'side_effecting': True,
|
||||
'desc': 'Send keystrokes or advance command in a flow tmux pane',
|
||||
},
|
||||
'flow.list': {
|
||||
'validate': _flow_list_validate, 'build': _flow_list_build,
|
||||
'timeout': 10, 'side_effecting': False,
|
||||
'desc': 'List all active agentic flow sessions and their statuses',
|
||||
},
|
||||
'flow.stop': {
|
||||
'validate': _flow_stop_validate, 'build': _flow_stop_build,
|
||||
'timeout': 15, 'side_effecting': True,
|
||||
'desc': 'Stop and terminate a flow tmux pane session',
|
||||
},
|
||||
'exec.ping': {
|
||||
'validate': _health_validate,
|
||||
'build': lambda a: ['/bin/echo', 'PONG'],
|
||||
@@ -2377,7 +2517,8 @@ DEFAULT_PERMS = {'dm.read', 'dm.log', 'chat.messages', 'health.check', 'fleet.un
|
||||
'thread.list', 'thread.view', 'exec.ping',
|
||||
'git.status', 'git.diff', 'git.log', 'job.next',
|
||||
'md.audit', 'md.list', 'md.read', 'md.diff',
|
||||
'approval.check', 'tmux.tally', 'tmux.auto_status', 'onboard.connects'}
|
||||
'approval.check', 'tmux.tally', 'tmux.auto_status', 'onboard.connects',
|
||||
'flow.read', 'flow.list'}
|
||||
|
||||
|
||||
def permitted(ident, op):
|
||||
|
||||
Reference in New Issue
Block a user