From 4dd4ce64d634524d87fc9f7d8ebbfbeca1ad14eb Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 26 May 2026 19:22:21 +0000 Subject: [PATCH 1/2] feat(dispatcher): inject AUTOMATION_SESSION_URL env var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds AUTOMATION_SESSION_URL to the sandbox env vars when SANDBOX_ID is available (Cloud mode). The URL is pre-built as {cloud_api_url}/conversations/{sandbox_id} so automation scripts can link back to the session without having to reconstruct it from SANDBOX_ID + OPENHANDS_CLOUD_API_URL individually — both of which are stripped by sanitized_env() before they reach the agent's bash subprocess. Also documents the new env var in the plugin preset sdk_main.py docstring. Co-authored-by: openhands --- openhands/automation/dispatcher.py | 6 ++++++ openhands/automation/presets/plugin/sdk_main.py | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/openhands/automation/dispatcher.py b/openhands/automation/dispatcher.py index 0fd02fe..ff606f6 100644 --- a/openhands/automation/dispatcher.py +++ b/openhands/automation/dispatcher.py @@ -221,6 +221,12 @@ async def _fail(error: str, disable: bool = False) -> None: if ctx.sandbox_id: env_vars["SANDBOX_ID"] = ctx.sandbox_id env_vars["SESSION_API_KEY"] = ctx.session_key + # Provide a pre-built conversation URL so automation scripts can link + # back to the session without reconstructing it from SANDBOX_ID + + # OPENHANDS_CLOUD_API_URL (which are stripped by sanitized_env() in + # bash subprocesses). + base_url = env_vars.get("OPENHANDS_CLOUD_API_URL", settings.resolved_base_url).rstrip("/") + env_vars["AUTOMATION_SESSION_URL"] = f"{base_url}/conversations/{ctx.sandbox_id}" # 4. Prepare tarball source try: diff --git a/openhands/automation/presets/plugin/sdk_main.py b/openhands/automation/presets/plugin/sdk_main.py index 2f50b92..6b093ac 100644 --- a/openhands/automation/presets/plugin/sdk_main.py +++ b/openhands/automation/presets/plugin/sdk_main.py @@ -54,7 +54,9 @@ AUTOMATION_CALLBACK_URL - completion callback endpoint (optional) AUTOMATION_RUN_ID - run ID for the callback payload (optional) AUTOMATION_EVENT_PAYLOAD - JSON with trigger info and event payload (optional) - AUTOMATION_MODEL - model profile name to load instead of default (optional) + AUTOMATION_MODEL - model profile name to load instead of default (optional) + AUTOMATION_SESSION_URL - direct URL to this conversation in the OpenHands UI + (Cloud mode only; omitted when SANDBOX_ID is unavailable) """ From a14efa6e22f5edb7837ff8be993765c734da4ea0 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Tue, 26 May 2026 19:44:10 +0000 Subject: [PATCH 2/2] fix(presets): inject session URL as secret using conversation.id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After Conversation(...) is created, build the session URL from conversation.id and inject it via conversation.update_secrets(). The SDK auto-exports secrets as env vars when the agent's bash command references the key name, so the prompt's ${AUTOMATION_SESSION_URL} just works — no prompt changes needed. Applied to both plugin and prompt presets for consistency. Co-authored-by: openhands --- openhands/automation/dispatcher.py | 6 ------ openhands/automation/presets/plugin/sdk_main.py | 13 +++++++++++-- openhands/automation/presets/prompt/sdk_main.py | 15 +++++++++++++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/openhands/automation/dispatcher.py b/openhands/automation/dispatcher.py index ff606f6..0fd02fe 100644 --- a/openhands/automation/dispatcher.py +++ b/openhands/automation/dispatcher.py @@ -221,12 +221,6 @@ async def _fail(error: str, disable: bool = False) -> None: if ctx.sandbox_id: env_vars["SANDBOX_ID"] = ctx.sandbox_id env_vars["SESSION_API_KEY"] = ctx.session_key - # Provide a pre-built conversation URL so automation scripts can link - # back to the session without reconstructing it from SANDBOX_ID + - # OPENHANDS_CLOUD_API_URL (which are stripped by sanitized_env() in - # bash subprocesses). - base_url = env_vars.get("OPENHANDS_CLOUD_API_URL", settings.resolved_base_url).rstrip("/") - env_vars["AUTOMATION_SESSION_URL"] = f"{base_url}/conversations/{ctx.sandbox_id}" # 4. Prepare tarball source try: diff --git a/openhands/automation/presets/plugin/sdk_main.py b/openhands/automation/presets/plugin/sdk_main.py index 6b093ac..69c30de 100644 --- a/openhands/automation/presets/plugin/sdk_main.py +++ b/openhands/automation/presets/plugin/sdk_main.py @@ -55,8 +55,10 @@ AUTOMATION_RUN_ID - run ID for the callback payload (optional) AUTOMATION_EVENT_PAYLOAD - JSON with trigger info and event payload (optional) AUTOMATION_MODEL - model profile name to load instead of default (optional) + +Runtime-injected secrets (via conversation.update_secrets after Conversation creation): AUTOMATION_SESSION_URL - direct URL to this conversation in the OpenHands UI - (Cloud mode only; omitted when SANDBOX_ID is unavailable) + (Cloud mode only; built from conversation.id) """ @@ -339,11 +341,18 @@ def event_callback(event) -> None: print(f" conversation created: {type(conversation).__name__}") print(f" plugins loaded: {len(plugin_sources)}") - # Inject secrets into the conversation (as LookupSecret references) + # Inject secrets into the conversation (auto-exported as env vars in bash) if secrets: conversation.update_secrets(secrets) print(f" injected {len(secrets)} secrets into conversation") + # Build session URL from conversation ID and inject as a secret so + # the agent can use $AUTOMATION_SESSION_URL in bash commands. + if not IS_LOCAL_MODE and api_url: + session_url = f"{api_url}/conversations/{conversation.id}" + conversation.update_secrets({"AUTOMATION_SESSION_URL": session_url}) + print(f" session URL: {session_url}") + try: print(f" sending prompt: {USER_PROMPT[:80]}...") conversation.send_message(USER_PROMPT) diff --git a/openhands/automation/presets/prompt/sdk_main.py b/openhands/automation/presets/prompt/sdk_main.py index 06561e2..c19ea35 100644 --- a/openhands/automation/presets/prompt/sdk_main.py +++ b/openhands/automation/presets/prompt/sdk_main.py @@ -58,7 +58,11 @@ AUTOMATION_CALLBACK_URL - completion callback endpoint (optional) AUTOMATION_RUN_ID - run ID for the callback payload (optional) AUTOMATION_EVENT_PAYLOAD - JSON with trigger info and event payload (optional) - AUTOMATION_MODEL - model profile name to load instead of default (optional) + AUTOMATION_MODEL - model profile name to load instead of default (optional) + +Runtime-injected secrets (via conversation.update_secrets after Conversation creation): + AUTOMATION_SESSION_URL - direct URL to this conversation in the OpenHands UI + (Cloud mode only; built from conversation.id) """ @@ -326,11 +330,18 @@ def event_callback(event) -> None: assert isinstance(conversation, RemoteConversation) print(f" conversation created: {type(conversation).__name__}") - # Inject secrets into the conversation (as LookupSecret references) + # Inject secrets into the conversation (auto-exported as env vars in bash) if secrets: conversation.update_secrets(secrets) print(f" injected {len(secrets)} secrets into conversation") + # Build session URL from conversation ID and inject as a secret so + # the agent can use $AUTOMATION_SESSION_URL in bash commands. + if not IS_LOCAL_MODE and api_url: + session_url = f"{api_url}/conversations/{conversation.id}" + conversation.update_secrets({"AUTOMATION_SESSION_URL": session_url}) + print(f" session URL: {session_url}") + try: print(f" sending prompt: {USER_PROMPT[:80]}...") conversation.send_message(USER_PROMPT)