openhands-agent-server 1.9.0__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.
- openhands/agent_server/bash_service.py +2 -0
- openhands/agent_server/conversation_service.py +10 -1
- openhands/agent_server/desktop_service.py +9 -2
- openhands/agent_server/env_parser.py +12 -1
- openhands/agent_server/event_service.py +24 -3
- openhands/agent_server/models.py +24 -1
- openhands/agent_server/skills_service.py +4 -2
- openhands/agent_server/vscode_service.py +2 -0
- {openhands_agent_server-1.9.0.dist-info → openhands_agent_server-1.10.0.dist-info}/METADATA +1 -1
- {openhands_agent_server-1.9.0.dist-info → openhands_agent_server-1.10.0.dist-info}/RECORD +13 -13
- {openhands_agent_server-1.9.0.dist-info → openhands_agent_server-1.10.0.dist-info}/WHEEL +1 -1
- {openhands_agent_server-1.9.0.dist-info → openhands_agent_server-1.10.0.dist-info}/entry_points.txt +0 -0
- {openhands_agent_server-1.9.0.dist-info → openhands_agent_server-1.10.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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 =
|
|
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"],
|
|
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:
|
|
@@ -278,8 +278,10 @@ class DiscriminatedUnionEnvParser(EnvParser):
|
|
|
278
278
|
|
|
279
279
|
def from_env(self, key: str) -> JsonType:
|
|
280
280
|
kind = os.environ.get(f"{key}_KIND", MISSING)
|
|
281
|
+
kind_missing = False
|
|
281
282
|
if kind is MISSING:
|
|
282
|
-
|
|
283
|
+
kind_missing = True
|
|
284
|
+
# If there are other fields and there is exactly one kind, use it directly
|
|
283
285
|
if len(self.parsers) == 1:
|
|
284
286
|
kind = next(iter(self.parsers.keys()))
|
|
285
287
|
else:
|
|
@@ -294,6 +296,15 @@ class DiscriminatedUnionEnvParser(EnvParser):
|
|
|
294
296
|
# Intentionally raise KeyError for invalid KIND - typos should fail early
|
|
295
297
|
parser = self.parsers[kind]
|
|
296
298
|
parser_result = parser.from_env(key)
|
|
299
|
+
|
|
300
|
+
# A kind was defined without other fields
|
|
301
|
+
if parser_result is MISSING:
|
|
302
|
+
# If the kind was not defined, the entry is MISSING
|
|
303
|
+
if kind_missing:
|
|
304
|
+
return MISSING
|
|
305
|
+
# Only a kind was defined
|
|
306
|
+
parser_result = {}
|
|
307
|
+
|
|
297
308
|
# Type narrowing: discriminated union parsers always return dicts
|
|
298
309
|
parser_result = cast(dict, parser_result)
|
|
299
310
|
parser_result["kind"] = kind
|
|
@@ -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()
|
openhands/agent_server/models.py
CHANGED
|
@@ -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=
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
12
|
-
openhands/agent_server/env_parser.py,sha256=
|
|
11
|
+
openhands/agent_server/desktop_service.py,sha256=GnLrQd2rqlRXBCNPbCXOZEfAXa42y9GNyFAQRjVE7y8,7603
|
|
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=
|
|
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=
|
|
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
|
|
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=
|
|
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.
|
|
38
|
-
openhands_agent_server-1.
|
|
39
|
-
openhands_agent_server-1.
|
|
40
|
-
openhands_agent_server-1.
|
|
41
|
-
openhands_agent_server-1.
|
|
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,,
|
{openhands_agent_server-1.9.0.dist-info → openhands_agent_server-1.10.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{openhands_agent_server-1.9.0.dist-info → openhands_agent_server-1.10.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|