Skip to content

Commit 3cdb886

Browse files
committed
fix: automations workflow_spec API + runtime shell (v0.43.0)
1 parent deca2f4 commit 3cdb886

4 files changed

Lines changed: 178 additions & 90 deletions

File tree

‎README.md‎

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -303,20 +303,25 @@ client.auth.social_login(provider="google", id_token="...")
303303
### Automations
304304

305305
```python
306-
# Create a cron-based automation
306+
# Create a cron-based automation (workflow_spec required)
307307
client.automations.create(
308-
agent_id,
309308
name="rotate-api-key",
310-
schedule="0 0 * * 0", # weekly
311-
action_type="secret_rotate",
312-
action_config={"path": "api-keys/stripe", "length": 64},
309+
agent_id=agent_id,
310+
trigger_type="cron",
311+
cron_expr="0 0 * * 0", # weekly
312+
timezone="UTC",
313+
workflow_spec={
314+
"steps": [
315+
{"type": "log", "action": "run_agent_task", "message": "Rotate weekly API keys"}
316+
]
317+
},
313318
)
314319

315-
# List automations
316-
autos = client.automations.list(agent_id)
320+
# List automations in the org
321+
autos = client.automations.list()
317322

318323
# Manually trigger
319-
client.automations.trigger(agent_id, automation_id)
324+
client.automations.trigger(automation_id)
320325
```
321326

322327
### Agent Memory
@@ -342,19 +347,23 @@ client.memory.clear(agent_id)
342347
runtime = client.runtimes.create(
343348
agent_id=agent_id,
344349
name="my-agent-runtime",
345-
image="ghcr.io/my-org/agent:latest",
346-
env={"MODEL": "gpt-4"},
347-
memory_mb=512,
350+
template="python",
351+
preset="small",
352+
env_public={"MODEL": "gpt-4"},
353+
shell_access_enabled=True,
348354
)
349355

350356
# List runtimes
351-
runtimes = client.runtimes.list(agent_id=agent_id)
357+
runtimes = client.runtimes.list()
352358

353-
# Get logs
359+
# Lifecycle
360+
client.runtimes.start(runtime_id)
354361
logs = client.runtimes.logs(runtime_id, limit=100)
362+
client.runtimes.stop(runtime_id)
355363

356-
# Restart
357-
client.runtimes.restart(runtime_id)
364+
# Interactive shell (human-only, step-up password / passkey / reauth token)
365+
session = client.runtimes.create_shell_session(runtime_id, password="...")
366+
# Connect a WebSocket client to session.data["ws_url"] with the session_token
358367
```
359368

360369
### Discovery

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "oneclaw"
7-
version = "0.42.0"
7+
version = "0.43.0"
88
description = "Official Python SDK for the 1Claw secrets management platform"
99
readme = "README.md"
1010
license = "MIT"

‎src/oneclaw/resources/automations.py‎

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,83 +10,96 @@
1010

1111

1212
class AutomationsResource:
13-
"""Cron-based scheduled tasks for agents."""
13+
"""Scheduled, event-driven, and webhook-triggered agent workflows."""
1414

1515
def __init__(self, http: HttpClient) -> None:
1616
self._http = http
1717

1818
def create(
1919
self,
20-
agent_id: str,
2120
*,
2221
name: str,
23-
schedule: str,
24-
action_type: str,
25-
action_config: dict[str, Any],
26-
description: str | None = None,
27-
enabled: bool = True,
22+
agent_id: str,
23+
trigger_type: str,
24+
workflow_spec: dict[str, Any] | list[Any],
25+
cron_expr: str | None = None,
26+
timezone: str = "UTC",
27+
event_filter: dict[str, Any] | None = None,
2828
) -> OneclawResponse[Any]:
29-
"""Create a new automation for an agent."""
29+
"""Create a new automation.
30+
31+
``workflow_spec`` is required: either a step array or ``{"steps": [...]}``.
32+
For cron triggers, pass ``cron_expr``. ``trigger_type="schedule"`` is
33+
accepted by the API and normalized to ``cron``.
34+
"""
3035
body: dict[str, Any] = {
3136
"name": name,
32-
"schedule": schedule,
33-
"action_type": action_type,
34-
"action_config": action_config,
35-
"enabled": enabled,
37+
"agent_id": agent_id,
38+
"trigger_type": trigger_type,
39+
"timezone": timezone,
40+
"workflow_spec": workflow_spec,
3641
}
37-
if description is not None:
38-
body["description"] = description
39-
return self._http.request("POST", f"/v1/agents/{agent_id}/automations", body=body)
42+
if cron_expr is not None:
43+
body["cron_expr"] = cron_expr
44+
if event_filter is not None:
45+
body["event_filter"] = event_filter
46+
return self._http.request("POST", "/v1/automations", body=body)
4047

41-
def list(self, agent_id: str) -> OneclawResponse[Any]:
42-
"""List automations for an agent."""
43-
return self._http.request("GET", f"/v1/agents/{agent_id}/automations")
48+
def list(self) -> OneclawResponse[Any]:
49+
"""List automations for the current organization."""
50+
return self._http.request("GET", "/v1/automations")
4451

45-
def get(self, agent_id: str, automation_id: str) -> OneclawResponse[Any]:
52+
def get(self, automation_id: str) -> OneclawResponse[Any]:
4653
"""Get a specific automation."""
47-
return self._http.request("GET", f"/v1/agents/{agent_id}/automations/{automation_id}")
54+
return self._http.request("GET", f"/v1/automations/{automation_id}")
4855

4956
def update(
5057
self,
51-
agent_id: str,
5258
automation_id: str,
5359
*,
5460
name: str | None = None,
55-
schedule: str | None = None,
56-
action_config: dict[str, Any] | None = None,
57-
enabled: bool | None = None,
61+
cron_expr: str | None = None,
62+
timezone: str | None = None,
63+
event_filter: dict[str, Any] | None = None,
64+
workflow_spec: dict[str, Any] | list[Any] | None = None,
65+
is_active: bool | None = None,
5866
) -> OneclawResponse[Any]:
5967
"""Update an existing automation."""
6068
body: dict[str, Any] = {}
6169
if name is not None:
6270
body["name"] = name
63-
if schedule is not None:
64-
body["schedule"] = schedule
65-
if action_config is not None:
66-
body["action_config"] = action_config
67-
if enabled is not None:
68-
body["enabled"] = enabled
69-
return self._http.request(
70-
"PATCH", f"/v1/agents/{agent_id}/automations/{automation_id}", body=body
71-
)
71+
if cron_expr is not None:
72+
body["cron_expr"] = cron_expr
73+
if timezone is not None:
74+
body["timezone"] = timezone
75+
if event_filter is not None:
76+
body["event_filter"] = event_filter
77+
if workflow_spec is not None:
78+
body["workflow_spec"] = workflow_spec
79+
if is_active is not None:
80+
body["is_active"] = is_active
81+
return self._http.request("PATCH", f"/v1/automations/{automation_id}", body=body)
7282

73-
def delete(self, agent_id: str, automation_id: str) -> OneclawResponse[Any]:
83+
def delete(self, automation_id: str) -> OneclawResponse[Any]:
7484
"""Delete an automation."""
75-
return self._http.request(
76-
"DELETE", f"/v1/agents/{agent_id}/automations/{automation_id}"
77-
)
85+
return self._http.request("DELETE", f"/v1/automations/{automation_id}")
7886

7987
def list_runs(
80-
self, agent_id: str, automation_id: str, *, limit: int | None = None
88+
self, automation_id: str, *, limit: int | None = None
8189
) -> OneclawResponse[Any]:
8290
"""List recent runs for an automation."""
8391
query: dict[str, Any] | None = {"limit": limit} if limit else None
8492
return self._http.request(
85-
"GET", f"/v1/agents/{agent_id}/automations/{automation_id}/runs", query=query
93+
"GET", f"/v1/automations/{automation_id}/runs", query=query
8694
)
8795

88-
def trigger(self, agent_id: str, automation_id: str) -> OneclawResponse[Any]:
96+
def trigger(
97+
self, automation_id: str, *, input: dict[str, Any] | None = None
98+
) -> OneclawResponse[Any]:
8999
"""Manually trigger an automation run."""
100+
body: dict[str, Any] = {}
101+
if input is not None:
102+
body["input"] = input
90103
return self._http.request(
91-
"POST", f"/v1/agents/{agent_id}/automations/{automation_id}/trigger"
104+
"POST", f"/v1/automations/{automation_id}/trigger", body=body
92105
)

‎src/oneclaw/resources/runtimes.py‎

Lines changed: 99 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,53 @@ def create(
2020
*,
2121
agent_id: str,
2222
name: str,
23-
image: str,
24-
env: dict[str, str] | None = None,
25-
cpu: str | None = None,
26-
memory_mb: int | None = None,
27-
replicas: int | None = None,
28-
health_check_path: str | None = None,
23+
template: str | None = None,
24+
preset: str | None = None,
25+
image: str | None = None,
26+
env_public: dict[str, str] | None = None,
27+
idle_timeout_secs: int | None = None,
28+
expose_http: bool | None = None,
29+
http_port: int | None = None,
30+
slug: str | None = None,
31+
inbound_auth: str | None = None,
32+
shell_access_enabled: bool | None = None,
33+
shell_auth_policy: str | None = None,
34+
shell_max_session_minutes: int | None = None,
2935
) -> OneclawResponse[Any]:
3036
"""Create a new runtime deployment."""
3137
body: dict[str, Any] = {
3238
"agent_id": agent_id,
3339
"name": name,
34-
"image": image,
3540
}
36-
if env is not None:
37-
body["env"] = env
38-
if cpu is not None:
39-
body["cpu"] = cpu
40-
if memory_mb is not None:
41-
body["memory_mb"] = memory_mb
42-
if replicas is not None:
43-
body["replicas"] = replicas
44-
if health_check_path is not None:
45-
body["health_check_path"] = health_check_path
41+
if template is not None:
42+
body["template"] = template
43+
if preset is not None:
44+
body["preset"] = preset
45+
if image is not None:
46+
body["image"] = image
47+
if env_public is not None:
48+
body["env_public"] = env_public
49+
if idle_timeout_secs is not None:
50+
body["idle_timeout_secs"] = idle_timeout_secs
51+
if expose_http is not None:
52+
body["expose_http"] = expose_http
53+
if http_port is not None:
54+
body["http_port"] = http_port
55+
if slug is not None:
56+
body["slug"] = slug
57+
if inbound_auth is not None:
58+
body["inbound_auth"] = inbound_auth
59+
if shell_access_enabled is not None:
60+
body["shell_access_enabled"] = shell_access_enabled
61+
if shell_auth_policy is not None:
62+
body["shell_auth_policy"] = shell_auth_policy
63+
if shell_max_session_minutes is not None:
64+
body["shell_max_session_minutes"] = shell_max_session_minutes
4665
return self._http.request("POST", "/v1/runtimes", body=body)
4766

48-
def list(self, *, agent_id: str | None = None) -> OneclawResponse[Any]:
49-
"""List runtimes, optionally filtered by agent."""
50-
query: dict[str, Any] | None = {"agent_id": agent_id} if agent_id else None
51-
return self._http.request("GET", "/v1/runtimes", query=query)
67+
def list(self) -> OneclawResponse[Any]:
68+
"""List runtimes for the current organization."""
69+
return self._http.request("GET", "/v1/runtimes")
5270

5371
def get(self, runtime_id: str) -> OneclawResponse[Any]:
5472
"""Get a runtime by ID."""
@@ -58,27 +76,56 @@ def update(
5876
self,
5977
runtime_id: str,
6078
*,
79+
name: str | None = None,
6180
image: str | None = None,
62-
env: dict[str, str] | None = None,
63-
replicas: int | None = None,
64-
status: str | None = None,
81+
env_public: dict[str, str] | None = None,
82+
idle_timeout_secs: int | None = None,
83+
expose_http: bool | None = None,
84+
http_port: int | None = None,
85+
slug: str | None = None,
86+
inbound_auth: str | None = None,
87+
shell_access_enabled: bool | None = None,
88+
shell_auth_policy: str | None = None,
89+
shell_max_session_minutes: int | None = None,
6590
) -> OneclawResponse[Any]:
6691
"""Update a runtime deployment."""
6792
body: dict[str, Any] = {}
93+
if name is not None:
94+
body["name"] = name
6895
if image is not None:
6996
body["image"] = image
70-
if env is not None:
71-
body["env"] = env
72-
if replicas is not None:
73-
body["replicas"] = replicas
74-
if status is not None:
75-
body["status"] = status
97+
if env_public is not None:
98+
body["env_public"] = env_public
99+
if idle_timeout_secs is not None:
100+
body["idle_timeout_secs"] = idle_timeout_secs
101+
if expose_http is not None:
102+
body["expose_http"] = expose_http
103+
if http_port is not None:
104+
body["http_port"] = http_port
105+
if slug is not None:
106+
body["slug"] = slug
107+
if inbound_auth is not None:
108+
body["inbound_auth"] = inbound_auth
109+
if shell_access_enabled is not None:
110+
body["shell_access_enabled"] = shell_access_enabled
111+
if shell_auth_policy is not None:
112+
body["shell_auth_policy"] = shell_auth_policy
113+
if shell_max_session_minutes is not None:
114+
body["shell_max_session_minutes"] = shell_max_session_minutes
76115
return self._http.request("PATCH", f"/v1/runtimes/{runtime_id}", body=body)
77116

78117
def delete(self, runtime_id: str) -> OneclawResponse[Any]:
79118
"""Delete a runtime deployment."""
80119
return self._http.request("DELETE", f"/v1/runtimes/{runtime_id}")
81120

121+
def start(self, runtime_id: str) -> OneclawResponse[Any]:
122+
"""Start a stopped runtime."""
123+
return self._http.request("POST", f"/v1/runtimes/{runtime_id}/start")
124+
125+
def stop(self, runtime_id: str) -> OneclawResponse[Any]:
126+
"""Stop a running runtime."""
127+
return self._http.request("POST", f"/v1/runtimes/{runtime_id}/stop")
128+
82129
def logs(
83130
self, runtime_id: str, *, since: str | None = None, limit: int | None = None
84131
) -> OneclawResponse[Any]:
@@ -90,6 +137,25 @@ def logs(
90137
query["limit"] = limit
91138
return self._http.request("GET", f"/v1/runtimes/{runtime_id}/logs", query=query or None)
92139

93-
def restart(self, runtime_id: str) -> OneclawResponse[Any]:
94-
"""Restart a runtime."""
95-
return self._http.request("POST", f"/v1/runtimes/{runtime_id}/restart")
140+
def create_shell_session(
141+
self,
142+
runtime_id: str,
143+
*,
144+
password: str | None = None,
145+
totp_code: str | None = None,
146+
passkey_credential: dict[str, Any] | None = None,
147+
reauth_token: str | None = None,
148+
) -> OneclawResponse[Any]:
149+
"""Create an interactive shell WebSocket session (human-only, step-up auth)."""
150+
body: dict[str, Any] = {}
151+
if password is not None:
152+
body["password"] = password
153+
if totp_code is not None:
154+
body["totp_code"] = totp_code
155+
if passkey_credential is not None:
156+
body["passkey_credential"] = passkey_credential
157+
if reauth_token is not None:
158+
body["reauth_token"] = reauth_token
159+
return self._http.request(
160+
"POST", f"/v1/runtimes/{runtime_id}/shell/session", body=body
161+
)

0 commit comments

Comments
 (0)