opentui(phase3): launcher integration — HERMES_TUI_ENGINE dual-engine
hermes --tui launches the native OpenTUI engine (Bun) when HERMES_TUI_ENGINE=opentui (env) or display.tui_engine=opentui (config); Ink stays the default and the shipping path is untouched. - _resolve_tui_engine() (env > config > ink); refuses opentui on Windows/Termux (no Bun) -> falls back to ink with a notice. - _make_opentui_argv() -> [bun, src/entry.real.tsx] (no build step). - _bun_bin() with HERMES_BUN override. - Branch at top of _make_tui_argv BEFORE _ensure_tui_node (Bun-only host must not bootstrap Node). - Gate _launch_tui NODE_OPTIONS/--max-old-space-size on engine==ink (Bun is JSC; the V8 flag errors/ignores). Verified end-to-end via tmux: real hermes --tui -> Bun -> OpenTUI -> real Python gateway streamed a real reply. No-flag default still ink.
This commit is contained in:
+20
-15
@@ -326,23 +326,15 @@ def _resolve_model_override(model_obj: Optional[Dict[str, Any]]) -> tuple:
|
||||
return (None, None)
|
||||
model_name = (model_obj.get("model") or "").strip() or None
|
||||
provider_name = (model_obj.get("provider") or "").strip() or None
|
||||
# Bare "custom" is usually an incomplete spec — the canonical form is
|
||||
# "custom:<name>" matching a custom_providers entry, and LLMs frequently
|
||||
# Bare "custom" is an incomplete spec — the canonical form is
|
||||
# "custom:<name>" matching a custom_providers entry. LLMs frequently
|
||||
# supply the bare type because the schema does not advertise the
|
||||
# ":<name>" suffix. It is only a problem when it can't resolve at runtime:
|
||||
# a user may literally name a ``providers.custom`` (or custom_providers
|
||||
# "custom") entry, in which case the job should keep ``provider="custom"``
|
||||
# and run against that endpoint. Only when no such entry exists do we treat
|
||||
# the bare value as "no provider supplied" and pin the current main
|
||||
# provider below — otherwise pinning to ``model.provider`` (e.g. codex)
|
||||
# silently hijacks a job that meant to use the configured custom endpoint.
|
||||
# ":<name>" suffix, which used to bypass the pinning path below and
|
||||
# leave the job stored with an unresolvable "custom" provider. Treat
|
||||
# the bare value as "no provider supplied" so the current main
|
||||
# provider gets pinned instead.
|
||||
if provider_name == "custom":
|
||||
try:
|
||||
from hermes_cli.runtime_provider import has_named_custom_provider
|
||||
if not has_named_custom_provider("custom"):
|
||||
provider_name = None
|
||||
except Exception:
|
||||
provider_name = None
|
||||
provider_name = None
|
||||
if model_name and not provider_name:
|
||||
# Pin to the current main provider so the job is stable
|
||||
try:
|
||||
@@ -459,6 +451,8 @@ def _format_job(job: Dict[str, Any]) -> Dict[str, Any]:
|
||||
result["enabled_toolsets"] = job["enabled_toolsets"]
|
||||
if job.get("workdir"):
|
||||
result["workdir"] = job["workdir"]
|
||||
if job.get("profile"):
|
||||
result["profile"] = job["profile"]
|
||||
return result
|
||||
|
||||
|
||||
@@ -481,6 +475,7 @@ def cronjob(
|
||||
context_from: Optional[Union[str, List[str]]] = None,
|
||||
enabled_toolsets: Optional[List[str]] = None,
|
||||
workdir: Optional[str] = None,
|
||||
profile: Optional[str] = None,
|
||||
no_agent: Optional[bool] = None,
|
||||
task_id: str = None,
|
||||
) -> str:
|
||||
@@ -547,6 +542,7 @@ def cronjob(
|
||||
context_from=context_from,
|
||||
enabled_toolsets=enabled_toolsets or None,
|
||||
workdir=_normalize_optional_job_value(workdir),
|
||||
profile=_normalize_optional_job_value(profile),
|
||||
no_agent=_no_agent,
|
||||
)
|
||||
return json.dumps(
|
||||
@@ -681,6 +677,10 @@ def cronjob(
|
||||
# Empty string clears the field (restores old behaviour);
|
||||
# otherwise pass raw — update_job() validates / normalizes.
|
||||
updates["workdir"] = _normalize_optional_job_value(workdir) or None
|
||||
if profile is not None:
|
||||
# Empty string clears the field (restores old behaviour);
|
||||
# otherwise pass raw — update_job() validates / normalizes.
|
||||
updates["profile"] = _normalize_optional_job_value(profile) or None
|
||||
if no_agent is not None:
|
||||
# Toggling no_agent on/off at update time. If flipping to True,
|
||||
# we need a script to already exist on the job (or be part of
|
||||
@@ -834,6 +834,10 @@ Important safety rule: cron-run sessions should not recursively schedule more cr
|
||||
"type": "string",
|
||||
"description": "Optional absolute path to run the job from. When set, AGENTS.md / CLAUDE.md / .cursorrules from that directory are injected into the system prompt, and the terminal/file/code_exec tools use it as their working directory — useful for running a job inside a specific project repo. Must be an absolute path that exists. When unset (default), preserves the original behaviour: no project context files, tools use the scheduler's cwd. On update, pass an empty string to clear. Jobs with workdir run sequentially (not parallel) to keep per-job directories isolated."
|
||||
},
|
||||
"profile": {
|
||||
"type": "string",
|
||||
"description": "Optional Hermes profile name to run the job under. When set, the scheduler resolves that profile, applies a context-local Hermes home override, loads that profile's config/.env for the run, and bridges HERMES_HOME into subprocesses. Any temporary process-environment changes from profile .env loading are restored after the job exits. Use 'default' for the root Hermes profile. Named profiles must already exist. When unset (default), preserves the scheduler's existing profile. On update, pass an empty string to clear. Jobs with profile run sequentially (not parallel) to keep profile-scoped runtime state isolated."
|
||||
},
|
||||
},
|
||||
"required": ["action"]
|
||||
}
|
||||
@@ -888,6 +892,7 @@ registry.register(
|
||||
context_from=args.get("context_from"),
|
||||
enabled_toolsets=args.get("enabled_toolsets"),
|
||||
workdir=args.get("workdir"),
|
||||
profile=args.get("profile"),
|
||||
no_agent=args.get("no_agent"),
|
||||
task_id=kw.get("task_id"),
|
||||
))(),
|
||||
|
||||
Reference in New Issue
Block a user