devops-bot-sdk 1.4.13__tar.gz → 1.4.17__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.13 → devops_bot_sdk-1.4.17}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/pyproject.toml +1 -1
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/client.py +1 -1
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/ipc/handlers.py +58 -17
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/local_exec.py +114 -7
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/README.md +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/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.17"
|
|
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.17
|
|
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.17"
|
|
34
34
|
__author__ = "AgentOS"
|
|
35
35
|
|
|
36
36
|
from sdk.client import BackendClient
|
|
@@ -36,7 +36,7 @@ from sdk.sse import _check_status, stream_with_reconnect
|
|
|
36
36
|
|
|
37
37
|
logger = logging.getLogger(__name__)
|
|
38
38
|
|
|
39
|
-
SDK_VERSION = "1.4.
|
|
39
|
+
SDK_VERSION = "1.4.17"
|
|
40
40
|
_POLL_INTERVAL = 3.0
|
|
41
41
|
_POLL_TIMEOUT = 600.0
|
|
42
42
|
_ORCHESTRATE_TIMEOUT = 2700.0 # 45 min — covers approval wait + VPS execution time
|
|
@@ -149,6 +149,7 @@ def _build_local_prompt(
|
|
|
149
149
|
structured_plan: dict | None,
|
|
150
150
|
human_instructions: str | None = None,
|
|
151
151
|
agent_mode: bool = True,
|
|
152
|
+
git_managed: bool = True,
|
|
152
153
|
) -> str:
|
|
153
154
|
"""Compose the coding prompt for the local claude CLI from the server plan.
|
|
154
155
|
|
|
@@ -204,14 +205,29 @@ def _build_local_prompt(
|
|
|
204
205
|
"above are authoritative if anything conflicts) ===\n"
|
|
205
206
|
+ _clip_text(user_input.strip())
|
|
206
207
|
)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
208
|
+
if git_managed:
|
|
209
|
+
# The target is a git repo the SDK manages — it branches before and
|
|
210
|
+
# commits/pushes/opens the PR after, so the agent must not touch git here.
|
|
211
|
+
parts.append(
|
|
212
|
+
"Complete all of the above in the current project directory. "
|
|
213
|
+
"Create and edit every file needed for a complete, working result.\n"
|
|
214
|
+
"Git for THIS repo is handled for you: a feature branch is already checked "
|
|
215
|
+
"out, and the system will commit, push, and open the PR after you finish — "
|
|
216
|
+
"so do NOT create branches, commit, push, or open a PR for the current repo. "
|
|
217
|
+
"(You may still clone and open PRs for OTHER repositories the instructions name.)"
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
# Plain local working folder (no GitHub repo / not under our git control) —
|
|
221
|
+
# e.g. a non-developer (BA/analyst) task that just produces files/docs.
|
|
222
|
+
parts.append(
|
|
223
|
+
"Complete all of the above in the current working directory by creating and "
|
|
224
|
+
"editing the necessary files (documents, .md files, spreadsheets, code — "
|
|
225
|
+
"whatever the task asks for). This is a plain local folder, NOT a git "
|
|
226
|
+
"repository under our control: do NOT initialise git, create branches, "
|
|
227
|
+
"commit, push, or open a PR for it unless the instructions explicitly say so. "
|
|
228
|
+
"Just produce the requested deliverables here. (You may still clone and open "
|
|
229
|
+
"PRs for OTHER repositories the instructions explicitly name.)"
|
|
230
|
+
)
|
|
215
231
|
# agentMode=true → require approval before crucial/irreversible actions.
|
|
216
232
|
if agent_mode:
|
|
217
233
|
from sdk import crucial
|
|
@@ -399,7 +415,6 @@ async def _code_one_repo(
|
|
|
399
415
|
repo_path: str,
|
|
400
416
|
repo_url: str | None,
|
|
401
417
|
pending: dict,
|
|
402
|
-
prompt: str,
|
|
403
418
|
gh_token: str | None,
|
|
404
419
|
agent_mode: bool,
|
|
405
420
|
approval_dir: str | None,
|
|
@@ -474,6 +489,14 @@ async def _code_one_repo(
|
|
|
474
489
|
data={"status": "running", "current_step": f"Executing locally in {name} (claude_code)"},
|
|
475
490
|
).model_dump())
|
|
476
491
|
|
|
492
|
+
# Build the prompt with framing that matches the target: a git repo we manage
|
|
493
|
+
# (branch + PR) vs a plain local folder (BA/analyst deliverables, no git/PR).
|
|
494
|
+
prompt = _build_local_prompt(
|
|
495
|
+
pending["user_input"], pending.get("structured_plan"),
|
|
496
|
+
pending.get("human_instructions"),
|
|
497
|
+
agent_mode=agent_mode, git_managed=bool(branch_info),
|
|
498
|
+
)
|
|
499
|
+
|
|
477
500
|
# 3. initial claude run
|
|
478
501
|
result = await local_exec.run_claude_local(
|
|
479
502
|
prompt, repo_path, on_event=_on_event,
|
|
@@ -636,11 +659,8 @@ async def handle_pipeline_approve(
|
|
|
636
659
|
return
|
|
637
660
|
|
|
638
661
|
# ── Approved: run the local claude CLI on EACH repo the ticket names ──
|
|
639
|
-
prompt
|
|
640
|
-
|
|
641
|
-
pending.get("human_instructions"),
|
|
642
|
-
agent_mode=bool(pending.get("agent_mode", True)),
|
|
643
|
-
)
|
|
662
|
+
# The prompt is built per-repo inside _code_one_repo so its framing matches
|
|
663
|
+
# whether that target is a git repo (branch + PR) or a plain local folder.
|
|
644
664
|
agent_mode = bool(pending.get("agent_mode", True))
|
|
645
665
|
|
|
646
666
|
# Fetch the user's GitHub token from the backend so the local agent can
|
|
@@ -675,7 +695,7 @@ async def handle_pipeline_approve(
|
|
|
675
695
|
runs.append(await _code_one_repo(
|
|
676
696
|
client, repo_path=tgt["path"], repo_url=tgt.get("url"),
|
|
677
697
|
base=tgt.get("base"),
|
|
678
|
-
pending=pending,
|
|
698
|
+
pending=pending, gh_token=gh_token,
|
|
679
699
|
agent_mode=agent_mode, approval_dir=approval_dir,
|
|
680
700
|
send=send, thread_id=thread_id, task_id=task_id,
|
|
681
701
|
))
|
|
@@ -1202,8 +1222,13 @@ _PATH_ROOTS = (
|
|
|
1202
1222
|
"home|root|var|opt|srv|usr|mnt|media|data|app|apps|"
|
|
1203
1223
|
"project|projects|workspace|workspaces|repos|code|tmp|Users"
|
|
1204
1224
|
)
|
|
1205
|
-
|
|
1206
|
-
|
|
1225
|
+
# Unix / ~-home paths. Folder names may contain SPACES too (e.g.
|
|
1226
|
+
# "/home/me/BA activities/repo"), so segments allow spaces and the path runs to
|
|
1227
|
+
# end-of-line. The root is anchored to a known top-level dir to limit false
|
|
1228
|
+
# positives in prose; for an inline path followed by other words on the SAME
|
|
1229
|
+
# line, quote it (see _QUOTED_PATH_RE) to avoid grabbing the trailing text.
|
|
1230
|
+
_ABS_PATH_RE = re.compile(rf"(?<![\w.])/(?:{_PATH_ROOTS})(?:/[A-Za-z0-9._\- ]+)+")
|
|
1231
|
+
_HOME_PATH_RE = re.compile(r"(?<![\w.])~(?:/[A-Za-z0-9._\- ]+)+")
|
|
1207
1232
|
# Windows path. Folder names may contain SPACES (e.g. "BA activities clone"), so a
|
|
1208
1233
|
# segment is any run of valid path chars (everything except \ / : * ? " < > | and
|
|
1209
1234
|
# newline) up to the next separator — it must NOT stop at the first space.
|
|
@@ -1429,6 +1454,19 @@ def _extract_repo_targets(text: str | None) -> list[dict]:
|
|
|
1429
1454
|
return targets
|
|
1430
1455
|
|
|
1431
1456
|
|
|
1457
|
+
def _task_created_key(task: dict):
|
|
1458
|
+
"""Sortable Jira creation time for a raw task (e.g. ``createdAt`` =
|
|
1459
|
+
"2026-06-22T15:07:52.452Z"). Used to process tickets oldest-first. A
|
|
1460
|
+
missing/invalid timestamp sorts LAST so well-formed, earlier tickets win."""
|
|
1461
|
+
from datetime import datetime, timezone
|
|
1462
|
+
|
|
1463
|
+
raw = str(task.get("createdAt") or task.get("created_at") or "").strip()
|
|
1464
|
+
try:
|
|
1465
|
+
return datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
|
1466
|
+
except (ValueError, TypeError):
|
|
1467
|
+
return datetime.max.replace(tzinfo=timezone.utc)
|
|
1468
|
+
|
|
1469
|
+
|
|
1432
1470
|
def _resolve_local_project_path(task: dict, payload: dict) -> str:
|
|
1433
1471
|
"""Pick the LOCAL directory to write a task's code into (on the user's box).
|
|
1434
1472
|
|
|
@@ -1579,6 +1617,9 @@ async def handle_orchestrate_auto_local_run(
|
|
|
1579
1617
|
|
|
1580
1618
|
# RAW /tasks-ml records carry agentMode / jiraIssueKey / humanInstructions.
|
|
1581
1619
|
tasks = await client.list_todo_tasks_raw(status="To Do")
|
|
1620
|
+
# Process oldest-created first (FIFO by Jira `createdAt`) so the ticket
|
|
1621
|
+
# raised earliest is started first. Unknown/invalid timestamps sort last.
|
|
1622
|
+
tasks = sorted(tasks, key=_task_created_key)
|
|
1582
1623
|
|
|
1583
1624
|
await send(Envelope(
|
|
1584
1625
|
type="pipeline_started", thread_id=thread_id,
|
|
@@ -22,6 +22,7 @@ import json
|
|
|
22
22
|
import os
|
|
23
23
|
import re
|
|
24
24
|
import shutil
|
|
25
|
+
from datetime import datetime
|
|
25
26
|
from pathlib import Path
|
|
26
27
|
from typing import Any, Awaitable, Callable
|
|
27
28
|
|
|
@@ -92,6 +93,70 @@ def _parse_reset_epoch(text: str | None) -> int | None:
|
|
|
92
93
|
return None
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
# ── Per-run claude log file (on the user's machine, local-timezone stamps) ──
|
|
97
|
+
# Every local claude run writes a readable, timestamped transcript so a box can
|
|
98
|
+
# be debugged after the fact. Dir is AGENTOS_LOG_DIR or ~/.agentos/logs.
|
|
99
|
+
|
|
100
|
+
def _open_run_log(project_path: str, resume_session_id: str | None):
|
|
101
|
+
"""Open a per-run log file under the local logs dir. Returns (file, path) or
|
|
102
|
+
(None, None). Best-effort — never breaks the run."""
|
|
103
|
+
try:
|
|
104
|
+
base = os.getenv("AGENTOS_LOG_DIR") or str(Path.home() / ".agentos" / "logs")
|
|
105
|
+
d = Path(base).expanduser()
|
|
106
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
now = datetime.now().astimezone() # local system tz
|
|
108
|
+
proj = Path(project_path).name or "run"
|
|
109
|
+
# Filenames are filesystem-safe (no ':'); stamp encodes the local tz offset.
|
|
110
|
+
fname = f"claude_{proj}_{now.strftime('%Y-%m-%d_%H-%M-%S%z')}.log"
|
|
111
|
+
path = d / fname
|
|
112
|
+
f = open(path, "a", encoding="utf-8")
|
|
113
|
+
return f, str(path)
|
|
114
|
+
except Exception: # noqa: BLE001
|
|
115
|
+
return None, None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _logln(f, msg: str) -> None:
|
|
119
|
+
"""Append a local-timezone timestamped line. Best-effort."""
|
|
120
|
+
if not f:
|
|
121
|
+
return
|
|
122
|
+
try:
|
|
123
|
+
ts = datetime.now().astimezone().strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
124
|
+
f.write(f"[{ts}] {msg}\n")
|
|
125
|
+
f.flush()
|
|
126
|
+
except Exception: # noqa: BLE001
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _event_logline(event: dict) -> str | None:
|
|
131
|
+
"""A compact, human-readable line for one stream-json event (or None to skip)."""
|
|
132
|
+
def _short(s, n=200):
|
|
133
|
+
return " ".join(str(s or "").split())[:n]
|
|
134
|
+
|
|
135
|
+
t = event.get("type")
|
|
136
|
+
if t == "system":
|
|
137
|
+
return f"SYSTEM {event.get('subtype', '')}".rstrip()
|
|
138
|
+
if t == "assistant":
|
|
139
|
+
parts: list[str] = []
|
|
140
|
+
for c in event.get("message", {}).get("content", []):
|
|
141
|
+
if c.get("type") == "text" and c.get("text", "").strip():
|
|
142
|
+
parts.append("TEXT: " + _short(c["text"], 300))
|
|
143
|
+
elif c.get("type") == "tool_use":
|
|
144
|
+
inp = c.get("input") or {}
|
|
145
|
+
detail = (inp.get("command") or inp.get("file_path") or inp.get("pattern")
|
|
146
|
+
or inp.get("url") or inp.get("description") or "")
|
|
147
|
+
parts.append(f"TOOL {c.get('name', 'tool')}" + (f" → {_short(detail)}" if detail else ""))
|
|
148
|
+
return " | ".join(parts) if parts else None
|
|
149
|
+
if t == "user":
|
|
150
|
+
tr = event.get("tool_use_result") or event.get("message", {})
|
|
151
|
+
if isinstance(tr, dict) and tr.get("is_error"):
|
|
152
|
+
return "TOOL_ERROR: " + _short(tr.get("content") or tr.get("stderr") or "", 300)
|
|
153
|
+
return None
|
|
154
|
+
if t == "result":
|
|
155
|
+
return (f"RESULT subtype={event.get('subtype')} is_error={event.get('is_error')} "
|
|
156
|
+
f"cost=${event.get('total_cost_usd', 0)}")
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
95
160
|
def _ctx_exceeded(text: str | None) -> bool:
|
|
96
161
|
low = (text or "").lower()
|
|
97
162
|
return any(m in low for m in _CONTEXT_MARKERS)
|
|
@@ -157,8 +222,12 @@ async def run_claude_local(
|
|
|
157
222
|
path.mkdir(parents=True, exist_ok=True)
|
|
158
223
|
|
|
159
224
|
tools = allowed_tools or DEFAULT_ALLOWED_TOOLS
|
|
225
|
+
# The prompt is fed via STDIN (not as an argv), so a long plan/instructions
|
|
226
|
+
# can't blow past the OS command-line limit — on Windows that's ~32 KB and
|
|
227
|
+
# raised "The command line is too long." `claude -p` reads the prompt from a
|
|
228
|
+
# piped stdin.
|
|
160
229
|
cmd = [
|
|
161
|
-
cli, "-p",
|
|
230
|
+
cli, "-p",
|
|
162
231
|
"--output-format", "stream-json",
|
|
163
232
|
"--verbose", # required for stream-json
|
|
164
233
|
"--permission-mode", "acceptEdits", # apply file edits without prompting
|
|
@@ -205,12 +274,24 @@ async def run_claude_local(
|
|
|
205
274
|
proc = await asyncio.create_subprocess_exec(
|
|
206
275
|
*cmd,
|
|
207
276
|
cwd=str(path),
|
|
277
|
+
stdin=asyncio.subprocess.PIPE,
|
|
208
278
|
stdout=asyncio.subprocess.PIPE,
|
|
209
279
|
stderr=asyncio.subprocess.PIPE,
|
|
210
280
|
env=env,
|
|
211
281
|
limit=_STREAM_LIMIT_BYTES,
|
|
212
282
|
)
|
|
213
283
|
|
|
284
|
+
# Feed the prompt via stdin (claude -p reads it), then close stdin so claude
|
|
285
|
+
# starts. Bounded write before draining stdout — no deadlock (claude buffers
|
|
286
|
+
# the prompt before emitting any events).
|
|
287
|
+
if proc.stdin is not None:
|
|
288
|
+
try:
|
|
289
|
+
proc.stdin.write((prompt or "").encode("utf-8"))
|
|
290
|
+
await proc.stdin.drain()
|
|
291
|
+
proc.stdin.close()
|
|
292
|
+
except Exception: # noqa: BLE001 — broken pipe if claude already exited
|
|
293
|
+
pass
|
|
294
|
+
|
|
214
295
|
result_event: dict | None = None
|
|
215
296
|
files_changed: list[str] = []
|
|
216
297
|
session_id: str | None = resume_session_id
|
|
@@ -218,6 +299,26 @@ async def run_claude_local(
|
|
|
218
299
|
started = _time.monotonic()
|
|
219
300
|
stall = {"timed_out": False, "reason": ""}
|
|
220
301
|
|
|
302
|
+
# Per-run local log (timezone-stamped) for after-the-fact debugging.
|
|
303
|
+
logf, logpath = _open_run_log(str(path), resume_session_id)
|
|
304
|
+
_logln(logf, f"START claude run | project={path} | resume={resume_session_id or '-'} "
|
|
305
|
+
f"| idle_timeout={idle_timeout:.0f}s | cap={timeout:.0f}s")
|
|
306
|
+
_logln(logf, "PROMPT: " + " ".join((prompt or "").split())[:600])
|
|
307
|
+
|
|
308
|
+
def _finish(result: dict) -> dict:
|
|
309
|
+
"""Write the run footer, close the log, and return the result."""
|
|
310
|
+
_logln(logf, f"END ok={result.get('ok')} error={result.get('error')} "
|
|
311
|
+
f"tokens={result.get('total_tokens')} cost=${result.get('cost_usd')} "
|
|
312
|
+
f"files_changed={len(result.get('files_changed') or [])} "
|
|
313
|
+
f"limit_exceeded={result.get('limit_exceeded')} "
|
|
314
|
+
f"session={result.get('session_id')}")
|
|
315
|
+
if logf:
|
|
316
|
+
try:
|
|
317
|
+
logf.close()
|
|
318
|
+
except Exception: # noqa: BLE001
|
|
319
|
+
pass
|
|
320
|
+
return result
|
|
321
|
+
|
|
221
322
|
async def _drain_stdout() -> None:
|
|
222
323
|
nonlocal result_event, session_id
|
|
223
324
|
assert proc.stdout is not None
|
|
@@ -250,6 +351,10 @@ async def run_claude_local(
|
|
|
250
351
|
except json.JSONDecodeError:
|
|
251
352
|
continue
|
|
252
353
|
|
|
354
|
+
_ll = _event_logline(event)
|
|
355
|
+
if _ll:
|
|
356
|
+
_logln(logf, _ll)
|
|
357
|
+
|
|
253
358
|
# Capture the claude session id (emitted on init + result events) so
|
|
254
359
|
# the caller can --resume this exact session later.
|
|
255
360
|
if event.get("session_id"):
|
|
@@ -284,23 +389,25 @@ async def run_claude_local(
|
|
|
284
389
|
proc.kill()
|
|
285
390
|
except ProcessLookupError:
|
|
286
391
|
pass
|
|
287
|
-
return {
|
|
392
|
+
return _finish({
|
|
288
393
|
"ok": False, "result": "", "error": f"claude CLI timed out — {stall['reason']}",
|
|
289
394
|
"input_tokens": 0, "output_tokens": 0, "total_tokens": 0,
|
|
290
395
|
"cost_usd": 0.0, "files_changed": files_changed,
|
|
291
396
|
"session_id": session_id, "context_exceeded": False,
|
|
292
|
-
}
|
|
397
|
+
})
|
|
293
398
|
|
|
294
399
|
if result_event is None:
|
|
295
400
|
stderr = (await proc.stderr.read()).decode("utf-8", errors="replace") if proc.stderr else ""
|
|
296
401
|
err = stderr.strip()[:500] or "no result event from claude CLI"
|
|
297
|
-
|
|
402
|
+
if stderr.strip():
|
|
403
|
+
_logln(logf, "STDERR: " + " ".join(stderr.split())[:600])
|
|
404
|
+
return _finish({
|
|
298
405
|
"ok": False, "result": "", "error": err,
|
|
299
406
|
"input_tokens": 0, "output_tokens": 0, "total_tokens": 0,
|
|
300
407
|
"cost_usd": 0.0, "files_changed": files_changed,
|
|
301
408
|
"session_id": session_id, "context_exceeded": _ctx_exceeded(err),
|
|
302
409
|
"limit_exceeded": _is_limit(err), "limit_reset_epoch": _parse_reset_epoch(err),
|
|
303
|
-
}
|
|
410
|
+
})
|
|
304
411
|
|
|
305
412
|
usage = result_event.get("usage") or {}
|
|
306
413
|
in_tok = int(usage.get("input_tokens", 0) or 0)
|
|
@@ -321,7 +428,7 @@ async def run_claude_local(
|
|
|
321
428
|
elif limit_hit:
|
|
322
429
|
err_text = result_text or "claude usage limit reached"
|
|
323
430
|
|
|
324
|
-
return {
|
|
431
|
+
return _finish({
|
|
325
432
|
"ok": (not is_error) and (not limit_hit),
|
|
326
433
|
"result": result_text,
|
|
327
434
|
"input_tokens": in_tok,
|
|
@@ -334,4 +441,4 @@ async def run_claude_local(
|
|
|
334
441
|
"context_exceeded": is_error and _ctx_exceeded(err_text),
|
|
335
442
|
"limit_exceeded": limit_hit,
|
|
336
443
|
"limit_reset_epoch": reset_epoch,
|
|
337
|
-
}
|
|
444
|
+
})
|
|
File without changes
|
|
File without changes
|
{devops_bot_sdk-1.4.13 → devops_bot_sdk-1.4.17}/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
|