devops-bot-sdk 1.4.85__tar.gz → 1.4.88__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.85 → devops_bot_sdk-1.4.88}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/pyproject.toml +1 -1
  4. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/client.py +1 -1
  6. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/ipc/handlers.py +128 -13
  7. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/README.md +0 -0
  8. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  9. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  10. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  11. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/devops_bot_sdk.egg-info/requires.txt +0 -0
  12. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  13. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/collectors/__init__.py +0 -0
  14. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/collectors/files.py +0 -0
  15. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/collectors/process.py +0 -0
  16. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/collectors/screenshot.py +0 -0
  17. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/config.py +0 -0
  18. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/crucial.py +0 -0
  19. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/exceptions.py +0 -0
  20. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/git_ops.py +0 -0
  21. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/graphify.py +0 -0
  22. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/hooks/__init__.py +0 -0
  23. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/hooks/crucial_guard.py +0 -0
  24. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/ipc/__init__.py +0 -0
  25. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/ipc/electron_bridge.py +0 -0
  26. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/local_exec.py +0 -0
  27. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/models/__init__.py +0 -0
  28. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/models/envelope.py +0 -0
  29. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/models/requests.py +0 -0
  30. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/models/responses.py +0 -0
  31. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/models/snapshots.py +0 -0
  32. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/py.typed +0 -0
  33. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/run_auto.py +0 -0
  34. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/sse.py +0 -0
  35. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/test.py +0 -0
  36. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/test_pipeline.py +0 -0
  37. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/sdk/updater.py +0 -0
  38. {devops_bot_sdk-1.4.85 → devops_bot_sdk-1.4.88}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.85
3
+ Version: 1.4.88
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.85
3
+ Version: 1.4.88
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.85"
7
+ version = "1.4.88"
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.85
3
+ Version: 1.4.88
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.85"
33
+ __version__ = "1.4.88"
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.85"
61
+ SDK_VERSION = "1.4.88"
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
@@ -648,12 +648,18 @@ 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/SLA 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
+ _EPIC_TIME_FIELDS = ("startDate", "endDate", "dueDate", "slaTarget", "slaTargets")
655
+
656
+
651
657
  def _epic_inherited_fields(task: dict) -> dict:
652
658
  """Parent-Epic fields a child Task should inherit (drops empties).
653
659
 
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.
660
+ Includes the Epic's time/SLA targets (startDate, endDate, dueDate,
661
+ slaTarget[s]) when present, so generated stories/tasks carry the same
662
+ deadlines and SL targets instead of being created without any.
657
663
  """
658
664
  fields = {
659
665
  "projectKey": task.get("projectKey"),
@@ -661,6 +667,11 @@ def _epic_inherited_fields(task: dict) -> dict:
661
667
  "assigneeEmail": (task.get("assignee") or {}).get("email"),
662
668
  "teamId": task.get("teamId"),
663
669
  }
670
+ for k in _EPIC_TIME_FIELDS:
671
+ v = task.get(k)
672
+ # Scalars only: a nested object would be mangled by the multipart str().
673
+ if v and isinstance(v, (str, int, float)):
674
+ fields[k] = v
664
675
  sprint = task.get("sprint") or {}
665
676
  if sprint.get("jiraSprintId"):
666
677
  fields["jiraSprintId"] = sprint["jiraSprintId"]
@@ -919,8 +930,12 @@ def _build_epic_titles_prompt(jira_key: str, summary: str, description: str | No
919
930
  parts: list[str] = [
920
931
  "You are decomposing a JIRA EPIC into a LIST of user-story TITLES — titles only, "
921
932
  "NOT full descriptions yet. This is ANALYSIS ONLY — do NOT create/edit any file.",
922
- "Faithfulness: derive titles ONLY from the epic; do NOT invent scope. Cover the "
923
- "epic's WHOLE scope; a few well-grounded titles beat many speculative ones.",
933
+ "Faithfulness (critical): derive the stories STRICTLY from the epic's own "
934
+ "Scope / Requirements / User-Stories content. Do NOT invent, add, or split "
935
+ "stories based on assumptions or on what the codebase happens to contain. Cover "
936
+ "the epic's stated scope and NOTHING beyond it. If the epic is too vague to split "
937
+ "faithfully, produce FEWER stories and note the ambiguity in the plan — never "
938
+ "guess to look complete.",
924
939
  f"=== EPIC {jira_key} ===\nSummary: {summary}",
925
940
  ]
926
941
  if description and description.strip():
@@ -928,8 +943,9 @@ def _build_epic_titles_prompt(jira_key: str, summary: str, description: str | No
928
943
  if human_instructions and human_instructions.strip():
929
944
  parts.append("Operator instructions:\n" + human_instructions.strip())
930
945
  parts.append(
931
- "If this is a software project, briefly inspect the codebase (CLAUDE.md, "
932
- "graphify, Read/Glob/Grep) so the split fits the real architecture.\n\n"
946
+ "You MAY glance at the codebase (CLAUDE.md, graphify, Read/Glob/Grep) ONLY to "
947
+ "phrase a title accurately or confirm a module name — NEVER to introduce scope "
948
+ "the epic does not state. The stories come from the EPIC, not the code.\n\n"
933
949
  + (("The epic already lists its stories — use EXACTLY these titles, VERBATIM, in "
934
950
  "order; do NOT invent, rename, merge, add, drop, or reorder:\n"
935
951
  + "\n".join(f" {i}. {t}" for i, t in enumerate(story_titles, 1)) + "\n")
@@ -1153,6 +1169,28 @@ def _wants_prd(text: str | None) -> bool:
1153
1169
  return bool(text and _PRD_RE.search(text))
1154
1170
 
1155
1171
 
1172
+ # An explicit directive to UPDATE the user story ITSELF ("update my user story
1173
+ # based on these parameters", "revise the story", "the user story should be
1174
+ # updated"). Here the story text IS the deliverable: its description is rewritten
1175
+ # per the given parameters and the ticket is marked Done — no Task conversion, no
1176
+ # PRD, no coding. The verb must sit DIRECTLY on the story object ("update the
1177
+ # user story"), so dev instructions that merely reference the story ("modify the
1178
+ # login form as per the user story") don't false-positive.
1179
+ _STORY_UPDATE_RE = re.compile(
1180
+ r"\b(?:update|revise|rewrite|refine|rework|amend|modify|edit)\s+"
1181
+ r"(?:(?:my|the|this|that|our|its)\s+)?(?:user[\s-]+)?stor(?:y|ies)\b"
1182
+ r"|\b(?:user[\s-]+)?stor(?:y|ies)\s+"
1183
+ r"(?:should\s+be|to\s+be|needs?(?:\s+to\s+be)?|must\s+be)\s+"
1184
+ r"(?:updated|revised|rewritten|refined|reworked|amended|modified)\b",
1185
+ re.I,
1186
+ )
1187
+
1188
+
1189
+ def _wants_story_update(text: str | None) -> bool:
1190
+ """True when the humanInstructions explicitly ask to update the user story itself."""
1191
+ return bool(text and _STORY_UPDATE_RE.search(text))
1192
+
1193
+
1156
1194
  # Signals that a ticket is DEVELOPMENT work (→ always gets a PRD). Deliberately
1157
1195
  # code-specific so Finance/HR/ops tickets don't false-positive (they get a PRD
1158
1196
  # only when explicitly requested).
@@ -1203,11 +1241,33 @@ def _build_prd_prompt(jira_key: str, summary: str, user_story: str | None,
1203
1241
  return "\n\n".join(parts)
1204
1242
 
1205
1243
 
1244
+ def _build_story_update_prompt(jira_key: str, summary: str, user_story: str | None,
1245
+ human_instructions: str) -> str:
1246
+ """Prompt that REWRITES a user story's description per the operator's parameters."""
1247
+ parts: list[str] = [
1248
+ "UPDATE the user story below by applying the operator's parameters/changes. "
1249
+ "This is a TEXT revision ONLY — do NOT create, edit, or delete any file. Keep "
1250
+ "every part of the story that still applies, change exactly what the parameters "
1251
+ "require, and keep it business-focused (no code, no technical design).",
1252
+ f"=== USER STORY {jira_key} ===\nSummary: {summary}",
1253
+ "Current description:\n"
1254
+ + (_clip_text(user_story.strip(), _GEN_MAX_CHARS)
1255
+ if user_story and user_story.strip() else "(none provided)"),
1256
+ "Operator parameters / instructions:\n" + human_instructions.strip(),
1257
+ "Output ONLY the full UPDATED user story as Markdown — no preamble, no "
1258
+ "commentary, no diff — following this format exactly:\n\n" + _USER_STORY_FORMAT,
1259
+ ]
1260
+ return "\n\n".join(parts)
1261
+
1262
+
1206
1263
  async def _handle_story(
1207
1264
  client: BackendClient, task: dict, payload: dict, send: Callable, thread_id: str,
1208
1265
  ) -> str | None:
1209
- """Run a Story: optionally generate a PRD (when instructed) into the description,
1210
- then convert the ticket to a Task (To Do) for the coding run.
1266
+ """Run a Story: when the humanInstructions explicitly ask to UPDATE the user
1267
+ story itself, rewrite its description per those parameters and mark it Done
1268
+ (no conversion, no coding). Otherwise: optionally generate a PRD (when
1269
+ instructed) into the description, then convert the ticket to a Task (To Do)
1270
+ for the coding run.
1211
1271
 
1212
1272
  Returns the effective (PRD-enriched) description on success, or None on failure.
1213
1273
  The caller uses the returned description to code the now-Task in the SAME run
@@ -1224,6 +1284,56 @@ async def _handle_story(
1224
1284
  summary = task.get("summary", "") or ""
1225
1285
  user_story = task.get("description") or ""
1226
1286
  human = task.get("humanInstructions")
1287
+
1288
+ # STEP 0 — explicit UPDATE directive ("update my user story based on these
1289
+ # parameters"): the story text is the deliverable. Rewrite the description per
1290
+ # the parameters and mark the Story Done — no Task conversion, no PRD, no
1291
+ # coding. This outranks the codebase heuristics below: the parameters may
1292
+ # mention dev terms (endpoints, repos) without asking for any code.
1293
+ if _wants_story_update(human):
1294
+ await send(Envelope(
1295
+ type="step_update", thread_id=thread_id,
1296
+ data={"jira_task_id": tid, "task_id": tid,
1297
+ "current_step": f"Story {jira_key}: updating the user story per the instructions"},
1298
+ ).model_dump())
1299
+ gen = await _run_generation(
1300
+ _build_story_update_prompt(jira_key, summary, user_story, human),
1301
+ _resolve_local_project_path(task, payload),
1302
+ send=send, thread_id=thread_id,
1303
+ label=f"Story {jira_key}: updating user story",
1304
+ github_token=await client.github_token(),
1305
+ )
1306
+ updated = "" if gen.get("limit_exceeded") else (gen.get("result") or "").strip()
1307
+ if not updated:
1308
+ # Leave the Story To Do so a later run retries; never mark Done on failure.
1309
+ reason = (gen.get("error") or "update generation produced no content")[:200]
1310
+ await client.log_activity(
1311
+ f"Story {jira_key}: user-story update failed ({reason}) — left To Do for retry"
1312
+ )
1313
+ await send(Envelope(
1314
+ type=Envelope.TYPE_ERROR, thread_id=thread_id,
1315
+ data={"code": "STORY_UPDATE_FAILED", "task_id": tid, "message": reason},
1316
+ ).model_dump())
1317
+ return None
1318
+ if not await client.update_task_ml(
1319
+ tid, {"description": updated[:20000], "status": "Done"}, model_used=_EPIC_MODELS,
1320
+ ):
1321
+ await client.log_activity(f"Story {jira_key}: user-story update could not be saved")
1322
+ await send(Envelope(
1323
+ type=Envelope.TYPE_ERROR, thread_id=thread_id,
1324
+ data={"code": "STORY_UPDATE_FAILED", "task_id": tid,
1325
+ "message": "could not save the updated user story"},
1326
+ ).model_dump())
1327
+ return None
1328
+ await client.log_activity(f"Story {jira_key}: user story updated per instructions — Done")
1329
+ await send(Envelope(
1330
+ type="step_update", thread_id=thread_id,
1331
+ data={"jira_task_id": tid,
1332
+ "current_step": f"Story {jira_key}: description updated → Done"},
1333
+ ).model_dump())
1334
+ # The Story is complete — nothing to convert or code in this run.
1335
+ return None
1336
+
1227
1337
  # STEP 1 — validate whether this is a CODEBASE (development) task: repo links /
1228
1338
  # file paths in the instructions, or dev signals (PRs, branches, endpoints,
1229
1339
  # deploy, …). A codebase task ALWAYS gets a technical PRD built from the current
@@ -3369,11 +3479,16 @@ async def handle_orchestrate_auto_local_run(
3369
3479
  ):
3370
3480
  processed += 1
3371
3481
  continue
3372
- # agentMode gate: wait for human approval before converting Story→Task.
3482
+ # agentMode gate: wait for human approval before actioning the
3483
+ # Story (update-in-place when instructed, else convert to Task).
3373
3484
  if agent_mode:
3485
+ action = (
3486
+ "update this Story's description per the instructions and mark it Done"
3487
+ if _wants_story_update(task.get("humanInstructions"))
3488
+ else "convert this Story into a Task"
3489
+ )
3374
3490
  dec = await _gate_transform_approval(
3375
- client, tid, jira_key, task.get("summary"),
3376
- "convert this Story into a Task",
3491
+ client, tid, jira_key, task.get("summary"), action,
3377
3492
  send, thread_id, should_stop,
3378
3493
  )
3379
3494
  if dec == "stopped":
@@ -3386,7 +3501,7 @@ async def handle_orchestrate_auto_local_run(
3386
3501
  if dec != "approved":
3387
3502
  await send(Envelope(
3388
3503
  type="step_update", thread_id=thread_id,
3389
- data={"current_step": f"{jira_key}: {dec} — Story not converted"},
3504
+ data={"current_step": f"{jira_key}: {dec} — Story not actioned"},
3390
3505
  ).model_dump())
3391
3506
  processed += 1
3392
3507
  continue