devops-bot-sdk 1.4.70__tar.gz → 1.4.74__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.70 → devops_bot_sdk-1.4.74}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/client.py +1 -1
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/ipc/handlers.py +88 -19
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/local_exec.py +6 -4
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/README.md +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/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.74"
|
|
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.74
|
|
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.74"
|
|
34
34
|
__author__ = "AgentOS"
|
|
35
35
|
|
|
36
36
|
from sdk.client import BackendClient
|
|
@@ -415,6 +415,56 @@ async def _run_with_heartbeat(coro, *, send: Callable, thread_id: str, label: st
|
|
|
415
415
|
pass
|
|
416
416
|
|
|
417
417
|
|
|
418
|
+
async def _run_generation(
|
|
419
|
+
prompt: str, project_path: str, *, send: Callable, thread_id: str, label: str,
|
|
420
|
+
github_token: str | None = None, resume_session_id: str | None = None,
|
|
421
|
+
name: str | None = None,
|
|
422
|
+
):
|
|
423
|
+
"""Run a read-only generation (epic decomposition / PRD) with a heartbeat AND
|
|
424
|
+
output-token-cap chunking: if a single response is cut off at the per-response
|
|
425
|
+
output cap, resume the session and continue — concatenating the pieces — so a
|
|
426
|
+
large result (a big JSON / PRD) completes across chunks instead of truncating.
|
|
427
|
+
"""
|
|
428
|
+
from sdk import local_exec
|
|
429
|
+
|
|
430
|
+
cap = local_exec._MAX_OUTPUT_TOKENS
|
|
431
|
+
res = await _run_with_heartbeat(
|
|
432
|
+
local_exec.run_claude_local(
|
|
433
|
+
prompt, project_path, allowed_tools=_RECON_ALLOWED_TOOLS,
|
|
434
|
+
github_token=github_token, resume_session_id=resume_session_id, max_output_tokens=cap,
|
|
435
|
+
),
|
|
436
|
+
send=send, thread_id=thread_id, label=label, name=name,
|
|
437
|
+
)
|
|
438
|
+
combined = res.get("result") or ""
|
|
439
|
+
sid = res.get("session_id")
|
|
440
|
+
chunks = 0
|
|
441
|
+
while res.get("output_cap_exceeded") and sid and chunks < _MAX_OUTPUT_CAP_RESUMES:
|
|
442
|
+
chunks += 1
|
|
443
|
+
cap = min(cap * 2, 128000)
|
|
444
|
+
await send(Envelope(
|
|
445
|
+
type="step_update", thread_id=thread_id,
|
|
446
|
+
data={"status": "running",
|
|
447
|
+
"current_step": (f"[{name}] " if name else "") + f"{label}: response hit the "
|
|
448
|
+
f"output-token cap — continuing in chunks (part {chunks + 1})"},
|
|
449
|
+
).model_dump())
|
|
450
|
+
res = await _run_with_heartbeat(
|
|
451
|
+
local_exec.run_claude_local(
|
|
452
|
+
"Your previous response was cut off at the output-token limit. Continue "
|
|
453
|
+
"EXACTLY where you left off — output ONLY the remaining content, with no "
|
|
454
|
+
"repetition and no preamble, so the two pieces concatenate seamlessly.",
|
|
455
|
+
project_path, allowed_tools=_RECON_ALLOWED_TOOLS,
|
|
456
|
+
github_token=github_token, resume_session_id=sid, max_output_tokens=cap,
|
|
457
|
+
),
|
|
458
|
+
send=send, thread_id=thread_id, label=f"{label} (cont.)", name=name,
|
|
459
|
+
)
|
|
460
|
+
combined += res.get("result") or ""
|
|
461
|
+
sid = res.get("session_id") or sid
|
|
462
|
+
out = dict(res)
|
|
463
|
+
out["result"] = combined # the full, reassembled output
|
|
464
|
+
out["session_id"] = sid
|
|
465
|
+
return out
|
|
466
|
+
|
|
467
|
+
|
|
418
468
|
# ── Epic decomposition ────────────────────────────────────────────────────
|
|
419
469
|
# An EPIC is not coded directly. It is analysed (grounded in the codebase when a
|
|
420
470
|
# project path exists) into a detailed user-story description + an implementation
|
|
@@ -547,10 +597,11 @@ def _extract_json_block(text: str | None) -> dict | None:
|
|
|
547
597
|
return None
|
|
548
598
|
import json as _json
|
|
549
599
|
candidates = re.findall(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.S | re.I)
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
600
|
+
# ALWAYS also consider the widest brace span — covers a missing/late closing
|
|
601
|
+
# fence, extra prose around the block, or a fenced capture that fails to parse.
|
|
602
|
+
start, end = text.find("{"), text.rfind("}")
|
|
603
|
+
if start != -1 and end > start:
|
|
604
|
+
candidates.append(text[start:end + 1])
|
|
554
605
|
for c in reversed(candidates):
|
|
555
606
|
try:
|
|
556
607
|
obj = _json.loads(c)
|
|
@@ -800,15 +851,32 @@ async def _handle_epic(
|
|
|
800
851
|
await graphify.prepare(project_path)
|
|
801
852
|
except Exception: # noqa: BLE001
|
|
802
853
|
pass
|
|
803
|
-
gen = await
|
|
854
|
+
gen = await _run_generation(
|
|
804
855
|
_build_epic_prompt(jira_key, summary, description, human),
|
|
805
|
-
project_path,
|
|
856
|
+
project_path, send=send, thread_id=thread_id,
|
|
857
|
+
label=f"Epic {jira_key}: generating user stories",
|
|
806
858
|
github_token=await client.github_token(),
|
|
807
859
|
)
|
|
808
|
-
|
|
860
|
+
# A usage-limit hit must stop and retry later. For ANY other outcome, still try
|
|
861
|
+
# to parse the JSON — the model often produces a valid decomposition even when the
|
|
862
|
+
# run is flagged is_error (e.g. a stray read-only tool denial during exploration),
|
|
863
|
+
# and the result then lands in `error`. Don't discard that work over an is_error flag.
|
|
864
|
+
if gen.get("limit_exceeded"):
|
|
865
|
+
await client.log_activity(f"Epic {jira_key}: usage limit during decomposition — will retry after reset")
|
|
866
|
+
await client.move_to_backlog(tid)
|
|
867
|
+
await send(Envelope(
|
|
868
|
+
type=Envelope.TYPE_ERROR, thread_id=thread_id,
|
|
869
|
+
data={"code": "EPIC_DECOMPOSE_LIMIT", "task_id": tid,
|
|
870
|
+
"message": "Claude usage limit reached during epic decomposition"},
|
|
871
|
+
).model_dump())
|
|
872
|
+
return
|
|
873
|
+
doc = _extract_json_block(gen.get("result") or gen.get("error") or "")
|
|
809
874
|
stories = (doc or {}).get("stories") or []
|
|
810
875
|
if not doc or not stories:
|
|
811
|
-
|
|
876
|
+
had_output = bool(gen.get("result") or gen.get("error"))
|
|
877
|
+
reason = ("model output did not contain a parseable decomposition JSON"
|
|
878
|
+
if had_output else
|
|
879
|
+
(gen.get("error") or "no output from the decomposition run"))[:300]
|
|
812
880
|
await client.log_activity(f"Epic {jira_key}: decomposition failed — {reason}")
|
|
813
881
|
await client.move_to_backlog(tid)
|
|
814
882
|
await send(Envelope(
|
|
@@ -1035,12 +1103,15 @@ async def _handle_story(
|
|
|
1035
1103
|
await graphify.prepare(project_path)
|
|
1036
1104
|
except Exception: # noqa: BLE001
|
|
1037
1105
|
pass
|
|
1038
|
-
gen = await
|
|
1106
|
+
gen = await _run_generation(
|
|
1039
1107
|
_build_prd_prompt(jira_key, summary, user_story, human),
|
|
1040
|
-
project_path,
|
|
1108
|
+
project_path, send=send, thread_id=thread_id,
|
|
1109
|
+
label=f"Story {jira_key}: generating PRD",
|
|
1041
1110
|
github_token=await client.github_token(),
|
|
1042
1111
|
)
|
|
1043
|
-
|
|
1112
|
+
# Use the output regardless of an is_error flag (a stray read-only tool denial
|
|
1113
|
+
# shouldn't discard a PRD the model actually produced); only a usage limit is fatal.
|
|
1114
|
+
prd = "" if gen.get("limit_exceeded") else (gen.get("result") or "").strip()
|
|
1044
1115
|
if prd:
|
|
1045
1116
|
# PRD lives alongside the user story in the (now Task) description.
|
|
1046
1117
|
new_description = f"{user_story}\n\n---\n\n{_PRD_MARKER}\n\n{prd}"[:20000]
|
|
@@ -1471,16 +1542,14 @@ async def _code_one_repo(
|
|
|
1471
1542
|
"code you found. Output ONLY the PRD as Markdown, following this format "
|
|
1472
1543
|
"exactly:\n\n" + _PRD_FORMAT
|
|
1473
1544
|
)
|
|
1474
|
-
prd_res = await
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
github_token=gh_token, resume_session_id=recon_session,
|
|
1479
|
-
),
|
|
1480
|
-
send=send, thread_id=thread_id, label="Working on the PRD", name=name,
|
|
1545
|
+
prd_res = await _run_generation(
|
|
1546
|
+
instruction, repo_path, send=send, thread_id=thread_id,
|
|
1547
|
+
label="Working on the PRD", name=name,
|
|
1548
|
+
github_token=gh_token, resume_session_id=recon_session, # continue the recon session
|
|
1481
1549
|
)
|
|
1482
1550
|
recon_session = prd_res.get("session_id") or recon_session
|
|
1483
|
-
|
|
1551
|
+
# Use the PRD regardless of an is_error flag; only skip on a real usage limit.
|
|
1552
|
+
prd = "" if prd_res.get("limit_exceeded") else (prd_res.get("result") or "").strip()
|
|
1484
1553
|
if prd:
|
|
1485
1554
|
enriched = f"{before}{_PRD_MARKER}\n\n{prd}"[:20000]
|
|
1486
1555
|
pending["user_input"] = enriched # shared across repos → reused
|
|
@@ -509,10 +509,12 @@ async def run_claude_local(
|
|
|
509
509
|
result_text = str(result_event.get("result", ""))
|
|
510
510
|
subtype = str(result_event.get("subtype", ""))
|
|
511
511
|
|
|
512
|
-
# Claude USAGE/RATE limit
|
|
513
|
-
#
|
|
514
|
-
#
|
|
515
|
-
|
|
512
|
+
# Claude USAGE/RATE limit. A real limit ABORTS the run (is_error) with a limit
|
|
513
|
+
# message, or the CLI flags it in the subtype. Do NOT infer a limit from a
|
|
514
|
+
# SUCCESSFUL run's result text — a task whose OUTPUT legitimately mentions
|
|
515
|
+
# "usage limit" / "rate limit" (e.g. building an observability/quota feature)
|
|
516
|
+
# would otherwise false-positive as a rate-limit hit.
|
|
517
|
+
limit_hit = "limit" in subtype.lower() or (is_error and _is_limit(result_text))
|
|
516
518
|
reset_epoch = _parse_reset_epoch(result_text)
|
|
517
519
|
|
|
518
520
|
err_text = None
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.70 → devops_bot_sdk-1.4.74}/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
|