devops-bot-sdk 1.4.151__tar.gz → 1.4.153__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.151 → devops_bot_sdk-1.4.153}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/devops_bot_sdk.egg-info/SOURCES.txt +1 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/config.py +10 -1
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/api.py +86 -67
- devops_bot_sdk-1.4.153/tests/test_agent_prompt_async.py +74 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/README.md +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/pyproject.toml +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/client.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/run_auto.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/health.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/models.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/service.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/setup.cfg +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/tests/test_agent_prompt_models.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/tests/test_agents_connectors_builder.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/tests/test_git_action.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/tests/test_health.py +0 -0
- {devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/tests/test_updater_extras.py +0 -0
|
@@ -86,6 +86,7 @@ sdk/ui/static/setup.html
|
|
|
86
86
|
sdk/ui/static/styles.css
|
|
87
87
|
sdk/ui/static/tickets.html
|
|
88
88
|
tests/test_agent_chat_features.py
|
|
89
|
+
tests/test_agent_prompt_async.py
|
|
89
90
|
tests/test_agent_prompt_models.py
|
|
90
91
|
tests/test_agents_connectors_builder.py
|
|
91
92
|
tests/test_git_action.py
|
|
@@ -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.153
|
|
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.153" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
|
|
34
34
|
__author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
|
|
35
35
|
|
|
36
36
|
from sdk.client import BackendClient
|
|
@@ -149,6 +149,13 @@ def _dotenv_value(key: str) -> str:
|
|
|
149
149
|
return ""
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
# Built-in permanent allowlist — the central AI backend's IP(s). Baked in so the
|
|
153
|
+
# backend can reach EVERY VPS's /api/ui/* out of the box (no per-VPS .env needed);
|
|
154
|
+
# always allowed, treated as permanent (not removable via the API). Extend/override
|
|
155
|
+
# with AGENTOS_UI_ALLOWED_IPS in .env or the process env.
|
|
156
|
+
_DEFAULT_ALLOWED_IPS: list[str] = ["20.12.201.148"]
|
|
157
|
+
|
|
158
|
+
|
|
152
159
|
def _parse_ip_list(raw: str) -> list[str]:
|
|
153
160
|
"""Split an IP allowlist string, tolerant of JSON-ish list syntax.
|
|
154
161
|
|
|
@@ -181,6 +188,7 @@ def get_allowed_ips() -> list[str]:
|
|
|
181
188
|
change. CIDR/prefix entries are supported by the middleware.
|
|
182
189
|
"""
|
|
183
190
|
sources = [
|
|
191
|
+
",".join(_DEFAULT_ALLOWED_IPS), # built-in backend IP(s) — always allowed
|
|
184
192
|
_dotenv_value("AGENTOS_UI_ALLOWED_IPS"),
|
|
185
193
|
os.environ.get("AGENTOS_UI_ALLOWED_IPS", ""),
|
|
186
194
|
_load_raw().get("allowed_ips", ""),
|
|
@@ -205,7 +213,8 @@ def permanent_allowed_ips() -> list[str]:
|
|
|
205
213
|
through the API — change them by editing ``.env`` and restarting.
|
|
206
214
|
"""
|
|
207
215
|
out: list[str] = []
|
|
208
|
-
for src in (
|
|
216
|
+
for src in (",".join(_DEFAULT_ALLOWED_IPS), # built-in backend IP(s) — always permanent
|
|
217
|
+
_dotenv_value("AGENTOS_UI_ALLOWED_IPS"),
|
|
209
218
|
os.environ.get("AGENTOS_UI_ALLOWED_IPS", "")):
|
|
210
219
|
for ip in _parse_ip_list(src):
|
|
211
220
|
if ip not in out:
|
|
@@ -994,55 +994,70 @@ async def _screen_agent_inputs(agent_name: str, agent_type: str, job: str) -> li
|
|
|
994
994
|
async def agent_prompt_generate(body: AgentPromptGenerateRequest) -> dict:
|
|
995
995
|
"""name + type + job → a structured system prompt + {{variables}} (claude_code).
|
|
996
996
|
|
|
997
|
-
|
|
998
|
-
|
|
997
|
+
ASYNC: local Claude Code generation can take a while, so this starts a detached
|
|
998
|
+
job and returns ``{job_id}`` immediately (navigation-safe, and no long request
|
|
999
|
+
held across a proxy/gateway — avoids 504s). Poll ``GET /api/ui/jobs/{job_id}``
|
|
1000
|
+
for the result: ``{status, engine, ai_comments, agent_name, agent_type,
|
|
1001
|
+
system_prompt, variables[]}`` (or ``status="needs_revision"`` / ``"error"``).
|
|
999
1002
|
"""
|
|
1000
1003
|
from sdk.agents.prompt_studio import build_generate_prompt, parse_generation
|
|
1004
|
+
from sdk.ui import jobs
|
|
1001
1005
|
|
|
1002
1006
|
# Empty agent_name/agent_type/job already 422'd by the request model.
|
|
1003
1007
|
agent_name = body.agent_name
|
|
1004
1008
|
agent_type = body.agent_type
|
|
1005
1009
|
job = body.job
|
|
1010
|
+
context = body.context
|
|
1011
|
+
|
|
1012
|
+
async def work():
|
|
1013
|
+
# Semantic screen (offensive / meaningless) → unified needs_revision. Fail-open.
|
|
1014
|
+
comments = await _screen_agent_inputs(agent_name, agent_type, job)
|
|
1015
|
+
if comments:
|
|
1016
|
+
return _needs_revision(comments)
|
|
1017
|
+
text = await _run_prompt_engine(build_generate_prompt(agent_name, agent_type, job, context))
|
|
1018
|
+
parsed = parse_generation(text)
|
|
1019
|
+
if not parsed["system_prompt"]:
|
|
1020
|
+
return {"status": "error", "engine": "claude_code", "ai_comments": [],
|
|
1021
|
+
"error": "generation returned no system_prompt"}
|
|
1022
|
+
return {"status": "ok", "engine": "claude_code", "ai_comments": [],
|
|
1023
|
+
"agent_name": agent_name, "agent_type": agent_type, **parsed}
|
|
1006
1024
|
|
|
1007
|
-
|
|
1008
|
-
comments = await _screen_agent_inputs(agent_name, agent_type, job)
|
|
1009
|
-
if comments:
|
|
1010
|
-
return _needs_revision(comments)
|
|
1011
|
-
|
|
1012
|
-
text = await _run_prompt_engine(
|
|
1013
|
-
build_generate_prompt(agent_name, agent_type, job, body.context)
|
|
1014
|
-
)
|
|
1015
|
-
parsed = parse_generation(text)
|
|
1016
|
-
if not parsed["system_prompt"]:
|
|
1017
|
-
raise HTTPException(status_code=502, detail="generation returned no system_prompt")
|
|
1018
|
-
return {"status": "ok", "engine": "claude_code", "ai_comments": [],
|
|
1019
|
-
"agent_name": agent_name, "agent_type": agent_type, **parsed}
|
|
1025
|
+
return {"job_id": jobs.start(work())}
|
|
1020
1026
|
|
|
1021
1027
|
|
|
1022
1028
|
@router.post("/agent-prompt/regenerate", dependencies=[_auth])
|
|
1023
1029
|
async def agent_prompt_regenerate(body: PromptRegenerateRequest) -> dict:
|
|
1024
1030
|
"""Improve an existing prompt (optional feedback) → prompt + {{variables}}.
|
|
1025
1031
|
|
|
1026
|
-
|
|
1027
|
-
``
|
|
1032
|
+
ASYNC (same as generate): returns ``{job_id}``; poll ``GET /api/ui/jobs/{job_id}``.
|
|
1033
|
+
Body: ``system_prompt``, optional ``feedback``, ``agent_name``, ``agent_type``, ``job``.
|
|
1028
1034
|
"""
|
|
1029
1035
|
from sdk.agents.prompt_studio import build_regenerate_prompt, parse_generation
|
|
1036
|
+
from sdk.ui import jobs
|
|
1030
1037
|
|
|
1031
|
-
# Empty system_prompt already 422'd by the request model.
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1038
|
+
# Empty system_prompt already 422'd by the request model.
|
|
1039
|
+
agent_name = body.agent_name
|
|
1040
|
+
agent_type = body.agent_type
|
|
1041
|
+
job = body.job
|
|
1042
|
+
system_prompt = body.system_prompt
|
|
1043
|
+
feedback = body.feedback
|
|
1044
|
+
|
|
1045
|
+
async def work():
|
|
1046
|
+
# Screen the agent metadata (blank fields ignored by the screener; fail-open).
|
|
1047
|
+
comments = await _screen_agent_inputs(agent_name, agent_type, job)
|
|
1048
|
+
if comments:
|
|
1049
|
+
return _needs_revision(comments)
|
|
1050
|
+
text = await _run_prompt_engine(build_regenerate_prompt(
|
|
1051
|
+
system_prompt, feedback, agent_name, agent_type, job,
|
|
1052
|
+
))
|
|
1053
|
+
parsed = parse_generation(text)
|
|
1054
|
+
if not parsed["system_prompt"]:
|
|
1055
|
+
return {"status": "error", "engine": "claude_code", "ai_comments": [],
|
|
1056
|
+
"error": "regeneration returned no system_prompt"}
|
|
1057
|
+
return {"status": "ok", "engine": "claude_code", "ai_comments": [],
|
|
1058
|
+
"agent_name": agent_name, "agent_type": agent_type, **parsed}
|
|
1036
1059
|
|
|
1037
|
-
|
|
1038
|
-
body.system_prompt, body.feedback,
|
|
1039
|
-
body.agent_name, body.agent_type, body.job,
|
|
1040
|
-
))
|
|
1041
|
-
parsed = parse_generation(text)
|
|
1042
|
-
if not parsed["system_prompt"]:
|
|
1043
|
-
raise HTTPException(status_code=502, detail="regeneration returned no system_prompt")
|
|
1044
|
-
return {"status": "ok", "engine": "claude_code", "ai_comments": [],
|
|
1045
|
-
"agent_name": body.agent_name, "agent_type": body.agent_type, **parsed}
|
|
1060
|
+
return {"job_id": jobs.start(work())}
|
|
1046
1061
|
|
|
1047
1062
|
|
|
1048
1063
|
@router.post("/agent-prompt/verify", dependencies=[_auth])
|
|
@@ -1051,53 +1066,57 @@ async def agent_prompt_verify(body: PromptVerifyRequest) -> dict:
|
|
|
1051
1066
|
|
|
1052
1067
|
Standards check is deterministic (our Role/Objective/Rules/Workflow/Output
|
|
1053
1068
|
template); secrets + variable metadata are inferred by Claude Code locally.
|
|
1054
|
-
|
|
1055
|
-
variables[], secrets[]}``.
|
|
1069
|
+
ASYNC: returns ``{job_id}``; poll ``GET /api/ui/jobs/{job_id}`` → ``{valid,
|
|
1070
|
+
missing_required, missing_recommended, variables[], secrets[], ...}``.
|
|
1056
1071
|
"""
|
|
1057
1072
|
from sdk.agents.prompt_studio import build_analyze_prompt, parse_analysis
|
|
1058
1073
|
from sdk.agents.prompt_template import check_system_prompt
|
|
1059
1074
|
from sdk.agents.prompt_vars import extract_variables
|
|
1075
|
+
from sdk.ui import jobs
|
|
1060
1076
|
|
|
1061
1077
|
# Too-short system_prompt already 422'd by the request model.
|
|
1062
1078
|
system_prompt = body.system_prompt
|
|
1063
1079
|
|
|
1064
|
-
|
|
1080
|
+
async def work():
|
|
1081
|
+
standards = check_system_prompt(system_prompt)
|
|
1065
1082
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1083
|
+
# Enrichment (secrets + variable metadata) via local Claude Code — best-effort:
|
|
1084
|
+
# a failure must not sink the deterministic verdict.
|
|
1085
|
+
analysis: dict = {"inputs": [], "secrets": []}
|
|
1086
|
+
try:
|
|
1087
|
+
analysis = parse_analysis(await _run_prompt_engine(build_analyze_prompt(system_prompt)))
|
|
1088
|
+
except HTTPException:
|
|
1089
|
+
pass
|
|
1090
|
+
|
|
1091
|
+
meta = {str(i.get("name", "")).strip(): i for i in analysis.get("inputs", []) if isinstance(i, dict)}
|
|
1092
|
+
variables = []
|
|
1093
|
+
for name in extract_variables(system_prompt): # {{placeholders}} are authoritative
|
|
1094
|
+
m = meta.get(name, {})
|
|
1095
|
+
variables.append({
|
|
1096
|
+
"name": name,
|
|
1097
|
+
"label": str(m.get("label") or name.replace("_", " ").title()).strip(),
|
|
1098
|
+
"description": str(m.get("description", "")).strip(),
|
|
1099
|
+
"type": str(m.get("type", "string")).strip() or "string",
|
|
1100
|
+
})
|
|
1101
|
+
|
|
1102
|
+
# Structural gaps → the SAME unified ai_comments list (all messages together).
|
|
1103
|
+
ai_comments = (
|
|
1104
|
+
[f"Missing required section: {s}." for s in standards["missing_required"]]
|
|
1105
|
+
+ [f"Recommended section missing: {s}." for s in standards["missing_recommended"]]
|
|
1106
|
+
)
|
|
1073
1107
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
"
|
|
1080
|
-
"
|
|
1081
|
-
"
|
|
1082
|
-
"
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
# Structural gaps → the SAME unified ai_comments list (all messages together).
|
|
1086
|
-
ai_comments = (
|
|
1087
|
-
[f"Missing required section: {s}." for s in standards["missing_required"]]
|
|
1088
|
-
+ [f"Recommended section missing: {s}." for s in standards["missing_recommended"]]
|
|
1089
|
-
)
|
|
1108
|
+
return {
|
|
1109
|
+
"status": "needs_revision" if standards["missing_required"] else "ok",
|
|
1110
|
+
"engine": "claude_code",
|
|
1111
|
+
"ai_comments": ai_comments,
|
|
1112
|
+
"valid": not standards["missing_required"],
|
|
1113
|
+
"missing_required": standards["missing_required"],
|
|
1114
|
+
"missing_recommended": standards["missing_recommended"],
|
|
1115
|
+
"variables": variables,
|
|
1116
|
+
"secrets": analysis.get("secrets", []),
|
|
1117
|
+
}
|
|
1090
1118
|
|
|
1091
|
-
return {
|
|
1092
|
-
"status": "needs_revision" if standards["missing_required"] else "ok",
|
|
1093
|
-
"engine": "claude_code",
|
|
1094
|
-
"ai_comments": ai_comments,
|
|
1095
|
-
"valid": not standards["missing_required"],
|
|
1096
|
-
"missing_required": standards["missing_required"],
|
|
1097
|
-
"missing_recommended": standards["missing_recommended"],
|
|
1098
|
-
"variables": variables,
|
|
1099
|
-
"secrets": analysis.get("secrets", []),
|
|
1100
|
-
}
|
|
1119
|
+
return {"job_id": jobs.start(work())}
|
|
1101
1120
|
|
|
1102
1121
|
|
|
1103
1122
|
@router.get("/agents/form", dependencies=[_auth])
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Agent-prompt endpoints are async job-based (avoids long requests / 504s).
|
|
2
|
+
|
|
3
|
+
POST /api/ui/agent-prompt/{generate,regenerate,verify} return `{job_id}`
|
|
4
|
+
immediately — the local Claude Code run happens in the background; the result is
|
|
5
|
+
fetched from GET /api/ui/jobs/{job_id}.
|
|
6
|
+
|
|
7
|
+
Note: we assert the async *contract* (an immediate `{job_id}`). Driving the
|
|
8
|
+
background job to completion isn't done via the sync TestClient (each request
|
|
9
|
+
runs its own event loop, so a detached task from the POST doesn't advance across
|
|
10
|
+
later GETs) — that's a harness limitation, not a code one. The job's *result*
|
|
11
|
+
logic is unchanged from the pre-async version (just wrapped in `work()`).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from unittest.mock import AsyncMock, patch
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
fastapi = pytest.importorskip("fastapi")
|
|
21
|
+
from fastapi.testclient import TestClient # noqa: E402
|
|
22
|
+
|
|
23
|
+
from sdk.ui.api import require_token, router # noqa: E402
|
|
24
|
+
|
|
25
|
+
_JOB_RE = re.compile(r"^[0-9a-f]{32}$") # uuid4().hex
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _client():
|
|
29
|
+
app = fastapi.FastAPI()
|
|
30
|
+
app.include_router(router)
|
|
31
|
+
app.dependency_overrides[require_token] = lambda: None
|
|
32
|
+
return TestClient(app)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Keep the local Claude Code call mocked so a background task (if it runs) never
|
|
36
|
+
# shells out to a real `claude`. Autouse across this module.
|
|
37
|
+
@pytest.fixture(autouse=True)
|
|
38
|
+
def _no_real_claude():
|
|
39
|
+
with patch("sdk.ui.api._run_prompt_engine", AsyncMock(return_value="<raw>")), \
|
|
40
|
+
patch("sdk.ui.api._screen_agent_inputs", AsyncMock(return_value=[])):
|
|
41
|
+
yield
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TestAsyncContract:
|
|
45
|
+
def test_generate_returns_job_id(self):
|
|
46
|
+
resp = _client().post("/api/ui/agent-prompt/generate", json={
|
|
47
|
+
"agent_name": "Repo Onboarder", "agent_type": "developer",
|
|
48
|
+
"job": "summarise a repo", "context": "", "engine": "claude_code"})
|
|
49
|
+
assert resp.status_code == 200
|
|
50
|
+
body = resp.json()
|
|
51
|
+
assert set(body) == {"job_id"} and _JOB_RE.match(body["job_id"])
|
|
52
|
+
|
|
53
|
+
def test_regenerate_returns_job_id(self):
|
|
54
|
+
resp = _client().post("/api/ui/agent-prompt/regenerate", json={
|
|
55
|
+
"system_prompt": "# Role\n## Objective\nDo X with {{repo_url}}.",
|
|
56
|
+
"feedback": "stricter", "engine": "claude_code"})
|
|
57
|
+
assert resp.status_code == 200
|
|
58
|
+
assert _JOB_RE.match(resp.json()["job_id"])
|
|
59
|
+
|
|
60
|
+
def test_verify_returns_job_id(self):
|
|
61
|
+
resp = _client().post("/api/ui/agent-prompt/verify", json={
|
|
62
|
+
"system_prompt": "# Role\n## Objective\n## Rules\n## Output\nUse {{repo_url}}."})
|
|
63
|
+
assert resp.status_code == 200
|
|
64
|
+
assert _JOB_RE.match(resp.json()["job_id"])
|
|
65
|
+
|
|
66
|
+
def test_verify_still_422_on_short_prompt(self):
|
|
67
|
+
# Validation stays synchronous (before the job) — no job id for bad input.
|
|
68
|
+
resp = _client().post("/api/ui/agent-prompt/verify", json={"system_prompt": "short"})
|
|
69
|
+
assert resp.status_code == 422
|
|
70
|
+
|
|
71
|
+
def test_generate_still_422_on_empty_required(self):
|
|
72
|
+
resp = _client().post("/api/ui/agent-prompt/generate",
|
|
73
|
+
json={"agent_name": "", "agent_type": "x", "job": "y"})
|
|
74
|
+
assert resp.status_code == 422
|
|
File without changes
|
{devops_bot_sdk-1.4.151 → devops_bot_sdk-1.4.153}/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
|
|
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
|
|
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
|