devops-bot-sdk 1.4.62__tar.gz → 1.4.65__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.62 → devops_bot_sdk-1.4.65}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/client.py +3 -3
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/ipc/handlers.py +100 -19
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/README.md +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/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.65"
|
|
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.65
|
|
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.65"
|
|
34
34
|
__author__ = "AgentOS"
|
|
35
35
|
|
|
36
36
|
from sdk.client import BackendClient
|
|
@@ -58,7 +58,7 @@ def _notify_status(task_id: str, status: str) -> None:
|
|
|
58
58
|
pass
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
SDK_VERSION = "1.4.
|
|
61
|
+
SDK_VERSION = "1.4.65"
|
|
62
62
|
_POLL_INTERVAL = 3.0
|
|
63
63
|
_POLL_TIMEOUT = 600.0
|
|
64
64
|
_ORCHESTRATE_TIMEOUT = 2700.0 # 45 min — covers approval wait + VPS execution time
|
|
@@ -821,7 +821,7 @@ class BackendClient:
|
|
|
821
821
|
# field like `modelUsed` can repeat.
|
|
822
822
|
files = [(k, (None, str(v))) for k, v in fields.items() if v is not None]
|
|
823
823
|
for m in (model_used or []):
|
|
824
|
-
files.append(("modelUsed", (None, str(m))))
|
|
824
|
+
files.append(("modelUsed[]", (None, str(m)))) # [] → backend parses as array
|
|
825
825
|
try:
|
|
826
826
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
827
827
|
resp = await client.put(url, files=files, headers=headers)
|
|
@@ -850,7 +850,7 @@ class BackendClient:
|
|
|
850
850
|
headers.update({"accept": "*/*", "developer-name": "null", "product": ""})
|
|
851
851
|
files = [(k, (None, str(v))) for k, v in fields.items() if v is not None]
|
|
852
852
|
for m in (model_used or []):
|
|
853
|
-
files.append(("modelUsed", (None, str(m))))
|
|
853
|
+
files.append(("modelUsed[]", (None, str(m)))) # [] → backend parses as array
|
|
854
854
|
try:
|
|
855
855
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
856
856
|
resp = await client.post(url, files=files, headers=headers)
|
|
@@ -371,6 +371,50 @@ def _parse_scope(recon_text: str | None) -> str:
|
|
|
371
371
|
return "in"
|
|
372
372
|
|
|
373
373
|
|
|
374
|
+
# Heartbeat cadence for long, quiet local runs (PRD generation, validation) so the
|
|
375
|
+
# log shows liveness even when the agent is generating text with no tool calls.
|
|
376
|
+
_HEARTBEAT_S = float(os.getenv("AGENTOS_HEARTBEAT_S", "") or 20.0)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
async def _run_with_heartbeat(coro, *, send: Callable, thread_id: str, label: str,
|
|
380
|
+
name: str | None = None, interval: float = _HEARTBEAT_S):
|
|
381
|
+
"""Await `coro` while emitting a periodic 'still working (Ns)' step_update, so a
|
|
382
|
+
long-but-quiet local run (e.g. PRD generation) never looks frozen in the log."""
|
|
383
|
+
import time as _t
|
|
384
|
+
|
|
385
|
+
prefix = f"[{name}] " if name else ""
|
|
386
|
+
stop = asyncio.Event()
|
|
387
|
+
started = _t.monotonic()
|
|
388
|
+
|
|
389
|
+
async def _beat() -> None:
|
|
390
|
+
try:
|
|
391
|
+
while not stop.is_set():
|
|
392
|
+
try:
|
|
393
|
+
await asyncio.wait_for(stop.wait(), timeout=interval)
|
|
394
|
+
return
|
|
395
|
+
except asyncio.TimeoutError:
|
|
396
|
+
pass
|
|
397
|
+
await send(Envelope(
|
|
398
|
+
type="step_update", thread_id=thread_id,
|
|
399
|
+
data={"status": "running",
|
|
400
|
+
"current_step": f"{prefix}{label} … still working "
|
|
401
|
+
f"({int(_t.monotonic() - started)}s)"},
|
|
402
|
+
).model_dump())
|
|
403
|
+
except asyncio.CancelledError:
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
beat = asyncio.create_task(_beat())
|
|
407
|
+
try:
|
|
408
|
+
return await coro
|
|
409
|
+
finally:
|
|
410
|
+
stop.set()
|
|
411
|
+
beat.cancel()
|
|
412
|
+
try:
|
|
413
|
+
await beat
|
|
414
|
+
except Exception: # noqa: BLE001
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
|
|
374
418
|
# ── Epic decomposition ────────────────────────────────────────────────────
|
|
375
419
|
# An EPIC is not coded directly. It is analysed (grounded in the codebase when a
|
|
376
420
|
# project path exists) into a detailed user-story description + an implementation
|
|
@@ -624,9 +668,14 @@ async def _validate_ticket_or_reject(
|
|
|
624
668
|
we never block a ticket on our own infrastructure error."""
|
|
625
669
|
from sdk import local_exec
|
|
626
670
|
|
|
671
|
+
from pathlib import Path
|
|
672
|
+
|
|
627
673
|
tid = str(task.get("id", ""))
|
|
628
674
|
jira_key = task.get("jiraIssueKey") or task.get("jiraKey", "") or kind.upper()
|
|
629
|
-
|
|
675
|
+
# Validation is a pure text judgment — run it in a guaranteed-writable scratch
|
|
676
|
+
# dir, never the resolved project path (which may not exist yet or be unwritable,
|
|
677
|
+
# e.g. a code-reference like /components). This decouples the gate from the repo.
|
|
678
|
+
scratch = str(Path.home() / ".agentos" / "validate")
|
|
630
679
|
|
|
631
680
|
await send(Envelope(
|
|
632
681
|
type="step_update", thread_id=thread_id,
|
|
@@ -634,11 +683,14 @@ async def _validate_ticket_or_reject(
|
|
|
634
683
|
"current_step": f"{kind.title()} {jira_key}: validating ticket against the standard"},
|
|
635
684
|
).model_dump())
|
|
636
685
|
|
|
637
|
-
res = await
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
686
|
+
res = await _run_with_heartbeat(
|
|
687
|
+
local_exec.run_claude_local(
|
|
688
|
+
_build_validation_prompt(kind, jira_key, task.get("summary", "") or "",
|
|
689
|
+
task.get("description"), task.get("humanInstructions")),
|
|
690
|
+
scratch, allowed_tools=_RECON_ALLOWED_TOOLS, # read-only judgment, scratch dir
|
|
691
|
+
github_token=await client.github_token(),
|
|
692
|
+
),
|
|
693
|
+
send=send, thread_id=thread_id, label=f"{kind.title()} {jira_key}: validating",
|
|
642
694
|
)
|
|
643
695
|
if not res.get("ok"):
|
|
644
696
|
await client.log_activity(
|
|
@@ -1237,13 +1289,16 @@ async def _code_one_repo(
|
|
|
1237
1289
|
data={"status": "running",
|
|
1238
1290
|
"current_step": f"[{name}] Recon — exploring the repo to ground the plan"},
|
|
1239
1291
|
).model_dump())
|
|
1240
|
-
recon = await
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1292
|
+
recon = await _run_with_heartbeat(
|
|
1293
|
+
local_exec.run_claude_local(
|
|
1294
|
+
_build_recon_prompt(
|
|
1295
|
+
name, pending["user_input"], pending.get("structured_plan"),
|
|
1296
|
+
pending.get("human_instructions"),
|
|
1297
|
+
),
|
|
1298
|
+
repo_path, on_event=_on_event, github_token=gh_token,
|
|
1299
|
+
allowed_tools=_RECON_ALLOWED_TOOLS, # read-only: no Write/Edit/Bash
|
|
1244
1300
|
),
|
|
1245
|
-
|
|
1246
|
-
allowed_tools=_RECON_ALLOWED_TOOLS, # read-only: no Write/Edit/Bash
|
|
1301
|
+
send=send, thread_id=thread_id, label="Exploring the repo (recon)", name=name,
|
|
1247
1302
|
)
|
|
1248
1303
|
recon_session = recon.get("session_id")
|
|
1249
1304
|
# A usage-limit hit during recon must be surfaced so the loop waits/retries
|
|
@@ -1343,9 +1398,13 @@ async def _code_one_repo(
|
|
|
1343
1398
|
"code you found. Output ONLY the PRD as Markdown, following this format "
|
|
1344
1399
|
"exactly:\n\n" + _PRD_FORMAT
|
|
1345
1400
|
)
|
|
1346
|
-
prd_res = await
|
|
1347
|
-
|
|
1348
|
-
|
|
1401
|
+
prd_res = await _run_with_heartbeat(
|
|
1402
|
+
local_exec.run_claude_local(
|
|
1403
|
+
instruction, repo_path, on_event=_on_event, # stream tool activity
|
|
1404
|
+
allowed_tools=_RECON_ALLOWED_TOOLS, # read-only
|
|
1405
|
+
github_token=gh_token, resume_session_id=recon_session,
|
|
1406
|
+
),
|
|
1407
|
+
send=send, thread_id=thread_id, label="Working on the PRD", name=name,
|
|
1349
1408
|
)
|
|
1350
1409
|
recon_session = prd_res.get("session_id") or recon_session
|
|
1351
1410
|
prd = (prd_res.get("result") or "").strip() if prd_res.get("ok") else ""
|
|
@@ -2588,6 +2647,16 @@ def _extract_repo_targets(text: str | None) -> list[dict]:
|
|
|
2588
2647
|
for p in _extract_all_paths(text):
|
|
2589
2648
|
name = Path(p).name
|
|
2590
2649
|
url = by_name.get(name.lower())
|
|
2650
|
+
# Ignore bare code-reference paths mentioned in the instructions (e.g.
|
|
2651
|
+
# "use shared components in `/components`") that are neither a real repo
|
|
2652
|
+
# (paired with a GitHub URL) nor an existing local directory. Without this
|
|
2653
|
+
# a path like /components gets picked as the work dir → Permission denied.
|
|
2654
|
+
if not url:
|
|
2655
|
+
try:
|
|
2656
|
+
if not Path(p).expanduser().is_dir():
|
|
2657
|
+
continue
|
|
2658
|
+
except OSError:
|
|
2659
|
+
continue
|
|
2591
2660
|
targets.append({
|
|
2592
2661
|
"path": p,
|
|
2593
2662
|
"url": url,
|
|
@@ -2626,13 +2695,25 @@ def _resolve_local_project_path(task: dict, payload: dict) -> str:
|
|
|
2626
2695
|
if overrides.get(tid):
|
|
2627
2696
|
return str(Path(overrides[tid]).expanduser())
|
|
2628
2697
|
|
|
2629
|
-
# PRIMARY — honour the location the human wrote in the Jira task.
|
|
2698
|
+
# PRIMARY — honour the location the human wrote in the Jira task. Prefer a REAL
|
|
2699
|
+
# repo (a path paired with a GitHub URL), then any surviving repo target
|
|
2700
|
+
# (existing local dir), then a bare path that actually exists — never a
|
|
2701
|
+
# code-reference path like `/components` that doesn't exist on disk.
|
|
2630
2702
|
for field in ("humanInstructions", "description"):
|
|
2631
|
-
|
|
2632
|
-
if
|
|
2703
|
+
text = task.get(field)
|
|
2704
|
+
if not text:
|
|
2705
|
+
continue
|
|
2706
|
+
targets = _extract_repo_targets(text) # spurious/non-existent paths filtered out
|
|
2707
|
+
with_url = next((t["path"] for t in targets if t.get("url")), None)
|
|
2708
|
+
if with_url:
|
|
2709
|
+
return with_url
|
|
2710
|
+
if targets:
|
|
2711
|
+
return targets[0]["path"]
|
|
2712
|
+
found = _extract_path_from_text(text)
|
|
2713
|
+
if found and Path(found).expanduser().exists():
|
|
2633
2714
|
return found
|
|
2634
2715
|
|
|
2635
|
-
# FALLBACK —
|
|
2716
|
+
# FALLBACK — no usable location instructed anywhere.
|
|
2636
2717
|
root = payload.get("workspace_root") or str(Path.home() / "AgentOS")
|
|
2637
2718
|
key = (
|
|
2638
2719
|
task.get("projectKey")
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.62 → devops_bot_sdk-1.4.65}/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
|