devops-bot-sdk 1.4.92__tar.gz → 1.4.96__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.92 → devops_bot_sdk-1.4.96}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/client.py +11 -3
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/ipc/handlers.py +42 -7
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/local_exec.py +15 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/README.md +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/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.96"
|
|
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.96
|
|
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.96"
|
|
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.96"
|
|
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
|
|
@@ -849,9 +849,17 @@ class BackendClient:
|
|
|
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"
|
|
852
|
+
# rejects any value not shaped like "8h" (an empty "" 400s with
|
|
853
|
+
# "Slatarget must be a positive integer of hours…"), and SLA is not ours
|
|
854
|
+
# to set. Drop it by NORMALISED name so every casing/separator variant
|
|
855
|
+
# (slaTarget, slaTargets, sla_target, SLATarget, sla-target, …) is
|
|
856
|
+
# excluded — never sent as "" or anything else, always an empty field.
|
|
857
|
+
def _is_sla(key: str) -> bool:
|
|
858
|
+
norm = "".join(ch for ch in key.lower() if ch.isalnum())
|
|
859
|
+
return norm in ("slatarget", "slatargets")
|
|
860
|
+
|
|
853
861
|
files = [(k, (None, str(v))) for k, v in fields.items()
|
|
854
|
-
if v is not None and
|
|
862
|
+
if v is not None and not _is_sla(k)]
|
|
855
863
|
for m in (model_used or []):
|
|
856
864
|
files.append(("modelUsed[]", (None, str(m)))) # [] → backend parses as array
|
|
857
865
|
try:
|
|
@@ -416,7 +416,10 @@ async def _run_with_heartbeat(coro, *, send: Callable, thread_id: str, label: st
|
|
|
416
416
|
beat.cancel()
|
|
417
417
|
try:
|
|
418
418
|
await beat
|
|
419
|
-
except Exception: # noqa: BLE001
|
|
419
|
+
except (asyncio.CancelledError, Exception): # noqa: BLE001
|
|
420
|
+
# CancelledError is a BaseException: raised here when the beat task
|
|
421
|
+
# was cancelled before it ever ran (coro finished immediately) — it
|
|
422
|
+
# must not propagate and sink the caller's result.
|
|
420
423
|
pass
|
|
421
424
|
|
|
422
425
|
|
|
@@ -441,6 +444,7 @@ async def _run_generation(
|
|
|
441
444
|
send=send, thread_id=thread_id, label=label, name=name,
|
|
442
445
|
)
|
|
443
446
|
combined = res.get("result") or ""
|
|
447
|
+
longest = res.get("longest_text") or ""
|
|
444
448
|
sid = res.get("session_id")
|
|
445
449
|
chunks = 0
|
|
446
450
|
while res.get("output_cap_exceeded") and sid and chunks < _MAX_OUTPUT_CAP_RESUMES:
|
|
@@ -463,9 +467,24 @@ async def _run_generation(
|
|
|
463
467
|
send=send, thread_id=thread_id, label=f"{label} (cont.)", name=name,
|
|
464
468
|
)
|
|
465
469
|
combined += res.get("result") or ""
|
|
470
|
+
if len(res.get("longest_text") or "") > len(longest):
|
|
471
|
+
longest = res.get("longest_text") or ""
|
|
466
472
|
sid = res.get("session_id") or sid
|
|
467
473
|
out = dict(res)
|
|
468
474
|
out["result"] = combined # the full, reassembled output
|
|
475
|
+
# Salvage a lost artifact: the CLI's `result` is only the LAST assistant
|
|
476
|
+
# message. If the model wrote the artifact and was then woken once more by a
|
|
477
|
+
# stray late event (e.g. a leftover background timer) for a short closing
|
|
478
|
+
# remark ("nothing further is pending"), `combined` is that remark and the
|
|
479
|
+
# real output is an EARLIER, much longer message. When an earlier message
|
|
480
|
+
# dwarfs the final one, it is the artifact — use it.
|
|
481
|
+
if (not out.get("limit_exceeded") and len(longest) > 1000
|
|
482
|
+
and len(longest) > 2 * len(combined.strip())):
|
|
483
|
+
logger.warning(
|
|
484
|
+
"generation.salvaged_longest_message final_len=%s longest_len=%s",
|
|
485
|
+
len(combined.strip()), len(longest),
|
|
486
|
+
)
|
|
487
|
+
out["result"] = longest.strip()
|
|
469
488
|
out["session_id"] = sid
|
|
470
489
|
return out
|
|
471
490
|
|
|
@@ -1336,13 +1355,22 @@ async def _handle_story(
|
|
|
1336
1355
|
# The Story is complete — nothing to convert or code in this run.
|
|
1337
1356
|
return None
|
|
1338
1357
|
|
|
1339
|
-
# STEP 1 —
|
|
1340
|
-
#
|
|
1341
|
-
#
|
|
1342
|
-
#
|
|
1358
|
+
# STEP 1 — do we have CODEBASE UNDERSTANDING to ground a PRD in? That means an
|
|
1359
|
+
# actual repo target (a github/gitlab/bitbucket link in the instructions OR the
|
|
1360
|
+
# story), or dev work called for in the OPERATOR INSTRUCTIONS (PRs, branches,
|
|
1361
|
+
# endpoints, deploy, …).
|
|
1362
|
+
#
|
|
1363
|
+
# Deliberately NOT keyed off the story DESCRIPTION's vocabulary or a "PRD"
|
|
1364
|
+
# mention: a business/BA user story that merely contains technical words
|
|
1365
|
+
# (endpoint, API, database, "PRD", …) is NOT a codebase task. Keying off the
|
|
1366
|
+
# description used to false-positive such stories into PRD generation, which
|
|
1367
|
+
# folded an unwanted PRD into — and mangled — the operator's own document.
|
|
1343
1368
|
has_repo = bool(_extract_repo_targets(human)) or bool(_extract_repo_targets(user_story))
|
|
1344
|
-
is_codebase_task = has_repo or _is_dev_task(human)
|
|
1345
|
-
|
|
1369
|
+
is_codebase_task = has_repo or _is_dev_task(human)
|
|
1370
|
+
# PRD ONLY when we have codebase understanding. Without a repo/codebase, the
|
|
1371
|
+
# Story is converted to a Task with its description left intact (the whole,
|
|
1372
|
+
# proper document the operator provided) — never rewritten with a PRD.
|
|
1373
|
+
wants_prd = is_codebase_task
|
|
1346
1374
|
|
|
1347
1375
|
if is_codebase_task:
|
|
1348
1376
|
step = ("validated as a codebase task — building a technical PRD from the "
|
|
@@ -1382,6 +1410,13 @@ async def _handle_story(
|
|
|
1382
1410
|
# Use the output regardless of an is_error flag (a stray read-only tool denial
|
|
1383
1411
|
# shouldn't discard a PRD the model actually produced); only a usage limit is fatal.
|
|
1384
1412
|
prd = "" if gen.get("limit_exceeded") else (gen.get("result") or "").strip()
|
|
1413
|
+
if prd and len(prd) < 400:
|
|
1414
|
+
# A real PRD (numbered sections per _PRD_FORMAT) is never this short —
|
|
1415
|
+
# this is a stub (e.g. a closing remark captured as the run's result).
|
|
1416
|
+
# Treat it as a failed generation rather than filing garbage in the ticket.
|
|
1417
|
+
if not gen.get("error"):
|
|
1418
|
+
gen["error"] = f"PRD too short ({len(prd)} chars) — not a document"
|
|
1419
|
+
prd = ""
|
|
1385
1420
|
if prd:
|
|
1386
1421
|
# PRD lives alongside the user story in the (now Task) description.
|
|
1387
1422
|
new_description = f"{user_story}\n\n---\n\n{_PRD_MARKER}\n\n{prd}"[:20000]
|
|
@@ -386,6 +386,12 @@ async def run_claude_local(
|
|
|
386
386
|
|
|
387
387
|
result_event: dict | None = None
|
|
388
388
|
files_changed: list[str] = []
|
|
389
|
+
# Longest single assistant TEXT message seen in the stream. The CLI's final
|
|
390
|
+
# `result` field is only the LAST assistant message — if the model outputs a
|
|
391
|
+
# large artifact (PRD/decomposition) and is then woken once more by a stray
|
|
392
|
+
# late event (e.g. a leftover background timer) for a short closing remark,
|
|
393
|
+
# the artifact would be lost. Callers use this to salvage it.
|
|
394
|
+
longest_text = {"text": ""}
|
|
389
395
|
session_id: str | None = resume_session_id
|
|
390
396
|
import time as _time
|
|
391
397
|
started = _time.monotonic()
|
|
@@ -452,6 +458,14 @@ async def run_claude_local(
|
|
|
452
458
|
if event.get("session_id"):
|
|
453
459
|
session_id = str(event["session_id"])
|
|
454
460
|
|
|
461
|
+
# Track the longest assistant text message (see `longest_text` above).
|
|
462
|
+
if event.get("type") == "assistant":
|
|
463
|
+
for c in event.get("message", {}).get("content", []) or []:
|
|
464
|
+
if isinstance(c, dict) and c.get("type") == "text":
|
|
465
|
+
txt = str(c.get("text") or "")
|
|
466
|
+
if len(txt) > len(longest_text["text"]):
|
|
467
|
+
longest_text["text"] = txt
|
|
468
|
+
|
|
455
469
|
# Track files the agent created/edited for the result summary.
|
|
456
470
|
if event.get("type") == "user":
|
|
457
471
|
tr = event.get("tool_use_result")
|
|
@@ -526,6 +540,7 @@ async def run_claude_local(
|
|
|
526
540
|
return _finish({
|
|
527
541
|
"ok": (not is_error) and (not limit_hit),
|
|
528
542
|
"result": result_text,
|
|
543
|
+
"longest_text": longest_text["text"],
|
|
529
544
|
"input_tokens": in_tok,
|
|
530
545
|
"output_tokens": out_tok,
|
|
531
546
|
"total_tokens": in_tok + out_tok,
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.92 → devops_bot_sdk-1.4.96}/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
|