feat(tmux): add shared muse tmux socket manager with CLI and agent [TOOL tmux.*] integration
This commit is contained in:
@@ -622,6 +622,113 @@ def _cron_timer_create_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), 'timer-create', a['name']]
|
||||
|
||||
|
||||
# ------------------------------------------------------- tmux ops
|
||||
def _tmux_session_name(s):
|
||||
if not isinstance(s, str) or not re.fullmatch(r'^[a-zA-Z0-9_.-]{1,64}$', s):
|
||||
raise OpError('session must match ^[a-zA-Z0-9_.-]{1,64}$')
|
||||
return s
|
||||
|
||||
|
||||
def _tmux_send_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
allowed = {'session', 'keys', 'no_enter'}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
if not raw.get('session'):
|
||||
raise OpError('session is required')
|
||||
if 'keys' not in raw:
|
||||
raise OpError('keys is required')
|
||||
return {
|
||||
'session': _tmux_session_name(raw['session']),
|
||||
'keys': str(raw['keys']),
|
||||
'no_enter': bool(raw.get('no_enter', False)),
|
||||
}
|
||||
|
||||
|
||||
def _tmux_send_build(a):
|
||||
cmd = [sys.executable, os.path.join(BIN_DIR, 'muse-tmux.py'), 'send', a['session'], a['keys']]
|
||||
if a.get('no_enter'):
|
||||
cmd.append('--no-enter')
|
||||
return cmd
|
||||
|
||||
|
||||
def _tmux_capture_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
allowed = {'session', 'lines'}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
if not raw.get('session'):
|
||||
raise OpError('session is required')
|
||||
lines = raw.get('lines', 30)
|
||||
try:
|
||||
lines = int(lines)
|
||||
if lines < 1 or lines > 500:
|
||||
lines = 30
|
||||
except Exception:
|
||||
lines = 30
|
||||
return {
|
||||
'session': _tmux_session_name(raw['session']),
|
||||
'lines': lines,
|
||||
}
|
||||
|
||||
|
||||
def _tmux_capture_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'muse-tmux.py'), 'capture', a['session'], '--lines', str(a['lines'])]
|
||||
|
||||
|
||||
def _tmux_list_validate(raw):
|
||||
return {}
|
||||
|
||||
|
||||
def _tmux_list_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'muse-tmux.py'), 'list']
|
||||
|
||||
|
||||
def _tmux_new_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
allowed = {'session', 'window', 'command'}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
if not raw.get('session'):
|
||||
raise OpError('session is required')
|
||||
return {
|
||||
'session': _tmux_session_name(raw['session']),
|
||||
'window': str(raw.get('window', '')) if raw.get('window') else None,
|
||||
'command': str(raw.get('command', '')) if raw.get('command') else None,
|
||||
}
|
||||
|
||||
|
||||
def _tmux_new_build(a):
|
||||
cmd = [sys.executable, os.path.join(BIN_DIR, 'muse-tmux.py'), 'new', a['session']]
|
||||
if a.get('window'):
|
||||
cmd.extend(['--window', a['window']])
|
||||
if a.get('command'):
|
||||
cmd.extend(['--command', a['command']])
|
||||
return cmd
|
||||
|
||||
|
||||
def _tmux_kill_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
allowed = {'session'}
|
||||
for k in raw:
|
||||
if k not in allowed:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
if not raw.get('session'):
|
||||
raise OpError('session is required')
|
||||
return {'session': _tmux_session_name(raw['session'])}
|
||||
|
||||
|
||||
def _tmux_kill_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'muse-tmux.py'), 'kill', a['session']]
|
||||
|
||||
|
||||
def _vars_list_validate(raw):
|
||||
if raw not in ({}, None):
|
||||
raise OpError('vars.list takes no required args')
|
||||
@@ -1042,6 +1149,31 @@ OPS = {
|
||||
'timeout': 30, 'side_effecting': True,
|
||||
'desc': 'Schedule a delayed autonomous self-followup reminder via transient systemd timer',
|
||||
},
|
||||
'tmux.send': {
|
||||
'validate': _tmux_send_validate, 'build': _tmux_send_build,
|
||||
'timeout': 15, 'side_effecting': True,
|
||||
'desc': 'Send keystrokes to a shared Muse tmux session (/tmp/tmux-muse.sock)',
|
||||
},
|
||||
'tmux.capture': {
|
||||
'validate': _tmux_capture_validate, 'build': _tmux_capture_build,
|
||||
'timeout': 15, 'side_effecting': False,
|
||||
'desc': 'Capture pane output from a shared Muse tmux session (/tmp/tmux-muse.sock)',
|
||||
},
|
||||
'tmux.list': {
|
||||
'validate': _tmux_list_validate, 'build': _tmux_list_build,
|
||||
'timeout': 10, 'side_effecting': False,
|
||||
'desc': 'List sessions on shared Muse tmux socket (/tmp/tmux-muse.sock)',
|
||||
},
|
||||
'tmux.new': {
|
||||
'validate': _tmux_new_validate, 'build': _tmux_new_build,
|
||||
'timeout': 15, 'side_effecting': True,
|
||||
'desc': 'Create a new session on shared Muse tmux socket (/tmp/tmux-muse.sock)',
|
||||
},
|
||||
'tmux.kill': {
|
||||
'validate': _tmux_kill_validate, 'build': _tmux_kill_build,
|
||||
'timeout': 15, 'side_effecting': True,
|
||||
'desc': 'Kill a session on shared Muse tmux socket (/tmp/tmux-muse.sock)',
|
||||
},
|
||||
'exec.ping': {
|
||||
'validate': _health_validate,
|
||||
'build': lambda a: ['/bin/echo', 'PONG'],
|
||||
|
||||
Reference in New Issue
Block a user