devops-bot-sdk 1.4.55__tar.gz → 1.4.58__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.55 → devops_bot_sdk-1.4.58}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/client.py +29 -1
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/ipc/handlers.py +122 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/README.md +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/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.58"
|
|
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.58
|
|
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.58"
|
|
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.58"
|
|
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
|
|
@@ -868,6 +868,34 @@ class BackendClient:
|
|
|
868
868
|
logger.warning("client.create_task_ml error err=%s", exc)
|
|
869
869
|
return None
|
|
870
870
|
|
|
871
|
+
async def add_comment(self, task_id: str, body: str) -> bool:
|
|
872
|
+
"""POST /tasks-ml/{id}/comments — add a comment to a ticket (best-effort).
|
|
873
|
+
|
|
874
|
+
Used by the validation gate to post AI feedback (prefixed with an
|
|
875
|
+
[AI Comments] label) when an Epic/Story is rejected. JSON body: {"body": ...}.
|
|
876
|
+
Returns True on 2xx.
|
|
877
|
+
"""
|
|
878
|
+
from sdk.config import BE_DEFAULT_URL
|
|
879
|
+
|
|
880
|
+
url = f"{BE_DEFAULT_URL.rstrip('/')}/tasks-ml/{task_id}/comments"
|
|
881
|
+
headers = {
|
|
882
|
+
**self._headers, # includes Content-Type: application/json
|
|
883
|
+
"accept": "*/*",
|
|
884
|
+
"developer-name": "null",
|
|
885
|
+
"product": "",
|
|
886
|
+
}
|
|
887
|
+
try:
|
|
888
|
+
async with httpx.AsyncClient(timeout=20.0) as client:
|
|
889
|
+
resp = await client.post(url, json={"body": str(body)}, headers=headers)
|
|
890
|
+
if resp.status_code >= 400:
|
|
891
|
+
logger.warning("client.add_comment failed task=%s status=%s body=%s",
|
|
892
|
+
task_id, resp.status_code, resp.text[:200])
|
|
893
|
+
return False
|
|
894
|
+
return True
|
|
895
|
+
except Exception as exc: # noqa: BLE001
|
|
896
|
+
logger.warning("client.add_comment error task=%s err=%s", task_id, exc)
|
|
897
|
+
return False
|
|
898
|
+
|
|
871
899
|
async def log_activity(self, activity: str) -> bool:
|
|
872
900
|
"""POST /ml-api/agent-activity — emit an agent activity event (best-effort).
|
|
873
901
|
|
|
@@ -550,6 +550,117 @@ def _epic_inherited_fields(task: dict) -> dict:
|
|
|
550
550
|
return {k: v for k, v in fields.items() if v}
|
|
551
551
|
|
|
552
552
|
|
|
553
|
+
# ── Ticket validation gate (Epics + USER-driven Stories) ────────────────────
|
|
554
|
+
# A strict quality gate: before the bot actions an Epic or a USER-created Story it
|
|
555
|
+
# checks the ticket meets the required standard. On failure it posts a harsh,
|
|
556
|
+
# technical comment (prefixed with the label below) and moves the ticket to
|
|
557
|
+
# Backlog. SYSTEM-created Stories (spun off an Epic by the bot) are trusted and
|
|
558
|
+
# skip validation. A later run re-validates; once the gaps are addressed in the
|
|
559
|
+
# description / Human Instructions, it passes and the normal flow proceeds.
|
|
560
|
+
_AI_COMMENT_LABEL = "[AI Comments]"
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _build_validation_prompt(kind: str, jira_key: str, summary: str,
|
|
564
|
+
description: str | None, human: str | None) -> str:
|
|
565
|
+
k = kind.upper()
|
|
566
|
+
parts: list[str] = [
|
|
567
|
+
f"You are a STRICT ticket-quality gate for a JIRA {k}. Decide whether this "
|
|
568
|
+
"ticket meets the required standard to be actioned autonomously. Be HARSH: if "
|
|
569
|
+
"the required structure or information is not strictly present, REJECT it — a "
|
|
570
|
+
"vague or under-specified ticket must NOT pass.",
|
|
571
|
+
f"=== {k} {jira_key} ===\nSummary: {summary or '(none)'}",
|
|
572
|
+
"Description:\n" + (_clip_text(description.strip()) if description and description.strip() else "(none provided)"),
|
|
573
|
+
"Human Instructions:\n" + (human.strip() if human and human.strip() else "(none provided)"),
|
|
574
|
+
]
|
|
575
|
+
parts.append(
|
|
576
|
+
"A VALID ticket MUST have:\n"
|
|
577
|
+
"- A clear, unambiguous goal and scope (no hand-waving).\n"
|
|
578
|
+
"- Explicit Human Instructions: for codebase work, the repository link(s) "
|
|
579
|
+
"and/or file path(s), the branch/PR convention, and build/run/test steps.\n"
|
|
580
|
+
"- Observable, testable acceptance criteria.\n"
|
|
581
|
+
+ ("- Enough concrete detail to decompose into independent user stories.\n"
|
|
582
|
+
if kind == "epic" else
|
|
583
|
+
"- A clear 'As a <role>, I want <capability>, so that <benefit>' intent plus "
|
|
584
|
+
"testable acceptance criteria.\n")
|
|
585
|
+
+ "- Any THIRD-PARTY INTEGRATION or human-effort dependency (API keys, OAuth "
|
|
586
|
+
"credentials, external accounts, secrets, manual provisioning, DNS, billing) "
|
|
587
|
+
"MUST be named explicitly WITH who provides it. If such a dependency is implied "
|
|
588
|
+
"but undefined/missing, REJECT.\n\n"
|
|
589
|
+
"Output ONLY a fenced ```json block, nothing else:\n"
|
|
590
|
+
"{\n"
|
|
591
|
+
' "valid": true or false,\n'
|
|
592
|
+
' "comment": "When invalid: a concise, TECHNICAL rejection that lists EXACTLY '
|
|
593
|
+
"what is missing or ambiguous and must be added. Use precise engineering "
|
|
594
|
+
"terminology (API contract, data model/schema, auth/OAuth flow, env/config, "
|
|
595
|
+
"acceptance criteria, target repository/branch, integration credentials & "
|
|
596
|
+
'ownership). When valid: empty string."\n'
|
|
597
|
+
"}"
|
|
598
|
+
)
|
|
599
|
+
return "\n\n".join(parts)
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _parse_validation(text: str | None) -> tuple[bool, str]:
|
|
603
|
+
"""(valid, comment) from the validator's JSON output. Defaults to invalid."""
|
|
604
|
+
obj = _extract_json_block(text) or {}
|
|
605
|
+
return bool(obj.get("valid")), str(obj.get("comment") or "").strip()
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
async def _validate_ticket_or_reject(
|
|
609
|
+
client: BackendClient, task: dict, kind: str, payload: dict,
|
|
610
|
+
send: Callable, thread_id: str,
|
|
611
|
+
) -> bool:
|
|
612
|
+
"""Strict gate. Returns True to proceed, False if the ticket was rejected
|
|
613
|
+
(comment posted + moved to Backlog). Fails OPEN if the validator can't run —
|
|
614
|
+
we never block a ticket on our own infrastructure error."""
|
|
615
|
+
from sdk import local_exec
|
|
616
|
+
|
|
617
|
+
tid = str(task.get("id", ""))
|
|
618
|
+
jira_key = task.get("jiraIssueKey") or task.get("jiraKey", "") or kind.upper()
|
|
619
|
+
project_path = _resolve_local_project_path(task, payload)
|
|
620
|
+
|
|
621
|
+
await send(Envelope(
|
|
622
|
+
type="step_update", thread_id=thread_id,
|
|
623
|
+
data={"jira_task_id": tid, "task_id": tid,
|
|
624
|
+
"current_step": f"{kind.title()} {jira_key}: validating ticket against the standard"},
|
|
625
|
+
).model_dump())
|
|
626
|
+
|
|
627
|
+
res = await local_exec.run_claude_local(
|
|
628
|
+
_build_validation_prompt(kind, jira_key, task.get("summary", "") or "",
|
|
629
|
+
task.get("description"), task.get("humanInstructions")),
|
|
630
|
+
project_path, allowed_tools=_RECON_ALLOWED_TOOLS, # read-only judgment
|
|
631
|
+
github_token=await client.github_token(),
|
|
632
|
+
)
|
|
633
|
+
if not res.get("ok"):
|
|
634
|
+
await client.log_activity(
|
|
635
|
+
f"{kind} {jira_key}: validation could not run ({(res.get('error') or '')[:120]}) — proceeding"
|
|
636
|
+
)
|
|
637
|
+
return True # fail-open on validator infra error
|
|
638
|
+
|
|
639
|
+
valid, comment = _parse_validation(res.get("result"))
|
|
640
|
+
if valid:
|
|
641
|
+
await send(Envelope(
|
|
642
|
+
type="step_update", thread_id=thread_id,
|
|
643
|
+
data={"jira_task_id": tid, "current_step": f"{kind.title()} {jira_key}: validation passed"},
|
|
644
|
+
).model_dump())
|
|
645
|
+
return True
|
|
646
|
+
|
|
647
|
+
# Rejected — post a labelled, technical comment and move to Backlog.
|
|
648
|
+
fallback = ("This ticket does not meet the required standard. Provide: a clear goal "
|
|
649
|
+
"and scope; explicit Human Instructions (target repository/branch and/or "
|
|
650
|
+
"file paths, PR convention, build/run/test steps); testable acceptance "
|
|
651
|
+
"criteria; and the credentials/ownership for any third-party integration.")
|
|
652
|
+
body = f"{_AI_COMMENT_LABEL}\n\n{comment or fallback}"
|
|
653
|
+
await client.add_comment(tid, body)
|
|
654
|
+
await client.move_to_backlog(tid)
|
|
655
|
+
await client.log_activity(f"{kind} {jira_key}: REJECTED by validation gate — commented + moved to Backlog")
|
|
656
|
+
await send(Envelope(
|
|
657
|
+
type=Envelope.TYPE_ERROR, thread_id=thread_id,
|
|
658
|
+
data={"code": f"{kind.upper()}_VALIDATION_REJECTED", "task_id": tid,
|
|
659
|
+
"message": (comment or fallback)[:300]},
|
|
660
|
+
).model_dump())
|
|
661
|
+
return False
|
|
662
|
+
|
|
663
|
+
|
|
553
664
|
async def _handle_epic(
|
|
554
665
|
client: BackendClient, task: dict, payload: dict, send: Callable, thread_id: str,
|
|
555
666
|
) -> None:
|
|
@@ -628,6 +739,7 @@ async def _handle_epic(
|
|
|
628
739
|
**inherited,
|
|
629
740
|
"type": "Story", # Epic decomposes into user Stories; a Story later
|
|
630
741
|
# becomes a Task (see _handle_story) when it is run.
|
|
742
|
+
"drivenBy": "SYSTEM", # bot-created → trusted, skips the validation gate
|
|
631
743
|
"status": "To Do",
|
|
632
744
|
"summary": str(st.get("summary", "")).strip()[:250],
|
|
633
745
|
"description": str(st.get("description", "") or "")[:20000],
|
|
@@ -2720,7 +2832,17 @@ async def handle_orchestrate_auto_local_run(
|
|
|
2720
2832
|
if ttype in ("epic", "story"):
|
|
2721
2833
|
handler = _handle_epic if ttype == "epic" else _handle_story
|
|
2722
2834
|
code = "EPIC_DECOMPOSE_FAILED" if ttype == "epic" else "STORY_HANDLE_FAILED"
|
|
2835
|
+
# Validation gate: Epics are always validated; a Story only when it
|
|
2836
|
+
# is USER-driven (a SYSTEM/bot-created Story is trusted). A rejected
|
|
2837
|
+
# ticket is commented + moved to Backlog inside the gate — skip it.
|
|
2838
|
+
driven_by = str(task.get("drivenBy") or "").strip().upper()
|
|
2839
|
+
needs_validation = (ttype == "epic") or (ttype == "story" and driven_by != "SYSTEM")
|
|
2723
2840
|
try:
|
|
2841
|
+
if needs_validation and not await _validate_ticket_or_reject(
|
|
2842
|
+
client, task, ttype, payload, send, thread_id
|
|
2843
|
+
):
|
|
2844
|
+
processed += 1
|
|
2845
|
+
continue
|
|
2724
2846
|
await handler(client, task, payload, send, thread_id)
|
|
2725
2847
|
except Exception as exc: # noqa: BLE001
|
|
2726
2848
|
logger.warning("auto_local.%s_failed task=%s err=%s", ttype, tid, exc)
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.55 → devops_bot_sdk-1.4.58}/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
|