openhands-agent-server 1.44.0__tar.gz → 1.45.0__tar.gz
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-1.44.0 → openhands_agent_server-1.45.0}/PKG-INFO +1 -1
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/agent_profiles_router.py +7 -3
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/api.py +3 -20
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/canvas_extensions/installed.py +2 -1
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/config.py +15 -4
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/conversation_service.py +119 -33
- openhands_agent_server-1.45.0/openhands/agent_server/desktop_router.py +28 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/docker/Dockerfile +139 -46
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/docker/build.py +127 -6
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/event_service.py +19 -4
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/persistence/store.py +9 -14
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/pub_sub.py +7 -1
- openhands_agent_server-1.45.0/openhands/agent_server/session_protocol.py +156 -0
- openhands_agent_server-1.45.0/openhands/agent_server/session_socket.py +437 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/skills_router.py +0 -12
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/sockets.py +3 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands_agent_server.egg-info/SOURCES.txt +4 -4
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/pyproject.toml +1 -1
- openhands_agent_server-1.44.0/openhands/agent_server/desktop_router.py +0 -47
- openhands_agent_server-1.44.0/openhands/agent_server/desktop_service.py +0 -224
- openhands_agent_server-1.44.0/openhands/agent_server/docker/wallpaper.svg +0 -22
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/_secret_redaction.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/canvas_extensions/__init__.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/canvas_extensions/manifest.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/canvas_extensions_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/conversation_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/credential_binding.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/init_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/mcp_oauth_store.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/mcp_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/models.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/openai/__init__.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/openai/models.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/openai/router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/openai/service.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/persistence/models.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/plugins_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/plugins_service.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/profiles_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/provider_connections_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/server_details_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/settings_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/skills_service.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/sub_agents_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/__init__.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/factory.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/http_exporter.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/models.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/policy.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/posthog_exporter.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/sanitizer.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/service.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/sink.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry/subscriber.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/telemetry_types.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.45.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
|
|
@@ -514,13 +514,17 @@ async def materialize_agent_profile(
|
|
|
514
514
|
mcp_config = settings.agent_settings.mcp_config
|
|
515
515
|
|
|
516
516
|
# Discover skills off the event loop so the dry-run can report which skills
|
|
517
|
-
# (catalog minus ``disabled_skills``) resolve.
|
|
518
|
-
#
|
|
517
|
+
# (catalog minus ``disabled_skills``) resolve. Mirrors the launch rule in
|
|
518
|
+
# ``conversation_service._resolve_agent_from_profile`` so the preview matches
|
|
519
|
+
# a real launch: an ACP profile is only given a catalog where the CLI cannot
|
|
520
|
+
# read the user's own configuration (#4019). A discovery failure must not 500
|
|
519
521
|
# the preview: pass ``available_skills=None`` and surface the failure as its
|
|
520
522
|
# own diagnostic below.
|
|
521
523
|
discovery_error: str | None = None
|
|
522
524
|
available_skills = None
|
|
523
|
-
if profile.agent_kind == "openhands"
|
|
525
|
+
if profile.agent_kind == "openhands" or (
|
|
526
|
+
config.acp_skill_sourcing == "openhands_managed"
|
|
527
|
+
):
|
|
524
528
|
try:
|
|
525
529
|
available_skills = await asyncio.to_thread(discover_profile_skills)
|
|
526
530
|
except Exception as exc:
|
{openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/api.py
RENAMED
|
@@ -39,7 +39,6 @@ from openhands.agent_server.dependencies import (
|
|
|
39
39
|
check_workspace_session,
|
|
40
40
|
)
|
|
41
41
|
from openhands.agent_server.desktop_router import desktop_router
|
|
42
|
-
from openhands.agent_server.desktop_service import get_desktop_service
|
|
43
42
|
from openhands.agent_server.event_router import event_router
|
|
44
43
|
from openhands.agent_server.file_router import file_router
|
|
45
44
|
from openhands.agent_server.git_router import git_router
|
|
@@ -66,6 +65,7 @@ from openhands.agent_server.server_details_router import (
|
|
|
66
65
|
mark_initialization_complete,
|
|
67
66
|
server_details_router,
|
|
68
67
|
)
|
|
68
|
+
from openhands.agent_server.session_socket import session_router
|
|
69
69
|
from openhands.agent_server.settings_router import settings_router
|
|
70
70
|
from openhands.agent_server.skills_router import skills_router
|
|
71
71
|
from openhands.agent_server.sockets import sockets_router
|
|
@@ -167,7 +167,6 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
167
167
|
emit_server_started()
|
|
168
168
|
|
|
169
169
|
vscode_service = get_vscode_service()
|
|
170
|
-
desktop_service = get_desktop_service()
|
|
171
170
|
tool_preload_service = get_tool_preload_service()
|
|
172
171
|
|
|
173
172
|
# Define async functions for starting each service
|
|
@@ -183,18 +182,6 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
183
182
|
else:
|
|
184
183
|
logger.info("VSCode service is disabled")
|
|
185
184
|
|
|
186
|
-
async def start_desktop_service():
|
|
187
|
-
if desktop_service is not None:
|
|
188
|
-
desktop_started = await desktop_service.start()
|
|
189
|
-
if desktop_started:
|
|
190
|
-
logger.info("Desktop service started successfully")
|
|
191
|
-
else:
|
|
192
|
-
logger.warning(
|
|
193
|
-
"Desktop service failed to start, continuing without desktop"
|
|
194
|
-
)
|
|
195
|
-
else:
|
|
196
|
-
logger.info("Desktop service is disabled")
|
|
197
|
-
|
|
198
185
|
async def start_tool_preload_service():
|
|
199
186
|
if tool_preload_service is not None:
|
|
200
187
|
tool_preload_started = await tool_preload_service.start()
|
|
@@ -208,7 +195,6 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
208
195
|
# Start all services concurrently
|
|
209
196
|
results = await asyncio.gather(
|
|
210
197
|
start_vscode_service(),
|
|
211
|
-
start_desktop_service(),
|
|
212
198
|
start_tool_preload_service(),
|
|
213
199
|
return_exceptions=True,
|
|
214
200
|
)
|
|
@@ -231,17 +217,12 @@ async def api_lifespan(api: FastAPI) -> AsyncIterator[None]:
|
|
|
231
217
|
if vscode_service is not None:
|
|
232
218
|
await vscode_service.stop()
|
|
233
219
|
|
|
234
|
-
async def stop_desktop_service():
|
|
235
|
-
if desktop_service is not None:
|
|
236
|
-
await desktop_service.stop()
|
|
237
|
-
|
|
238
220
|
async def stop_tool_preload_service():
|
|
239
221
|
if tool_preload_service is not None:
|
|
240
222
|
await tool_preload_service.stop()
|
|
241
223
|
|
|
242
224
|
await asyncio.gather(
|
|
243
225
|
stop_vscode_service(),
|
|
244
|
-
stop_desktop_service(),
|
|
245
226
|
stop_tool_preload_service(),
|
|
246
227
|
return_exceptions=True,
|
|
247
228
|
)
|
|
@@ -471,6 +452,8 @@ def _add_api_routes(app: FastAPI) -> None:
|
|
|
471
452
|
|
|
472
453
|
app.include_router(sockets_router)
|
|
473
454
|
|
|
455
|
+
app.include_router(session_router)
|
|
456
|
+
|
|
474
457
|
|
|
475
458
|
def _setup_static_files(app: FastAPI, config: Config) -> None:
|
|
476
459
|
"""Set up static file serving and root redirect if configured.
|
|
@@ -35,6 +35,7 @@ from openhands.sdk.extensions.installation import (
|
|
|
35
35
|
InstallationMetadata,
|
|
36
36
|
)
|
|
37
37
|
from openhands.sdk.extensions.installation.manager import DEFAULT_CACHE_DIR
|
|
38
|
+
from openhands.sdk.utils.path import get_user_persistence_dir
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
# Matches the InstalledPluginInfo naming convention.
|
|
@@ -43,7 +44,7 @@ InstalledCanvasExtensionInfo = InstallationInfo
|
|
|
43
44
|
|
|
44
45
|
def get_installed_canvas_extensions_dir() -> Path:
|
|
45
46
|
"""Get the default directory for installed canvas extensions."""
|
|
46
|
-
return
|
|
47
|
+
return get_user_persistence_dir() / "canvas-extensions" / "installed"
|
|
47
48
|
|
|
48
49
|
|
|
49
50
|
class CanvasExtensionInstallationInterface(
|
{openhands_agent_server-1.44.0 → openhands_agent_server-1.45.0}/openhands/agent_server/config.py
RENAMED
|
@@ -27,6 +27,7 @@ CONFIG_PATH_ENV = "OPENHANDS_AGENT_SERVER_CONFIG_PATH"
|
|
|
27
27
|
DEFAULT_CONFIG_PATH = Path("workspace/openhands_agent_server_config.json")
|
|
28
28
|
# 20 minutes, matching the idle timeout used by OpenHands Cloud.
|
|
29
29
|
DEFAULT_CONVERSATION_IDLE_TTL_SECONDS: Final[float] = 20 * 60.0
|
|
30
|
+
ACPSkillSourcing = Literal["native", "openhands_managed"]
|
|
30
31
|
_logger = logging.getLogger(__name__)
|
|
31
32
|
|
|
32
33
|
|
|
@@ -318,10 +319,6 @@ class Config(BaseModel):
|
|
|
318
319
|
"For example, '/{runtime_id}/vscode' when using path-based routing."
|
|
319
320
|
),
|
|
320
321
|
)
|
|
321
|
-
enable_vnc: bool = Field(
|
|
322
|
-
default=False,
|
|
323
|
-
description="Whether to enable VNC desktop functionality",
|
|
324
|
-
)
|
|
325
322
|
preload_tools: bool = Field(
|
|
326
323
|
default=True,
|
|
327
324
|
description="Whether to preload tools",
|
|
@@ -349,6 +346,20 @@ class Config(BaseModel):
|
|
|
349
346
|
"The URL where this agent server instance is available externally"
|
|
350
347
|
),
|
|
351
348
|
)
|
|
349
|
+
acp_skill_sourcing: ACPSkillSourcing = Field(
|
|
350
|
+
default="native",
|
|
351
|
+
description=(
|
|
352
|
+
"Who supplies an ACP agent's skills. 'native' (the default, for a "
|
|
353
|
+
"host-local agent-server): nobody but the ACP CLI — it reads the "
|
|
354
|
+
"user's own home configuration and the repository, so OpenHands "
|
|
355
|
+
"injects none of its managed skills. 'openhands_managed' (for "
|
|
356
|
+
"container runtimes, where that host configuration is absent): also "
|
|
357
|
+
"inject the user/org/public/marketplace skills the server "
|
|
358
|
+
"discovers. Project/repository skills are never injected either way "
|
|
359
|
+
"— the CLI reads AGENTS.md itself (#4019). Set explicitly per "
|
|
360
|
+
"deployment; the agent-server image sets 'openhands_managed'."
|
|
361
|
+
),
|
|
362
|
+
)
|
|
352
363
|
registered_marketplaces: list[MarketplaceRegistration] = Field(
|
|
353
364
|
default_factory=list,
|
|
354
365
|
description=(
|
|
@@ -15,7 +15,7 @@ from weakref import WeakValueDictionary
|
|
|
15
15
|
import httpx
|
|
16
16
|
from pydantic import BaseModel
|
|
17
17
|
|
|
18
|
-
from openhands.agent_server.config import Config, WebhookSpec
|
|
18
|
+
from openhands.agent_server.config import ACPSkillSourcing, Config, WebhookSpec
|
|
19
19
|
from openhands.agent_server.conversation_lease import (
|
|
20
20
|
DEFAULT_LEASE_TTL_SECONDS,
|
|
21
21
|
ConversationLeaseHeldError,
|
|
@@ -130,6 +130,22 @@ def _append_system_message_suffix(agent: AgentBase, addition: str) -> AgentBase:
|
|
|
130
130
|
return agent.model_copy(update={"agent_context": updated_context})
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
def _with_load_memory(agent: AgentBase) -> AgentBase:
|
|
134
|
+
"""Stamp the global persistent-memory preference onto an agent.
|
|
135
|
+
|
|
136
|
+
``load_memory`` is a user-level setting, not part of any agent, profile or
|
|
137
|
+
client payload, so it is applied here regardless of how the agent reached
|
|
138
|
+
the request.
|
|
139
|
+
"""
|
|
140
|
+
# current_datetime stays suppressed on a synthesized context: a null
|
|
141
|
+
# agent_context means "no prompt context", and ACPAgent._render_suffix
|
|
142
|
+
# relies on that to keep a <CURRENT_DATETIME> block out of the prompt.
|
|
143
|
+
context = agent.agent_context or AgentContext(current_datetime=None)
|
|
144
|
+
return agent.model_copy(
|
|
145
|
+
update={"agent_context": context.model_copy(update={"load_memory": True})}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
133
149
|
def _has_git_remote(repo_root: Path, remote: str = "origin") -> bool:
|
|
134
150
|
try:
|
|
135
151
|
run_git_command(["git", "remote", "get-url", remote], repo_root)
|
|
@@ -290,11 +306,52 @@ def _same_workspace(a: LocalWorkspace, b: LocalWorkspace) -> bool:
|
|
|
290
306
|
return Path(a.working_dir).resolve() == Path(b.working_dir).resolve()
|
|
291
307
|
|
|
292
308
|
|
|
309
|
+
def _apply_acp_skill_sourcing(
|
|
310
|
+
agent: "AgentBase", sourcing: ACPSkillSourcing
|
|
311
|
+
) -> "AgentBase":
|
|
312
|
+
"""Strip OpenHands-managed skills from an ACP agent under ``native`` sourcing.
|
|
313
|
+
|
|
314
|
+
A host-local ACP CLI reads the user's own skills from its home directory, so
|
|
315
|
+
a second, OpenHands-managed set injected into its prompt is at best noise —
|
|
316
|
+
and the catalog listing tells it to call ``invoke_skill``, a tool no ACP
|
|
317
|
+
agent has. Container runtimes set ``openhands_managed`` because that home
|
|
318
|
+
configuration is absent there. Project skills are excluded either way, by
|
|
319
|
+
``ACPAgent`` itself (#4019).
|
|
320
|
+
|
|
321
|
+
A caller that sends ``agent`` / ``agent_settings`` puts its own skills on the
|
|
322
|
+
context, so the strip happens here rather than at profile resolution.
|
|
323
|
+
"""
|
|
324
|
+
if sourcing != "native" or not isinstance(agent, ACPAgent):
|
|
325
|
+
return agent
|
|
326
|
+
context = agent.agent_context
|
|
327
|
+
if context is None:
|
|
328
|
+
return agent
|
|
329
|
+
if not (
|
|
330
|
+
context.skills
|
|
331
|
+
or context.load_user_skills
|
|
332
|
+
or context.load_public_skills
|
|
333
|
+
or context.registered_marketplaces
|
|
334
|
+
):
|
|
335
|
+
return agent
|
|
336
|
+
return agent.model_copy(
|
|
337
|
+
update={
|
|
338
|
+
"agent_context": context.model_copy(
|
|
339
|
+
update={
|
|
340
|
+
"skills": [],
|
|
341
|
+
"load_user_skills": False,
|
|
342
|
+
"load_public_skills": False,
|
|
343
|
+
"registered_marketplaces": [],
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
293
350
|
def _resolve_agent_from_profile(
|
|
294
351
|
profile_id: "UUID",
|
|
295
352
|
cipher: "Cipher | None",
|
|
296
353
|
mcp_config: "dict[str, MCPServer]",
|
|
297
|
-
|
|
354
|
+
acp_skill_sourcing: ACPSkillSourcing = "native",
|
|
298
355
|
) -> "tuple[AgentBase, LaunchedAgentProfile]":
|
|
299
356
|
"""Load and resolve an agent profile by id, returning the built agent + provenance.
|
|
300
357
|
|
|
@@ -305,11 +362,9 @@ def _resolve_agent_from_profile(
|
|
|
305
362
|
server's cipher. Passed explicitly so this free function never
|
|
306
363
|
touches the settings-store singleton (which may not have been
|
|
307
364
|
initialised with the correct cipher yet).
|
|
308
|
-
|
|
309
|
-
(``
|
|
310
|
-
|
|
311
|
-
profile — it is stamped onto the resolved agent below, else a
|
|
312
|
-
profile-launched conversation would silently ignore the setting.
|
|
365
|
+
acp_skill_sourcing: This deployment's ACP skill policy
|
|
366
|
+
(``Config.acp_skill_sourcing``). Decides whether an ACP profile is
|
|
367
|
+
resolved with the server's managed skill catalog or with none.
|
|
313
368
|
|
|
314
369
|
Raises:
|
|
315
370
|
ProfileNotFound: No stored profile has ``profile_id``.
|
|
@@ -340,11 +395,15 @@ def _resolve_agent_from_profile(
|
|
|
340
395
|
) from exc
|
|
341
396
|
|
|
342
397
|
# OpenHands profiles get the discovered catalog minus their ``disabled_skills``
|
|
343
|
-
# deny-list
|
|
344
|
-
#
|
|
345
|
-
#
|
|
398
|
+
# deny-list. An ACP profile gets it only where the CLI cannot reach the user's
|
|
399
|
+
# own configuration (``openhands_managed``); under ``native`` it sources its
|
|
400
|
+
# own skills and OpenHands injects none (#4019). A genuine discovery failure
|
|
401
|
+
# fails the launch loudly rather than silently producing a zero-skill agent.
|
|
346
402
|
available_skills = None
|
|
347
|
-
|
|
403
|
+
wants_skills = profile.agent_kind == "openhands" or (
|
|
404
|
+
acp_skill_sourcing == "openhands_managed"
|
|
405
|
+
)
|
|
406
|
+
if wants_skills:
|
|
348
407
|
try:
|
|
349
408
|
available_skills = discover_profile_skills()
|
|
350
409
|
except Exception as exc:
|
|
@@ -386,15 +445,7 @@ def _resolve_agent_from_profile(
|
|
|
386
445
|
agent = agent.model_copy(
|
|
387
446
|
update={"tools": [*agent.tools, Tool(name=BROWSER_TOOL_NAME)]}
|
|
388
447
|
)
|
|
389
|
-
|
|
390
|
-
# is carried across the profile-resolution boundary the same way the global
|
|
391
|
-
# ``mcp_config`` is. Left untouched when off, so the resolved agent stays
|
|
392
|
-
# byte-identical for everyone who hasn't opted in.
|
|
393
|
-
if load_memory:
|
|
394
|
-
context = agent.agent_context or AgentContext()
|
|
395
|
-
agent = agent.model_copy(
|
|
396
|
-
update={"agent_context": context.model_copy(update={"load_memory": True})}
|
|
397
|
-
)
|
|
448
|
+
|
|
398
449
|
launched = LaunchedAgentProfile(
|
|
399
450
|
agent_profile_id=profile.id,
|
|
400
451
|
revision=profile.revision,
|
|
@@ -641,6 +692,7 @@ class ConversationService:
|
|
|
641
692
|
conversation_worktree_root: Path = field(
|
|
642
693
|
default=Path("/tmp/conversation-worktrees")
|
|
643
694
|
)
|
|
695
|
+
acp_skill_sourcing: ACPSkillSourcing = "native"
|
|
644
696
|
_event_services: dict[UUID, EventService] | None = field(default=None, init=False)
|
|
645
697
|
_conversation_records: dict[UUID, _ConversationRecord] = field(
|
|
646
698
|
default_factory=dict, init=False
|
|
@@ -1511,31 +1563,64 @@ class ConversationService:
|
|
|
1511
1563
|
f"to a different workspace"
|
|
1512
1564
|
)
|
|
1513
1565
|
|
|
1514
|
-
# Profile resolution must happen before
|
|
1515
|
-
# asserts request.agent is not None)
|
|
1516
|
-
# agent is captured in request_data.
|
|
1566
|
+
# Profile resolution and the load_memory stamp must happen before
|
|
1567
|
+
# _prepare_request_workspace (which asserts request.agent is not None)
|
|
1568
|
+
# and before model_dump so the resolved agent is captured in request_data.
|
|
1517
1569
|
launched_agent_profile: LaunchedAgentProfile | None = None
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1570
|
+
|
|
1571
|
+
from openhands.agent_server.persistence import (
|
|
1572
|
+
PersistedSettings,
|
|
1573
|
+
get_settings_store,
|
|
1574
|
+
)
|
|
1575
|
+
|
|
1576
|
+
# get_settings_store() is safe here: get_instance() initialises the
|
|
1577
|
+
# singleton with the server cipher before any conversation can start.
|
|
1578
|
+
# FileSettingsStore.load re-raises PermissionError/OSError by design;
|
|
1579
|
+
# now that every launch reads it, a bad file mode must not take down
|
|
1580
|
+
# request shapes that need nothing from settings.
|
|
1581
|
+
try:
|
|
1582
|
+
settings = await asyncio.to_thread(
|
|
1583
|
+
lambda: get_settings_store().load() or PersistedSettings()
|
|
1584
|
+
)
|
|
1585
|
+
except (PermissionError, OSError):
|
|
1586
|
+
logger.warning(
|
|
1587
|
+
"Cannot read settings; starting without the stored agent preferences",
|
|
1588
|
+
exc_info=True,
|
|
1524
1589
|
)
|
|
1590
|
+
settings = PersistedSettings()
|
|
1525
1591
|
|
|
1526
|
-
|
|
1592
|
+
# ``ACPAgentSettings.agent_context`` is nullable, hence the guard.
|
|
1593
|
+
stored_context = settings.agent_settings.agent_context
|
|
1594
|
+
load_memory = bool(stored_context and stored_context.load_memory)
|
|
1595
|
+
|
|
1596
|
+
if request.agent_profile_id is not None:
|
|
1527
1597
|
mcp_config = settings.agent_settings.mcp_config
|
|
1528
|
-
# ``ACPAgentSettings.agent_context`` is nullable, hence the guard.
|
|
1529
|
-
stored_context = settings.agent_settings.agent_context
|
|
1530
1598
|
resolved_agent, launched_agent_profile = await asyncio.to_thread(
|
|
1531
1599
|
_resolve_agent_from_profile,
|
|
1532
1600
|
request.agent_profile_id,
|
|
1533
1601
|
self.cipher,
|
|
1534
1602
|
mcp_config,
|
|
1535
|
-
|
|
1603
|
+
acp_skill_sourcing=self.acp_skill_sourcing,
|
|
1536
1604
|
)
|
|
1537
1605
|
request = request.model_copy(update={"agent": resolved_agent})
|
|
1538
1606
|
|
|
1607
|
+
# Applied unconditionally: a serialized agent always carries
|
|
1608
|
+
# ``load_memory`` (model_dump emits defaults), so there is no way to
|
|
1609
|
+
# tell a deliberate ``false`` from an echoed one. Opting a single
|
|
1610
|
+
# conversation out needs a tri-state field; tracked separately.
|
|
1611
|
+
if load_memory and request.agent is not None:
|
|
1612
|
+
request = request.model_copy(
|
|
1613
|
+
update={"agent": _with_load_memory(request.agent)}
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
request = request.model_copy(
|
|
1617
|
+
update={
|
|
1618
|
+
"agent": _apply_acp_skill_sourcing(
|
|
1619
|
+
request.agent, self.acp_skill_sourcing
|
|
1620
|
+
)
|
|
1621
|
+
}
|
|
1622
|
+
)
|
|
1623
|
+
|
|
1539
1624
|
additions = request.agent_launch_additions
|
|
1540
1625
|
suffix = (
|
|
1541
1626
|
additions.system_message_suffix_append.strip()
|
|
@@ -2238,6 +2323,7 @@ class ConversationService:
|
|
|
2238
2323
|
lease_ttl_seconds=config.lease_ttl_seconds,
|
|
2239
2324
|
conversation_idle_ttl_seconds=config.conversation_idle_ttl_seconds,
|
|
2240
2325
|
conversation_worktree_root=config.conversation_worktree_root,
|
|
2326
|
+
acp_skill_sourcing=config.acp_skill_sourcing,
|
|
2241
2327
|
)
|
|
2242
2328
|
|
|
2243
2329
|
async def _start_event_service(
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Desktop router for agent server API endpoints."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
desktop_router = APIRouter(prefix="/desktop", tags=["Desktop"])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DesktopUrlResponse(BaseModel):
|
|
11
|
+
"""Response model for Desktop URL."""
|
|
12
|
+
|
|
13
|
+
url: str | None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@desktop_router.get("/url", response_model=DesktopUrlResponse, deprecated=True)
|
|
17
|
+
async def get_desktop_url(
|
|
18
|
+
base_url: str = "http://localhost:8002", # noqa: ARG001
|
|
19
|
+
) -> DesktopUrlResponse:
|
|
20
|
+
"""Deprecated since v1.44.1 and scheduled for removal in v1.49.0.
|
|
21
|
+
|
|
22
|
+
The VNC/desktop stack has been removed; this always returns 503, the
|
|
23
|
+
same response every deployment has always gotten since VNC defaults off.
|
|
24
|
+
"""
|
|
25
|
+
raise HTTPException(
|
|
26
|
+
status_code=503,
|
|
27
|
+
detail="Desktop is disabled. VNC/desktop support has been removed.",
|
|
28
|
+
)
|