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.
Files changed (38) hide show
  1. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/pyproject.toml +1 -1
  4. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/client.py +5 -2
  6. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/ipc/handlers.py +121 -9
  7. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/README.md +0 -0
  8. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  9. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  10. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  11. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/requires.txt +0 -0
  12. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  13. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/collectors/__init__.py +0 -0
  14. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/collectors/files.py +0 -0
  15. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/collectors/process.py +0 -0
  16. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/collectors/screenshot.py +0 -0
  17. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/config.py +0 -0
  18. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/crucial.py +0 -0
  19. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/exceptions.py +0 -0
  20. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/git_ops.py +0 -0
  21. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/graphify.py +0 -0
  22. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/hooks/__init__.py +0 -0
  23. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/hooks/crucial_guard.py +0 -0
  24. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/ipc/__init__.py +0 -0
  25. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/ipc/electron_bridge.py +0 -0
  26. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/local_exec.py +0 -0
  27. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/__init__.py +0 -0
  28. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/envelope.py +0 -0
  29. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/requests.py +0 -0
  30. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/responses.py +0 -0
  31. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/models/snapshots.py +0 -0
  32. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/py.typed +0 -0
  33. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/run_auto.py +0 -0
  34. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/sse.py +0 -0
  35. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/test.py +0 -0
  36. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/test_pipeline.py +0 -0
  37. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/sdk/updater.py +0 -0
  38. {devops_bot_sdk-1.4.86 → devops_bot_sdk-1.4.90}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.86
3
+ Version: 1.4.90
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.86
3
+ Version: 1.4.90
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devops-bot-sdk"
7
- version = "1.4.86"
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.86
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.86"
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.86"
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
- files = [(k, (None, str(v))) for k, v in fields.items() if v is not None]
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
- Time-related fields (startDate, dueDate, slaTarget) are intentionally NOT
655
- inherited/sent they're optional on the POST and left for the backend/board to
656
- set, so the create call never carries a stale or wrongly-formatted date.
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: optionally generate a PRD (when instructed) into the description,
1215
- then convert the ticket to a Task (To Do) for the coding run.
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 converting Story→Task.
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 converted"},
3506
+ data={"current_step": f"{jira_key}: {dec} — Story not actioned"},
3395
3507
  ).model_dump())
3396
3508
  processed += 1
3397
3509
  continue