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.
Files changed (38) hide show
  1. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/pyproject.toml +1 -1
  4. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/client.py +26 -1
  6. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/run_auto.py +90 -18
  7. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/README.md +0 -0
  8. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  9. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  10. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  11. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/requires.txt +0 -0
  12. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  13. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/collectors/__init__.py +0 -0
  14. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/collectors/files.py +0 -0
  15. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/collectors/process.py +0 -0
  16. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/collectors/screenshot.py +0 -0
  17. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/config.py +0 -0
  18. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/crucial.py +0 -0
  19. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/exceptions.py +0 -0
  20. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/git_ops.py +0 -0
  21. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/graphify.py +0 -0
  22. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/hooks/__init__.py +0 -0
  23. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/hooks/crucial_guard.py +0 -0
  24. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/ipc/__init__.py +0 -0
  25. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/ipc/electron_bridge.py +0 -0
  26. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/ipc/handlers.py +0 -0
  27. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/local_exec.py +0 -0
  28. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/__init__.py +0 -0
  29. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/envelope.py +0 -0
  30. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/requests.py +0 -0
  31. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/responses.py +0 -0
  32. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/models/snapshots.py +0 -0
  33. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/py.typed +0 -0
  34. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/sse.py +0 -0
  35. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/test.py +0 -0
  36. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/test_pipeline.py +0 -0
  37. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/sdk/updater.py +0 -0
  38. {devops_bot_sdk-1.4.31 → devops_bot_sdk-1.4.33}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.31
3
+ Version: 1.4.33
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.31
3
+ Version: 1.4.33
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devops-bot-sdk"
7
- version = "1.4.31"
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.31
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.31"
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
- SDK_VERSION = "1.4.31"
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`) and returns the in-flight ticket to "To Do" so a
33
- later run restarts it cleanly, rather than pausing after the current task. The
34
- tracked in-flight ticket id is kept in ~/.agentos/auto.current. `resume` (or just
35
- re-running) starts fresh over whatever is still To Do.
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
- # in-flight marker (the queue is drained on a clean finish).
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
- from sdk.updater import auto_update
273
- updated, cur, latest = auto_update(quiet=False)
274
- if not updated:
275
- print(f"already up to date (v{cur})" if latest else f"v{cur} (could not reach PyPI)")
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 back to To Do.
322
- if cur:
323
- try:
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
- ok = asyncio.run(BackendClient.from_config().move_to_todo(cur))
326
- print(f"in-flight ticket → To Do ({'ok' if ok else 'failed'})")
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 move in-flight ticket to To Do ({exc})")
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: