devops-bot-sdk 1.4.23__tar.gz → 1.4.25__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.23 → devops_bot_sdk-1.4.25}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/pyproject.toml +1 -1
  4. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/client.py +1 -1
  6. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/ipc/handlers.py +14 -4
  7. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/local_exec.py +49 -3
  8. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/updater.py +10 -3
  9. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/README.md +0 -0
  10. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  11. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  12. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  13. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/devops_bot_sdk.egg-info/requires.txt +0 -0
  14. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  15. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/collectors/__init__.py +0 -0
  16. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/collectors/files.py +0 -0
  17. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/collectors/process.py +0 -0
  18. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/collectors/screenshot.py +0 -0
  19. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/config.py +0 -0
  20. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/crucial.py +0 -0
  21. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/exceptions.py +0 -0
  22. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/git_ops.py +0 -0
  23. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/graphify.py +0 -0
  24. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/hooks/__init__.py +0 -0
  25. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/hooks/crucial_guard.py +0 -0
  26. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/ipc/__init__.py +0 -0
  27. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/ipc/electron_bridge.py +0 -0
  28. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/models/__init__.py +0 -0
  29. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/models/envelope.py +0 -0
  30. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/models/requests.py +0 -0
  31. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/models/responses.py +0 -0
  32. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/models/snapshots.py +0 -0
  33. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/py.typed +0 -0
  34. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/run_auto.py +0 -0
  35. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/sse.py +0 -0
  36. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/test.py +0 -0
  37. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/sdk/test_pipeline.py +0 -0
  38. {devops_bot_sdk-1.4.23 → devops_bot_sdk-1.4.25}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.23
3
+ Version: 1.4.25
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.23
3
+ Version: 1.4.25
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.23"
7
+ version = "1.4.25"
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.23
3
+ Version: 1.4.25
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.23"
33
+ __version__ = "1.4.25"
34
34
  __author__ = "AgentOS"
35
35
 
36
36
  from sdk.client import BackendClient
@@ -36,7 +36,7 @@ from sdk.sse import _check_status, stream_with_reconnect
36
36
 
37
37
  logger = logging.getLogger(__name__)
38
38
 
39
- SDK_VERSION = "1.4.23"
39
+ SDK_VERSION = "1.4.25"
40
40
  _POLL_INTERVAL = 3.0
41
41
  _POLL_TIMEOUT = 600.0
42
42
  _ORCHESTRATE_TIMEOUT = 2700.0 # 45 min — covers approval wait + VPS execution time
@@ -1871,13 +1871,23 @@ async def handle_orchestrate_auto_local_run(
1871
1871
  ).model_dump())
1872
1872
  return
1873
1873
  continue # re-run this ticket from phase 1
1874
- # Out of retries leave it in Backlog for a later run.
1874
+ # Out of retries. The usage/session limit is ACCOUNT-WIDE, so
1875
+ # every remaining ticket would hit the same wall and burn its
1876
+ # own retry budget (up to 6h each). Pause the whole run instead
1877
+ # of marching on — this ticket and the rest stay in To Do/
1878
+ # Backlog, and `agentos-auto resume` picks them up after reset.
1875
1879
  await send(Envelope(
1876
1880
  type="step_update", thread_id=thread_id,
1877
- data={"current_step": f"{jira_key}: usage limit — max retries reached, "
1878
- f"left in Backlog"},
1881
+ data={"current_step": f"{jira_key}: usage limit — max retries reached; "
1882
+ f"pausing run (resume after the window resets)"},
1879
1883
  ).model_dump())
1880
- break
1884
+ await send(Envelope(
1885
+ type=Envelope.TYPE_DONE, thread_id=thread_id,
1886
+ data={"mode": "orchestrate-auto-local", "status": "paused",
1887
+ "reason": "usage_limit", "processed": processed,
1888
+ "task_count": total},
1889
+ ).model_dump())
1890
+ return
1881
1891
 
1882
1892
  processed += 1
1883
1893
  break
@@ -23,7 +23,7 @@ import os
23
23
  import re
24
24
  import shutil
25
25
  import sys
26
- from datetime import datetime
26
+ from datetime import datetime, timedelta
27
27
  from pathlib import Path
28
28
  from typing import Any, Awaitable, Callable
29
29
 
@@ -86,9 +86,21 @@ _LIMIT_MARKERS = (
86
86
  "usage limit reached", "usage limit", "rate limit", "rate_limit",
87
87
  "too many requests", "limit will reset", "limit reached", "overloaded_error",
88
88
  "resets at", "429",
89
+ # Newer Claude Code CLI wording: "You've hit your session limit · resets 10:20pm
90
+ # (Asia/Karachi)" and "You've hit your usage limit". Without these the message
91
+ # fell through to the generic-failure path and the auto-loop never waited for
92
+ # the window to reset — every following ticket hit the same limit and cascaded
93
+ # to Backlog.
94
+ "session limit", "you've hit your", "youve hit your",
89
95
  )
90
96
  # A unix-seconds reset timestamp claude sometimes appends (e.g. "...reached|1719500400").
91
97
  _RESET_EPOCH_RE = re.compile(r"\b(1[6-9]\d{8}|20\d{8})\b")
98
+ # A wall-clock reset the CLI prints instead, e.g. "resets 10:20pm (Asia/Karachi)"
99
+ # or "resets at 10pm". Captures hour, optional minutes, am/pm, optional IANA tz.
100
+ _RESET_CLOCK_RE = re.compile(
101
+ r"reset[s]?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*([ap]m)\b(?:\s*\(([^)]+)\))?",
102
+ re.IGNORECASE,
103
+ )
92
104
  # Fallback wait when the limit message carries no reset time (env-tunable).
93
105
  _LIMIT_DEFAULT_WAIT_S = _f("AGENTOS_LIMIT_DEFAULT_WAIT_S", 60 * 60.0) # 1 h
94
106
 
@@ -99,7 +111,15 @@ def _is_limit(text: str | None) -> bool:
99
111
 
100
112
 
101
113
  def _parse_reset_epoch(text: str | None) -> int | None:
102
- """A unix-seconds reset time embedded in a limit message, if present."""
114
+ """A unix-seconds reset time embedded in a limit message, if present.
115
+
116
+ Handles two shapes the CLI uses:
117
+ 1. A literal unix timestamp (e.g. "...reached|1719500400").
118
+ 2. A wall-clock time (e.g. "resets 10:20pm (Asia/Karachi)") — resolved to
119
+ the next future occurrence of that time in the named tz (local tz when
120
+ none/unknown). Returns None when neither is present, so callers fall
121
+ back to the default wait.
122
+ """
103
123
  if not text:
104
124
  return None
105
125
  m = _RESET_EPOCH_RE.search(text)
@@ -108,7 +128,33 @@ def _parse_reset_epoch(text: str | None) -> int | None:
108
128
  return int(m.group(1))
109
129
  except ValueError:
110
130
  return None
111
- return None
131
+ return _parse_reset_clock(text)
132
+
133
+
134
+ def _parse_reset_clock(text: str) -> int | None:
135
+ """Resolve a 'resets 10:20pm (Asia/Karachi)'-style reset to a unix epoch."""
136
+ m = _RESET_CLOCK_RE.search(text)
137
+ if not m:
138
+ return None
139
+ try:
140
+ hour = int(m.group(1)) % 12 # 12am → 0, 12pm → 0 (+12 below)
141
+ minute = int(m.group(2) or 0)
142
+ if m.group(3).lower() == "pm":
143
+ hour += 12
144
+ tz = None
145
+ if m.group(4):
146
+ try:
147
+ from zoneinfo import ZoneInfo
148
+ tz = ZoneInfo(m.group(4).strip())
149
+ except Exception: # noqa: BLE001 — unknown tz → local time
150
+ tz = None
151
+ now = datetime.now(tz)
152
+ target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
153
+ if target <= now: # already passed today → next day
154
+ target += timedelta(days=1)
155
+ return int(target.timestamp())
156
+ except Exception: # noqa: BLE001 — never let reset parsing break limit handling
157
+ return None
112
158
 
113
159
 
114
160
  # ── Per-run claude log file (on the user's machine, local-timezone stamps) ──
@@ -184,11 +184,18 @@ def _fetch_github_token() -> str | None:
184
184
  # ── Core update logic ─────────────────────────────────────────────────────────
185
185
 
186
186
  def _install_candidates() -> list[str]:
187
- """Ordered pip targets: detected URLSSH → token-HTTPS branch → PyPI dist."""
188
- cands = list(_unique([_detect_install_url(), _GITHUB_SSH]))
187
+ """Ordered pip targets: SSH (default)detected URL → token-HTTPS branch → PyPI dist.
188
+
189
+ SSH is tried FIRST on every platform (Windows + Linux) so the normal upgrade
190
+ path uses the user's git SSH key — no token required. HTTPS-with-token is only
191
+ a fallback for boxes that have no SSH key configured.
192
+ """
193
+ branch = _sdk_branch()
194
+ ssh_branch = f"{_GITHUB_SSH}@{branch}" # honour AGENTOS_SDK_BRANCH (default 'main')
195
+ cands = list(_unique([ssh_branch, _GITHUB_SSH, _detect_install_url()]))
189
196
  gh_token = _fetch_github_token()
190
197
  if gh_token:
191
- cands.append(f"git+https://{gh_token}@github.com/{_GITHUB_REPO}.git@{_sdk_branch()}")
198
+ cands.append(f"git+https://{gh_token}@github.com/{_GITHUB_REPO}.git@{branch}")
192
199
  cands.append(_DIST)
193
200
  return cands
194
201