openhands-agent-server 1.9.1__py3-none-any.whl → 1.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ from openhands.agent_server.models import (
16
16
  )
17
17
  from openhands.agent_server.pub_sub import PubSub, Subscriber
18
18
  from openhands.sdk.logger import get_logger
19
+ from openhands.sdk.utils import sanitized_env
19
20
 
20
21
 
21
22
  logger = get_logger(__name__)
@@ -194,6 +195,7 @@ class BashEventService:
194
195
  stdout=asyncio.subprocess.PIPE,
195
196
  stderr=asyncio.subprocess.PIPE,
196
197
  shell=True,
198
+ env=sanitized_env(),
197
199
  )
198
200
 
199
201
  # Track output order and buffers
@@ -235,7 +235,16 @@ class ConversationService:
235
235
  f"{list(request.tool_module_qualnames.keys())}"
236
236
  )
237
237
 
238
- stored = StoredConversation(id=conversation_id, **request.model_dump())
238
+ # Plugin loading is now handled lazily by LocalConversation.
239
+ # Just pass the plugin specs through to StoredConversation.
240
+ # LocalConversation will:
241
+ # 1. Fetch and load plugins on first run()/send_message()
242
+ # 2. Resolve refs to commit SHAs for deterministic resume
243
+ # 3. Merge plugin skills/MCP/hooks into the agent
244
+ stored = StoredConversation(
245
+ id=conversation_id,
246
+ **request.model_dump(),
247
+ )
239
248
  event_service = await self._start_event_service(stored)
240
249
  initial_message = request.initial_message
241
250
  if initial_message:
@@ -9,6 +9,7 @@ from pathlib import Path
9
9
 
10
10
  from openhands.agent_server.config import get_default_config
11
11
  from openhands.sdk.logger import get_logger
12
+ from openhands.sdk.utils import sanitized_env
12
13
 
13
14
 
14
15
  logger = get_logger(__name__)
@@ -28,7 +29,7 @@ class DesktopService:
28
29
  return True
29
30
 
30
31
  # --- Env defaults (match bash behavior) ---
31
- env = os.environ.copy()
32
+ env = sanitized_env()
32
33
  display = env.get("DISPLAY", ":1")
33
34
  user = env.get("USER") or env.get("USERNAME") or "openhands"
34
35
  home = Path(env.get("HOME") or f"/home/{user}")
@@ -68,6 +69,7 @@ class DesktopService:
68
69
  capture_output=True,
69
70
  text=True,
70
71
  timeout=3,
72
+ env=env,
71
73
  ).returncode
72
74
  == 0
73
75
  )
@@ -105,6 +107,7 @@ class DesktopService:
105
107
  capture_output=True,
106
108
  text=True,
107
109
  timeout=3,
110
+ env=env,
108
111
  ).returncode
109
112
  == 0
110
113
  )
@@ -180,7 +183,11 @@ class DesktopService:
180
183
  # Check if VNC server is running
181
184
  try:
182
185
  result = subprocess.run(
183
- ["pgrep", "-f", "Xvnc"], capture_output=True, text=True, timeout=3
186
+ ["pgrep", "-f", "Xvnc"],
187
+ capture_output=True,
188
+ text=True,
189
+ timeout=3,
190
+ env=sanitized_env(),
184
191
  )
185
192
  return result.returncode == 0
186
193
  except Exception:
@@ -46,6 +46,7 @@ class EventService:
46
46
  _pub_sub: PubSub[Event] = field(default_factory=lambda: PubSub[Event](), init=False)
47
47
  _run_task: asyncio.Task | None = field(default=None, init=False)
48
48
  _run_lock: asyncio.Lock = field(default_factory=asyncio.Lock, init=False)
49
+ _callback_wrapper: AsyncCallbackWrapper | None = field(default=None, init=False)
49
50
 
50
51
  @property
51
52
  def conversation_dir(self):
@@ -430,19 +431,29 @@ class EventService:
430
431
  self.stored.agent.model_dump(context={"expose_secrets": True}),
431
432
  )
432
433
 
434
+ # Create LocalConversation with plugins and hook_config.
435
+ # Plugins are loaded lazily on first run()/send_message() call.
436
+ # Hook execution semantics: OpenHands runs hooks sequentially with early-exit
437
+ # on block (PreToolUse), unlike Claude Code's parallel execution model.
438
+
439
+ # Create and store callback wrapper to allow flushing pending events
440
+ self._callback_wrapper = AsyncCallbackWrapper(
441
+ self._pub_sub, loop=asyncio.get_running_loop()
442
+ )
443
+
433
444
  conversation = LocalConversation(
434
445
  agent=agent,
435
446
  workspace=workspace,
447
+ plugins=self.stored.plugins,
436
448
  persistence_dir=str(self.conversations_dir),
437
449
  conversation_id=self.stored.id,
438
- callbacks=[
439
- AsyncCallbackWrapper(self._pub_sub, loop=asyncio.get_running_loop())
440
- ],
450
+ callbacks=[self._callback_wrapper],
441
451
  max_iteration_per_run=self.stored.max_iterations,
442
452
  stuck_detection=self.stored.stuck_detection,
443
453
  visualizer=None,
444
454
  secrets=self.stored.secrets,
445
455
  cipher=self.cipher,
456
+ hook_config=self.stored.hook_config,
446
457
  )
447
458
 
448
459
  # Set confirmation mode if enabled
@@ -517,6 +528,16 @@ class EventService:
517
528
  except Exception:
518
529
  logger.exception("Error during conversation run")
519
530
  finally:
531
+ # Wait for all pending events to be published via
532
+ # AsyncCallbackWrapper before publishing the final state update.
533
+ # This prevents a race condition where the conversation status
534
+ # becomes FINISHED before agent events (MessageEvent, ActionEvent,
535
+ # etc.) are published to WebSocket subscribers.
536
+ if self._callback_wrapper:
537
+ await loop.run_in_executor(
538
+ None, self._callback_wrapper.wait_for_pending, 30.0
539
+ )
540
+
520
541
  # Clear task reference and publish state update
521
542
  self._run_task = None
522
543
  await self._publish_state_update()
@@ -12,7 +12,9 @@ from openhands.sdk.conversation.state import (
12
12
  ConversationExecutionStatus,
13
13
  ConversationState,
14
14
  )
15
+ from openhands.sdk.hooks import HookConfig
15
16
  from openhands.sdk.llm.utils.metrics import MetricsSnapshot
17
+ from openhands.sdk.plugin import PluginSource
16
18
  from openhands.sdk.secret import SecretSource
17
19
  from openhands.sdk.security.analyzer import SecurityAnalyzerBase
18
20
  from openhands.sdk.security.confirmation_policy import (
@@ -106,10 +108,31 @@ class StartConversationRequest(BaseModel):
106
108
  "to register the tools for this conversation."
107
109
  ),
108
110
  )
111
+ plugins: list[PluginSource] | None = Field(
112
+ default=None,
113
+ description=(
114
+ "List of plugins to load for this conversation. Plugins are loaded "
115
+ "and their skills/MCP config are merged into the agent. "
116
+ "Hooks are extracted and stored for runtime execution."
117
+ ),
118
+ )
119
+ hook_config: HookConfig | None = Field(
120
+ default=None,
121
+ description=(
122
+ "Optional hook configuration for this conversation. Hooks are shell "
123
+ "scripts that run at key lifecycle events (PreToolUse, PostToolUse, "
124
+ "UserPromptSubmit, Stop, etc.). If both hook_config and plugins are "
125
+ "provided, they are merged with explicit hooks running before plugin "
126
+ "hooks."
127
+ ),
128
+ )
109
129
 
110
130
 
111
131
  class StoredConversation(StartConversationRequest):
112
- """Stored details about a conversation"""
132
+ """Stored details about a conversation.
133
+
134
+ Extends StartConversationRequest with server-assigned fields.
135
+ """
113
136
 
114
137
  id: OpenHandsUUID
115
138
  title: str | None = Field(
@@ -14,7 +14,6 @@ Precedence (later overrides earlier):
14
14
  sandbox < public < user < org < project
15
15
  """
16
16
 
17
- import os
18
17
  import shutil
19
18
  import subprocess
20
19
  import tempfile
@@ -37,6 +36,7 @@ from openhands.sdk.context.skills.utils import (
37
36
  update_skills_repository,
38
37
  )
39
38
  from openhands.sdk.logger import get_logger
39
+ from openhands.sdk.utils import sanitized_env
40
40
 
41
41
 
42
42
  logger = get_logger(__name__)
@@ -120,6 +120,8 @@ def load_org_skills_from_url(
120
120
  # Clone the organization repository (shallow clone for efficiency)
121
121
  logger.info(f"Cloning organization skills repository for {org_name}")
122
122
  try:
123
+ env = sanitized_env()
124
+ env["GIT_TERMINAL_PROMPT"] = "0"
123
125
  subprocess.run(
124
126
  [
125
127
  "git",
@@ -132,7 +134,7 @@ def load_org_skills_from_url(
132
134
  check=True,
133
135
  capture_output=True,
134
136
  timeout=120,
135
- env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
137
+ env=env,
136
138
  )
137
139
  except subprocess.CalledProcessError:
138
140
  # Repository doesn't exist or access denied - this is expected.
@@ -5,6 +5,7 @@ import os
5
5
  from pathlib import Path
6
6
 
7
7
  from openhands.sdk.logger import get_logger
8
+ from openhands.sdk.utils import sanitized_env
8
9
 
9
10
 
10
11
  logger = get_logger(__name__)
@@ -160,6 +161,7 @@ class VSCodeService:
160
161
  cmd,
161
162
  stdout=asyncio.subprocess.PIPE,
162
163
  stderr=asyncio.subprocess.STDOUT,
164
+ env=sanitized_env(),
163
165
  )
164
166
 
165
167
  # Wait for server to start (look for startup message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-agent-server
3
- Version: 1.9.1
3
+ Version: 1.10.0
4
4
  Summary: OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent
5
5
  Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
6
6
  Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
@@ -2,40 +2,40 @@ openhands/agent_server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
2
2
  openhands/agent_server/__main__.py,sha256=QCdBRWVV9gNtPwRwYieQisvKsmJljjJ8f293RhtHl_w,3642
3
3
  openhands/agent_server/api.py,sha256=LqU4BWXc4VQ29sIiABZykznSMJ_dUQEOHixxeLBTuGY,12009
4
4
  openhands/agent_server/bash_router.py,sha256=9GRsLeVZgqR1XeayJvBBycVqfg5O-Fye2ek75kGCuno,3353
5
- openhands/agent_server/bash_service.py,sha256=QgTSyQoxlk52BnZuo35xlX7u7-3xs5BvvaKU3AEva_w,14083
5
+ openhands/agent_server/bash_service.py,sha256=dhoV5jEBitsb6UNSf1ht4hkf2E9i5fZ_26qx8VZkuj0,14166
6
6
  openhands/agent_server/config.py,sha256=EKxVV0QyD3KLLtFxNuUextxJplLW_zZ32nPFhli6ozA,6403
7
7
  openhands/agent_server/conversation_router.py,sha256=lz-dnfPXrVBiGZ9GhYqfteCWJ4pq7AcnWqxsKWwCfc0,11178
8
- openhands/agent_server/conversation_service.py,sha256=Oj8IBSY9ZYbduOOBatd3I0H_kSyH7xi22Y_MyhfL5Tk,27058
8
+ openhands/agent_server/conversation_service.py,sha256=rucfKX1SnVZVPVr0fN4jTyr6AbACbKUmJojds2qbTmg,27454
9
9
  openhands/agent_server/dependencies.py,sha256=H3zyOc8uthpXseB3E7rWNccKIj7PlyfcgCYwFvmFq4c,2629
10
10
  openhands/agent_server/desktop_router.py,sha256=OaCmevO33eUo3jTwiXBmQ3uT3ONu4-tqgBfYpZWrHSA,1349
11
- openhands/agent_server/desktop_service.py,sha256=iCwQJXK4DvGuBXKOQ1oko60wXkf_pYHCubOzBsd2k60,7415
11
+ openhands/agent_server/desktop_service.py,sha256=GnLrQd2rqlRXBCNPbCXOZEfAXa42y9GNyFAQRjVE7y8,7603
12
12
  openhands/agent_server/env_parser.py,sha256=6b4-Iegq82crMbwjo1w4C_nu5B0Wm0xysrhGhfiyMF8,16770
13
13
  openhands/agent_server/event_router.py,sha256=XM46zcqPOXStISfihzsPXPfsW_23E50brmBHk04ncVI,6156
14
- openhands/agent_server/event_service.py,sha256=EFjIkWbTrROAv2zFzsIOhcRDTZIhf0uIGQStNDg8nFY,26838
14
+ openhands/agent_server/event_service.py,sha256=aKBbm2PO3l28QDSgibJ8JBPxD-kcUaFkpU2loGpfz1c,28042
15
15
  openhands/agent_server/file_router.py,sha256=MqFmTcDFE42EEPwRncBtT-Vu8_U78OZfC6pm0tnlBZk,4161
16
16
  openhands/agent_server/git_router.py,sha256=z-jbkY4me1HuLOWTzJu_osI19ZGGri5ffYujVAWe31s,974
17
17
  openhands/agent_server/logging_config.py,sha256=b3N5LVGuwDd0bBsMxrVdCHa8N1Nsreawgi23hTkDrro,3985
18
18
  openhands/agent_server/middleware.py,sha256=WQN5T14E58cvG2ZG6qfaJL4Dk9DqUGqD5tyAva1Un_4,1407
19
- openhands/agent_server/models.py,sha256=R3WSDIQN1burIyV4Dz05YITR9WWcC_pXgZDk-7WG6hw,9723
19
+ openhands/agent_server/models.py,sha256=QQtm9J9HNf0yqDmz171TNKo1CUPV8fbQ12zAhk2Ssnk,10668
20
20
  openhands/agent_server/openapi.py,sha256=RJWaOnM9NjzrH-fJi3PoIBv5d0sH5z8zZTdvYzSqoFU,482
21
21
  openhands/agent_server/pub_sub.py,sha256=yPB84Wub7A2uwUWsW_grbxomWmKaWyGuo6dVNUzg1FU,2755
22
22
  openhands/agent_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  openhands/agent_server/server_details_router.py,sha256=LCa0NQ3SOw-IgJh3rDJL5GolFOpZcX_-XEbDLHF8dN4,950
24
24
  openhands/agent_server/skills_router.py,sha256=mYiIAOGJxnpMz_OsbtP2cBjHNvo3qYUXqGPHKQs_pEw,5601
25
- openhands/agent_server/skills_service.py,sha256=hmyo1FtUEwfPWk1NnMyGGhvIqPwO28Mjk3chUw_98XM,14125
25
+ openhands/agent_server/skills_service.py,sha256=-KcI28xDwnMQ9KXRQVWR6CXo2OdZBXmKE_iVDZo0r-A,14201
26
26
  openhands/agent_server/sockets.py,sha256=UhZr_QoHsIX7x_tzvk9_AJa2S8KJmRr4syhIU4He8Rs,6617
27
27
  openhands/agent_server/tool_preload_service.py,sha256=2QuFyn7jC4Ifq2aUhs9j8876wSBdY0eeoLo4qEVi-yA,2341
28
28
  openhands/agent_server/tool_router.py,sha256=vM_9UKUzfChLK9B9Z3DL4VtKNdDw4w635knu9N36y0c,676
29
29
  openhands/agent_server/utils.py,sha256=ajivE_kGCJ9qUhF9H3Qu7DUKg7uDvDQk16JcO3XntEs,1926
30
30
  openhands/agent_server/vscode_router.py,sha256=tPmXzN6teuqMa1jvKS4Q3aWpk9p9wsp4LleKoDvkYGs,2133
31
- openhands/agent_server/vscode_service.py,sha256=xS_vwIU5W5KwXTbOTzHPkPRIB4xbjmYlAygmRfBOAF8,7606
31
+ openhands/agent_server/vscode_service.py,sha256=te7PWP0eedV7JTlLrBnCujg1dPYMmYZRvbIkxKPavgw,7685
32
32
  openhands/agent_server/docker/Dockerfile,sha256=rdFlMdI_uITipzR7-pPEGFx2Ld-jYhOBfGKONRk4dbU,10724
33
33
  openhands/agent_server/docker/build.py,sha256=UgHoLkgHZtgWqloG2MQ2RCzc6zTIyttOFL_vUzmw_c0,28189
34
34
  openhands/agent_server/docker/wallpaper.svg,sha256=FR2g_b5mzz0x5EvRTKO93ASnWPagAyeS9RI3vRQBAsw,11532
35
35
  openhands/agent_server/vscode_extensions/openhands-settings/extension.js,sha256=xoCKZ6YXlzlTWnTC52HuzX0sn9s77Vma-47WgEibO88,858
36
36
  openhands/agent_server/vscode_extensions/openhands-settings/package.json,sha256=eCkuBBYEVArEjpp7c_m0H207OCLEygZhBLUEkeFNWOg,289
37
- openhands_agent_server-1.9.1.dist-info/METADATA,sha256=B0VjqaMpQpjLUEpETaqh1uOPDKKE28xDE4G4NOnVkRc,748
38
- openhands_agent_server-1.9.1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
39
- openhands_agent_server-1.9.1.dist-info/entry_points.txt,sha256=uLQzPhqDqe85Dy9DvPiBE2CeqkwCryggr1Ty_mq65NA,70
40
- openhands_agent_server-1.9.1.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
41
- openhands_agent_server-1.9.1.dist-info/RECORD,,
37
+ openhands_agent_server-1.10.0.dist-info/METADATA,sha256=hCtt3IEZL-iGj6ybOZrjr5AJ-A_Rb61_yWDFXNXsf-c,749
38
+ openhands_agent_server-1.10.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
+ openhands_agent_server-1.10.0.dist-info/entry_points.txt,sha256=uLQzPhqDqe85Dy9DvPiBE2CeqkwCryggr1Ty_mq65NA,70
40
+ openhands_agent_server-1.10.0.dist-info/top_level.txt,sha256=jHgVu9I0Blam8BXFgedoGKfglPF8XvW1TsJFIjcgP4E,10
41
+ openhands_agent_server-1.10.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5