refactor(cli): derive relaunch flag table from argparse introspection
Pull the top-level + chat parser construction out of main() into hermes_cli/_parser.py so relaunch.py can introspect parser._actions to discover which flags exist and whether they take values, instead of maintaining a parallel hand-rolled (flag, takes_value) tuple list. - _parser.py: build_top_level_parser() returns (parser, subparsers, chat_parser); side-effect-free import. - main.py: ~290 lines of inline parser construction collapsed to a helper call. Other subparsers stay inline (dispatch is bound to module-level cmd_* functions). - _parser._inherited_flag(parser, ...): wraps parser.add_argument and sets action.inherit_on_relaunch = True. Used in place of parser.add_argument for the 25 flags (top-level + chat) that need to carry over. - _parser.PRE_ARGPARSE_INHERITED_FLAGS: holds --profile/-p, which isn't on argparse (consumed earlier by main._apply_profile_override). - relaunch.py: drops _CRITICAL_DESTS and _PRE_ARGPARSE_FLAGS; the table builder now filters by getattr(action, 'inherit_on_relaunch', False). - test_ignore_user_config_flags.py: brittle inspect.getsource grep replaced with proper parser introspection. - test_relaunch.py: introspection sanity tests added. Salvaged from PR #17549; added top-level -t/--toolsets flag to _parser.py so #17623 (fix(tui): honor launch toolsets) behavior is preserved on current main. Co-authored-by: ethernet <arilotter@gmail.com>
This commit is contained in:
+49
-38
@@ -13,50 +13,60 @@ import shutil
|
||||
import sys
|
||||
from typing import Optional, Sequence
|
||||
|
||||
|
||||
# (option_string, takes_value) — flags whose presence (and value, where
|
||||
# applicable) on the original argv must survive a self-relaunch.
|
||||
_CRITICAL_FLAGS: list[tuple[str, bool]] = [
|
||||
("--tui", False),
|
||||
("--dev", False),
|
||||
("--profile", True),
|
||||
("-p", True),
|
||||
("--model", True),
|
||||
("-m", True),
|
||||
("--provider", True),
|
||||
("--yolo", False),
|
||||
("--ignore-user-config", False),
|
||||
("--ignore-rules", False),
|
||||
("--pass-session-id", False),
|
||||
("--accept-hooks", False),
|
||||
("--worktree", False),
|
||||
("-w", False),
|
||||
("--skills", True),
|
||||
("-s", True),
|
||||
("--quiet", False),
|
||||
("-Q", False),
|
||||
("--verbose", False),
|
||||
("-v", False),
|
||||
("--source", True),
|
||||
]
|
||||
from hermes_cli._parser import (
|
||||
PRE_ARGPARSE_INHERITED_FLAGS,
|
||||
build_top_level_parser,
|
||||
)
|
||||
|
||||
|
||||
def _extract_critical_flags(argv: Sequence[str]) -> list[str]:
|
||||
"""Pull out flags that affect session behaviour / UI mode."""
|
||||
def _build_inherited_flag_table() -> list[tuple[str, bool]]:
|
||||
"""Build the ``(option_string, takes_value)`` table of flags that must
|
||||
survive a self-relaunch, by introspecting the real parser used by
|
||||
``hermes`` itself.
|
||||
|
||||
A flag participates if its argparse Action carries
|
||||
``inherit_on_relaunch = True`` — set by ``_parser._inherited_flag``.
|
||||
"""
|
||||
parser, _subparsers, chat_parser = build_top_level_parser()
|
||||
|
||||
table: list[tuple[str, bool]] = []
|
||||
seen: set[tuple[str, bool]] = set()
|
||||
for p in (parser, chat_parser):
|
||||
for action in p._actions:
|
||||
if not action.option_strings:
|
||||
continue # positional / no flag form
|
||||
if not getattr(action, "inherit_on_relaunch", False):
|
||||
continue
|
||||
takes_value = action.nargs != 0 # store_true/false set nargs=0
|
||||
for opt in action.option_strings:
|
||||
key = (opt, takes_value)
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
table.append(key)
|
||||
|
||||
table.extend(PRE_ARGPARSE_INHERITED_FLAGS)
|
||||
return table
|
||||
|
||||
|
||||
_INHERITED_FLAGS_TABLE = _build_inherited_flag_table()
|
||||
|
||||
|
||||
def _extract_inherited_flags(argv: Sequence[str]) -> list[str]:
|
||||
"""Pull out flags that should carry over into a self-relaunched hermes."""
|
||||
flags: list[str] = []
|
||||
i = 0
|
||||
while i < len(argv):
|
||||
arg = argv[i]
|
||||
if "=" in arg:
|
||||
key = arg.split("=", 1)[0]
|
||||
for flag, _ in _CRITICAL_FLAGS:
|
||||
for flag, _ in _INHERITED_FLAGS_TABLE:
|
||||
if key == flag:
|
||||
flags.append(arg)
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
|
||||
for flag, takes_value in _CRITICAL_FLAGS:
|
||||
for flag, takes_value in _INHERITED_FLAGS_TABLE:
|
||||
if arg == flag:
|
||||
flags.append(arg)
|
||||
if takes_value and i + 1 < len(argv) and not argv[i + 1].startswith("-"):
|
||||
@@ -98,14 +108,15 @@ def resolve_hermes_bin() -> Optional[str]:
|
||||
def build_relaunch_argv(
|
||||
extra_args: Sequence[str],
|
||||
*,
|
||||
preserve_critical: bool = True,
|
||||
preserve_inherited: bool = True,
|
||||
original_argv: Optional[Sequence[str]] = None,
|
||||
) -> list[str]:
|
||||
"""Construct an argv list for replacing the current process with hermes.
|
||||
|
||||
Args:
|
||||
extra_args: Arguments to append (e.g. ``["--resume", id]``).
|
||||
preserve_critical: Whether to carry over UI / behaviour flags.
|
||||
preserve_inherited: Whether to carry over UI / behaviour flags
|
||||
tagged with ``inherit_on_relaunch`` in the parser.
|
||||
original_argv: The original argv to scan for flags (defaults to
|
||||
``sys.argv[1:]``).
|
||||
"""
|
||||
@@ -118,8 +129,8 @@ def build_relaunch_argv(
|
||||
|
||||
src = list(original_argv) if original_argv is not None else list(sys.argv[1:])
|
||||
|
||||
if preserve_critical:
|
||||
argv.extend(_extract_critical_flags(src))
|
||||
if preserve_inherited:
|
||||
argv.extend(_extract_inherited_flags(src))
|
||||
|
||||
argv.extend(extra_args)
|
||||
return argv
|
||||
@@ -128,20 +139,20 @@ def build_relaunch_argv(
|
||||
def relaunch(
|
||||
extra_args: Sequence[str],
|
||||
*,
|
||||
preserve_critical: bool = True,
|
||||
preserve_inherited: bool = True,
|
||||
original_argv: Optional[Sequence[str]] = None,
|
||||
) -> None:
|
||||
"""Replace the current process with a fresh hermes invocation."""
|
||||
new_argv = build_relaunch_argv(
|
||||
extra_args, preserve_critical=preserve_critical, original_argv=original_argv
|
||||
extra_args, preserve_inherited=preserve_inherited, original_argv=original_argv
|
||||
)
|
||||
os.execvp(new_argv[0], new_argv)
|
||||
|
||||
|
||||
def relaunch_chat(
|
||||
*,
|
||||
preserve_critical: bool = True,
|
||||
preserve_inherited: bool = True,
|
||||
original_argv: Optional[Sequence[str]] = None,
|
||||
) -> None:
|
||||
"""Convenience wrapper: relaunch into ``hermes chat``."""
|
||||
relaunch(["chat"], preserve_critical=preserve_critical, original_argv=original_argv)
|
||||
relaunch(["chat"], preserve_inherited=preserve_inherited, original_argv=original_argv)
|
||||
|
||||
Reference in New Issue
Block a user