hypercli-sdk 2026.6.21__tar.gz → 2026.6.26__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.
Potentially problematic release.
This version of hypercli-sdk might be problematic. Click here for more details.
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/.gitignore +1 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/PKG-INFO +5 -2
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/README.md +4 -1
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/__init__.py +4 -2
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/agents.py +188 -3
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/pyproject.toml +1 -1
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_agents.py +82 -6
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/_compat.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/agent.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/billing.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/client.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/config.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/files.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/gateway.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/http.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/instances.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/job/__init__.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/job/base.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/job/comfyui.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/job/gradio.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/jobs.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/keys.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/logs.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/models.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/openclaw/__init__.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/openclaw/gateway.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/renders.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/shell.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/user.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/voice.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/x402.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/conftest.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_agents.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_auth.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_billing.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_instances.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_jobs_dryrun.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_keys.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_renders.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_apply_params.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_bootstrap_console_test_key.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_bootstrap_dev_test_keys.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_claw.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_config.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_exec_shell_dryrun.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_gateway.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_gateway_retry.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_graph_to_api.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_http.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_jobs.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_keys.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_models.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_renders_subscription.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_status.py +0 -0
- {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_voice.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypercli-sdk
|
|
3
|
-
Version: 2026.6.
|
|
3
|
+
Version: 2026.6.26
|
|
4
4
|
Summary: Python SDK for HyperCLI - GPU orchestration and HyperAgent API
|
|
5
5
|
Project-URL: Homepage, https://hypercli.com
|
|
6
6
|
Project-URL: Documentation, https://docs.hypercli.com
|
|
@@ -144,14 +144,17 @@ OpenClaw uses the generic deployment launch surface. `registry_url`, `registry_a
|
|
|
144
144
|
agent = client.deployments.create_openclaw(
|
|
145
145
|
name="docs-demo",
|
|
146
146
|
start=True,
|
|
147
|
-
heartbeat={"every": "0m"}, # disable upstream OpenClaw heartbeat runs
|
|
148
147
|
registry_url="git.nedos.co",
|
|
149
148
|
registry_auth={"username": "ci", "password": "token"},
|
|
150
149
|
)
|
|
151
150
|
```
|
|
152
151
|
|
|
152
|
+
Use `create_openclaw_pro(...)` for the desktop/browser image. It enables noVNC through the protected `desktop-<agent>.hypercli.app` route and sets `OPENCLAW_DESKTOP_ENABLED=1`.
|
|
153
|
+
|
|
153
154
|
`heartbeat` maps directly to upstream OpenClaw config at `config.agents.defaults.heartbeat`. Omit it to keep upstream defaults, or pass values such as `heartbeat={"every": "1h", "target": "last"}`.
|
|
154
155
|
|
|
156
|
+
Automatic memory indexing is off by default. Opt in with `memory_index={"on_session_start": True, "on_search": True, "watch": True, "watch_debounce_ms": 30000, "interval_minutes": 0}`.
|
|
157
|
+
|
|
155
158
|
## Error Handling
|
|
156
159
|
|
|
157
160
|
```python
|
|
@@ -113,14 +113,17 @@ OpenClaw uses the generic deployment launch surface. `registry_url`, `registry_a
|
|
|
113
113
|
agent = client.deployments.create_openclaw(
|
|
114
114
|
name="docs-demo",
|
|
115
115
|
start=True,
|
|
116
|
-
heartbeat={"every": "0m"}, # disable upstream OpenClaw heartbeat runs
|
|
117
116
|
registry_url="git.nedos.co",
|
|
118
117
|
registry_auth={"username": "ci", "password": "token"},
|
|
119
118
|
)
|
|
120
119
|
```
|
|
121
120
|
|
|
121
|
+
Use `create_openclaw_pro(...)` for the desktop/browser image. It enables noVNC through the protected `desktop-<agent>.hypercli.app` route and sets `OPENCLAW_DESKTOP_ENABLED=1`.
|
|
122
|
+
|
|
122
123
|
`heartbeat` maps directly to upstream OpenClaw config at `config.agents.defaults.heartbeat`. Omit it to keep upstream defaults, or pass values such as `heartbeat={"every": "1h", "target": "last"}`.
|
|
123
124
|
|
|
125
|
+
Automatic memory indexing is off by default. Opt in with `memory_index={"on_session_start": True, "on_search": True, "watch": True, "watch_debounce_ms": 30000, "interval_minutes": 0}`.
|
|
126
|
+
|
|
124
127
|
## Error Handling
|
|
125
128
|
|
|
126
129
|
```python
|
|
@@ -42,7 +42,7 @@ from .files import File, AsyncFiles
|
|
|
42
42
|
from .user import AuthMe, RuntimeIdentity, User, UserAPI
|
|
43
43
|
from .job import BaseJob, ComfyUIJob, GradioJob, apply_params, apply_graph_modes, find_node, find_nodes, load_template, graph_to_api, expand_subgraphs, DEFAULT_OBJECT_INFO
|
|
44
44
|
from .logs import LogStream, stream_logs, fetch_logs
|
|
45
|
-
from .agents import Deployments, Agent, OpenClawAgent, ExecResult, build_openclaw_routes
|
|
45
|
+
from .agents import Deployments, Agent, OpenClawAgent, OpenClawProAgent, ExecResult, build_openclaw_routes, build_openclaw_memory_index_env
|
|
46
46
|
from .shell import ShellSession, shell_connect
|
|
47
47
|
from .agent import (
|
|
48
48
|
HyperAgent,
|
|
@@ -83,7 +83,7 @@ from .gateway import (
|
|
|
83
83
|
extract_gateway_chat_tool_calls,
|
|
84
84
|
normalize_gateway_chat_message,
|
|
85
85
|
)
|
|
86
|
-
__version__ = "2026.6.
|
|
86
|
+
__version__ = "2026.6.26"
|
|
87
87
|
__all__ = [
|
|
88
88
|
"HyperCLI",
|
|
89
89
|
"configure",
|
|
@@ -161,8 +161,10 @@ __all__ = [
|
|
|
161
161
|
"Deployments",
|
|
162
162
|
"Agent",
|
|
163
163
|
"OpenClawAgent",
|
|
164
|
+
"OpenClawProAgent",
|
|
164
165
|
"ExecResult",
|
|
165
166
|
"build_openclaw_routes",
|
|
167
|
+
"build_openclaw_memory_index_env",
|
|
166
168
|
# Shell
|
|
167
169
|
"ShellSession",
|
|
168
170
|
"shell_connect",
|
|
@@ -31,6 +31,15 @@ AGENTS_WS_URL = "wss://api.agents.hypercli.com/ws"
|
|
|
31
31
|
DEV_AGENTS_API_BASE = "https://api.dev.hypercli.com/agents"
|
|
32
32
|
DEV_AGENTS_WS_URL = "wss://api.agents.dev.hypercli.com/ws"
|
|
33
33
|
DEFAULT_OPENCLAW_IMAGE = "ghcr.io/hypercli/hypercli-openclaw:prod"
|
|
34
|
+
DEFAULT_OPENCLAW_PRO_IMAGE = "ghcr.io/hypercli/hypercli-openclaw:pro-prod"
|
|
35
|
+
OPENCLAW_MEMORY_SEARCH_ENV_DEFAULTS = {
|
|
36
|
+
"OPENCLAW_MEMORY_SEARCH_ENABLED": "1",
|
|
37
|
+
"OPENCLAW_MEMORY_SEARCH_SYNC_ON_SESSION_START": "0",
|
|
38
|
+
"OPENCLAW_MEMORY_SEARCH_SYNC_ON_SEARCH": "0",
|
|
39
|
+
"OPENCLAW_MEMORY_SEARCH_SYNC_WATCH": "0",
|
|
40
|
+
"OPENCLAW_MEMORY_SEARCH_SYNC_WATCH_DEBOUNCE_MS": "30000",
|
|
41
|
+
"OPENCLAW_MEMORY_SEARCH_SYNC_INTERVAL_MINUTES": "0",
|
|
42
|
+
}
|
|
34
43
|
LAUNCH_CONFIG_KEYS = frozenset({"image", "env", "routes", "ports", "command", "entrypoint", "sync_root", "sync_enabled", "sync_uid", "sync_gid", "registry_url", "registry_auth"})
|
|
35
44
|
DEFAULT_OPENCLAW_SYNC_ROOT = "/home/node"
|
|
36
45
|
AGENT_FILE_MAX_BYTES = 50 * 1024 * 1024
|
|
@@ -87,6 +96,48 @@ def _resolve_openclaw_routes(
|
|
|
87
96
|
return build_openclaw_routes(**dict(openclaw_route_options or {}))
|
|
88
97
|
|
|
89
98
|
|
|
99
|
+
def _env_bool(value: object) -> str:
|
|
100
|
+
return "1" if bool(value) else "0"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _env_non_negative_int(name: str, value: object) -> str:
|
|
104
|
+
integer = int(value)
|
|
105
|
+
if integer < 0:
|
|
106
|
+
raise ValueError(f"{name} must be non-negative")
|
|
107
|
+
return str(integer)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_openclaw_memory_index_env(memory_index: dict | None = None) -> dict[str, str]:
|
|
111
|
+
"""Build OpenClaw memory-search indexing environment variables.
|
|
112
|
+
|
|
113
|
+
No env vars are emitted unless memory_index is provided; the image config
|
|
114
|
+
carries the no-auto-indexing defaults. Passing an empty dict explicitly
|
|
115
|
+
emits the default env block.
|
|
116
|
+
"""
|
|
117
|
+
if memory_index is None:
|
|
118
|
+
return {}
|
|
119
|
+
env = dict(OPENCLAW_MEMORY_SEARCH_ENV_DEFAULTS)
|
|
120
|
+
if memory_index.get("enabled") is not None:
|
|
121
|
+
env["OPENCLAW_MEMORY_SEARCH_ENABLED"] = _env_bool(memory_index["enabled"])
|
|
122
|
+
if memory_index.get("on_session_start") is not None:
|
|
123
|
+
env["OPENCLAW_MEMORY_SEARCH_SYNC_ON_SESSION_START"] = _env_bool(memory_index["on_session_start"])
|
|
124
|
+
if memory_index.get("on_search") is not None:
|
|
125
|
+
env["OPENCLAW_MEMORY_SEARCH_SYNC_ON_SEARCH"] = _env_bool(memory_index["on_search"])
|
|
126
|
+
if memory_index.get("watch") is not None:
|
|
127
|
+
env["OPENCLAW_MEMORY_SEARCH_SYNC_WATCH"] = _env_bool(memory_index["watch"])
|
|
128
|
+
if memory_index.get("watch_debounce_ms") is not None:
|
|
129
|
+
env["OPENCLAW_MEMORY_SEARCH_SYNC_WATCH_DEBOUNCE_MS"] = _env_non_negative_int(
|
|
130
|
+
"watch_debounce_ms",
|
|
131
|
+
memory_index["watch_debounce_ms"],
|
|
132
|
+
)
|
|
133
|
+
if memory_index.get("interval_minutes") is not None:
|
|
134
|
+
env["OPENCLAW_MEMORY_SEARCH_SYNC_INTERVAL_MINUTES"] = _env_non_negative_int(
|
|
135
|
+
"interval_minutes",
|
|
136
|
+
memory_index["interval_minutes"],
|
|
137
|
+
)
|
|
138
|
+
return env
|
|
139
|
+
|
|
140
|
+
|
|
90
141
|
def _default_gateway_timeout() -> float | None:
|
|
91
142
|
raw = (
|
|
92
143
|
os.environ.get("HYPERCLI_GATEWAY_TIMEOUT")
|
|
@@ -259,6 +310,16 @@ def _default_openclaw_image(image: str | None) -> str | None:
|
|
|
259
310
|
return DEFAULT_OPENCLAW_IMAGE
|
|
260
311
|
|
|
261
312
|
|
|
313
|
+
def _default_openclaw_pro_image(image: str | None) -> str | None:
|
|
314
|
+
if image is not None:
|
|
315
|
+
return image
|
|
316
|
+
return DEFAULT_OPENCLAW_PRO_IMAGE
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _truthy_env(value: object) -> bool:
|
|
320
|
+
return str(value or "").strip().lower() in {"1", "true", "yes", "on", "enabled"}
|
|
321
|
+
|
|
322
|
+
|
|
262
323
|
def _parse_dt(val):
|
|
263
324
|
if isinstance(val, str) and val:
|
|
264
325
|
return datetime.fromisoformat(val.replace("Z", "+00:00"))
|
|
@@ -317,6 +378,17 @@ def _is_openclaw_agent_data(data: dict) -> bool:
|
|
|
317
378
|
return False
|
|
318
379
|
|
|
319
380
|
|
|
381
|
+
def _is_openclaw_pro_agent_data(data: dict) -> bool:
|
|
382
|
+
launch_config = data.get("launch_config")
|
|
383
|
+
if not isinstance(launch_config, dict):
|
|
384
|
+
return False
|
|
385
|
+
env = launch_config.get("env")
|
|
386
|
+
if isinstance(env, dict) and _truthy_env(env.get("OPENCLAW_DESKTOP_ENABLED")):
|
|
387
|
+
return True
|
|
388
|
+
image = str(launch_config.get("image") or "")
|
|
389
|
+
return "hypercli-openclaw:pro" in image or image.endswith("-pro")
|
|
390
|
+
|
|
391
|
+
|
|
320
392
|
@dataclass
|
|
321
393
|
class Agent:
|
|
322
394
|
"""Generic agent returned by the HyperClaw backend."""
|
|
@@ -953,6 +1025,11 @@ class OpenClawAgent(Agent):
|
|
|
953
1025
|
return await self.channel_upsert("discord", channel_config, account_id=account_id)
|
|
954
1026
|
|
|
955
1027
|
|
|
1028
|
+
@dataclass
|
|
1029
|
+
class OpenClawProAgent(OpenClawAgent):
|
|
1030
|
+
"""OpenClaw agent launched with the pro desktop/browser preset."""
|
|
1031
|
+
|
|
1032
|
+
|
|
956
1033
|
@dataclass
|
|
957
1034
|
class ExecResult:
|
|
958
1035
|
"""Result of a one-shot command execution."""
|
|
@@ -1009,7 +1086,9 @@ class Deployments:
|
|
|
1009
1086
|
)
|
|
1010
1087
|
|
|
1011
1088
|
def _hydrate_agent(self, data: dict) -> Agent:
|
|
1012
|
-
if
|
|
1089
|
+
if _is_openclaw_pro_agent_data(data):
|
|
1090
|
+
agent = OpenClawProAgent.from_dict(data)
|
|
1091
|
+
elif _is_openclaw_agent_data(data):
|
|
1013
1092
|
agent = OpenClawAgent.from_dict(data)
|
|
1014
1093
|
else:
|
|
1015
1094
|
agent = Agent.from_dict(data)
|
|
@@ -1182,8 +1261,9 @@ class Deployments:
|
|
|
1182
1261
|
start: bool = True,
|
|
1183
1262
|
openclaw_routes: dict | None = None,
|
|
1184
1263
|
openclaw_route_options: dict | None = None,
|
|
1264
|
+
memory_index: dict | None = None,
|
|
1185
1265
|
) -> Agent:
|
|
1186
|
-
effective_env = dict(env or {})
|
|
1266
|
+
effective_env = {**build_openclaw_memory_index_env(memory_index), **dict(env or {})}
|
|
1187
1267
|
return self.create(
|
|
1188
1268
|
name=name,
|
|
1189
1269
|
size=size,
|
|
@@ -1212,6 +1292,62 @@ class Deployments:
|
|
|
1212
1292
|
start=start,
|
|
1213
1293
|
)
|
|
1214
1294
|
|
|
1295
|
+
def create_openclaw_pro(
|
|
1296
|
+
self,
|
|
1297
|
+
name: str = None,
|
|
1298
|
+
size: str = None,
|
|
1299
|
+
config: dict = None,
|
|
1300
|
+
tags: list[str] = None,
|
|
1301
|
+
env: dict = None,
|
|
1302
|
+
ports: list = None,
|
|
1303
|
+
routes: dict = None,
|
|
1304
|
+
command: list[str] = None,
|
|
1305
|
+
entrypoint: list[str] = None,
|
|
1306
|
+
image: str = None,
|
|
1307
|
+
sync_root: str = None,
|
|
1308
|
+
sync_enabled: bool = None,
|
|
1309
|
+
sync_uid: int = None,
|
|
1310
|
+
sync_gid: int = None,
|
|
1311
|
+
registry_url: str = None,
|
|
1312
|
+
registry_auth: dict = None,
|
|
1313
|
+
gateway_token: str = None,
|
|
1314
|
+
heartbeat: dict = None,
|
|
1315
|
+
meta_ui: dict = None,
|
|
1316
|
+
dry_run: bool = False,
|
|
1317
|
+
start: bool = True,
|
|
1318
|
+
openclaw_routes: dict | None = None,
|
|
1319
|
+
openclaw_route_options: dict | None = None,
|
|
1320
|
+
memory_index: dict | None = None,
|
|
1321
|
+
) -> Agent:
|
|
1322
|
+
effective_env = {"OPENCLAW_DESKTOP_ENABLED": "1", **dict(env or {})}
|
|
1323
|
+
effective_route_options = {"include_desktop": True, **dict(openclaw_route_options or {})}
|
|
1324
|
+
return self.create_openclaw(
|
|
1325
|
+
name=name,
|
|
1326
|
+
size=size,
|
|
1327
|
+
config=config,
|
|
1328
|
+
tags=tags,
|
|
1329
|
+
env=effective_env,
|
|
1330
|
+
ports=ports,
|
|
1331
|
+
routes=routes,
|
|
1332
|
+
command=command,
|
|
1333
|
+
entrypoint=entrypoint,
|
|
1334
|
+
image=_default_openclaw_pro_image(image),
|
|
1335
|
+
sync_root=sync_root,
|
|
1336
|
+
sync_enabled=sync_enabled,
|
|
1337
|
+
sync_uid=sync_uid,
|
|
1338
|
+
sync_gid=sync_gid,
|
|
1339
|
+
registry_url=registry_url,
|
|
1340
|
+
registry_auth=registry_auth,
|
|
1341
|
+
gateway_token=gateway_token,
|
|
1342
|
+
heartbeat=heartbeat,
|
|
1343
|
+
meta_ui=meta_ui,
|
|
1344
|
+
dry_run=dry_run,
|
|
1345
|
+
start=start,
|
|
1346
|
+
openclaw_routes=openclaw_routes,
|
|
1347
|
+
openclaw_route_options=effective_route_options,
|
|
1348
|
+
memory_index=memory_index,
|
|
1349
|
+
)
|
|
1350
|
+
|
|
1215
1351
|
def budget(self) -> dict:
|
|
1216
1352
|
"""Get the user's current agent resource budget and usage.
|
|
1217
1353
|
|
|
@@ -1345,8 +1481,9 @@ class Deployments:
|
|
|
1345
1481
|
dry_run: bool = False,
|
|
1346
1482
|
openclaw_routes: dict | None = None,
|
|
1347
1483
|
openclaw_route_options: dict | None = None,
|
|
1484
|
+
memory_index: dict | None = None,
|
|
1348
1485
|
) -> Agent:
|
|
1349
|
-
effective_env = dict(env or {})
|
|
1486
|
+
effective_env = {**build_openclaw_memory_index_env(memory_index), **dict(env or {})}
|
|
1350
1487
|
return self.start(
|
|
1351
1488
|
agent_id,
|
|
1352
1489
|
config=config,
|
|
@@ -1371,6 +1508,54 @@ class Deployments:
|
|
|
1371
1508
|
dry_run=dry_run,
|
|
1372
1509
|
)
|
|
1373
1510
|
|
|
1511
|
+
def start_openclaw_pro(
|
|
1512
|
+
self,
|
|
1513
|
+
agent_id: str,
|
|
1514
|
+
config: dict = None,
|
|
1515
|
+
env: dict = None,
|
|
1516
|
+
ports: list = None,
|
|
1517
|
+
routes: dict = None,
|
|
1518
|
+
command: list[str] = None,
|
|
1519
|
+
entrypoint: list[str] = None,
|
|
1520
|
+
image: str = None,
|
|
1521
|
+
sync_root: str = None,
|
|
1522
|
+
sync_enabled: bool = None,
|
|
1523
|
+
sync_uid: int = None,
|
|
1524
|
+
sync_gid: int = None,
|
|
1525
|
+
registry_url: str = None,
|
|
1526
|
+
registry_auth: dict = None,
|
|
1527
|
+
gateway_token: str = None,
|
|
1528
|
+
heartbeat: dict = None,
|
|
1529
|
+
dry_run: bool = False,
|
|
1530
|
+
openclaw_routes: dict | None = None,
|
|
1531
|
+
openclaw_route_options: dict | None = None,
|
|
1532
|
+
memory_index: dict | None = None,
|
|
1533
|
+
) -> Agent:
|
|
1534
|
+
effective_env = {"OPENCLAW_DESKTOP_ENABLED": "1", **dict(env or {})}
|
|
1535
|
+
effective_route_options = {"include_desktop": True, **dict(openclaw_route_options or {})}
|
|
1536
|
+
return self.start_openclaw(
|
|
1537
|
+
agent_id,
|
|
1538
|
+
config=config,
|
|
1539
|
+
env=effective_env,
|
|
1540
|
+
ports=ports,
|
|
1541
|
+
routes=routes,
|
|
1542
|
+
command=command,
|
|
1543
|
+
entrypoint=entrypoint,
|
|
1544
|
+
image=_default_openclaw_pro_image(image),
|
|
1545
|
+
sync_root=sync_root,
|
|
1546
|
+
sync_enabled=sync_enabled,
|
|
1547
|
+
sync_uid=sync_uid,
|
|
1548
|
+
sync_gid=sync_gid,
|
|
1549
|
+
registry_url=registry_url,
|
|
1550
|
+
registry_auth=registry_auth,
|
|
1551
|
+
gateway_token=gateway_token,
|
|
1552
|
+
heartbeat=heartbeat,
|
|
1553
|
+
dry_run=dry_run,
|
|
1554
|
+
openclaw_routes=openclaw_routes,
|
|
1555
|
+
openclaw_route_options=effective_route_options,
|
|
1556
|
+
memory_index=memory_index,
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1374
1559
|
def update(
|
|
1375
1560
|
self,
|
|
1376
1561
|
agent_id: str,
|
|
@@ -15,8 +15,10 @@ from hypercli.agents import (
|
|
|
15
15
|
AGENT_FILE_TRANSFER_CHUNK_BYTES,
|
|
16
16
|
Agent,
|
|
17
17
|
DEFAULT_OPENCLAW_IMAGE,
|
|
18
|
+
DEFAULT_OPENCLAW_PRO_IMAGE,
|
|
18
19
|
Deployments,
|
|
19
20
|
OpenClawAgent,
|
|
21
|
+
OpenClawProAgent,
|
|
20
22
|
ExecResult,
|
|
21
23
|
_build_agent_launch,
|
|
22
24
|
build_openclaw_routes,
|
|
@@ -454,7 +456,7 @@ def test_build_agent_launch_includes_command_and_entrypoint():
|
|
|
454
456
|
def test_build_agent_launch_merges_heartbeat_defaults():
|
|
455
457
|
launch, _gateway_token = _build_agent_launch(
|
|
456
458
|
{"agents": {"defaults": {"model": "openai/gpt-5.4", "heartbeat": {"target": "last"}}}},
|
|
457
|
-
heartbeat={"every": "
|
|
459
|
+
heartbeat={"every": "1h", "target": "last"},
|
|
458
460
|
gateway_token="gw-token",
|
|
459
461
|
)
|
|
460
462
|
|
|
@@ -464,8 +466,7 @@ def test_build_agent_launch_merges_heartbeat_defaults():
|
|
|
464
466
|
"model": "openai/gpt-5.4",
|
|
465
467
|
"heartbeat": {
|
|
466
468
|
"target": "last",
|
|
467
|
-
"every": "
|
|
468
|
-
"includeSystemPromptSection": False,
|
|
469
|
+
"every": "1h",
|
|
469
470
|
},
|
|
470
471
|
}
|
|
471
472
|
}
|
|
@@ -543,6 +544,81 @@ def test_create_openclaw_respects_explicit_empty_routes(agents_client):
|
|
|
543
544
|
assert posted_json["routes"] == {}
|
|
544
545
|
|
|
545
546
|
|
|
547
|
+
def test_create_openclaw_pro_defaults_desktop_image_env_and_routes(agents_client):
|
|
548
|
+
with patch("httpx.Client") as mock_client_class, patch("hypercli.agents.secrets.token_hex", return_value="gw-token-123"):
|
|
549
|
+
mock_client = MagicMock()
|
|
550
|
+
mock_response = Mock()
|
|
551
|
+
mock_response.status_code = 200
|
|
552
|
+
mock_response.json.return_value = {
|
|
553
|
+
"id": "agent-123",
|
|
554
|
+
"user_id": "user-456",
|
|
555
|
+
"pod_id": "pod-789",
|
|
556
|
+
"pod_name": "test-pod",
|
|
557
|
+
"state": "starting",
|
|
558
|
+
"launch_config": {
|
|
559
|
+
"image": DEFAULT_OPENCLAW_PRO_IMAGE,
|
|
560
|
+
"env": {"OPENCLAW_DESKTOP_ENABLED": "1"},
|
|
561
|
+
"routes": {"openclaw": {"port": 18789, "auth": False, "prefix": ""}},
|
|
562
|
+
},
|
|
563
|
+
}
|
|
564
|
+
mock_client.post.return_value = mock_response
|
|
565
|
+
mock_client.__enter__.return_value = mock_client
|
|
566
|
+
mock_client.__exit__.return_value = False
|
|
567
|
+
mock_client_class.return_value = mock_client
|
|
568
|
+
|
|
569
|
+
agent = agents_client.create_openclaw_pro(name="test-agent")
|
|
570
|
+
|
|
571
|
+
posted_json = mock_client.post.call_args[1]["json"]
|
|
572
|
+
assert posted_json["image"] == DEFAULT_OPENCLAW_PRO_IMAGE
|
|
573
|
+
assert posted_json["env"]["OPENCLAW_DESKTOP_ENABLED"] == "1"
|
|
574
|
+
assert "OPENCLAW_MEMORY_SEARCH_SYNC_ON_SESSION_START" not in posted_json["env"]
|
|
575
|
+
assert posted_json["routes"] == {
|
|
576
|
+
"openclaw": {"port": 18789, "auth": False, "prefix": ""},
|
|
577
|
+
"desktop": {"port": 3000, "auth": True, "prefix": "desktop"},
|
|
578
|
+
}
|
|
579
|
+
assert isinstance(agent, OpenClawProAgent)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def test_create_openclaw_accepts_memory_index_options(agents_client):
|
|
583
|
+
with patch("httpx.Client") as mock_client_class, patch("hypercli.agents.secrets.token_hex", return_value="gw-token-123"):
|
|
584
|
+
mock_client = MagicMock()
|
|
585
|
+
mock_response = Mock()
|
|
586
|
+
mock_response.status_code = 200
|
|
587
|
+
mock_response.json.return_value = {
|
|
588
|
+
"id": "agent-123",
|
|
589
|
+
"user_id": "user-456",
|
|
590
|
+
"pod_id": "pod-789",
|
|
591
|
+
"pod_name": "test-pod",
|
|
592
|
+
"state": "starting",
|
|
593
|
+
"launch_config": {
|
|
594
|
+
"env": {},
|
|
595
|
+
"routes": {"openclaw": {"port": 18789, "auth": False, "prefix": ""}},
|
|
596
|
+
},
|
|
597
|
+
}
|
|
598
|
+
mock_client.post.return_value = mock_response
|
|
599
|
+
mock_client.__enter__.return_value = mock_client
|
|
600
|
+
mock_client.__exit__.return_value = False
|
|
601
|
+
mock_client_class.return_value = mock_client
|
|
602
|
+
|
|
603
|
+
agents_client.create_openclaw(
|
|
604
|
+
name="test-agent",
|
|
605
|
+
memory_index={
|
|
606
|
+
"on_session_start": True,
|
|
607
|
+
"on_search": True,
|
|
608
|
+
"watch": True,
|
|
609
|
+
"watch_debounce_ms": 60000,
|
|
610
|
+
"interval_minutes": 120,
|
|
611
|
+
},
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
posted_json = mock_client.post.call_args[1]["json"]
|
|
615
|
+
assert posted_json["env"]["OPENCLAW_MEMORY_SEARCH_SYNC_ON_SESSION_START"] == "1"
|
|
616
|
+
assert posted_json["env"]["OPENCLAW_MEMORY_SEARCH_SYNC_ON_SEARCH"] == "1"
|
|
617
|
+
assert posted_json["env"]["OPENCLAW_MEMORY_SEARCH_SYNC_WATCH"] == "1"
|
|
618
|
+
assert posted_json["env"]["OPENCLAW_MEMORY_SEARCH_SYNC_WATCH_DEBOUNCE_MS"] == "60000"
|
|
619
|
+
assert posted_json["env"]["OPENCLAW_MEMORY_SEARCH_SYNC_INTERVAL_MINUTES"] == "120"
|
|
620
|
+
|
|
621
|
+
|
|
546
622
|
def test_create_openclaw_includes_heartbeat_when_requested(agents_client):
|
|
547
623
|
with patch("httpx.Client") as mock_client_class, patch("hypercli.agents.secrets.token_hex", return_value="gw-token-123"):
|
|
548
624
|
mock_client = MagicMock()
|
|
@@ -562,13 +638,13 @@ def test_create_openclaw_includes_heartbeat_when_requested(agents_client):
|
|
|
562
638
|
|
|
563
639
|
agents_client.create_openclaw(
|
|
564
640
|
name="test-agent",
|
|
565
|
-
heartbeat={"every": "
|
|
641
|
+
heartbeat={"every": "1h", "target": "last"},
|
|
566
642
|
)
|
|
567
643
|
|
|
568
644
|
posted_json = mock_client.post.call_args[1]["json"]
|
|
569
645
|
assert posted_json["config"]["agents"]["defaults"]["heartbeat"] == {
|
|
570
|
-
"every": "
|
|
571
|
-
"
|
|
646
|
+
"every": "1h",
|
|
647
|
+
"target": "last",
|
|
572
648
|
}
|
|
573
649
|
|
|
574
650
|
@pytest.fixture
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|