feat(messaging): balanced TOOL parsing, DM shorthand, box.exec, tools.list
- response-harvester: extract [TOOL]/[EXEC] JSON args with balanced-brace
scanning (']' and nesting inside args no longer truncate calls); add
[DM {...}] shorthand mapping to dm.send; native aliases (dm, box,
tools) plus arg-synonym normalization; formatters and expanded hints.
- exec-constrained: new read-only box.exec op (27 allowlisted box-ctl
reads) and tools.list op backed by --list-ops for dynamic discovery.
- prompt_envelope: advertise dm.send/box.exec/tools.list in every timer
DM; add dm_call builder.
- lookup_engine + regex_patterns.json: canonical tool_call pattern
accepts the DM engine, ']' in args, one nesting level.
- tests/test_tool_calls.py: 38 tests; docs/INBAND-MESSAGING-SPEC.md:
accepted decision record (Final).
This commit is contained in:
@@ -994,6 +994,67 @@ def _swarm_results_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), 'swarm-results', a['swarm_id']]
|
||||
|
||||
|
||||
# box.exec allowlist: read-only box-ctl actions only (mirrors the read-only
|
||||
# subset of box-ctl.py IDEMPOTENT_ACTIONS). Actions with side-effecting
|
||||
# subverbs (main-loop enable/disable), path args (git-diff/git-log), or
|
||||
# complex argv shapes (job-next, policy-*, quality-validate, job-result)
|
||||
# are deliberately excluded.
|
||||
BOX_EXEC_NOARG_ACTIONS = frozenset({
|
||||
'fleet-status', 'watchdog-alerts', 'relay-health', 'cdp-latency',
|
||||
'chrome-errors', 'identity-audit', 'timer-list', 'job-list',
|
||||
'vars-list', 'strat-list', 'loop-status', 'loop-health', 'loop-breaks',
|
||||
'thread-list', 'dm-log', 'unread', 'swarm-list', 'quality-check',
|
||||
'git-status',
|
||||
})
|
||||
BOX_EXEC_ONEARG_ACTIONS = frozenset({
|
||||
'job-get', 'job-status', 'timer-status', 'vars-get', 'vars-history',
|
||||
'strat-get', 'swarm-status', 'swarm-results',
|
||||
})
|
||||
_BOX_ARG_RE = re.compile(r'^[A-Za-z0-9][A-Za-z0-9/_.-]{0,127}$')
|
||||
|
||||
|
||||
def _box_exec_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
for k in raw:
|
||||
if k not in {'action', 'arg', 'agent'}:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
action = raw.get('action')
|
||||
if action in BOX_EXEC_NOARG_ACTIONS:
|
||||
if raw.get('arg') is not None:
|
||||
raise OpError(f'{action} takes no arg')
|
||||
return {'action': action}
|
||||
if action in BOX_EXEC_ONEARG_ACTIONS:
|
||||
arg = raw.get('arg')
|
||||
if arg is None:
|
||||
return {'action': action}
|
||||
if not isinstance(arg, str) or not _BOX_ARG_RE.fullmatch(arg):
|
||||
raise OpError('arg must match safe token')
|
||||
return {'action': action, 'arg': arg}
|
||||
raise OpError(f'unknown or non-read-only box action: {action}')
|
||||
|
||||
|
||||
def _box_exec_build(a):
|
||||
argv = [sys.executable, os.path.join(BIN_DIR, 'box-ctl.py'), a['action']]
|
||||
if a.get('arg'):
|
||||
argv.append(a['arg'])
|
||||
return argv
|
||||
|
||||
|
||||
def _tools_list_validate(raw):
|
||||
if not isinstance(raw, dict):
|
||||
raise OpError('args must be an object')
|
||||
for k in raw:
|
||||
if k not in {'agent'}:
|
||||
raise OpError(f'unknown arg: {k}')
|
||||
return {}
|
||||
|
||||
|
||||
def _tools_list_build(a):
|
||||
return [sys.executable, os.path.join(BIN_DIR, 'exec-constrained.py'),
|
||||
'--list-ops']
|
||||
|
||||
|
||||
# op -> {validate, build, timeout, side_effecting, description}
|
||||
OPS = {
|
||||
'dm.send': {
|
||||
@@ -1207,6 +1268,16 @@ OPS = {
|
||||
'timeout': 10, 'side_effecting': False,
|
||||
'desc': 'Canary no-op for watchdogs',
|
||||
},
|
||||
'box.exec': {
|
||||
'validate': _box_exec_validate, 'build': _box_exec_build,
|
||||
'timeout': 60, 'side_effecting': False,
|
||||
'desc': 'Call a read-only box-ctl action (fleet-status, dm-log, job-get, ...)',
|
||||
},
|
||||
'tools.list': {
|
||||
'validate': _tools_list_validate, 'build': _tools_list_build,
|
||||
'timeout': 15, 'side_effecting': False,
|
||||
'desc': 'List all exec ops with descriptions (dynamic discovery)',
|
||||
},
|
||||
}
|
||||
|
||||
# identity -> set of ops. 'master' may invoke everything. Unknown identities
|
||||
@@ -1495,6 +1566,19 @@ def main():
|
||||
ap.add_argument('--key-file',
|
||||
default='/home/super/.exec-constrained-key.pem')
|
||||
ap.add_argument('--work-dir', default='/home/super')
|
||||
ap.add_argument('--list-ops', action='store_true',
|
||||
help='Print the op registry as JSON and exit (backs tools.list)')
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.list_ops:
|
||||
print(json.dumps({
|
||||
'ok': True,
|
||||
'ops': [{'op': k, 'desc': v.get('desc', ''),
|
||||
'side_effecting': bool(v.get('side_effecting', False)),
|
||||
'timeout': v.get('timeout', 30)}
|
||||
for k, v in sorted(OPS.items())],
|
||||
}))
|
||||
return
|
||||
args = ap.parse_args()
|
||||
|
||||
TOKEN_FILE, TOKEN_DIR = args.token_file, args.token_dir
|
||||
|
||||
Reference in New Issue
Block a user