devops-bot-sdk 1.4.31__tar.gz → 1.4.33__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.
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/client.py +26 -1
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/run_auto.py +90 -18
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/README.md +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/setup.cfg +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devops-bot-sdk"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.33"
|
|
8
8
|
description = "DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "LicenseRef-Proprietary"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.33
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -30,7 +30,7 @@ Rules:
|
|
|
30
30
|
- All data egress through submit_webhook only
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
__version__ = "1.4.
|
|
33
|
+
__version__ = "1.4.33"
|
|
34
34
|
__author__ = "AgentOS"
|
|
35
35
|
|
|
36
36
|
from sdk.client import BackendClient
|
|
@@ -36,7 +36,29 @@ from sdk.sse import _check_status, stream_with_reconnect
|
|
|
36
36
|
|
|
37
37
|
logger = logging.getLogger(__name__)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
# ── Ticket status-change observers ────────────────────────────────────────
|
|
40
|
+
# Best-effort, synchronous hooks fired when the client changes a ticket's backend
|
|
41
|
+
# status (In Progress / Backlog / To Do). The CLI loop registers one to track
|
|
42
|
+
# which tickets the run has parked in Backlog, so `agentos-auto stop` can restore
|
|
43
|
+
# exactly those to To Do. Kept generic here — the client never touches CLI files.
|
|
44
|
+
_status_observers: list = []
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register_status_observer(cb) -> None:
|
|
48
|
+
"""Register cb(task_id: str, status: str) — status ∈ {in_progress, backlog, to_do}."""
|
|
49
|
+
if cb not in _status_observers:
|
|
50
|
+
_status_observers.append(cb)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _notify_status(task_id: str, status: str) -> None:
|
|
54
|
+
for cb in list(_status_observers):
|
|
55
|
+
try:
|
|
56
|
+
cb(task_id, status)
|
|
57
|
+
except Exception: # noqa: BLE001 — observers must never break a request
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
SDK_VERSION = "1.4.33"
|
|
40
62
|
_POLL_INTERVAL = 3.0
|
|
41
63
|
_POLL_TIMEOUT = 600.0
|
|
42
64
|
_ORCHESTRATE_TIMEOUT = 2700.0 # 45 min — covers approval wait + VPS execution time
|
|
@@ -686,6 +708,7 @@ class BackendClient:
|
|
|
686
708
|
task_id, resp.status_code, resp.text[:200])
|
|
687
709
|
return False
|
|
688
710
|
await self.log_activity(f"Task {task_id}: status → Backlog")
|
|
711
|
+
_notify_status(task_id, "backlog")
|
|
689
712
|
return True
|
|
690
713
|
except Exception as exc: # noqa: BLE001
|
|
691
714
|
logger.warning("client.move_to_backlog error task=%s err=%s", task_id, exc)
|
|
@@ -713,6 +736,7 @@ class BackendClient:
|
|
|
713
736
|
task_id, resp.status_code, resp.text[:200])
|
|
714
737
|
return False
|
|
715
738
|
await self.log_activity(f"Task {task_id}: status → To Do")
|
|
739
|
+
_notify_status(task_id, "to_do")
|
|
716
740
|
return True
|
|
717
741
|
except Exception as exc: # noqa: BLE001
|
|
718
742
|
logger.warning("client.move_to_todo error task=%s err=%s", task_id, exc)
|
|
@@ -739,6 +763,7 @@ class BackendClient:
|
|
|
739
763
|
task_id, resp.status_code, resp.text[:200])
|
|
740
764
|
return False
|
|
741
765
|
await self.log_activity(f"Task {task_id}: status → In Progress")
|
|
766
|
+
_notify_status(task_id, "in_progress")
|
|
742
767
|
return True
|
|
743
768
|
except Exception as exc: # noqa: BLE001
|
|
744
769
|
logger.warning("client.mark_in_progress error task=%s err=%s", task_id, exc)
|
|
@@ -29,10 +29,12 @@ itself (then re-runs on the new code) BEFORE detaching, so non-technical users
|
|
|
29
29
|
never reinstall.
|
|
30
30
|
|
|
31
31
|
Stop model: `stop` is immediate — it kills the loop's whole process group (the
|
|
32
|
-
daemon and any child `claude`)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
daemon and any child `claude`), then returns to "To Do" both the in-flight ticket
|
|
33
|
+
AND every ticket this run parked in Backlog (e.g. 15-min approval timeouts), so a
|
|
34
|
+
later run re-attempts them; human-parked Backlog tickets are left untouched. The
|
|
35
|
+
in-flight id is kept in ~/.agentos/auto.current and the run's Backlog set in
|
|
36
|
+
~/.agentos/auto.backlogged (maintained via a client status observer). `resume`
|
|
37
|
+
(or just re-running) starts fresh over whatever is still To Do.
|
|
36
38
|
"""
|
|
37
39
|
from __future__ import annotations
|
|
38
40
|
|
|
@@ -49,6 +51,10 @@ _LOG_FILE = _AGENTOS_DIR / "auto.log"
|
|
|
49
51
|
# Backend id (UUID) of the ticket currently being processed. `stop` reads it to
|
|
50
52
|
# return the in-flight ticket to "To Do" before killing the loop.
|
|
51
53
|
_CURRENT_FILE = _AGENTOS_DIR / "auto.current"
|
|
54
|
+
# Backend ids the CURRENT run has moved to Backlog (e.g. 15-min approval
|
|
55
|
+
# timeouts). `stop` restores exactly these to "To Do" too — not human-parked
|
|
56
|
+
# Backlog tickets. Maintained live via a client status observer.
|
|
57
|
+
_BACKLOGGED_FILE = _AGENTOS_DIR / "auto.backlogged"
|
|
52
58
|
|
|
53
59
|
|
|
54
60
|
def _stop_requested() -> bool:
|
|
@@ -90,6 +96,41 @@ def _clear_current() -> None:
|
|
|
90
96
|
pass
|
|
91
97
|
|
|
92
98
|
|
|
99
|
+
def _read_backlogged() -> set[str]:
|
|
100
|
+
try:
|
|
101
|
+
return set(json.loads(_BACKLOGGED_FILE.read_text()))
|
|
102
|
+
except (FileNotFoundError, ValueError, TypeError):
|
|
103
|
+
return set()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _write_backlogged(ids: set[str]) -> None:
|
|
107
|
+
try:
|
|
108
|
+
_AGENTOS_DIR.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
_BACKLOGGED_FILE.write_text(json.dumps(sorted(ids)))
|
|
110
|
+
except Exception: # noqa: BLE001
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _clear_backlogged() -> None:
|
|
115
|
+
try:
|
|
116
|
+
_BACKLOGGED_FILE.unlink()
|
|
117
|
+
except FileNotFoundError:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _track_status(task_id: str, status: str) -> None:
|
|
122
|
+
"""Client status observer (runs in the daemon): keep the set of tickets THIS
|
|
123
|
+
run has parked in Backlog current, so `stop` can restore them to To Do."""
|
|
124
|
+
if not task_id:
|
|
125
|
+
return
|
|
126
|
+
ids = _read_backlogged()
|
|
127
|
+
if status == "backlog":
|
|
128
|
+
ids.add(task_id)
|
|
129
|
+
else: # in_progress / to_do → no longer backlogged by us
|
|
130
|
+
ids.discard(task_id)
|
|
131
|
+
_write_backlogged(ids)
|
|
132
|
+
|
|
133
|
+
|
|
93
134
|
# ── Background-daemon support ─────────────────────────────────────────────
|
|
94
135
|
# `agentos-auto` must keep running after the terminal is closed and stop ONLY on
|
|
95
136
|
# `agentos-auto stop`. We fully detach from the controlling terminal (double-fork
|
|
@@ -224,7 +265,7 @@ async def _send(env: dict) -> None:
|
|
|
224
265
|
|
|
225
266
|
def _run_loop(workspace_root: str | None) -> None:
|
|
226
267
|
from sdk import local_exec
|
|
227
|
-
from sdk.client import BackendClient
|
|
268
|
+
from sdk.client import BackendClient, register_status_observer
|
|
228
269
|
from sdk.ipc import handlers
|
|
229
270
|
|
|
230
271
|
if not local_exec.is_available():
|
|
@@ -243,6 +284,8 @@ def _run_loop(workspace_root: str | None) -> None:
|
|
|
243
284
|
payload["workspace_root"] = workspace_root
|
|
244
285
|
|
|
245
286
|
_clear_stop() # a fresh run/resume clears any stale stop flag
|
|
287
|
+
_clear_backlogged() # session-scoped: only track Backlog moves from THIS run
|
|
288
|
+
register_status_observer(_track_status) # keep the run's Backlog set current
|
|
246
289
|
try:
|
|
247
290
|
asyncio.run(
|
|
248
291
|
handlers.handle_orchestrate_auto_local_run(
|
|
@@ -254,14 +297,15 @@ def _run_loop(workspace_root: str | None) -> None:
|
|
|
254
297
|
print("\n⏸ interrupted — stop flag set. run `agentos-auto resume` to continue.")
|
|
255
298
|
finally:
|
|
256
299
|
# Whether the loop finished, paused, or crashed, drop our PID file so
|
|
257
|
-
# `status` doesn't report a dead daemon as running, and clear the
|
|
258
|
-
#
|
|
300
|
+
# `status` doesn't report a dead daemon as running, and clear the in-flight
|
|
301
|
+
# + backlog markers (a clean finish leaves nothing for `stop` to restore).
|
|
259
302
|
if _read_pid() == os.getpid():
|
|
260
303
|
try:
|
|
261
304
|
_PID_FILE.unlink()
|
|
262
305
|
except FileNotFoundError:
|
|
263
306
|
pass
|
|
264
307
|
_clear_current()
|
|
308
|
+
_clear_backlogged()
|
|
265
309
|
|
|
266
310
|
|
|
267
311
|
def main() -> None:
|
|
@@ -269,10 +313,23 @@ def main() -> None:
|
|
|
269
313
|
cmd = argv[0] if argv and not argv[0].startswith("-") else "run"
|
|
270
314
|
|
|
271
315
|
if cmd == "update":
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
316
|
+
# Update-only: upgrade the installed SDK package to the latest version and
|
|
317
|
+
# report before → after. Does NOT start, stop, or touch the running loop —
|
|
318
|
+
# a running daemon keeps its current code until it is restarted. Honours
|
|
319
|
+
# AGENTOS_NO_UPDATE=1 (auto_update no-ops).
|
|
320
|
+
from sdk.updater import auto_update, current_version
|
|
321
|
+
before = current_version()
|
|
322
|
+
print("checking for SDK updates …")
|
|
323
|
+
auto_update(quiet=True)
|
|
324
|
+
after = current_version()
|
|
325
|
+
if after != before:
|
|
326
|
+
print(f"✓ updated {before} → {after}")
|
|
327
|
+
pid = _daemon_running()
|
|
328
|
+
if pid:
|
|
329
|
+
print("note: a background loop is running the OLD version — "
|
|
330
|
+
"`agentos-auto stop` then `agentos-auto` to run the new one.")
|
|
331
|
+
else:
|
|
332
|
+
print(f"already up to date (v{after})")
|
|
276
333
|
return
|
|
277
334
|
|
|
278
335
|
if cmd == "stop":
|
|
@@ -282,6 +339,7 @@ def main() -> None:
|
|
|
282
339
|
_set_stop()
|
|
283
340
|
pid = _daemon_running()
|
|
284
341
|
cur = _read_current()
|
|
342
|
+
backlogged = _read_backlogged() # tickets THIS run parked in Backlog
|
|
285
343
|
|
|
286
344
|
# 1. Kill the loop's ENTIRE process group first (daemon + any child
|
|
287
345
|
# `claude` it spawned) so nothing keeps mutating the ticket. The daemon
|
|
@@ -318,17 +376,31 @@ def main() -> None:
|
|
|
318
376
|
else:
|
|
319
377
|
print("no background loop running — stop flag set for any foreground run.")
|
|
320
378
|
|
|
321
|
-
# 2. Now that the loop is dead, move the in-flight ticket
|
|
322
|
-
|
|
323
|
-
|
|
379
|
+
# 2. Now that the loop is dead, move the in-flight ticket AND every ticket
|
|
380
|
+
# this run parked in Backlog (e.g. 15-min approval timeouts) back to
|
|
381
|
+
# To Do, so a later run re-attempts them. Human-parked Backlog tickets
|
|
382
|
+
# are untouched — only ids this run recorded are restored.
|
|
383
|
+
to_restore = [t for t in ({cur} | backlogged) if t]
|
|
384
|
+
if to_restore:
|
|
385
|
+
async def _restore(ids: list[str]) -> int:
|
|
324
386
|
from sdk.client import BackendClient
|
|
325
|
-
|
|
326
|
-
|
|
387
|
+
client = BackendClient.from_config()
|
|
388
|
+
done = 0
|
|
389
|
+
for t in ids:
|
|
390
|
+
try:
|
|
391
|
+
if await client.move_to_todo(t):
|
|
392
|
+
done += 1
|
|
393
|
+
except Exception: # noqa: BLE001
|
|
394
|
+
pass
|
|
395
|
+
return done
|
|
396
|
+
try:
|
|
397
|
+
n = asyncio.run(_restore(to_restore))
|
|
398
|
+
print(f"restored {n}/{len(to_restore)} ticket(s) → To Do")
|
|
327
399
|
except Exception as exc: # noqa: BLE001
|
|
328
|
-
print(f"warning: could not
|
|
400
|
+
print(f"warning: could not restore tickets to To Do ({exc})")
|
|
329
401
|
|
|
330
402
|
# 3. Clean up state files.
|
|
331
|
-
for f in (_PID_FILE, _CURRENT_FILE):
|
|
403
|
+
for f in (_PID_FILE, _CURRENT_FILE, _BACKLOGGED_FILE):
|
|
332
404
|
try:
|
|
333
405
|
f.unlink()
|
|
334
406
|
except FileNotFoundError:
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/dependency_links.txt
RENAMED
|
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
|