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.
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/client.py +35 -1
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/ipc/handlers.py +22 -6
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/run_auto.py +58 -8
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/README.md +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/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.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.
|
|
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.
|
|
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.
|
|
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
|
-
#
|
|
2133
|
-
|
|
2134
|
-
#
|
|
2135
|
-
|
|
2136
|
-
|
|
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
|
|
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
|
-
|
|
449
|
-
|
|
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
|
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.35 → devops_bot_sdk-1.4.37}/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
|