devops-bot-sdk 1.4.86__tar.gz → 1.4.90__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.86 → devops_bot_sdk-1.4.90}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/client.py +5 -2
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/ipc/handlers.py +121 -9
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/README.md +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/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.90"
|
|
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.90
|
|
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.90"
|
|
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.90"
|
|
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
|
|
@@ -848,7 +848,10 @@ class BackendClient:
|
|
|
848
848
|
url = f"{BE_DEFAULT_URL.rstrip('/')}/tasks-ml"
|
|
849
849
|
headers = {k: v for k, v in self._headers.items() if k.lower() != "content-type"}
|
|
850
850
|
headers.update({"accept": "*/*", "developer-name": "null", "product": ""})
|
|
851
|
-
|
|
851
|
+
# POST /tasks-ml must never carry an SLA target — the backend validator
|
|
852
|
+
# rejects any value not shaped like "8h", and SLA is not ours to set.
|
|
853
|
+
files = [(k, (None, str(v))) for k, v in fields.items()
|
|
854
|
+
if v is not None and k not in ("slaTarget", "slaTargets")]
|
|
852
855
|
for m in (model_used or []):
|
|
853
856
|
files.append(("modelUsed[]", (None, str(m)))) # [] → backend parses as array
|
|
854
857
|
try:
|
|
@@ -648,12 +648,20 @@ def _extract_email(text: str | None) -> str | None:
|
|
|
648
648
|
return m.group(0).strip().lower() if m else None
|
|
649
649
|
|
|
650
650
|
|
|
651
|
+
# Time fields inherited from the parent Epic onto each generated child.
|
|
652
|
+
# Passed through VERBATIM (exactly as the backend returned them on GET) so the
|
|
653
|
+
# POST never carries a reformatted-and-wrong date; absent/empty ones are dropped.
|
|
654
|
+
# slaTarget[s] are deliberately excluded: POST /tasks-ml must never receive an
|
|
655
|
+
# SLA target (its validator rejects anything not shaped like "8h").
|
|
656
|
+
_EPIC_TIME_FIELDS = ("startDate", "endDate", "dueDate")
|
|
657
|
+
|
|
658
|
+
|
|
651
659
|
def _epic_inherited_fields(task: dict) -> dict:
|
|
652
660
|
"""Parent-Epic fields a child Task should inherit (drops empties).
|
|
653
661
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
662
|
+
Includes the Epic's time targets (startDate, endDate, dueDate) when
|
|
663
|
+
present, so generated stories/tasks carry the same deadlines instead of
|
|
664
|
+
being created without any.
|
|
657
665
|
"""
|
|
658
666
|
fields = {
|
|
659
667
|
"projectKey": task.get("projectKey"),
|
|
@@ -661,6 +669,11 @@ def _epic_inherited_fields(task: dict) -> dict:
|
|
|
661
669
|
"assigneeEmail": (task.get("assignee") or {}).get("email"),
|
|
662
670
|
"teamId": task.get("teamId"),
|
|
663
671
|
}
|
|
672
|
+
for k in _EPIC_TIME_FIELDS:
|
|
673
|
+
v = task.get(k)
|
|
674
|
+
# Scalars only: a nested object would be mangled by the multipart str().
|
|
675
|
+
if v and isinstance(v, (str, int, float)):
|
|
676
|
+
fields[k] = v
|
|
664
677
|
sprint = task.get("sprint") or {}
|
|
665
678
|
if sprint.get("jiraSprintId"):
|
|
666
679
|
fields["jiraSprintId"] = sprint["jiraSprintId"]
|
|
@@ -1158,6 +1171,28 @@ def _wants_prd(text: str | None) -> bool:
|
|
|
1158
1171
|
return bool(text and _PRD_RE.search(text))
|
|
1159
1172
|
|
|
1160
1173
|
|
|
1174
|
+
# An explicit directive to UPDATE the user story ITSELF ("update my user story
|
|
1175
|
+
# based on these parameters", "revise the story", "the user story should be
|
|
1176
|
+
# updated"). Here the story text IS the deliverable: its description is rewritten
|
|
1177
|
+
# per the given parameters and the ticket is marked Done — no Task conversion, no
|
|
1178
|
+
# PRD, no coding. The verb must sit DIRECTLY on the story object ("update the
|
|
1179
|
+
# user story"), so dev instructions that merely reference the story ("modify the
|
|
1180
|
+
# login form as per the user story") don't false-positive.
|
|
1181
|
+
_STORY_UPDATE_RE = re.compile(
|
|
1182
|
+
r"\b(?:update|revise|rewrite|refine|rework|amend|modify|edit)\s+"
|
|
1183
|
+
r"(?:(?:my|the|this|that|our|its)\s+)?(?:user[\s-]+)?stor(?:y|ies)\b"
|
|
1184
|
+
r"|\b(?:user[\s-]+)?stor(?:y|ies)\s+"
|
|
1185
|
+
r"(?:should\s+be|to\s+be|needs?(?:\s+to\s+be)?|must\s+be)\s+"
|
|
1186
|
+
r"(?:updated|revised|rewritten|refined|reworked|amended|modified)\b",
|
|
1187
|
+
re.I,
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
def _wants_story_update(text: str | None) -> bool:
|
|
1192
|
+
"""True when the humanInstructions explicitly ask to update the user story itself."""
|
|
1193
|
+
return bool(text and _STORY_UPDATE_RE.search(text))
|
|
1194
|
+
|
|
1195
|
+
|
|
1161
1196
|
# Signals that a ticket is DEVELOPMENT work (→ always gets a PRD). Deliberately
|
|
1162
1197
|
# code-specific so Finance/HR/ops tickets don't false-positive (they get a PRD
|
|
1163
1198
|
# only when explicitly requested).
|
|
@@ -1208,11 +1243,33 @@ def _build_prd_prompt(jira_key: str, summary: str, user_story: str | None,
|
|
|
1208
1243
|
return "\n\n".join(parts)
|
|
1209
1244
|
|
|
1210
1245
|
|
|
1246
|
+
def _build_story_update_prompt(jira_key: str, summary: str, user_story: str | None,
|
|
1247
|
+
human_instructions: str) -> str:
|
|
1248
|
+
"""Prompt that REWRITES a user story's description per the operator's parameters."""
|
|
1249
|
+
parts: list[str] = [
|
|
1250
|
+
"UPDATE the user story below by applying the operator's parameters/changes. "
|
|
1251
|
+
"This is a TEXT revision ONLY — do NOT create, edit, or delete any file. Keep "
|
|
1252
|
+
"every part of the story that still applies, change exactly what the parameters "
|
|
1253
|
+
"require, and keep it business-focused (no code, no technical design).",
|
|
1254
|
+
f"=== USER STORY {jira_key} ===\nSummary: {summary}",
|
|
1255
|
+
"Current description:\n"
|
|
1256
|
+
+ (_clip_text(user_story.strip(), _GEN_MAX_CHARS)
|
|
1257
|
+
if user_story and user_story.strip() else "(none provided)"),
|
|
1258
|
+
"Operator parameters / instructions:\n" + human_instructions.strip(),
|
|
1259
|
+
"Output ONLY the full UPDATED user story as Markdown — no preamble, no "
|
|
1260
|
+
"commentary, no diff — following this format exactly:\n\n" + _USER_STORY_FORMAT,
|
|
1261
|
+
]
|
|
1262
|
+
return "\n\n".join(parts)
|
|
1263
|
+
|
|
1264
|
+
|
|
1211
1265
|
async def _handle_story(
|
|
1212
1266
|
client: BackendClient, task: dict, payload: dict, send: Callable, thread_id: str,
|
|
1213
1267
|
) -> str | None:
|
|
1214
|
-
"""Run a Story:
|
|
1215
|
-
|
|
1268
|
+
"""Run a Story: when the humanInstructions explicitly ask to UPDATE the user
|
|
1269
|
+
story itself, rewrite its description per those parameters and mark it Done
|
|
1270
|
+
(no conversion, no coding). Otherwise: optionally generate a PRD (when
|
|
1271
|
+
instructed) into the description, then convert the ticket to a Task (To Do)
|
|
1272
|
+
for the coding run.
|
|
1216
1273
|
|
|
1217
1274
|
Returns the effective (PRD-enriched) description on success, or None on failure.
|
|
1218
1275
|
The caller uses the returned description to code the now-Task in the SAME run
|
|
@@ -1229,6 +1286,56 @@ async def _handle_story(
|
|
|
1229
1286
|
summary = task.get("summary", "") or ""
|
|
1230
1287
|
user_story = task.get("description") or ""
|
|
1231
1288
|
human = task.get("humanInstructions")
|
|
1289
|
+
|
|
1290
|
+
# STEP 0 — explicit UPDATE directive ("update my user story based on these
|
|
1291
|
+
# parameters"): the story text is the deliverable. Rewrite the description per
|
|
1292
|
+
# the parameters and mark the Story Done — no Task conversion, no PRD, no
|
|
1293
|
+
# coding. This outranks the codebase heuristics below: the parameters may
|
|
1294
|
+
# mention dev terms (endpoints, repos) without asking for any code.
|
|
1295
|
+
if _wants_story_update(human):
|
|
1296
|
+
await send(Envelope(
|
|
1297
|
+
type="step_update", thread_id=thread_id,
|
|
1298
|
+
data={"jira_task_id": tid, "task_id": tid,
|
|
1299
|
+
"current_step": f"Story {jira_key}: updating the user story per the instructions"},
|
|
1300
|
+
).model_dump())
|
|
1301
|
+
gen = await _run_generation(
|
|
1302
|
+
_build_story_update_prompt(jira_key, summary, user_story, human),
|
|
1303
|
+
_resolve_local_project_path(task, payload),
|
|
1304
|
+
send=send, thread_id=thread_id,
|
|
1305
|
+
label=f"Story {jira_key}: updating user story",
|
|
1306
|
+
github_token=await client.github_token(),
|
|
1307
|
+
)
|
|
1308
|
+
updated = "" if gen.get("limit_exceeded") else (gen.get("result") or "").strip()
|
|
1309
|
+
if not updated:
|
|
1310
|
+
# Leave the Story To Do so a later run retries; never mark Done on failure.
|
|
1311
|
+
reason = (gen.get("error") or "update generation produced no content")[:200]
|
|
1312
|
+
await client.log_activity(
|
|
1313
|
+
f"Story {jira_key}: user-story update failed ({reason}) — left To Do for retry"
|
|
1314
|
+
)
|
|
1315
|
+
await send(Envelope(
|
|
1316
|
+
type=Envelope.TYPE_ERROR, thread_id=thread_id,
|
|
1317
|
+
data={"code": "STORY_UPDATE_FAILED", "task_id": tid, "message": reason},
|
|
1318
|
+
).model_dump())
|
|
1319
|
+
return None
|
|
1320
|
+
if not await client.update_task_ml(
|
|
1321
|
+
tid, {"description": updated[:20000], "status": "Done"}, model_used=_EPIC_MODELS,
|
|
1322
|
+
):
|
|
1323
|
+
await client.log_activity(f"Story {jira_key}: user-story update could not be saved")
|
|
1324
|
+
await send(Envelope(
|
|
1325
|
+
type=Envelope.TYPE_ERROR, thread_id=thread_id,
|
|
1326
|
+
data={"code": "STORY_UPDATE_FAILED", "task_id": tid,
|
|
1327
|
+
"message": "could not save the updated user story"},
|
|
1328
|
+
).model_dump())
|
|
1329
|
+
return None
|
|
1330
|
+
await client.log_activity(f"Story {jira_key}: user story updated per instructions — Done")
|
|
1331
|
+
await send(Envelope(
|
|
1332
|
+
type="step_update", thread_id=thread_id,
|
|
1333
|
+
data={"jira_task_id": tid,
|
|
1334
|
+
"current_step": f"Story {jira_key}: description updated → Done"},
|
|
1335
|
+
).model_dump())
|
|
1336
|
+
# The Story is complete — nothing to convert or code in this run.
|
|
1337
|
+
return None
|
|
1338
|
+
|
|
1232
1339
|
# STEP 1 — validate whether this is a CODEBASE (development) task: repo links /
|
|
1233
1340
|
# file paths in the instructions, or dev signals (PRs, branches, endpoints,
|
|
1234
1341
|
# deploy, …). A codebase task ALWAYS gets a technical PRD built from the current
|
|
@@ -3374,11 +3481,16 @@ async def handle_orchestrate_auto_local_run(
|
|
|
3374
3481
|
):
|
|
3375
3482
|
processed += 1
|
|
3376
3483
|
continue
|
|
3377
|
-
# agentMode gate: wait for human approval before
|
|
3484
|
+
# agentMode gate: wait for human approval before actioning the
|
|
3485
|
+
# Story (update-in-place when instructed, else convert to Task).
|
|
3378
3486
|
if agent_mode:
|
|
3487
|
+
action = (
|
|
3488
|
+
"update this Story's description per the instructions and mark it Done"
|
|
3489
|
+
if _wants_story_update(task.get("humanInstructions"))
|
|
3490
|
+
else "convert this Story into a Task"
|
|
3491
|
+
)
|
|
3379
3492
|
dec = await _gate_transform_approval(
|
|
3380
|
-
client, tid, jira_key, task.get("summary"),
|
|
3381
|
-
"convert this Story into a Task",
|
|
3493
|
+
client, tid, jira_key, task.get("summary"), action,
|
|
3382
3494
|
send, thread_id, should_stop,
|
|
3383
3495
|
)
|
|
3384
3496
|
if dec == "stopped":
|
|
@@ -3391,7 +3503,7 @@ async def handle_orchestrate_auto_local_run(
|
|
|
3391
3503
|
if dec != "approved":
|
|
3392
3504
|
await send(Envelope(
|
|
3393
3505
|
type="step_update", thread_id=thread_id,
|
|
3394
|
-
data={"current_step": f"{jira_key}: {dec} — Story not
|
|
3506
|
+
data={"current_step": f"{jira_key}: {dec} — Story not actioned"},
|
|
3395
3507
|
).model_dump())
|
|
3396
3508
|
processed += 1
|
|
3397
3509
|
continue
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/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
|