devops-bot-sdk 1.4.60__tar.gz → 1.4.65__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.60 → devops_bot_sdk-1.4.65}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/client.py +3 -3
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/ipc/handlers.py +195 -63
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/README.md +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/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.65"
|
|
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.65
|
|
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.65"
|
|
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.65"
|
|
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
|
|
@@ -821,7 +821,7 @@ class BackendClient:
|
|
|
821
821
|
# field like `modelUsed` can repeat.
|
|
822
822
|
files = [(k, (None, str(v))) for k, v in fields.items() if v is not None]
|
|
823
823
|
for m in (model_used or []):
|
|
824
|
-
files.append(("modelUsed", (None, str(m))))
|
|
824
|
+
files.append(("modelUsed[]", (None, str(m)))) # [] → backend parses as array
|
|
825
825
|
try:
|
|
826
826
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
827
827
|
resp = await client.put(url, files=files, headers=headers)
|
|
@@ -850,7 +850,7 @@ class BackendClient:
|
|
|
850
850
|
headers.update({"accept": "*/*", "developer-name": "null", "product": ""})
|
|
851
851
|
files = [(k, (None, str(v))) for k, v in fields.items() if v is not None]
|
|
852
852
|
for m in (model_used or []):
|
|
853
|
-
files.append(("modelUsed", (None, str(m))))
|
|
853
|
+
files.append(("modelUsed[]", (None, str(m)))) # [] → backend parses as array
|
|
854
854
|
try:
|
|
855
855
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
856
856
|
resp = await client.post(url, files=files, headers=headers)
|
|
@@ -371,6 +371,50 @@ def _parse_scope(recon_text: str | None) -> str:
|
|
|
371
371
|
return "in"
|
|
372
372
|
|
|
373
373
|
|
|
374
|
+
# Heartbeat cadence for long, quiet local runs (PRD generation, validation) so the
|
|
375
|
+
# log shows liveness even when the agent is generating text with no tool calls.
|
|
376
|
+
_HEARTBEAT_S = float(os.getenv("AGENTOS_HEARTBEAT_S", "") or 20.0)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
async def _run_with_heartbeat(coro, *, send: Callable, thread_id: str, label: str,
|
|
380
|
+
name: str | None = None, interval: float = _HEARTBEAT_S):
|
|
381
|
+
"""Await `coro` while emitting a periodic 'still working (Ns)' step_update, so a
|
|
382
|
+
long-but-quiet local run (e.g. PRD generation) never looks frozen in the log."""
|
|
383
|
+
import time as _t
|
|
384
|
+
|
|
385
|
+
prefix = f"[{name}] " if name else ""
|
|
386
|
+
stop = asyncio.Event()
|
|
387
|
+
started = _t.monotonic()
|
|
388
|
+
|
|
389
|
+
async def _beat() -> None:
|
|
390
|
+
try:
|
|
391
|
+
while not stop.is_set():
|
|
392
|
+
try:
|
|
393
|
+
await asyncio.wait_for(stop.wait(), timeout=interval)
|
|
394
|
+
return
|
|
395
|
+
except asyncio.TimeoutError:
|
|
396
|
+
pass
|
|
397
|
+
await send(Envelope(
|
|
398
|
+
type="step_update", thread_id=thread_id,
|
|
399
|
+
data={"status": "running",
|
|
400
|
+
"current_step": f"{prefix}{label} … still working "
|
|
401
|
+
f"({int(_t.monotonic() - started)}s)"},
|
|
402
|
+
).model_dump())
|
|
403
|
+
except asyncio.CancelledError:
|
|
404
|
+
return
|
|
405
|
+
|
|
406
|
+
beat = asyncio.create_task(_beat())
|
|
407
|
+
try:
|
|
408
|
+
return await coro
|
|
409
|
+
finally:
|
|
410
|
+
stop.set()
|
|
411
|
+
beat.cancel()
|
|
412
|
+
try:
|
|
413
|
+
await beat
|
|
414
|
+
except Exception: # noqa: BLE001
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
|
|
374
418
|
# ── Epic decomposition ────────────────────────────────────────────────────
|
|
375
419
|
# An EPIC is not coded directly. It is analysed (grounded in the codebase when a
|
|
376
420
|
# project path exists) into a detailed user-story description + an implementation
|
|
@@ -624,9 +668,14 @@ async def _validate_ticket_or_reject(
|
|
|
624
668
|
we never block a ticket on our own infrastructure error."""
|
|
625
669
|
from sdk import local_exec
|
|
626
670
|
|
|
671
|
+
from pathlib import Path
|
|
672
|
+
|
|
627
673
|
tid = str(task.get("id", ""))
|
|
628
674
|
jira_key = task.get("jiraIssueKey") or task.get("jiraKey", "") or kind.upper()
|
|
629
|
-
|
|
675
|
+
# Validation is a pure text judgment — run it in a guaranteed-writable scratch
|
|
676
|
+
# dir, never the resolved project path (which may not exist yet or be unwritable,
|
|
677
|
+
# e.g. a code-reference like /components). This decouples the gate from the repo.
|
|
678
|
+
scratch = str(Path.home() / ".agentos" / "validate")
|
|
630
679
|
|
|
631
680
|
await send(Envelope(
|
|
632
681
|
type="step_update", thread_id=thread_id,
|
|
@@ -634,11 +683,14 @@ async def _validate_ticket_or_reject(
|
|
|
634
683
|
"current_step": f"{kind.title()} {jira_key}: validating ticket against the standard"},
|
|
635
684
|
).model_dump())
|
|
636
685
|
|
|
637
|
-
res = await
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
686
|
+
res = await _run_with_heartbeat(
|
|
687
|
+
local_exec.run_claude_local(
|
|
688
|
+
_build_validation_prompt(kind, jira_key, task.get("summary", "") or "",
|
|
689
|
+
task.get("description"), task.get("humanInstructions")),
|
|
690
|
+
scratch, allowed_tools=_RECON_ALLOWED_TOOLS, # read-only judgment, scratch dir
|
|
691
|
+
github_token=await client.github_token(),
|
|
692
|
+
),
|
|
693
|
+
send=send, thread_id=thread_id, label=f"{kind.title()} {jira_key}: validating",
|
|
642
694
|
)
|
|
643
695
|
if not res.get("ok"):
|
|
644
696
|
await client.log_activity(
|
|
@@ -854,10 +906,14 @@ def _build_prd_prompt(jira_key: str, summary: str, user_story: str | None,
|
|
|
854
906
|
|
|
855
907
|
async def _handle_story(
|
|
856
908
|
client: BackendClient, task: dict, payload: dict, send: Callable, thread_id: str,
|
|
857
|
-
) -> None:
|
|
909
|
+
) -> str | None:
|
|
858
910
|
"""Run a Story: optionally generate a PRD (when instructed) into the description,
|
|
859
911
|
then convert the ticket to a Task (To Do) for the coding run.
|
|
860
912
|
|
|
913
|
+
Returns the effective (PRD-enriched) description on success, or None on failure.
|
|
914
|
+
The caller uses the returned description to code the now-Task in the SAME run
|
|
915
|
+
(default `agentos-auto`); the `story` command discards it (convert-only).
|
|
916
|
+
|
|
861
917
|
Best-effort. If a PRD was explicitly instructed but generation fails, the ticket
|
|
862
918
|
goes to Backlog for retry (so the PRD requirement isn't silently dropped); a
|
|
863
919
|
conversion failure likewise backlogs it.
|
|
@@ -955,9 +1011,11 @@ async def _handle_story(
|
|
|
955
1011
|
type="step_update", thread_id=thread_id,
|
|
956
1012
|
data={"jira_task_id": tid,
|
|
957
1013
|
"current_step": f"Story {jira_key} → Task (To Do)"
|
|
958
|
-
+ (" + PRD in description" if prd_in_desc else "")
|
|
959
|
-
+ " — run again to implement"},
|
|
1014
|
+
+ (" + PRD in description" if prd_in_desc else "")},
|
|
960
1015
|
).model_dump())
|
|
1016
|
+
# The effective description (PRD-enriched when generated/present) so the caller
|
|
1017
|
+
# can code the now-Task in the same run.
|
|
1018
|
+
return new_description or user_story
|
|
961
1019
|
|
|
962
1020
|
|
|
963
1021
|
async def _run_local_pipeline(
|
|
@@ -1231,13 +1289,16 @@ async def _code_one_repo(
|
|
|
1231
1289
|
data={"status": "running",
|
|
1232
1290
|
"current_step": f"[{name}] Recon — exploring the repo to ground the plan"},
|
|
1233
1291
|
).model_dump())
|
|
1234
|
-
recon = await
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1292
|
+
recon = await _run_with_heartbeat(
|
|
1293
|
+
local_exec.run_claude_local(
|
|
1294
|
+
_build_recon_prompt(
|
|
1295
|
+
name, pending["user_input"], pending.get("structured_plan"),
|
|
1296
|
+
pending.get("human_instructions"),
|
|
1297
|
+
),
|
|
1298
|
+
repo_path, on_event=_on_event, github_token=gh_token,
|
|
1299
|
+
allowed_tools=_RECON_ALLOWED_TOOLS, # read-only: no Write/Edit/Bash
|
|
1238
1300
|
),
|
|
1239
|
-
|
|
1240
|
-
allowed_tools=_RECON_ALLOWED_TOOLS, # read-only: no Write/Edit/Bash
|
|
1301
|
+
send=send, thread_id=thread_id, label="Exploring the repo (recon)", name=name,
|
|
1241
1302
|
)
|
|
1242
1303
|
recon_session = recon.get("session_id")
|
|
1243
1304
|
# A usage-limit hit during recon must be surfaced so the loop waits/retries
|
|
@@ -1298,35 +1359,59 @@ async def _code_one_repo(
|
|
|
1298
1359
|
except Exception: # noqa: BLE001
|
|
1299
1360
|
branch_info = None
|
|
1300
1361
|
|
|
1301
|
-
# 3a. Technical PRD
|
|
1302
|
-
#
|
|
1303
|
-
#
|
|
1304
|
-
#
|
|
1305
|
-
# (
|
|
1306
|
-
#
|
|
1307
|
-
#
|
|
1308
|
-
if
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1362
|
+
# 3a. Technical PRD grounding (codebase tasks). The BACKEND converts a Story to a
|
|
1363
|
+
# Task and adds a FIRST-PASS PRD to the description; here we GROUND that PRD
|
|
1364
|
+
# against the REAL codebase (correcting file paths, contracts, data models to
|
|
1365
|
+
# what actually exists) before building to it. If no PRD is present at all
|
|
1366
|
+
# (edge case), we generate one. Reuses the recon session (already understands
|
|
1367
|
+
# the codebase), persists the result to the ticket + pending (shared → other
|
|
1368
|
+
# repos reuse it), and runs once per ticket. Skipped on a resume / non-git.
|
|
1369
|
+
if is_git and recon_session and not resume_session_id and not pending.get("_prd_grounded"):
|
|
1370
|
+
current = pending.get("user_input") or ""
|
|
1371
|
+
has_prd = _PRD_MARKER in current
|
|
1372
|
+
if has_prd:
|
|
1373
|
+
before, _, existing_prd = current.partition(_PRD_MARKER)
|
|
1374
|
+
await send(Envelope(
|
|
1375
|
+
type="step_update", thread_id=thread_id,
|
|
1376
|
+
data={"status": "running",
|
|
1377
|
+
"current_step": f"[{name}] Grounding the PRD against the current codebase"},
|
|
1378
|
+
).model_dump())
|
|
1379
|
+
instruction = (
|
|
1380
|
+
"The task description contains a PRD (below), drafted by the planner and "
|
|
1381
|
+
"NOT necessarily written against THIS exact codebase. Using your "
|
|
1382
|
+
"exploration above, REFINE it so every file path, module, API contract, "
|
|
1383
|
+
"data model and integration matches what ACTUALLY exists in this repo. "
|
|
1384
|
+
"Keep the same PRD sections/format; correct anything inaccurate; do NOT "
|
|
1385
|
+
"invent. Output ONLY the refined PRD as Markdown.\n\n"
|
|
1386
|
+
"=== Current PRD ===\n" + existing_prd.strip()
|
|
1387
|
+
)
|
|
1388
|
+
else:
|
|
1389
|
+
before = current.rstrip() + "\n\n---\n\n"
|
|
1390
|
+
await send(Envelope(
|
|
1391
|
+
type="step_update", thread_id=thread_id,
|
|
1392
|
+
data={"status": "running",
|
|
1393
|
+
"current_step": f"[{name}] Codebase task — writing a technical PRD from the codebase"},
|
|
1394
|
+
).model_dump())
|
|
1395
|
+
instruction = (
|
|
1396
|
+
"Using your exploration above, write a TECHNICAL PRD for this task that "
|
|
1397
|
+
"fully specifies the mechanism (how it works end-to-end) against the REAL "
|
|
1398
|
+
"code you found. Output ONLY the PRD as Markdown, following this format "
|
|
1399
|
+
"exactly:\n\n" + _PRD_FORMAT
|
|
1400
|
+
)
|
|
1401
|
+
prd_res = await _run_with_heartbeat(
|
|
1402
|
+
local_exec.run_claude_local(
|
|
1403
|
+
instruction, repo_path, on_event=_on_event, # stream tool activity
|
|
1404
|
+
allowed_tools=_RECON_ALLOWED_TOOLS, # read-only
|
|
1405
|
+
github_token=gh_token, resume_session_id=recon_session,
|
|
1406
|
+
),
|
|
1407
|
+
send=send, thread_id=thread_id, label="Working on the PRD", name=name,
|
|
1322
1408
|
)
|
|
1323
1409
|
recon_session = prd_res.get("session_id") or recon_session
|
|
1324
1410
|
prd = (prd_res.get("result") or "").strip() if prd_res.get("ok") else ""
|
|
1325
1411
|
if prd:
|
|
1326
|
-
enriched =
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
pending["user_input"] = enriched # shared across repos → reused, not regenerated
|
|
1412
|
+
enriched = f"{before}{_PRD_MARKER}\n\n{prd}"[:20000]
|
|
1413
|
+
pending["user_input"] = enriched # shared across repos → reused
|
|
1414
|
+
pending["_prd_grounded"] = True # ground once per ticket
|
|
1330
1415
|
try:
|
|
1331
1416
|
await client.update_task_ml(task_id, {"description": enriched}, model_used=_EPIC_MODELS)
|
|
1332
1417
|
except Exception: # noqa: BLE001 — persisting the PRD is best-effort
|
|
@@ -2562,6 +2647,16 @@ def _extract_repo_targets(text: str | None) -> list[dict]:
|
|
|
2562
2647
|
for p in _extract_all_paths(text):
|
|
2563
2648
|
name = Path(p).name
|
|
2564
2649
|
url = by_name.get(name.lower())
|
|
2650
|
+
# Ignore bare code-reference paths mentioned in the instructions (e.g.
|
|
2651
|
+
# "use shared components in `/components`") that are neither a real repo
|
|
2652
|
+
# (paired with a GitHub URL) nor an existing local directory. Without this
|
|
2653
|
+
# a path like /components gets picked as the work dir → Permission denied.
|
|
2654
|
+
if not url:
|
|
2655
|
+
try:
|
|
2656
|
+
if not Path(p).expanduser().is_dir():
|
|
2657
|
+
continue
|
|
2658
|
+
except OSError:
|
|
2659
|
+
continue
|
|
2565
2660
|
targets.append({
|
|
2566
2661
|
"path": p,
|
|
2567
2662
|
"url": url,
|
|
@@ -2600,13 +2695,25 @@ def _resolve_local_project_path(task: dict, payload: dict) -> str:
|
|
|
2600
2695
|
if overrides.get(tid):
|
|
2601
2696
|
return str(Path(overrides[tid]).expanduser())
|
|
2602
2697
|
|
|
2603
|
-
# PRIMARY — honour the location the human wrote in the Jira task.
|
|
2698
|
+
# PRIMARY — honour the location the human wrote in the Jira task. Prefer a REAL
|
|
2699
|
+
# repo (a path paired with a GitHub URL), then any surviving repo target
|
|
2700
|
+
# (existing local dir), then a bare path that actually exists — never a
|
|
2701
|
+
# code-reference path like `/components` that doesn't exist on disk.
|
|
2604
2702
|
for field in ("humanInstructions", "description"):
|
|
2605
|
-
|
|
2606
|
-
if
|
|
2703
|
+
text = task.get(field)
|
|
2704
|
+
if not text:
|
|
2705
|
+
continue
|
|
2706
|
+
targets = _extract_repo_targets(text) # spurious/non-existent paths filtered out
|
|
2707
|
+
with_url = next((t["path"] for t in targets if t.get("url")), None)
|
|
2708
|
+
if with_url:
|
|
2709
|
+
return with_url
|
|
2710
|
+
if targets:
|
|
2711
|
+
return targets[0]["path"]
|
|
2712
|
+
found = _extract_path_from_text(text)
|
|
2713
|
+
if found and Path(found).expanduser().exists():
|
|
2607
2714
|
return found
|
|
2608
2715
|
|
|
2609
|
-
# FALLBACK —
|
|
2716
|
+
# FALLBACK — no usable location instructed anywhere.
|
|
2610
2717
|
root = payload.get("workspace_root") or str(Path.home() / "AgentOS")
|
|
2611
2718
|
key = (
|
|
2612
2719
|
task.get("projectKey")
|
|
@@ -2846,38 +2953,63 @@ async def handle_orchestrate_auto_local_run(
|
|
|
2846
2953
|
# never marked In Progress/Done — only ever converted
|
|
2847
2954
|
# Task → implemented by the normal pipeline below (In Progress → Done)
|
|
2848
2955
|
# Each is isolated: a failure must not abort the remaining queue.
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
driven_by = str(task.get("drivenBy") or "").strip().upper()
|
|
2856
|
-
needs_validation = (ttype == "epic") or (ttype == "story" and driven_by != "SYSTEM")
|
|
2956
|
+
# Validation gate: Epics always; a Story only when USER-driven (a
|
|
2957
|
+
# SYSTEM/bot-created Story is trusted). A rejected ticket is commented +
|
|
2958
|
+
# moved to Backlog inside the gate — skip it here.
|
|
2959
|
+
driven_by = str(task.get("drivenBy") or "").strip().upper()
|
|
2960
|
+
|
|
2961
|
+
if ttype == "epic":
|
|
2857
2962
|
try:
|
|
2858
|
-
if
|
|
2859
|
-
client, task, ttype, payload, send, thread_id
|
|
2860
|
-
):
|
|
2963
|
+
if not await _validate_ticket_or_reject(client, task, "epic", payload, send, thread_id):
|
|
2861
2964
|
processed += 1
|
|
2862
2965
|
continue
|
|
2863
|
-
await
|
|
2966
|
+
await _handle_epic(client, task, payload, send, thread_id)
|
|
2864
2967
|
except Exception as exc: # noqa: BLE001
|
|
2865
|
-
logger.warning("auto_local
|
|
2968
|
+
logger.warning("auto_local.epic_failed task=%s err=%s", tid, exc)
|
|
2866
2969
|
try:
|
|
2867
|
-
await client.log_activity(f"Task {tid}:
|
|
2868
|
-
|
|
2869
|
-
# marked In Progress/Done/Backlog) so developers keep it.
|
|
2870
|
-
if ttype == "epic":
|
|
2871
|
-
await client.move_to_backlog(tid)
|
|
2970
|
+
await client.log_activity(f"Task {tid}: epic handling errored — {str(exc)[:200]}")
|
|
2971
|
+
await client.move_to_backlog(tid)
|
|
2872
2972
|
except Exception: # noqa: BLE001
|
|
2873
2973
|
pass
|
|
2874
2974
|
await send(Envelope(
|
|
2875
2975
|
type=Envelope.TYPE_ERROR, thread_id=thread_id,
|
|
2876
|
-
data={"code":
|
|
2976
|
+
data={"code": "EPIC_DECOMPOSE_FAILED", "task_id": tid, "message": str(exc)[:200]},
|
|
2877
2977
|
).model_dump())
|
|
2878
2978
|
processed += 1
|
|
2879
2979
|
continue
|
|
2880
2980
|
|
|
2981
|
+
if ttype == "story":
|
|
2982
|
+
try:
|
|
2983
|
+
if driven_by != "SYSTEM" and not await _validate_ticket_or_reject(
|
|
2984
|
+
client, task, "story", payload, send, thread_id
|
|
2985
|
+
):
|
|
2986
|
+
processed += 1
|
|
2987
|
+
continue
|
|
2988
|
+
new_desc = await _handle_story(client, task, payload, send, thread_id)
|
|
2989
|
+
except Exception as exc: # noqa: BLE001
|
|
2990
|
+
logger.warning("auto_local.story_failed task=%s err=%s", tid, exc)
|
|
2991
|
+
await client.log_activity(f"Task {tid}: story handling errored — {str(exc)[:200]}")
|
|
2992
|
+
await send(Envelope(
|
|
2993
|
+
type=Envelope.TYPE_ERROR, thread_id=thread_id,
|
|
2994
|
+
data={"code": "STORY_HANDLE_FAILED", "task_id": tid, "message": str(exc)[:200]},
|
|
2995
|
+
).model_dump())
|
|
2996
|
+
processed += 1
|
|
2997
|
+
continue
|
|
2998
|
+
# `agentos-auto story` (stories_only) = convert-only, stop here.
|
|
2999
|
+
# Conversion failure (new_desc None) also stops. Otherwise fall
|
|
3000
|
+
# through and code the now-Task IN THE SAME RUN (default agentos-auto).
|
|
3001
|
+
if stories_only or not new_desc:
|
|
3002
|
+
processed += 1
|
|
3003
|
+
continue
|
|
3004
|
+
task = {**task, "type": "Task", "description": new_desc}
|
|
3005
|
+
ttype = "task"
|
|
3006
|
+
await send(Envelope(
|
|
3007
|
+
type="step_update", thread_id=thread_id,
|
|
3008
|
+
data={"jira_task_id": tid,
|
|
3009
|
+
"current_step": f"{jira_key}: implementing the converted Task now (In Progress → Done)"},
|
|
3010
|
+
).model_dump())
|
|
3011
|
+
# ↓ falls through to the normal Task pipeline below
|
|
3012
|
+
|
|
2881
3013
|
task_payload = {
|
|
2882
3014
|
"thread_id": thread_id,
|
|
2883
3015
|
"jira_task_id": tid,
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.60 → devops_bot_sdk-1.4.65}/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
|
|
File without changes
|