2026-08-02 15:53:30 -03:00
|
|
|
# syntax=docker/dockerfile:1
|
|
|
|
|
# dockerfiles/Dockerfile.hermes-agent
|
|
|
|
|
FROM nousresearch/hermes-agent@sha256:9c841866021c54c4596849f6135717e8a4d52ba510b7f52c50aef1de1a283973
|
|
|
|
|
|
|
|
|
|
USER root
|
|
|
|
|
|
2026-08-10 02:15:34 -03:00
|
|
|
# Codex uses the distribution bubblewrap package when a bounded sandbox is
|
|
|
|
|
# requested. Full-access workers rely on the pod as their isolation boundary,
|
|
|
|
|
# but keep bwrap installed for diagnostics and any future restricted profile.
|
|
|
|
|
RUN apt-get update \
|
|
|
|
|
&& apt-get install -y --no-install-recommends bubblewrap \
|
|
|
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
|
|
2026-08-08 23:24:58 -03:00
|
|
|
# Keep a credential-free search provider available for private chat tenants.
|
|
|
|
|
# Paid/provider-backed search remains selectable through normal Hermes config.
|
|
|
|
|
RUN uv pip install --python /opt/hermes/.venv/bin/python ddgs==9.14.4
|
|
|
|
|
|
2026-08-02 16:46:21 -03:00
|
|
|
# Keep dashboard chat sockets tied to the intended React mount and conversation.
|
|
|
|
|
# A resumed conversation needs a different PTY attachment key from a fresh chat;
|
|
|
|
|
# reconnects to that same conversation must keep using the same key.
|
2026-08-02 15:53:30 -03:00
|
|
|
RUN node <<'NODE'
|
|
|
|
|
const fs = require("node:fs");
|
|
|
|
|
const path = "/opt/hermes/web/src/pages/ChatPage.tsx";
|
2026-08-02 16:46:21 -03:00
|
|
|
let source = fs.readFileSync(path, "utf8");
|
|
|
|
|
const socketBefore = [
|
2026-08-02 15:53:30 -03:00
|
|
|
' const url = await api.buildWsUrl("/api/pty", params);',
|
|
|
|
|
' const ws = new WebSocket(url);',
|
|
|
|
|
].join("\n");
|
2026-08-02 16:46:21 -03:00
|
|
|
const socketAfter = [
|
2026-08-02 15:53:30 -03:00
|
|
|
' const url = await api.buildWsUrl("/api/pty", params);',
|
|
|
|
|
' if (unmounting) return;',
|
|
|
|
|
' const ws = new WebSocket(url);',
|
|
|
|
|
].join("\n");
|
2026-08-02 16:46:21 -03:00
|
|
|
const attachBefore = ' params.attach = ptyAttachToken(forceFresh);';
|
|
|
|
|
const attachAfter = [
|
|
|
|
|
' const attachScope = resumeParam',
|
|
|
|
|
' ? `resume:${resumeParam}:${scopedProfile ?? ""}`',
|
|
|
|
|
' : `fresh:${scopedProfile ?? ""}`;',
|
|
|
|
|
' params.attach = `${ptyAttachToken(forceFresh)}:${attachScope}`;',
|
|
|
|
|
].join("\n");
|
2026-08-02 15:53:30 -03:00
|
|
|
|
2026-08-02 16:46:21 -03:00
|
|
|
if (!source.includes(socketBefore)) {
|
2026-08-02 15:53:30 -03:00
|
|
|
throw new Error("Hermes ChatPage WebSocket patch context changed");
|
|
|
|
|
}
|
2026-08-02 16:46:21 -03:00
|
|
|
if (!source.includes(attachBefore)) {
|
|
|
|
|
throw new Error("Hermes ChatPage PTY attachment patch context changed");
|
|
|
|
|
}
|
|
|
|
|
source = source.replace(socketBefore, socketAfter);
|
|
|
|
|
source = source.replace(attachBefore, attachAfter);
|
|
|
|
|
fs.writeFileSync(path, source);
|
2026-08-02 15:53:30 -03:00
|
|
|
NODE
|
|
|
|
|
|
2026-08-02 17:00:40 -03:00
|
|
|
# The upstream OIDC gate authenticates users but deliberately treats the
|
|
|
|
|
# dashboard as one shared workstation. Allow a deployment to narrow that
|
|
|
|
|
# workstation to explicit OIDC subjects. Enforce this after normal provider
|
|
|
|
|
# verification so a denied account is a 403, not a misleading provider 503.
|
2026-08-02 16:46:21 -03:00
|
|
|
RUN python - <<'PY'
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-08-02 17:00:40 -03:00
|
|
|
path = Path("/opt/hermes/hermes_cli/dashboard_auth/middleware.py")
|
2026-08-02 16:46:21 -03:00
|
|
|
source = path.read_text()
|
2026-08-02 17:00:40 -03:00
|
|
|
helper_before = '''def _client_ip(request: Request) -> str:
|
|
|
|
|
fwd = request.headers.get("x-forwarded-for", "")
|
|
|
|
|
if fwd:
|
|
|
|
|
return fwd.split(",")[0].strip()
|
|
|
|
|
return request.client.host if request.client else ""
|
|
|
|
|
|
2026-08-02 16:46:21 -03:00
|
|
|
|
|
|
|
|
'''
|
2026-08-02 17:00:40 -03:00
|
|
|
helper_after = helper_before + '''def _dashboard_user_allowed(session) -> bool:
|
|
|
|
|
"""Apply an optional deployment-level OIDC-subject allowlist."""
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
allowed = {
|
|
|
|
|
value.strip()
|
|
|
|
|
for value in os.environ.get(
|
|
|
|
|
"HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS", ""
|
|
|
|
|
).split(",")
|
|
|
|
|
if value.strip()
|
|
|
|
|
}
|
|
|
|
|
return not allowed or session.user_id in allowed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _user_forbidden_response() -> Response:
|
|
|
|
|
"""Return an authorization failure without exposing identities."""
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
{
|
|
|
|
|
"error": "forbidden",
|
|
|
|
|
"detail": "This Atlas account is not authorized for this dashboard.",
|
|
|
|
|
},
|
|
|
|
|
status_code=403,
|
|
|
|
|
)
|
2026-08-02 16:46:21 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
2026-08-02 17:00:40 -03:00
|
|
|
refresh_before = ''' new_session, refreshing_provider = refreshed
|
|
|
|
|
request.state.session = new_session
|
|
|
|
|
response = await call_next(request)
|
|
|
|
|
'''
|
|
|
|
|
refresh_after = ''' new_session, refreshing_provider = refreshed
|
|
|
|
|
if not _dashboard_user_allowed(new_session):
|
|
|
|
|
return _user_forbidden_response()
|
|
|
|
|
request.state.session = new_session
|
|
|
|
|
response = await call_next(request)
|
|
|
|
|
'''
|
|
|
|
|
final_before = ''' request.state.session = session
|
|
|
|
|
return await call_next(request)
|
|
|
|
|
'''
|
|
|
|
|
final_after = ''' if not _dashboard_user_allowed(session):
|
|
|
|
|
return _user_forbidden_response()
|
|
|
|
|
request.state.session = session
|
|
|
|
|
return await call_next(request)
|
|
|
|
|
'''
|
|
|
|
|
for before, after, label in (
|
|
|
|
|
(helper_before, helper_after, "allowlist helper"),
|
|
|
|
|
(refresh_before, refresh_after, "refreshed session"),
|
|
|
|
|
(final_before, final_after, "verified session"),
|
|
|
|
|
):
|
|
|
|
|
if before not in source:
|
|
|
|
|
raise SystemExit(f"Hermes dashboard auth {label} patch context changed")
|
|
|
|
|
source = source.replace(before, after, 1)
|
|
|
|
|
path.write_text(source)
|
2026-08-02 16:46:21 -03:00
|
|
|
PY
|
|
|
|
|
|
2026-08-09 02:42:11 -03:00
|
|
|
# Give trusted plugins a pre-turn routing hook. It runs after fallback runtime
|
|
|
|
|
# restoration but before Hermes builds its provider-specific system prompt.
|
|
|
|
|
RUN python - <<'PY'
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
plugins_path = Path("/opt/hermes/hermes_cli/plugins.py")
|
|
|
|
|
plugins = plugins_path.read_text()
|
|
|
|
|
hooks_before = ''' "pre_llm_call",
|
|
|
|
|
"post_llm_call",
|
|
|
|
|
'''
|
|
|
|
|
hooks_after = ''' "pre_llm_call",
|
|
|
|
|
"pre_turn_route",
|
2026-08-10 04:31:41 -03:00
|
|
|
"pre_internal_route",
|
2026-08-10 05:16:30 -03:00
|
|
|
"pre_subagent_route",
|
2026-08-09 02:42:11 -03:00
|
|
|
"post_llm_call",
|
|
|
|
|
'''
|
|
|
|
|
if plugins.count(hooks_before) != 1:
|
|
|
|
|
raise SystemExit(
|
|
|
|
|
"Hermes pre-turn hook registry context changed: expected 1, "
|
|
|
|
|
f"found {plugins.count(hooks_before)}"
|
|
|
|
|
)
|
|
|
|
|
plugins_path.write_text(plugins.replace(hooks_before, hooks_after, 1))
|
|
|
|
|
|
|
|
|
|
turn_path = Path("/opt/hermes/agent/turn_context.py")
|
|
|
|
|
turn = turn_path.read_text()
|
|
|
|
|
turn_before = ''' agent._restore_primary_runtime()
|
|
|
|
|
'''
|
|
|
|
|
turn_after = turn_before + '''
|
|
|
|
|
|
|
|
|
|
# Trusted coordinator plugins may select the provider/model/effort for this
|
|
|
|
|
# turn. Run this before system-prompt restoration so the prompt and runtime
|
|
|
|
|
# always describe the same selected provider.
|
|
|
|
|
try:
|
|
|
|
|
from hermes_cli.plugins import has_hook, invoke_hook
|
|
|
|
|
|
|
|
|
|
if has_hook("pre_turn_route"):
|
|
|
|
|
invoke_hook(
|
|
|
|
|
"pre_turn_route",
|
|
|
|
|
agent=agent,
|
|
|
|
|
user_message=user_message,
|
|
|
|
|
conversation_history=list(conversation_history or []),
|
|
|
|
|
session_id=agent.session_id or "",
|
|
|
|
|
platform=agent.platform or "",
|
|
|
|
|
)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.warning("pre_turn_route hook failed", exc_info=True)
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
if turn.count(turn_before) != 1:
|
|
|
|
|
raise SystemExit(
|
|
|
|
|
"Hermes pre-turn routing context changed: expected 1, "
|
|
|
|
|
f"found {turn.count(turn_before)}"
|
|
|
|
|
)
|
|
|
|
|
turn_path.write_text(turn.replace(turn_before, turn_after, 1))
|
2026-08-10 04:31:41 -03:00
|
|
|
|
|
|
|
|
loop_path = Path("/opt/hermes/agent/conversation_loop.py")
|
|
|
|
|
loop = loop_path.read_text()
|
|
|
|
|
loop_before = ''' # Prepare messages for API call
|
|
|
|
|
'''
|
|
|
|
|
loop_after = ''' # Reclassify every internal tool-loop continuation before provider-
|
|
|
|
|
# specific prompt construction. The first request was already routed by
|
|
|
|
|
# pre_turn_route; later requests include the tool evidence accumulated
|
|
|
|
|
# since that decision.
|
|
|
|
|
if api_call_count > 1:
|
|
|
|
|
try:
|
|
|
|
|
from hermes_cli.plugins import has_hook, invoke_hook
|
|
|
|
|
|
|
|
|
|
if has_hook("pre_internal_route"):
|
|
|
|
|
invoke_hook(
|
|
|
|
|
"pre_internal_route",
|
|
|
|
|
agent=agent,
|
|
|
|
|
user_message=original_user_message,
|
|
|
|
|
conversation_history=list(messages),
|
|
|
|
|
session_id=agent.session_id or "",
|
|
|
|
|
platform=agent.platform or "",
|
|
|
|
|
api_call_count=api_call_count,
|
|
|
|
|
)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.warning("pre_internal_route hook failed", exc_info=True)
|
|
|
|
|
|
|
|
|
|
# Prepare messages for API call
|
|
|
|
|
'''
|
|
|
|
|
if loop.count(loop_before) != 1:
|
|
|
|
|
raise SystemExit(
|
|
|
|
|
"Hermes internal routing context changed: expected 1, "
|
|
|
|
|
f"found {loop.count(loop_before)}"
|
|
|
|
|
)
|
|
|
|
|
loop_path.write_text(loop.replace(loop_before, loop_after, 1))
|
2026-08-10 05:16:30 -03:00
|
|
|
|
|
|
|
|
delegate_path = Path("/opt/hermes/tools/delegate_tool.py")
|
|
|
|
|
delegate = delegate_path.read_text()
|
|
|
|
|
delegate_before = ''' # Override with correct parent tool names (before child construction mutated global)
|
|
|
|
|
child._delegate_saved_tool_names = _parent_tool_names
|
|
|
|
|
children.append((i, t, child))
|
|
|
|
|
'''
|
|
|
|
|
delegate_after = ''' # Route each bounded child independently before its first LLM call.
|
|
|
|
|
# This keeps one multi-part objective from pinning every leaf to
|
|
|
|
|
# the parent coordinator's provider/model/effort.
|
|
|
|
|
try:
|
|
|
|
|
from hermes_cli.plugins import has_hook, invoke_hook
|
|
|
|
|
|
|
|
|
|
if has_hook("pre_subagent_route"):
|
|
|
|
|
invoke_hook(
|
|
|
|
|
"pre_subagent_route",
|
|
|
|
|
agent=child,
|
|
|
|
|
parent_agent=parent_agent,
|
|
|
|
|
goal=t["goal"],
|
|
|
|
|
context=t.get("context"),
|
|
|
|
|
task_index=i,
|
|
|
|
|
task_count=n_tasks,
|
|
|
|
|
)
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.warning("pre_subagent_route hook failed", exc_info=True)
|
|
|
|
|
|
|
|
|
|
# Override with correct parent tool names (before child construction mutated global)
|
|
|
|
|
child._delegate_saved_tool_names = _parent_tool_names
|
|
|
|
|
children.append((i, t, child))
|
|
|
|
|
'''
|
|
|
|
|
if delegate.count(delegate_before) != 1:
|
|
|
|
|
raise SystemExit(
|
|
|
|
|
"Hermes subagent routing context changed: expected 1, "
|
|
|
|
|
f"found {delegate.count(delegate_before)}"
|
|
|
|
|
)
|
|
|
|
|
delegate_path.write_text(delegate.replace(delegate_before, delegate_after, 1))
|
2026-08-09 02:42:11 -03:00
|
|
|
PY
|
|
|
|
|
|
2026-08-08 23:24:58 -03:00
|
|
|
# Hermes WebUI sends its model/provider/reasoning selection on /v1/runs.
|
|
|
|
|
# Upstream currently applies only statically declared model_routes there, so
|
|
|
|
|
# the UI can display one model while the gateway silently runs another. Honor
|
|
|
|
|
# trusted first-party provider selections and cap all chat reasoning at xhigh.
|
|
|
|
|
RUN python - <<'PY'
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
path = Path("/opt/hermes/gateway/platforms/api_server.py")
|
|
|
|
|
source = path.read_text()
|
|
|
|
|
|
|
|
|
|
route_before = ''' def _resolve_route(self, model_alias: Any) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""Return the model_routes entry for *model_alias*, or None."""
|
|
|
|
|
if not self._model_routes or not isinstance(model_alias, str):
|
|
|
|
|
return None
|
|
|
|
|
return self._model_routes.get(model_alias)
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
route_after = route_before + ''' def _resolve_request_route(self, body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""Resolve a static route or a trusted WebUI provider/model selection."""
|
|
|
|
|
route = self._resolve_route(body.get("model"))
|
|
|
|
|
if route is not None:
|
|
|
|
|
return route
|
|
|
|
|
|
|
|
|
|
provider = body.get("provider")
|
|
|
|
|
model = body.get("model")
|
|
|
|
|
allowed_providers = {"openai-codex", "anthropic"}
|
|
|
|
|
if provider not in allowed_providers or not isinstance(model, str):
|
|
|
|
|
return None
|
|
|
|
|
model = model.strip()
|
|
|
|
|
if not model or len(model) > 128 or any(
|
|
|
|
|
char not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._:/+-"
|
|
|
|
|
for char in model
|
|
|
|
|
):
|
|
|
|
|
return None
|
|
|
|
|
return {"provider": provider, "model": model}
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
signature_before = ''' gateway_session_key: Optional[str] = None,
|
|
|
|
|
route: Optional[Dict[str, Any]] = None,
|
|
|
|
|
) -> Any:
|
|
|
|
|
'''
|
|
|
|
|
signature_after = ''' gateway_session_key: Optional[str] = None,
|
|
|
|
|
route: Optional[Dict[str, Any]] = None,
|
|
|
|
|
reasoning_effort: Any = None,
|
|
|
|
|
) -> Any:
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
reasoning_before = ''' runtime_kwargs = _resolve_runtime_agent_kwargs()
|
|
|
|
|
reasoning_config = GatewayRunner._load_reasoning_config()
|
|
|
|
|
model = _resolve_gateway_model()
|
|
|
|
|
'''
|
|
|
|
|
reasoning_after = ''' runtime_kwargs = _resolve_runtime_agent_kwargs()
|
|
|
|
|
reasoning_config = GatewayRunner._load_reasoning_config()
|
|
|
|
|
from hermes_constants import parse_reasoning_effort
|
|
|
|
|
|
|
|
|
|
requested_reasoning = parse_reasoning_effort(reasoning_effort)
|
|
|
|
|
if requested_reasoning is not None:
|
|
|
|
|
reasoning_config = requested_reasoning
|
|
|
|
|
if reasoning_config and reasoning_config.get("effort") == "max":
|
|
|
|
|
reasoning_config = {"enabled": True, "effort": "xhigh"}
|
|
|
|
|
model = _resolve_gateway_model()
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
runs_route_before = ''' # Per-client model routing for /v1/runs (see model_routes).
|
|
|
|
|
route = self._resolve_route(body.get("model"))
|
|
|
|
|
'''
|
|
|
|
|
runs_route_after = ''' # Honor both static routes and the WebUI's trusted provider/model pick.
|
|
|
|
|
route = self._resolve_request_route(body)
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
runs_agent_before = ''' gateway_session_key=gateway_session_key,
|
|
|
|
|
route=route,
|
|
|
|
|
)
|
|
|
|
|
'''
|
|
|
|
|
runs_agent_after = ''' gateway_session_key=gateway_session_key,
|
|
|
|
|
route=route,
|
|
|
|
|
reasoning_effort=body.get("reasoning_effort"),
|
|
|
|
|
)
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
for before, after, label, count in (
|
|
|
|
|
(route_before, route_after, "request route resolver", 1),
|
|
|
|
|
(signature_before, signature_after, "agent reasoning argument", 1),
|
|
|
|
|
(reasoning_before, reasoning_after, "reasoning clamp", 1),
|
|
|
|
|
(runs_route_before, runs_route_after, "runs route", 1),
|
|
|
|
|
):
|
|
|
|
|
if source.count(before) != count:
|
|
|
|
|
raise SystemExit(
|
|
|
|
|
f"Hermes API {label} patch context changed: expected {count}, "
|
|
|
|
|
f"found {source.count(before)}"
|
|
|
|
|
)
|
|
|
|
|
source = source.replace(before, after, count)
|
|
|
|
|
|
|
|
|
|
# The same argument tail appears in other handlers. Restrict replacement to
|
|
|
|
|
# the /v1/runs section so non-WebUI API surfaces retain upstream behavior.
|
|
|
|
|
runs_start = source.index(" async def _handle_runs(")
|
|
|
|
|
runs_source = source[runs_start:]
|
|
|
|
|
if runs_source.count(runs_agent_before) != 1:
|
|
|
|
|
raise SystemExit(
|
|
|
|
|
"Hermes API /v1/runs agent-call patch context changed: expected 1, "
|
|
|
|
|
f"found {runs_source.count(runs_agent_before)}"
|
|
|
|
|
)
|
|
|
|
|
runs_source = runs_source.replace(runs_agent_before, runs_agent_after, 1)
|
|
|
|
|
source = source[:runs_start] + runs_source
|
|
|
|
|
path.write_text(source)
|
|
|
|
|
PY
|
|
|
|
|
|
|
|
|
|
COPY dockerfiles/hermes-python-sandbox-tool.py /opt/hermes/tools/python_sandbox_tool.py
|
|
|
|
|
COPY dockerfiles/hermes-public-extract/__init__.py /opt/hermes/plugins/web/public_extract/__init__.py
|
|
|
|
|
COPY dockerfiles/hermes-public-extract/plugin.yaml /opt/hermes/plugins/web/public_extract/plugin.yaml
|
|
|
|
|
COPY dockerfiles/hermes-public-extract/provider.py /opt/hermes/plugins/web/public_extract/provider.py
|
|
|
|
|
|
2026-08-08 23:59:05 -03:00
|
|
|
# Per-capability custom backends are resolved before upstream discovers web
|
|
|
|
|
# plugins, causing extract_backend to fall through to the shared search-only
|
|
|
|
|
# backend. Discover bundled plugins before checking a custom capability name.
|
|
|
|
|
RUN python - <<'PY'
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
path = Path("/opt/hermes/tools/web_tools.py")
|
|
|
|
|
source = path.read_text()
|
|
|
|
|
before = ''' cfg = _load_web_config()
|
|
|
|
|
specific = (cfg.get(f"{capability}_backend") or "").lower().strip()
|
|
|
|
|
if specific and _is_backend_available(specific):
|
|
|
|
|
return specific
|
|
|
|
|
'''
|
|
|
|
|
after = ''' cfg = _load_web_config()
|
|
|
|
|
specific = (cfg.get(f"{capability}_backend") or "").lower().strip()
|
|
|
|
|
if specific and specific not in _LEGACY_WEB_BACKENDS:
|
|
|
|
|
_ensure_web_plugins_loaded()
|
|
|
|
|
if specific and _is_backend_available(specific):
|
|
|
|
|
return specific
|
|
|
|
|
'''
|
|
|
|
|
if source.count(before) != 1:
|
|
|
|
|
raise SystemExit(
|
|
|
|
|
"Hermes custom web capability patch context changed: expected 1, "
|
|
|
|
|
f"found {source.count(before)}"
|
|
|
|
|
)
|
|
|
|
|
path.write_text(source.replace(before, after, 1))
|
|
|
|
|
PY
|
|
|
|
|
|
2026-08-02 16:46:21 -03:00
|
|
|
COPY dockerfiles/hermes-session-migrate.py /opt/hermes/bin/hermes-session-migrate
|
|
|
|
|
|
2026-08-02 15:53:30 -03:00
|
|
|
RUN cd /opt/hermes/web \
|
|
|
|
|
&& npm run build \
|
2026-08-02 16:46:21 -03:00
|
|
|
&& grep -Fq 'if (unmounting) return;' src/pages/ChatPage.tsx \
|
|
|
|
|
&& grep -Fq 'resume:${resumeParam}' src/pages/ChatPage.tsx \
|
|
|
|
|
&& grep -Fq 'HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS' \
|
2026-08-02 17:00:40 -03:00
|
|
|
/opt/hermes/hermes_cli/dashboard_auth/middleware.py \
|
2026-08-08 23:24:58 -03:00
|
|
|
&& grep -Fq '_resolve_request_route' \
|
|
|
|
|
/opt/hermes/gateway/platforms/api_server.py \
|
|
|
|
|
&& grep -Fq 'reasoning_effort=body.get("reasoning_effort")' \
|
|
|
|
|
/opt/hermes/gateway/platforms/api_server.py \
|
2026-08-09 02:42:11 -03:00
|
|
|
&& grep -Fq '"pre_turn_route"' /opt/hermes/hermes_cli/plugins.py \
|
2026-08-10 04:31:41 -03:00
|
|
|
&& grep -Fq '"pre_internal_route"' /opt/hermes/hermes_cli/plugins.py \
|
2026-08-10 05:16:30 -03:00
|
|
|
&& grep -Fq '"pre_subagent_route"' /opt/hermes/hermes_cli/plugins.py \
|
2026-08-09 02:42:11 -03:00
|
|
|
&& grep -Fq 'invoke_hook(' /opt/hermes/agent/turn_context.py \
|
2026-08-10 04:31:41 -03:00
|
|
|
&& grep -Fq 'pre_internal_route hook failed' \
|
|
|
|
|
/opt/hermes/agent/conversation_loop.py \
|
2026-08-10 05:16:30 -03:00
|
|
|
&& grep -Fq 'pre_subagent_route hook failed' \
|
|
|
|
|
/opt/hermes/tools/delegate_tool.py \
|
2026-08-08 23:24:58 -03:00
|
|
|
&& /opt/hermes/.venv/bin/python -m py_compile \
|
|
|
|
|
/opt/hermes/gateway/platforms/api_server.py \
|
2026-08-09 02:42:11 -03:00
|
|
|
/opt/hermes/agent/turn_context.py \
|
2026-08-10 04:31:41 -03:00
|
|
|
/opt/hermes/agent/conversation_loop.py \
|
2026-08-10 05:16:30 -03:00
|
|
|
/opt/hermes/tools/delegate_tool.py \
|
2026-08-08 23:59:05 -03:00
|
|
|
/opt/hermes/tools/web_tools.py \
|
2026-08-08 23:24:58 -03:00
|
|
|
/opt/hermes/tools/python_sandbox_tool.py \
|
|
|
|
|
/opt/hermes/plugins/web/public_extract/provider.py \
|
|
|
|
|
&& /opt/hermes/.venv/bin/python -c 'import ddgs' \
|
2026-08-02 16:46:21 -03:00
|
|
|
&& chmod 0755 /opt/hermes/bin/hermes-session-migrate
|