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.

Files changed (55) hide show
  1. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/.gitignore +1 -0
  2. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/PKG-INFO +5 -2
  3. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/README.md +4 -1
  4. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/__init__.py +4 -2
  5. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/agents.py +188 -3
  6. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/pyproject.toml +1 -1
  7. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_agents.py +82 -6
  8. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/_compat.py +0 -0
  9. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/agent.py +0 -0
  10. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/billing.py +0 -0
  11. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/client.py +0 -0
  12. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/config.py +0 -0
  13. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/files.py +0 -0
  14. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/gateway.py +0 -0
  15. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/http.py +0 -0
  16. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/instances.py +0 -0
  17. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/job/__init__.py +0 -0
  18. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/job/base.py +0 -0
  19. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/job/comfyui.py +0 -0
  20. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/job/gradio.py +0 -0
  21. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/jobs.py +0 -0
  22. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/keys.py +0 -0
  23. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/logs.py +0 -0
  24. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/models.py +0 -0
  25. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/openclaw/__init__.py +0 -0
  26. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/openclaw/gateway.py +0 -0
  27. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/renders.py +0 -0
  28. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/shell.py +0 -0
  29. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/user.py +0 -0
  30. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/voice.py +0 -0
  31. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/hypercli/x402.py +0 -0
  32. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/conftest.py +0 -0
  33. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_agents.py +0 -0
  34. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_auth.py +0 -0
  35. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_billing.py +0 -0
  36. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_instances.py +0 -0
  37. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_jobs_dryrun.py +0 -0
  38. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_keys.py +0 -0
  39. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/integration/test_renders.py +0 -0
  40. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_apply_params.py +0 -0
  41. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_bootstrap_console_test_key.py +0 -0
  42. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_bootstrap_dev_test_keys.py +0 -0
  43. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_claw.py +0 -0
  44. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_config.py +0 -0
  45. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_exec_shell_dryrun.py +0 -0
  46. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_gateway.py +0 -0
  47. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_gateway_retry.py +0 -0
  48. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_graph_to_api.py +0 -0
  49. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_http.py +0 -0
  50. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_jobs.py +0 -0
  51. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_keys.py +0 -0
  52. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_models.py +0 -0
  53. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_renders_subscription.py +0 -0
  54. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_status.py +0 -0
  55. {hypercli_sdk-2026.6.21 → hypercli_sdk-2026.6.26}/tests/test_voice.py +0 -0
@@ -27,6 +27,7 @@ coverage
27
27
  # Misc
28
28
  .DS_Store
29
29
  .agents/
30
+ .opencode/
30
31
  *.pem
31
32
 
32
33
  # IDE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypercli-sdk
3
- Version: 2026.6.21
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.21"
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 _is_openclaw_agent_data(data):
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,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hypercli-sdk"
7
- version = "2026.6.21"
7
+ version = "2026.6.26"
8
8
  description = "Python SDK for HyperCLI - GPU orchestration and HyperAgent API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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": "0m", "includeSystemPromptSection": False},
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": "0m",
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": "0m", "includeSystemPromptSection": False},
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": "0m",
571
- "includeSystemPromptSection": False,
646
+ "every": "1h",
647
+ "target": "last",
572
648
  }
573
649
 
574
650
  @pytest.fixture