feat(relay): add subagent spawn, thread tools, and container box client
- Upgrade exec-constrained.py with subagent.spawn, thread.list, thread.view, pipeline.run ops - Grant full ops permissions to all fleet agent identities (646, pip, muse, opm) - Implement bin/box-relay.sh zero-dependency client supporting bearer and SSH signature auth - Add fast hybrid gateway path to dm.py for sub-2s verified deliveries - Fix wait=0 handling in super-cli.py subagent deployments - Add hourly check-in jobs and scheduler for 646, pip, muse - Document agent tooling and relay APIs in docs/AGENT-TOOLING.md
This commit is contained in:
+120
-8
@@ -425,6 +425,18 @@ def _chat_send_build(a):
|
||||
return argv
|
||||
|
||||
|
||||
def _safe_str(v, max_len=120, name='string'):
|
||||
if v is None:
|
||||
return ''
|
||||
if not isinstance(v, str):
|
||||
raise OpError(f'{name} must be a string')
|
||||
if len(v) > max_len:
|
||||
raise OpError(f'{name} exceeds max length {max_len}')
|
||||
if any(ord(c) < 32 and c not in '\n\t' for c in v):
|
||||
raise OpError(f'{name} contains control characters')
|
||||
return v.strip()
|
||||
|
||||
|
||||
def _health_validate(raw):
|
||||
if raw not in ({}, None):
|
||||
raise OpError('health.check takes no args')
|
||||
@@ -432,7 +444,83 @@ def _health_validate(raw):
|
||||
|
||||
|
||||
def _health_build(a):
|
||||
return ['/bin/bash', os.path.join(BIN_DIR, 'agent-health.sh'), '--check']
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'super-cli.py'), 'fleet', 'status', '--json']
|
||||
|
||||
|
||||
def _subagent_spawn_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
allowed = {'agent', 'title', 'prompt', 'wait'}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
return {
|
||||
'agent': _agent(raw.get('agent')),
|
||||
'title': _safe_str(raw.get('title', 'subagent'), 120, 'title') or 'subagent',
|
||||
'prompt': _clean_message(raw.get('prompt')),
|
||||
'wait': _opt_int(raw.get('wait', 0), 0, 60, 'wait') or 0,
|
||||
}
|
||||
|
||||
|
||||
def _subagent_spawn_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'super-cli.py'),
|
||||
'deploy', 'subagent',
|
||||
'--agent', a['agent'],
|
||||
'--title', a['title'],
|
||||
'--wait', str(a['wait']),
|
||||
a['prompt']]
|
||||
|
||||
|
||||
def _thread_list_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
allowed = {'agent'}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
return {'agent': _agent(raw.get('agent'))}
|
||||
|
||||
|
||||
def _thread_list_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'super-cli.py'),
|
||||
'thread', 'list', a['agent'], '--json']
|
||||
|
||||
|
||||
def _thread_view_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
allowed = {'agent', 'thread', 'limit'}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
th = raw.get('thread')
|
||||
if not isinstance(th, str) or not TARGET_RE.fullmatch(th):
|
||||
raise OpError('thread must match target pattern')
|
||||
return {
|
||||
'agent': _agent(raw.get('agent')),
|
||||
'thread': th,
|
||||
'limit': _opt_int(raw.get('limit', 15), 1, 100, 'limit') or 15,
|
||||
}
|
||||
|
||||
|
||||
def _thread_view_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'super-cli.py'),
|
||||
'thread', 'view', a['agent'], a['thread'],
|
||||
'--limit', str(a['limit']), '--json']
|
||||
|
||||
|
||||
def _pipeline_run_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 _pipeline_run_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'super-cli.py'), 'deploy', 'pipeline', a['name']]
|
||||
|
||||
|
||||
# op -> {validate, build, timeout, side_effecting, description}
|
||||
@@ -469,8 +557,28 @@ OPS = {
|
||||
},
|
||||
'health.check': {
|
||||
'validate': _health_validate, 'build': _health_build,
|
||||
'timeout': 120, 'side_effecting': False,
|
||||
'desc': 'Run agent-health.sh --check (read-only)',
|
||||
'timeout': 30, 'side_effecting': False,
|
||||
'desc': 'Run fleet status health check (read-only)',
|
||||
},
|
||||
'subagent.spawn': {
|
||||
'validate': _subagent_spawn_validate, 'build': _subagent_spawn_build,
|
||||
'timeout': 120, 'side_effecting': True,
|
||||
'desc': 'Spawn an autonomous subagent session via muse-cli gateway',
|
||||
},
|
||||
'thread.list': {
|
||||
'validate': _thread_list_validate, 'build': _thread_list_build,
|
||||
'timeout': 60, 'side_effecting': False,
|
||||
'desc': 'List threads/sessions for an agent',
|
||||
},
|
||||
'thread.view': {
|
||||
'validate': _thread_view_validate, 'build': _thread_view_build,
|
||||
'timeout': 60, 'side_effecting': False,
|
||||
'desc': 'View messages in a thread/session',
|
||||
},
|
||||
'pipeline.run': {
|
||||
'validate': _pipeline_run_validate, 'build': _pipeline_run_build,
|
||||
'timeout': 120, 'side_effecting': True,
|
||||
'desc': 'Dispatch a multi-step pipeline across agents',
|
||||
},
|
||||
'exec.ping': {
|
||||
'validate': _health_validate,
|
||||
@@ -486,13 +594,17 @@ OPS = {
|
||||
PERMISSIONS = {
|
||||
'master': set(OPS),
|
||||
'operator-main': set(OPS),
|
||||
'operator-646': {'dm.send', 'dm.thread', 'dm.read', 'job.run',
|
||||
'chat.messages', 'health.check', 'exec.ping'},
|
||||
'operator-muse': {'dm.send', 'dm.read', 'chat.messages', 'exec.ping'},
|
||||
'operator-pip': {'dm.send', 'dm.read', 'chat.messages', 'exec.ping'},
|
||||
'operator-646': set(OPS),
|
||||
'operator-muse': set(OPS),
|
||||
'operator-pip': set(OPS),
|
||||
'operator-opm': set(OPS),
|
||||
'646': set(OPS),
|
||||
'pip': set(OPS),
|
||||
'muse': set(OPS),
|
||||
'opm': set(OPS),
|
||||
'exec-canary': {'exec.ping'},
|
||||
}
|
||||
DEFAULT_PERMS = {'dm.read', 'chat.messages', 'health.check', 'exec.ping'}
|
||||
DEFAULT_PERMS = {'dm.read', 'chat.messages', 'health.check', 'thread.list', 'thread.view', 'exec.ping'}
|
||||
|
||||
|
||||
def permitted(ident, op):
|
||||
|
||||
Reference in New Issue
Block a user