devops-bot-sdk 1.4.35__tar.gz → 1.4.37__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.35 → devops_bot_sdk-1.4.37}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/pyproject.toml +1 -1
  4. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/client.py +35 -1
  6. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/ipc/handlers.py +22 -6
  7. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/run_auto.py +58 -8
  8. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/README.md +0 -0
  9. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  10. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  11. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  12. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/requires.txt +0 -0
  13. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  14. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/collectors/__init__.py +0 -0
  15. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/collectors/files.py +0 -0
  16. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/collectors/process.py +0 -0
  17. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/collectors/screenshot.py +0 -0
  18. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/config.py +0 -0
  19. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/crucial.py +0 -0
  20. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/exceptions.py +0 -0
  21. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/git_ops.py +0 -0
  22. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/graphify.py +0 -0
  23. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/hooks/__init__.py +0 -0
  24. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/hooks/crucial_guard.py +0 -0
  25. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/ipc/__init__.py +0 -0
  26. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/ipc/electron_bridge.py +0 -0
  27. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/local_exec.py +0 -0
  28. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/__init__.py +0 -0
  29. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/envelope.py +0 -0
  30. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/requests.py +0 -0
  31. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/responses.py +0 -0
  32. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/snapshots.py +0 -0
  33. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/py.typed +0 -0
  34. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/sse.py +0 -0
  35. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/test.py +0 -0
  36. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/test_pipeline.py +0 -0
  37. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/updater.py +0 -0
  38. {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.35
3
+ Version: 1.4.37
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.35
3
+ Version: 1.4.37
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.35"
7
+ version = "1.4.37"
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.35
3
+ Version: 1.4.37
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.35"
33
+ __version__ = "1.4.37"
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.35"
61
+ SDK_VERSION = "1.4.37"
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
@@ -621,6 +621,40 @@ class BackendClient:
621
621
  except Exception:
622
622
  return []
623
623
 
624
+ async def list_tasks_by_keys(self, keys: list[str], limit: int = 10000) -> list[dict]:
625
+ """GET /tasks-ml (ML backend) — RAW records for specific Jira keys.
626
+
627
+ Unlike list_todo_tasks_raw this does NOT filter by board status: a
628
+ targeted run (`agentos-auto ticket BBW-81`) should start the named ticket
629
+ whatever its current status (To Do / In Progress / Done), so a finished
630
+ ticket can be re-run on demand. Matches `jiraIssueKey` (falls back to
631
+ `jiraKey`) case-insensitively. Best-effort: returns [] on error. Preserves
632
+ the order of `keys` (each key's first matching record), so the run follows
633
+ the order the caller asked for.
634
+ """
635
+ from sdk.config import BE_DEFAULT_URL
636
+
637
+ want = [k.strip().upper() for k in keys if k and k.strip()]
638
+ if not want:
639
+ return []
640
+ url = f"{BE_DEFAULT_URL.rstrip('/')}/tasks-ml"
641
+ headers = {**self._headers, "accept": "*/*", "developer-name": "null", "product": ""}
642
+ try:
643
+ async with httpx.AsyncClient(timeout=30.0) as client:
644
+ resp = await client.get(url, params={"limit": limit}, headers=headers)
645
+ if resp.status_code >= 400:
646
+ return []
647
+ records = ((resp.json() or {}).get("data") or {}).get("records") or []
648
+ except Exception:
649
+ return []
650
+
651
+ by_key: dict[str, dict] = {}
652
+ for rec in records:
653
+ k = str(rec.get("jiraIssueKey") or rec.get("jiraKey") or "").strip().upper()
654
+ if k and k not in by_key:
655
+ by_key[k] = rec
656
+ return [by_key[k] for k in want if k in by_key]
657
+
624
658
  async def approval_record(self, task_id: str) -> dict | None:
625
659
  """GET /api/v1/mgmt/approve/{task_id} → the full decision record (or None).
626
660
 
@@ -2129,15 +2129,31 @@ async def handle_orchestrate_auto_local_run(
2129
2129
  ).model_dump())
2130
2130
  return
2131
2131
 
2132
- # RAW /tasks-ml records carry agentMode / jiraIssueKey / humanInstructions.
2133
- tasks = await client.list_todo_tasks_raw(status="To Do")
2134
- # Process oldest-created first (FIFO by Jira `createdAt`) so the ticket
2135
- # raised earliest is started first. Unknown/invalid timestamps sort last.
2136
- tasks = sorted(tasks, key=_task_created_key)
2132
+ # Targeted run: `only_keys` names specific Jira tickets to run (via
2133
+ # `agentos-auto ticket BBW-81 …`). Those run regardless of board status and
2134
+ # in the ORDER requested. Otherwise fetch the whole To Do queue (default).
2135
+ only_keys = [str(k).strip().upper() for k in (payload.get("only_keys") or []) if str(k).strip()]
2136
+ if only_keys:
2137
+ tasks = await client.list_tasks_by_keys(only_keys)
2138
+ found = {str(t.get("jiraIssueKey") or t.get("jiraKey") or "").strip().upper() for t in tasks}
2139
+ missing = [k for k in only_keys if k not in found]
2140
+ if missing:
2141
+ await send(Envelope(
2142
+ type="step_update", thread_id=thread_id,
2143
+ data={"current_step": f"⚠ not found (skipped): {', '.join(missing)}"},
2144
+ ).model_dump())
2145
+ # Keep the requested order (list_tasks_by_keys already does), no re-sort.
2146
+ else:
2147
+ # RAW /tasks-ml records carry agentMode / jiraIssueKey / humanInstructions.
2148
+ tasks = await client.list_todo_tasks_raw(status="To Do")
2149
+ # Process oldest-created first (FIFO by Jira `createdAt`) so the ticket
2150
+ # raised earliest is started first. Unknown/invalid timestamps sort last.
2151
+ tasks = sorted(tasks, key=_task_created_key)
2137
2152
 
2138
2153
  await send(Envelope(
2139
2154
  type="pipeline_started", thread_id=thread_id,
2140
- data={"mode": "orchestrate-auto-local", "task_count": len(tasks)},
2155
+ data={"mode": "orchestrate-auto-local", "task_count": len(tasks),
2156
+ "only_keys": only_keys or None},
2141
2157
  ).model_dump())
2142
2158
 
2143
2159
  if not tasks:
@@ -9,6 +9,8 @@ directory, and the result is reported back. The only required input is the
9
9
  Commands:
10
10
  agentos-auto run every To Do task locally, in the BACKGROUND
11
11
  agentos-auto resume alias for run — re-fetches remaining To Do tasks
12
+ agentos-auto ticket K [K...] run ONLY the named Jira ticket(s), e.g. BBW-81 —
13
+ any status, in the order given (alias: tickets)
12
14
  agentos-auto stop ask the running loop to pause after the current task
13
15
  agentos-auto status show whether the background loop is running / stop pending
14
16
  agentos-auto update upgrade the SDK to the latest version
@@ -224,6 +226,39 @@ def _parse_workspace_root(argv: list[str]) -> str | None:
224
226
  return None
225
227
 
226
228
 
229
+ def _parse_ticket_keys(argv: list[str]) -> list[str]:
230
+ """Jira keys passed to `agentos-auto ticket <KEY> [<KEY> ...]`.
231
+
232
+ Accepts space- and/or comma-separated keys (e.g. `BBW-81 BBW-80` or
233
+ `BBW-81,BBW-80`), skips the leading subcommand and any flags (and the value
234
+ of --workspace-root), upper-cases and de-dupes while preserving order.
235
+ """
236
+ keys: list[str] = []
237
+ skip_next = False
238
+ for i, tok in enumerate(argv):
239
+ if i == 0:
240
+ continue # the subcommand itself (ticket/tickets)
241
+ if skip_next:
242
+ skip_next = False
243
+ continue
244
+ if tok.startswith("-"):
245
+ if tok == "--workspace-root":
246
+ skip_next = True # its value is a directory, not a ticket key
247
+ continue
248
+ for part in tok.split(","):
249
+ k = part.strip()
250
+ if k:
251
+ keys.append(k)
252
+ seen: set[str] = set()
253
+ out: list[str] = []
254
+ for k in keys:
255
+ ku = k.upper()
256
+ if ku not in seen:
257
+ seen.add(ku)
258
+ out.append(ku)
259
+ return out
260
+
261
+
227
262
  _current_task: str | None = None
228
263
 
229
264
 
@@ -263,7 +298,7 @@ async def _send(env: dict) -> None:
263
298
  print(f" [{etype}] {json.dumps(data)[:200]}")
264
299
 
265
300
 
266
- def _run_loop(workspace_root: str | None) -> None:
301
+ def _run_loop(workspace_root: str | None, only_keys: list[str] | None = None) -> None:
267
302
  from sdk import local_exec
268
303
  from sdk.client import BackendClient, register_status_observer
269
304
  from sdk.ipc import handlers
@@ -282,6 +317,9 @@ def _run_loop(workspace_root: str | None) -> None:
282
317
  payload: dict = {"thread_id": "agentos-auto-cli"}
283
318
  if workspace_root:
284
319
  payload["workspace_root"] = workspace_root
320
+ if only_keys:
321
+ # Targeted run: only these Jira tickets, in this order, regardless of status.
322
+ payload["only_keys"] = only_keys
285
323
 
286
324
  _clear_stop() # a fresh run/resume clears any stale stop flag
287
325
  _clear_backlogged() # session-scoped: only track Backlog moves from THIS run
@@ -420,7 +458,18 @@ def main() -> None:
420
458
  print("stop pending: yes" if _stop_requested() else "stop pending: no")
421
459
  return
422
460
 
423
- if cmd in ("run", "resume"):
461
+ if cmd in ("run", "resume", "ticket", "tickets"):
462
+ # Targeted run: `ticket`/`tickets` runs ONLY the named Jira keys (any
463
+ # status, in the order given); `run`/`resume` runs the whole To Do queue.
464
+ only_keys: list[str] | None = None
465
+ if cmd in ("ticket", "tickets"):
466
+ only_keys = _parse_ticket_keys(argv)
467
+ if not only_keys:
468
+ print("usage: agentos-auto ticket <JIRA-KEY> [<JIRA-KEY> ...]")
469
+ print(" e.g. agentos-auto ticket BBW-81")
470
+ print(" agentos-auto ticket BBW-81 BBW-80")
471
+ sys.exit(1)
472
+
424
473
  # Self-update FIRST (before the already-running check) so the installed
425
474
  # package is refreshed even when a daemon is already running — otherwise a
426
475
  # long-lived old daemon would pin `stop`/`status` to a stale version. Runs
@@ -430,7 +479,7 @@ def main() -> None:
430
479
  from sdk.updater import auto_update_and_reexec
431
480
  auto_update_and_reexec("sdk.run_auto", sys.argv[1:])
432
481
 
433
- # Refuse to start a second loop — one background daemon owns the To Do queue.
482
+ # Refuse to start a second loop — one background daemon owns the queue.
434
483
  pid = _daemon_running()
435
484
  if pid:
436
485
  print(f"agentos-auto is already running in the background (pid {pid}).")
@@ -445,13 +494,14 @@ def main() -> None:
445
494
  # attached (useful for debugging). On platforms without fork we stay attached.
446
495
  foreground = "--foreground" in argv or "-f" in argv
447
496
  if not foreground and hasattr(os, "fork"):
448
- print("▶ agentos-auto started in the background it keeps running after you "
449
- "close this terminal.")
497
+ target = f" tickets: {', '.join(only_keys)}" if only_keys else ""
498
+ print(f" agentos-auto started in the background{target} — it keeps running "
499
+ "after you close this terminal.")
450
500
  print(f" logs: {_LOG_FILE}")
451
501
  print(" status: agentos-auto status")
452
502
  print(" stop: agentos-auto stop")
453
503
  if not foreground and _daemonize():
454
- _run_loop(workspace_root) # only the detached daemon child reaches here
504
+ _run_loop(workspace_root, only_keys) # only the detached daemon child reaches here
455
505
  return
456
506
  if not foreground:
457
507
  print("note: background mode unavailable on this platform — running in the "
@@ -460,10 +510,10 @@ def main() -> None:
460
510
  # Foreground run still records a PID so stop/status can find it.
461
511
  _AGENTOS_DIR.mkdir(parents=True, exist_ok=True)
462
512
  _PID_FILE.write_text(str(os.getpid()))
463
- _run_loop(workspace_root)
513
+ _run_loop(workspace_root, only_keys)
464
514
  return
465
515
 
466
- print(f"unknown command '{cmd}'. use: run | resume | stop | status | update")
516
+ print(f"unknown command '{cmd}'. use: run | resume | ticket | stop | status | update")
467
517
  sys.exit(1)
468
518
 
469
519