feat(tmux): add shared muse tmux socket manager with CLI and agent [TOOL tmux.*] integration

This commit is contained in:
operator
2026-10-05 16:11:19 +00:00
parent 07c67e5da3
commit 0ca0fd0c1a
5 changed files with 346 additions and 1 deletions
+132
View File
@@ -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'],