diff --git a/agent/command_token_source.py b/agent/command_token_source.py index 38ace24324..08c6d04763 100644 --- a/agent/command_token_source.py +++ b/agent/command_token_source.py @@ -51,7 +51,7 @@ def _mint(command: str, label: str) -> tuple[str, Optional[float]]: try: completed = subprocess.run( - command, shell=True, capture_output=True, text=True, timeout=_MINT_TIMEOUT_SECONDS, + command, shell=True, capture_output=True, text=True, errors="replace", timeout=_MINT_TIMEOUT_SECONDS, env=served_profile_child_env(inherit_credentials=True), ) except subprocess.TimeoutExpired as exc: diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index 3957b89a34..57af87b558 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -40,7 +40,7 @@ from agent.turn_retry_state import TurnRetryState from agent.turn_api_call import handle_api_interrupt, nous_rate_limit_guard, perform_api_call from agent.turn_api_error import handle_api_error from agent.turn_api_request import build_api_request -from agent.turn_failure_copy import failed_turn_notice, site_copy +from agent.turn_failure_copy import FAILED_TURN_DISPLAY_KIND, failed_turn_notice, site_copy from agent.turn_final_response import finish_text_response from agent.turn_finalizer import finalize_turn from agent.turn_iteration_prep import ( @@ -1693,7 +1693,9 @@ def _close_durable_failed_turn(agent, result: Any) -> None: # hedge over the whole list rather than under-report a possible side effect. start = result.get("current_turn_user_idx") turn_messages = messages[start:] if isinstance(start, int) and 0 <= start < len(messages) else messages - append_message(messages, {"role": "assistant", "content": failed_turn_notice(turn_messages)}) + append_message(messages, { + "role": "assistant", "content": failed_turn_notice(turn_messages), "display_kind": FAILED_TURN_DISPLAY_KIND, + }) agent._flush_messages_to_session_db(messages) except Exception: logger.debug("failed-turn boundary not written", exc_info=True) diff --git a/agent/turn_failure_copy.py b/agent/turn_failure_copy.py index a980a9ff2d..1686f28c63 100644 --- a/agent/turn_failure_copy.py +++ b/agent/turn_failure_copy.py @@ -43,6 +43,19 @@ PARTIAL_FAILED_TURN_NOTICE = ( "This turn did not complete. Some actions may already have run; verify their effects " "before resending." ) +# ``messages.display_kind`` of that row: display-only (stripped before every provider request), +# so renderers show a Hermes notice and room pollers never read it as the model's reply. +FAILED_TURN_DISPLAY_KIND = "failed_turn" + + +def untyped_failed_turn_display_kind(role: Any, content: Any) -> Optional[str]: + """``FAILED_TURN_DISPLAY_KIND`` for a boundary row persisted before the closers typed it + (exact notice text, so a real reply quoting it stays a reply); read-side only.""" + if role == "assistant" and isinstance(content, str) and content.strip() in ( + FAILED_TURN_NOTICE, PARTIAL_FAILED_TURN_NOTICE, + ): + return FAILED_TURN_DISPLAY_KIND + return None def failed_turn_notice(turn_messages: Any) -> str: diff --git a/apps/desktop/e2e/fixtures.ts b/apps/desktop/e2e/fixtures.ts index 46495af595..1f3995e486 100644 --- a/apps/desktop/e2e/fixtures.ts +++ b/apps/desktop/e2e/fixtures.ts @@ -73,6 +73,16 @@ function isCredentialEnvVar(name: string): boolean { return CREDENTIAL_SUFFIXES.some((suffix) => name.endsWith(suffix)) } +// Runtime state of whatever Hermes launched this run. A spec driven from inside +// an agent's terminal inherits HERMES_YOLO_MODE, HERMES_INTERACTIVE, +// HERMES_SESSION_ID…, and the sandboxed backend then skips approvals or binds +// the caller's session — the approval spec failed locally on the leaked yolo +// flag while CI (which never has these) stayed green. The fixtures set every +// HERMES_* the app needs themselves; only the harness's own knobs pass. +function isInheritedHermesRuntimeVar(name: string): boolean { + return name.startsWith('HERMES_') && !name.startsWith('HERMES_DESKTOP_') && !name.startsWith('HERMES_E2E_') +} + function stripCredentials(env: Record): Record { const clean: Record = {} @@ -81,7 +91,7 @@ function stripCredentials(env: Record): Record