devops-bot-sdk 1.4.20__tar.gz → 1.4.23__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.20 → devops_bot_sdk-1.4.23}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/client.py +1 -1
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/ipc/handlers.py +50 -4
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/local_exec.py +36 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/README.md +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/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.23"
|
|
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.23
|
|
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.23"
|
|
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.
|
|
39
|
+
SDK_VERSION = "1.4.23"
|
|
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
|
|
@@ -296,8 +296,14 @@ async def _run_local_pipeline(
|
|
|
296
296
|
body["user_id"] = derived
|
|
297
297
|
|
|
298
298
|
# 1. Kick off the background run (server plans, gates — does NOT execute).
|
|
299
|
+
# The kickoff sends background=True and should return a session_id quickly, but
|
|
300
|
+
# under load the backend can take well over 30s to ack — which previously raised
|
|
301
|
+
# a (blank-message) ReadTimeout that sent the whole ticket to Backlog. Connect
|
|
302
|
+
# fast, but allow a generous read window so a slow-but-working ack isn't killed.
|
|
299
303
|
import httpx
|
|
300
|
-
|
|
304
|
+
_kick_read_s = float(os.getenv("AGENTOS_ORCHESTRATE_KICKOFF_TIMEOUT_S", "") or 120.0)
|
|
305
|
+
_kick_timeout = httpx.Timeout(_kick_read_s, connect=15.0)
|
|
306
|
+
async with httpx.AsyncClient(timeout=_kick_timeout) as http:
|
|
301
307
|
resp = await http.post(client._url("/api/v1/orchestrate"), json=body, headers=client._headers)
|
|
302
308
|
if resp.status_code >= 400:
|
|
303
309
|
await send(_error_envelope(thread_id, RuntimeError(f"orchestrate failed: {resp.status_code} {resp.text[:200]}")).model_dump())
|
|
@@ -560,6 +566,36 @@ async def _code_one_repo(
|
|
|
560
566
|
)
|
|
561
567
|
session_id = result.get("session_id") or session_id
|
|
562
568
|
|
|
569
|
+
# 4b. output-token-cap recovery. A single assistant turn that would exceed the
|
|
570
|
+
# CLI's per-response output cap aborts the run ("response exceeded the N output
|
|
571
|
+
# token maximum"). Resume the session with a doubled cap and an instruction to
|
|
572
|
+
# emit the work in smaller chunks, so a large dump finishes across turns instead
|
|
573
|
+
# of failing the ticket.
|
|
574
|
+
cap_resumes = 0
|
|
575
|
+
next_cap = local_exec._MAX_OUTPUT_TOKENS
|
|
576
|
+
while (not result.get("ok")) and result.get("output_cap_exceeded") \
|
|
577
|
+
and session_id and cap_resumes < _MAX_OUTPUT_CAP_RESUMES:
|
|
578
|
+
cap_resumes += 1
|
|
579
|
+
next_cap = min(next_cap * 2, 128000) # CLI/model output ceiling
|
|
580
|
+
await send(Envelope(
|
|
581
|
+
type="step_update", thread_id=thread_id,
|
|
582
|
+
data={"status": "running",
|
|
583
|
+
"current_step": f"[{name}] Response exceeded output-token cap — "
|
|
584
|
+
f"resuming with cap {next_cap} and chunked output "
|
|
585
|
+
f"(attempt {cap_resumes}/{_MAX_OUTPUT_CAP_RESUMES})"},
|
|
586
|
+
).model_dump())
|
|
587
|
+
result = await local_exec.run_claude_local(
|
|
588
|
+
"Your previous response was cut off because it exceeded the output-token "
|
|
589
|
+
"limit for a single turn. Continue from where you left off and finish the "
|
|
590
|
+
"task. Keep each response within the limit: write results incrementally to "
|
|
591
|
+
"files in smaller steps rather than emitting one very large response, and "
|
|
592
|
+
"avoid dumping large content back as chat text.",
|
|
593
|
+
repo_path, on_event=_on_event,
|
|
594
|
+
github_token=gh_token, resume_session_id=session_id,
|
|
595
|
+
approval_dir=approval_dir, max_output_tokens=next_cap,
|
|
596
|
+
)
|
|
597
|
+
session_id = result.get("session_id") or session_id
|
|
598
|
+
|
|
563
599
|
# 5. mid-run crucial-decision gate
|
|
564
600
|
if agent_mode and session_id:
|
|
565
601
|
from sdk import crucial
|
|
@@ -1529,6 +1565,9 @@ _FIRST_GATE_TIMEOUT_S = 900.0
|
|
|
1529
1565
|
_APPROVAL_POLL_TIMEOUT_S = 1200.0
|
|
1530
1566
|
# Max times a single ticket's session is resumed after a context overflow.
|
|
1531
1567
|
_MAX_CONTEXT_RESUMES = 3
|
|
1568
|
+
# Max times a ticket's session is resumed after hitting the per-response output-token
|
|
1569
|
+
# cap, each time with a doubled cap, to let a large turn finish across chunks.
|
|
1570
|
+
_MAX_OUTPUT_CAP_RESUMES = 3
|
|
1532
1571
|
# Max mid-run crucial-decision approval gates per ticket (safety bound).
|
|
1533
1572
|
_MAX_CRUCIAL_GATES = 10
|
|
1534
1573
|
# Max times a ticket is retried after hitting the Claude usage limit.
|
|
@@ -1843,12 +1882,19 @@ async def handle_orchestrate_auto_local_run(
|
|
|
1843
1882
|
processed += 1
|
|
1844
1883
|
break
|
|
1845
1884
|
except Exception as exc: # noqa: BLE001 — isolate per-ticket failures
|
|
1846
|
-
|
|
1847
|
-
|
|
1885
|
+
# str(exc) is EMPTY for some exceptions (notably TimeoutError /
|
|
1886
|
+
# asyncio.TimeoutError / CancelledError), which used to render as a
|
|
1887
|
+
# blank "failed — (continuing)" with no clue to the cause. Always
|
|
1888
|
+
# prefix the exception type so the real reason is visible; log the
|
|
1889
|
+
# full repr for after-the-fact debugging.
|
|
1890
|
+
detail = f"{type(exc).__name__}: {exc}".rstrip(": ")
|
|
1891
|
+
logger.warning("orchestrate_auto.task_failed",
|
|
1892
|
+
extra={"task_id": tid, "error": repr(exc)})
|
|
1893
|
+
await client.log_activity(f"Task {tid}: unexpected error — {detail[:200]}")
|
|
1848
1894
|
await client.move_to_backlog(tid)
|
|
1849
1895
|
await send(Envelope(
|
|
1850
1896
|
type="step_update", thread_id=thread_id,
|
|
1851
|
-
data={"current_step": f"{jira_key or tid}: failed — {
|
|
1897
|
+
data={"current_step": f"{jira_key or tid}: failed — {detail[:160]} (continuing)"},
|
|
1852
1898
|
).model_dump())
|
|
1853
1899
|
continue
|
|
1854
1900
|
|
|
@@ -62,6 +62,23 @@ _CONTEXT_MARKERS = (
|
|
|
62
62
|
"token limit", "prompt is too long", "exceeds the maximum", "context_length_exceeded",
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
+
# Markers that indicate the CLI's per-response OUTPUT-token cap was hit — distinct
|
|
66
|
+
# from a context overflow (input side) and a usage/rate limit (account side). The
|
|
67
|
+
# CLI aborts a single assistant turn that would exceed CLAUDE_CODE_MAX_OUTPUT_TOKENS
|
|
68
|
+
# (default 32000). We raise that cap below and, if it's still hit, resume the
|
|
69
|
+
# session to finish the turn in smaller chunks.
|
|
70
|
+
_OUTPUT_CAP_MARKERS = (
|
|
71
|
+
"output token maximum", "max_output_tokens", "claude_code_max_output_tokens",
|
|
72
|
+
"max output tokens", "output_tokens exceed",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Per-response output-token cap handed to the CLI via CLAUDE_CODE_MAX_OUTPUT_TOKENS.
|
|
76
|
+
# The CLI default is 32000, which a large single dump (e.g. extracting a whole
|
|
77
|
+
# taxonomy from a spreadsheet) can blow past, failing the whole ticket. 64000 is a
|
|
78
|
+
# safe headroom for the Opus/Sonnet families. Env-tunable; a value the user already
|
|
79
|
+
# exported in the environment is respected and never overridden.
|
|
80
|
+
_MAX_OUTPUT_TOKENS = int(_f("AGENTOS_MAX_OUTPUT_TOKENS", 64000.0))
|
|
81
|
+
|
|
65
82
|
# Markers that indicate the Claude USAGE/RATE limit (the N-hour window) was hit —
|
|
66
83
|
# distinct from a context overflow. The run didn't fail on the task; the account
|
|
67
84
|
# is throttled until the window resets.
|
|
@@ -162,6 +179,12 @@ def _ctx_exceeded(text: str | None) -> bool:
|
|
|
162
179
|
low = (text or "").lower()
|
|
163
180
|
return any(m in low for m in _CONTEXT_MARKERS)
|
|
164
181
|
|
|
182
|
+
|
|
183
|
+
def _output_cap_exceeded(text: str | None) -> bool:
|
|
184
|
+
"""True when the CLI aborted because the response hit the per-turn output cap."""
|
|
185
|
+
low = (text or "").lower()
|
|
186
|
+
return any(m in low for m in _OUTPUT_CAP_MARKERS)
|
|
187
|
+
|
|
165
188
|
EventHandler = Callable[[dict], Awaitable[None]]
|
|
166
189
|
|
|
167
190
|
|
|
@@ -186,6 +209,7 @@ async def run_claude_local(
|
|
|
186
209
|
resume_session_id: str | None = None,
|
|
187
210
|
approval_dir: str | None = None,
|
|
188
211
|
idle_timeout: float = _IDLE_TIMEOUT_S,
|
|
212
|
+
max_output_tokens: int | None = None,
|
|
189
213
|
) -> dict[str, Any]:
|
|
190
214
|
"""Run the local `claude` CLI non-interactively against ``project_path``.
|
|
191
215
|
|
|
@@ -258,6 +282,16 @@ async def run_claude_local(
|
|
|
258
282
|
# `git clone https://github.com/owner/repo` authenticates — without touching
|
|
259
283
|
# the user's global git config. Tokens stay in the subprocess env only.
|
|
260
284
|
env = dict(os.environ)
|
|
285
|
+
|
|
286
|
+
# Raise the CLI's per-response output-token cap. Without this the CLI errors
|
|
287
|
+
# out ("Claude's response exceeded the 32000 output token maximum") the moment
|
|
288
|
+
# a single assistant turn would exceed 32000 tokens — a large dump (extracting
|
|
289
|
+
# a whole taxonomy, generating many files at once) fails the entire ticket.
|
|
290
|
+
# A value the operator already exported wins; the `max_output_tokens` override
|
|
291
|
+
# lets a retry escalate the cap after a cap-exceeded run (see handlers.py).
|
|
292
|
+
if "CLAUDE_CODE_MAX_OUTPUT_TOKENS" not in env or max_output_tokens:
|
|
293
|
+
env["CLAUDE_CODE_MAX_OUTPUT_TOKENS"] = str(max_output_tokens or _MAX_OUTPUT_TOKENS)
|
|
294
|
+
|
|
261
295
|
if github_token:
|
|
262
296
|
env["GH_TOKEN"] = github_token
|
|
263
297
|
env["GITHUB_TOKEN"] = github_token
|
|
@@ -419,6 +453,7 @@ async def run_claude_local(
|
|
|
419
453
|
"cost_usd": 0.0, "files_changed": files_changed,
|
|
420
454
|
"session_id": session_id, "context_exceeded": _ctx_exceeded(err),
|
|
421
455
|
"limit_exceeded": _is_limit(err), "limit_reset_epoch": _parse_reset_epoch(err),
|
|
456
|
+
"output_cap_exceeded": _output_cap_exceeded(err),
|
|
422
457
|
})
|
|
423
458
|
|
|
424
459
|
usage = result_event.get("usage") or {}
|
|
@@ -453,4 +488,5 @@ async def run_claude_local(
|
|
|
453
488
|
"context_exceeded": is_error and _ctx_exceeded(err_text),
|
|
454
489
|
"limit_exceeded": limit_hit,
|
|
455
490
|
"limit_reset_epoch": reset_epoch,
|
|
491
|
+
"output_cap_exceeded": is_error and _output_cap_exceeded(err_text),
|
|
456
492
|
})
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.20 → devops_bot_sdk-1.4.23}/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
|