devops-bot-sdk 1.4.126__tar.gz → 1.4.129__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.126 → devops_bot_sdk-1.4.129}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/SOURCES.txt +1 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/run_auto.py +11 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/api.py +88 -120
- devops_bot_sdk-1.4.129/sdk/ui/models.py +366 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/service.py +35 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/README.md +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/pyproject.toml +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/client.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/ip_allowlist.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/server.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/setup.cfg +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/tests/test_agents_connectors_builder.py +0 -0
|
@@ -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.129
|
|
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.129" # 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
|
|
@@ -565,6 +565,17 @@ def main() -> None:
|
|
|
565
565
|
after = current_version()
|
|
566
566
|
if after != before:
|
|
567
567
|
print(f"✓ updated {before} → {after}")
|
|
568
|
+
# Reload the control-panel server so newly-added /api/ui/* routes take
|
|
569
|
+
# effect — a running server keeps the code it imported at boot. Only
|
|
570
|
+
# restarts a managed (systemd/launchd/schtask) service; a manually-run
|
|
571
|
+
# panel is left alone with a hint.
|
|
572
|
+
try:
|
|
573
|
+
from sdk.ui import service as ui_service
|
|
574
|
+
ok, msg = ui_service.restart()
|
|
575
|
+
print(f" control panel: {msg}" if ok
|
|
576
|
+
else f" control panel: not reloaded — {msg}")
|
|
577
|
+
except Exception as exc: # noqa: BLE001 — never fail the update on this
|
|
578
|
+
print(f" control panel: not reloaded ({exc})")
|
|
568
579
|
pid = _daemon_running()
|
|
569
580
|
if pid:
|
|
570
581
|
print("note: a background loop is running the OLD version — "
|
|
@@ -12,6 +12,24 @@ from fastapi.responses import StreamingResponse
|
|
|
12
12
|
from fastapi.security import APIKeyHeader
|
|
13
13
|
|
|
14
14
|
from sdk.ui import dashboard, runner, security, setup_manager
|
|
15
|
+
from sdk.ui.models import (
|
|
16
|
+
AgentChatRequest,
|
|
17
|
+
AgentKeysSet,
|
|
18
|
+
AgentPromptGenerateRequest,
|
|
19
|
+
AgentRunRequest,
|
|
20
|
+
AllowlistEntry,
|
|
21
|
+
ConfigureRequest,
|
|
22
|
+
PromptRegenerateRequest,
|
|
23
|
+
PromptRunRequest,
|
|
24
|
+
PromptVerifyRequest,
|
|
25
|
+
SetupRequest,
|
|
26
|
+
StoryRequest,
|
|
27
|
+
SystemChatRequest,
|
|
28
|
+
SystemPromptRequest,
|
|
29
|
+
TicketRequest,
|
|
30
|
+
UpdateAgentRequest,
|
|
31
|
+
WorkspaceRequest,
|
|
32
|
+
)
|
|
15
33
|
|
|
16
34
|
router = APIRouter(prefix="/api/ui")
|
|
17
35
|
|
|
@@ -82,15 +100,6 @@ async def status() -> dict:
|
|
|
82
100
|
# middleware re-reads it per request so a new IP works immediately (no restart).
|
|
83
101
|
# The backend proxies here (POST /api/v1/sdk/whitelist-ip) to open access for an IP.
|
|
84
102
|
|
|
85
|
-
def _valid_ip_or_cidr(value: str) -> bool:
|
|
86
|
-
import ipaddress
|
|
87
|
-
try:
|
|
88
|
-
ipaddress.ip_network(value, strict=False)
|
|
89
|
-
return True
|
|
90
|
-
except ValueError:
|
|
91
|
-
return False
|
|
92
|
-
|
|
93
|
-
|
|
94
103
|
@router.get("/allowlist", dependencies=[_auth])
|
|
95
104
|
async def get_allowlist() -> dict:
|
|
96
105
|
"""This SDK's allowlist, split into permanent vs runtime.
|
|
@@ -109,20 +118,17 @@ async def get_allowlist() -> dict:
|
|
|
109
118
|
|
|
110
119
|
|
|
111
120
|
@router.post("/allowlist", dependencies=[_auth])
|
|
112
|
-
async def add_to_allowlist(body:
|
|
121
|
+
async def add_to_allowlist(body: AllowlistEntry) -> dict:
|
|
113
122
|
"""Whitelist an IP/CIDR for this SDK at runtime. Body: ``{ip}``. Idempotent.
|
|
114
123
|
|
|
115
124
|
Opens the IP gate for that client across every path (all endpoints + UI + docs)
|
|
116
125
|
until it's disabled. Effective immediately (the middleware re-reads per request).
|
|
126
|
+
The ``ip`` field is validated (IPv4/IPv6 address or CIDR) by ``AllowlistEntry``;
|
|
127
|
+
a malformed or missing value returns 422 before this handler runs.
|
|
117
128
|
"""
|
|
118
129
|
from sdk.config import add_allowed_ip
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
if not ip:
|
|
122
|
-
raise HTTPException(status_code=400, detail="ip is required.")
|
|
123
|
-
if not _valid_ip_or_cidr(ip):
|
|
124
|
-
raise HTTPException(status_code=400, detail=f"invalid IP or CIDR: {ip!r}")
|
|
125
|
-
return {"added": ip, "allowed_ips": add_allowed_ip(ip)}
|
|
131
|
+
return {"added": body.ip, "allowed_ips": add_allowed_ip(body.ip)}
|
|
126
132
|
|
|
127
133
|
|
|
128
134
|
@router.delete("/allowlist", dependencies=[_auth])
|
|
@@ -137,20 +143,17 @@ async def remove_from_allowlist(ip: str = Query(...)) -> dict:
|
|
|
137
143
|
|
|
138
144
|
|
|
139
145
|
@router.post("/configure", dependencies=[_auth])
|
|
140
|
-
async def configure(body:
|
|
146
|
+
async def configure(body: ConfigureRequest) -> dict:
|
|
141
147
|
from sdk import config as sdk_config
|
|
142
148
|
|
|
143
|
-
|
|
144
|
-
if not token.startswith("co_"):
|
|
145
|
-
raise HTTPException(status_code=400, detail="Token must start with 'co_'.")
|
|
146
|
-
sdk_config.save(sdk_config.get_base_url(), token)
|
|
149
|
+
sdk_config.save(sdk_config.get_base_url(), body.token)
|
|
147
150
|
ok, message = await dashboard.validate_token()
|
|
148
151
|
return {"saved": True, "connected": ok, "message": message}
|
|
149
152
|
|
|
150
153
|
|
|
151
154
|
@router.post("/setup", dependencies=[_auth])
|
|
152
|
-
async def setup(body:
|
|
153
|
-
upgrade =
|
|
155
|
+
async def setup(body: SetupRequest | None = None) -> StreamingResponse:
|
|
156
|
+
upgrade = (body or SetupRequest()).upgrade
|
|
154
157
|
|
|
155
158
|
async def gen():
|
|
156
159
|
async for chunk in setup_manager.reinstall_sdk(upgrade=upgrade):
|
|
@@ -184,44 +187,34 @@ async def restart_loop() -> dict:
|
|
|
184
187
|
# ── Execution (one per agentos-auto subcommand) ────────────────────────────
|
|
185
188
|
|
|
186
189
|
@router.post("/run", dependencies=[_auth])
|
|
187
|
-
async def run(body:
|
|
190
|
+
async def run(body: WorkspaceRequest | None = None) -> dict:
|
|
188
191
|
args = ["run"]
|
|
189
|
-
root = (body or
|
|
192
|
+
root = (body or WorkspaceRequest()).workspace_root
|
|
190
193
|
if root:
|
|
191
|
-
args += ["--workspace-root",
|
|
194
|
+
args += ["--workspace-root", root]
|
|
192
195
|
return runner.spawn_auto(*args)
|
|
193
196
|
|
|
194
197
|
|
|
195
198
|
@router.post("/resume", dependencies=[_auth])
|
|
196
|
-
async def resume(body:
|
|
199
|
+
async def resume(body: WorkspaceRequest | None = None) -> dict:
|
|
197
200
|
args = ["resume"]
|
|
198
|
-
root = (body or
|
|
201
|
+
root = (body or WorkspaceRequest()).workspace_root
|
|
199
202
|
if root:
|
|
200
|
-
args += ["--workspace-root",
|
|
203
|
+
args += ["--workspace-root", root]
|
|
201
204
|
return runner.spawn_auto(*args)
|
|
202
205
|
|
|
203
206
|
|
|
204
207
|
@router.post("/ticket", dependencies=[_auth])
|
|
205
|
-
async def ticket(body:
|
|
206
|
-
|
|
207
|
-
if
|
|
208
|
-
|
|
209
|
-
keys = [str(k).strip() for k in keys if str(k).strip()]
|
|
210
|
-
if not keys:
|
|
211
|
-
raise HTTPException(status_code=400, detail="Provide at least one ticket key.")
|
|
212
|
-
args = ["ticket", *keys]
|
|
213
|
-
root = body.get("workspace_root")
|
|
214
|
-
if root:
|
|
215
|
-
args += ["--workspace-root", str(root)]
|
|
208
|
+
async def ticket(body: TicketRequest) -> dict:
|
|
209
|
+
args = ["ticket", *body.keys]
|
|
210
|
+
if body.workspace_root:
|
|
211
|
+
args += ["--workspace-root", body.workspace_root]
|
|
216
212
|
return runner.spawn_auto(*args)
|
|
217
213
|
|
|
218
214
|
|
|
219
215
|
@router.post("/story", dependencies=[_auth])
|
|
220
|
-
async def story(body:
|
|
221
|
-
keys = (body or
|
|
222
|
-
if isinstance(keys, str):
|
|
223
|
-
keys = keys.split()
|
|
224
|
-
keys = [str(k).strip() for k in keys if str(k).strip()]
|
|
216
|
+
async def story(body: StoryRequest | None = None) -> dict:
|
|
217
|
+
keys = (body or StoryRequest()).keys
|
|
225
218
|
return runner.spawn_auto("story", *keys)
|
|
226
219
|
|
|
227
220
|
|
|
@@ -426,7 +419,7 @@ async def agent_keys_list(name: str = Query(...)) -> dict:
|
|
|
426
419
|
|
|
427
420
|
|
|
428
421
|
@router.post("/agents/keys", dependencies=[_auth])
|
|
429
|
-
async def agent_keys_set(body:
|
|
422
|
+
async def agent_keys_set(body: AgentKeysSet) -> dict:
|
|
430
423
|
"""Persist/update a custom agent's API keys (encrypted, local, per-agent).
|
|
431
424
|
|
|
432
425
|
Body: ``name``, ``keys`` ({ENV_VAR: value}). Merge semantics — keys omitted
|
|
@@ -435,14 +428,10 @@ async def agent_keys_set(body: dict) -> dict:
|
|
|
435
428
|
"""
|
|
436
429
|
from sdk.agents import secrets as agent_secrets
|
|
437
430
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if not isinstance(keys, dict) or not keys:
|
|
443
|
-
raise HTTPException(status_code=400, detail="keys must be a non-empty object.")
|
|
444
|
-
stored = agent_secrets.save_secrets(name, {str(k): str(v) for k, v in keys.items()}, merge=True)
|
|
445
|
-
return {"name": name, "keys": sorted(stored.keys())}
|
|
431
|
+
stored = agent_secrets.save_secrets(
|
|
432
|
+
body.name, {str(k): str(v) for k, v in body.keys.items()}, merge=True
|
|
433
|
+
)
|
|
434
|
+
return {"name": body.name, "keys": sorted(stored.keys())}
|
|
446
435
|
|
|
447
436
|
|
|
448
437
|
@router.delete("/agents/keys", dependencies=[_auth])
|
|
@@ -516,7 +505,7 @@ async def job_cancel(job_id: str) -> dict:
|
|
|
516
505
|
|
|
517
506
|
|
|
518
507
|
@router.patch("/agents", dependencies=[_auth])
|
|
519
|
-
async def update_agent(body:
|
|
508
|
+
async def update_agent(body: UpdateAgentRequest) -> dict:
|
|
520
509
|
"""Update a local custom agent (description / prompt / tools / enabled status).
|
|
521
510
|
|
|
522
511
|
Re-validates and rewrites the local manifest, and best-effort syncs the change
|
|
@@ -526,20 +515,19 @@ async def update_agent(body: dict) -> dict:
|
|
|
526
515
|
|
|
527
516
|
from sdk.agents.manifest import AGENTS_DIR, AgentManifest, load_agents
|
|
528
517
|
|
|
529
|
-
name =
|
|
530
|
-
|
|
531
|
-
raise HTTPException(status_code=400, detail="name is required.")
|
|
518
|
+
name = body.name
|
|
519
|
+
sent = body.model_fields_set # PATCH: only fields the caller sent are applied
|
|
532
520
|
manifest = next((a for a in load_agents() if a.name == name), None)
|
|
533
521
|
if manifest is None:
|
|
534
522
|
raise HTTPException(status_code=404, detail=f"no local agent '{name}'")
|
|
535
523
|
|
|
536
524
|
local_changes: dict = {}
|
|
537
|
-
if "description" in
|
|
538
|
-
local_changes["description"] = str(body.
|
|
539
|
-
if "prompt" in
|
|
540
|
-
local_changes["prompt"] = str(body.
|
|
541
|
-
if "allowed_tools" in
|
|
542
|
-
local_changes["allowed_tools"] = list(body.
|
|
525
|
+
if "description" in sent:
|
|
526
|
+
local_changes["description"] = str(body.description or "")
|
|
527
|
+
if "prompt" in sent:
|
|
528
|
+
local_changes["prompt"] = str(body.prompt or "")
|
|
529
|
+
if "allowed_tools" in sent:
|
|
530
|
+
local_changes["allowed_tools"] = list(body.allowed_tools or [])
|
|
543
531
|
|
|
544
532
|
updated = None
|
|
545
533
|
if local_changes:
|
|
@@ -552,7 +540,7 @@ async def update_agent(body: dict) -> dict:
|
|
|
552
540
|
encoding="utf-8",
|
|
553
541
|
)
|
|
554
542
|
|
|
555
|
-
status =
|
|
543
|
+
status = (body.status or "").strip()
|
|
556
544
|
# Backend sync (best-effort). Agent CREATION/authoring is not owned by the SDK
|
|
557
545
|
# anymore, so a content edit is applied LOCALLY only — no re-register upsert.
|
|
558
546
|
# A status change still syncs to the backend (per-agent enable/disable).
|
|
@@ -593,7 +581,7 @@ async def delete_agent(name: str = Query(...)) -> dict:
|
|
|
593
581
|
|
|
594
582
|
|
|
595
583
|
@router.post("/agents/chat", dependencies=[_auth])
|
|
596
|
-
async def agent_chat(body:
|
|
584
|
+
async def agent_chat(body: AgentChatRequest) -> dict:
|
|
597
585
|
"""Chat with an agent (runs its engine on this machine).
|
|
598
586
|
|
|
599
587
|
Body: ``name``, ``message``, optional ``variables`` ({{var}} values), optional
|
|
@@ -603,13 +591,11 @@ async def agent_chat(body: dict) -> dict:
|
|
|
603
591
|
"""
|
|
604
592
|
from sdk.ui import jobs
|
|
605
593
|
|
|
606
|
-
name =
|
|
607
|
-
message =
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
history = body.get("history") or []
|
|
612
|
-
department_id = str(body.get("department_id", "")).strip()
|
|
594
|
+
name = body.name
|
|
595
|
+
message = body.message
|
|
596
|
+
variables = body.variables
|
|
597
|
+
history = body.history
|
|
598
|
+
department_id = body.department_id
|
|
613
599
|
|
|
614
600
|
async def work() -> dict:
|
|
615
601
|
import time as _time
|
|
@@ -637,7 +623,7 @@ async def agent_chat(body: dict) -> dict:
|
|
|
637
623
|
|
|
638
624
|
|
|
639
625
|
@router.post("/agents/run", dependencies=[_auth])
|
|
640
|
-
async def agent_run(body:
|
|
626
|
+
async def agent_run(body: AgentRunRequest) -> dict:
|
|
641
627
|
"""Run an agent once with fresh ``{{variable}}`` values — no chat.
|
|
642
628
|
|
|
643
629
|
Body: ``name``, ``variables`` ({{var}} → value; may differ every run),
|
|
@@ -649,12 +635,10 @@ async def agent_run(body: dict) -> dict:
|
|
|
649
635
|
"""
|
|
650
636
|
from sdk.ui import jobs
|
|
651
637
|
|
|
652
|
-
name =
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
department_id = str(body.get("department_id", "")).strip()
|
|
657
|
-
scope = str(body.get("scope") or body.get("source") or "custom").strip() or "custom"
|
|
638
|
+
name = body.name
|
|
639
|
+
variables = body.variables
|
|
640
|
+
department_id = body.department_id
|
|
641
|
+
scope = body.scope
|
|
658
642
|
|
|
659
643
|
async def work() -> dict:
|
|
660
644
|
import time as _time
|
|
@@ -688,7 +672,7 @@ async def system_inventory() -> dict:
|
|
|
688
672
|
|
|
689
673
|
|
|
690
674
|
@router.post("/system/chat", dependencies=[_auth])
|
|
691
|
-
async def system_chat(body:
|
|
675
|
+
async def system_chat(body: SystemChatRequest) -> dict:
|
|
692
676
|
"""Ask the System Assistant about agents, subagents, tools, plugins & usage.
|
|
693
677
|
|
|
694
678
|
Grounds the local model in the live inventory JSON and answers the question.
|
|
@@ -697,10 +681,8 @@ async def system_chat(body: dict) -> dict:
|
|
|
697
681
|
"""
|
|
698
682
|
from sdk.ui import jobs
|
|
699
683
|
|
|
700
|
-
message =
|
|
701
|
-
|
|
702
|
-
raise HTTPException(status_code=400, detail="message is required.")
|
|
703
|
-
history = body.get("history") or []
|
|
684
|
+
message = body.message
|
|
685
|
+
history = body.history
|
|
704
686
|
|
|
705
687
|
async def work() -> dict:
|
|
706
688
|
from pathlib import Path
|
|
@@ -748,7 +730,7 @@ async def agent_prompt(name: str = Query(...)) -> dict:
|
|
|
748
730
|
|
|
749
731
|
|
|
750
732
|
@router.post("/prompt/analyze", dependencies=[_auth])
|
|
751
|
-
async def prompt_analyze(body:
|
|
733
|
+
async def prompt_analyze(body: SystemPromptRequest) -> dict:
|
|
752
734
|
"""AI reads a system prompt and infers its input variables + output/format.
|
|
753
735
|
|
|
754
736
|
Body: ``system_prompt``. Runs as a detached job (navigation-safe); poll
|
|
@@ -756,9 +738,7 @@ async def prompt_analyze(body: dict) -> dict:
|
|
|
756
738
|
"""
|
|
757
739
|
from sdk.ui import jobs
|
|
758
740
|
|
|
759
|
-
system_prompt =
|
|
760
|
-
if len(system_prompt) < 10:
|
|
761
|
-
raise HTTPException(status_code=400, detail="Write a system prompt first (a bit longer).")
|
|
741
|
+
system_prompt = body.system_prompt
|
|
762
742
|
|
|
763
743
|
async def work() -> dict:
|
|
764
744
|
from pathlib import Path
|
|
@@ -778,7 +758,7 @@ async def prompt_analyze(body: dict) -> dict:
|
|
|
778
758
|
|
|
779
759
|
|
|
780
760
|
@router.post("/prompt/comments", dependencies=[_auth])
|
|
781
|
-
async def prompt_comments(body:
|
|
761
|
+
async def prompt_comments(body: SystemPromptRequest) -> dict:
|
|
782
762
|
"""AI reviews the system prompt and returns open questions as 'comments'.
|
|
783
763
|
|
|
784
764
|
Resolve these by editing the prompt BEFORE building the agent — so the agent
|
|
@@ -787,9 +767,7 @@ async def prompt_comments(body: dict) -> dict:
|
|
|
787
767
|
"""
|
|
788
768
|
from sdk.ui import jobs
|
|
789
769
|
|
|
790
|
-
system_prompt =
|
|
791
|
-
if len(system_prompt) < 10:
|
|
792
|
-
raise HTTPException(status_code=400, detail="Write a system prompt first (a bit longer).")
|
|
770
|
+
system_prompt = body.system_prompt
|
|
793
771
|
|
|
794
772
|
async def work() -> dict:
|
|
795
773
|
from pathlib import Path
|
|
@@ -809,7 +787,7 @@ async def prompt_comments(body: dict) -> dict:
|
|
|
809
787
|
|
|
810
788
|
|
|
811
789
|
@router.post("/prompt/run", dependencies=[_auth])
|
|
812
|
-
async def prompt_run(body:
|
|
790
|
+
async def prompt_run(body: PromptRunRequest) -> dict:
|
|
813
791
|
"""Run a system prompt with the user-supplied input values → the response.
|
|
814
792
|
|
|
815
793
|
Body: ``system_prompt``, ``inputs`` ({name: value}), optional ``output_format``,
|
|
@@ -820,14 +798,12 @@ async def prompt_run(body: dict) -> dict:
|
|
|
820
798
|
from sdk.agents.prompt_studio import sanitize_tools
|
|
821
799
|
from sdk.ui import jobs
|
|
822
800
|
|
|
823
|
-
system_prompt =
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
inputs = body.get("inputs") or {}
|
|
827
|
-
output_format = str(body.get("output_format", "")).strip()
|
|
801
|
+
system_prompt = body.system_prompt
|
|
802
|
+
inputs = body.inputs
|
|
803
|
+
output_format = body.output_format
|
|
828
804
|
# AI-assessed tools; fall back to a minimal read-only tool so a pure text task
|
|
829
805
|
# never inherits the broad default allowlist.
|
|
830
|
-
tools = sanitize_tools(body.
|
|
806
|
+
tools = sanitize_tools(body.tools) or ["Read"]
|
|
831
807
|
|
|
832
808
|
async def work() -> dict:
|
|
833
809
|
import time as _time
|
|
@@ -874,7 +850,7 @@ async def _run_prompt_engine(prompt: str) -> str:
|
|
|
874
850
|
|
|
875
851
|
|
|
876
852
|
@router.post("/agent-prompt/generate", dependencies=[_auth])
|
|
877
|
-
async def agent_prompt_generate(body:
|
|
853
|
+
async def agent_prompt_generate(body: AgentPromptGenerateRequest) -> dict:
|
|
878
854
|
"""name + type + job → a structured system prompt + {{variables}} (claude_code).
|
|
879
855
|
|
|
880
856
|
Body: ``agent_name``, ``agent_type``, ``job``, optional ``context``.
|
|
@@ -882,14 +858,12 @@ async def agent_prompt_generate(body: dict) -> dict:
|
|
|
882
858
|
"""
|
|
883
859
|
from sdk.agents.prompt_studio import build_generate_prompt, parse_generation
|
|
884
860
|
|
|
885
|
-
agent_name =
|
|
886
|
-
agent_type =
|
|
887
|
-
job =
|
|
888
|
-
if not agent_name or not agent_type or not job:
|
|
889
|
-
raise HTTPException(status_code=400, detail="agent_name, agent_type, and job are required.")
|
|
861
|
+
agent_name = body.agent_name
|
|
862
|
+
agent_type = body.agent_type
|
|
863
|
+
job = body.job
|
|
890
864
|
|
|
891
865
|
text = await _run_prompt_engine(
|
|
892
|
-
build_generate_prompt(agent_name, agent_type, job,
|
|
866
|
+
build_generate_prompt(agent_name, agent_type, job, body.context)
|
|
893
867
|
)
|
|
894
868
|
parsed = parse_generation(text)
|
|
895
869
|
if not parsed["system_prompt"]:
|
|
@@ -898,7 +872,7 @@ async def agent_prompt_generate(body: dict) -> dict:
|
|
|
898
872
|
|
|
899
873
|
|
|
900
874
|
@router.post("/agent-prompt/regenerate", dependencies=[_auth])
|
|
901
|
-
async def agent_prompt_regenerate(body:
|
|
875
|
+
async def agent_prompt_regenerate(body: PromptRegenerateRequest) -> dict:
|
|
902
876
|
"""Improve an existing prompt (optional feedback) → prompt + {{variables}}.
|
|
903
877
|
|
|
904
878
|
Body: ``system_prompt``, optional ``feedback``, ``agent_name``, ``agent_type``,
|
|
@@ -906,24 +880,20 @@ async def agent_prompt_regenerate(body: dict) -> dict:
|
|
|
906
880
|
"""
|
|
907
881
|
from sdk.agents.prompt_studio import build_regenerate_prompt, parse_generation
|
|
908
882
|
|
|
909
|
-
system_prompt = str(body.get("system_prompt", "")).strip()
|
|
910
|
-
if not system_prompt:
|
|
911
|
-
raise HTTPException(status_code=400, detail="system_prompt is required.")
|
|
912
|
-
|
|
913
883
|
text = await _run_prompt_engine(build_regenerate_prompt(
|
|
914
|
-
system_prompt,
|
|
915
|
-
|
|
884
|
+
body.system_prompt, body.feedback,
|
|
885
|
+
body.agent_name, body.agent_type, body.job,
|
|
916
886
|
))
|
|
917
887
|
parsed = parse_generation(text)
|
|
918
888
|
if not parsed["system_prompt"]:
|
|
919
889
|
raise HTTPException(status_code=502, detail="regeneration returned no system_prompt")
|
|
920
|
-
return {"agent_name":
|
|
921
|
-
"agent_type":
|
|
890
|
+
return {"agent_name": body.agent_name,
|
|
891
|
+
"agent_type": body.agent_type,
|
|
922
892
|
"engine": "claude_code", **parsed}
|
|
923
893
|
|
|
924
894
|
|
|
925
895
|
@router.post("/agent-prompt/verify", dependencies=[_auth])
|
|
926
|
-
async def agent_prompt_verify(body:
|
|
896
|
+
async def agent_prompt_verify(body: PromptVerifyRequest) -> dict:
|
|
927
897
|
"""Manual-trigger flow: verify a user-written prompt, then extract vars + secrets.
|
|
928
898
|
|
|
929
899
|
Standards check is deterministic (our Role/Objective/Rules/Workflow/Output
|
|
@@ -935,9 +905,7 @@ async def agent_prompt_verify(body: dict) -> dict:
|
|
|
935
905
|
from sdk.agents.prompt_template import check_system_prompt
|
|
936
906
|
from sdk.agents.prompt_vars import extract_variables
|
|
937
907
|
|
|
938
|
-
system_prompt =
|
|
939
|
-
if len(system_prompt) < 10:
|
|
940
|
-
raise HTTPException(status_code=400, detail="Write a system prompt first (a bit longer).")
|
|
908
|
+
system_prompt = body.system_prompt
|
|
941
909
|
|
|
942
910
|
standards = check_system_prompt(system_prompt)
|
|
943
911
|
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Pydantic request models for the ``/api/ui/*`` control-panel API.
|
|
2
|
+
|
|
3
|
+
One model per endpoint body. These replace the previous bare ``body: dict``
|
|
4
|
+
signatures so inputs are validated (types, required fields, formats) before a
|
|
5
|
+
handler runs, and Swagger renders a real schema/example instead of a free-form
|
|
6
|
+
object. A validation failure surfaces as HTTP 422 (FastAPI's standard) with a
|
|
7
|
+
field-level message.
|
|
8
|
+
|
|
9
|
+
Behaviour is preserved from the old hand-rolled checks: strings are stripped,
|
|
10
|
+
optional fields keep their previous defaults, and the polymorphic / alias inputs
|
|
11
|
+
(``keys`` as string-or-list, ``scope`` falling back to ``source``) are normalised
|
|
12
|
+
here via validators so handlers stay thin.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import ipaddress
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _normalise_keys(v: object) -> list[str]:
|
|
22
|
+
"""A whitespace-separated string OR a list → a clean, non-empty-item list."""
|
|
23
|
+
if v is None:
|
|
24
|
+
return []
|
|
25
|
+
if isinstance(v, str):
|
|
26
|
+
v = v.split()
|
|
27
|
+
if not isinstance(v, (list, tuple)):
|
|
28
|
+
raise ValueError("keys must be a string or a list.")
|
|
29
|
+
return [str(k).strip() for k in v if str(k).strip()]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── IP allowlist ────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
class AllowlistEntry(BaseModel):
|
|
35
|
+
"""Body for ``POST /api/ui/allowlist`` — one IP or CIDR to whitelist."""
|
|
36
|
+
|
|
37
|
+
ip: str = Field(
|
|
38
|
+
...,
|
|
39
|
+
description="IPv4/IPv6 address or CIDR block to whitelist, "
|
|
40
|
+
"e.g. '203.0.113.4' or '10.0.0.0/8'.",
|
|
41
|
+
examples=["203.0.113.4"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@field_validator("ip")
|
|
45
|
+
@classmethod
|
|
46
|
+
def _valid_ip_or_cidr(cls, value: str) -> str:
|
|
47
|
+
value = (value or "").strip()
|
|
48
|
+
if not value:
|
|
49
|
+
raise ValueError("ip is required.")
|
|
50
|
+
try:
|
|
51
|
+
ipaddress.ip_network(value, strict=False)
|
|
52
|
+
except ValueError:
|
|
53
|
+
raise ValueError(f"invalid IP or CIDR: {value!r}")
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ── Setup / configuration ────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
class ConfigureRequest(BaseModel):
|
|
60
|
+
"""Body for ``POST /api/ui/configure`` — save the DevOpsBot API token."""
|
|
61
|
+
|
|
62
|
+
token: str = Field(..., description="DevOpsBot API token; must start with 'co_'.",
|
|
63
|
+
examples=["co_<userUuid>_<secret>"])
|
|
64
|
+
|
|
65
|
+
@field_validator("token")
|
|
66
|
+
@classmethod
|
|
67
|
+
def _co_token(cls, value: str) -> str:
|
|
68
|
+
value = (value or "").strip()
|
|
69
|
+
if not value.startswith("co_"):
|
|
70
|
+
raise ValueError("Token must start with 'co_'.")
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class SetupRequest(BaseModel):
|
|
75
|
+
"""Body for ``POST /api/ui/setup`` (optional)."""
|
|
76
|
+
|
|
77
|
+
upgrade: bool = True
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ── Execution (agentos-auto subcommands) ──────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
class WorkspaceRequest(BaseModel):
|
|
83
|
+
"""Body for ``POST /api/ui/run`` and ``/resume`` (both optional)."""
|
|
84
|
+
|
|
85
|
+
workspace_root: str | None = None
|
|
86
|
+
|
|
87
|
+
@field_validator("workspace_root")
|
|
88
|
+
@classmethod
|
|
89
|
+
def _strip(cls, value: str | None) -> str | None:
|
|
90
|
+
if value is None:
|
|
91
|
+
return None
|
|
92
|
+
value = str(value).strip()
|
|
93
|
+
return value or None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TicketRequest(BaseModel):
|
|
97
|
+
"""Body for ``POST /api/ui/ticket`` — one or more Jira ticket keys."""
|
|
98
|
+
|
|
99
|
+
keys: list[str] = Field(default_factory=list, examples=[["PROJ-123", "PROJ-124"]])
|
|
100
|
+
workspace_root: str | None = None
|
|
101
|
+
|
|
102
|
+
@field_validator("keys", mode="before")
|
|
103
|
+
@classmethod
|
|
104
|
+
def _norm(cls, v: object) -> list[str]:
|
|
105
|
+
return _normalise_keys(v)
|
|
106
|
+
|
|
107
|
+
@field_validator("keys")
|
|
108
|
+
@classmethod
|
|
109
|
+
def _at_least_one(cls, v: list[str]) -> list[str]:
|
|
110
|
+
if not v:
|
|
111
|
+
raise ValueError("Provide at least one ticket key.")
|
|
112
|
+
return v
|
|
113
|
+
|
|
114
|
+
@field_validator("workspace_root")
|
|
115
|
+
@classmethod
|
|
116
|
+
def _strip(cls, value: str | None) -> str | None:
|
|
117
|
+
if value is None:
|
|
118
|
+
return None
|
|
119
|
+
value = str(value).strip()
|
|
120
|
+
return value or None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class StoryRequest(BaseModel):
|
|
124
|
+
"""Body for ``POST /api/ui/story`` (optional) — zero or more story keys."""
|
|
125
|
+
|
|
126
|
+
keys: list[str] = Field(default_factory=list)
|
|
127
|
+
|
|
128
|
+
@field_validator("keys", mode="before")
|
|
129
|
+
@classmethod
|
|
130
|
+
def _norm(cls, v: object) -> list[str]:
|
|
131
|
+
return _normalise_keys(v)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ── Custom agents: keys / update / chat / run ────────────────────────────────
|
|
135
|
+
|
|
136
|
+
class AgentKeysSet(BaseModel):
|
|
137
|
+
"""Body for ``POST /api/ui/agents/keys`` — persist a custom agent's API keys."""
|
|
138
|
+
|
|
139
|
+
name: str = Field(..., description="Custom agent name.")
|
|
140
|
+
# Values are coerced to str by the handler (matching prior behaviour), so accept
|
|
141
|
+
# any JSON scalar here rather than rejecting e.g. a numeric value with 422.
|
|
142
|
+
keys: dict[str, object] = Field(..., description="{ENV_VAR: value} — merged into stored keys.")
|
|
143
|
+
|
|
144
|
+
@field_validator("name")
|
|
145
|
+
@classmethod
|
|
146
|
+
def _name(cls, value: str) -> str:
|
|
147
|
+
value = (value or "").strip()
|
|
148
|
+
if not value:
|
|
149
|
+
raise ValueError("name is required.")
|
|
150
|
+
return value
|
|
151
|
+
|
|
152
|
+
@field_validator("keys")
|
|
153
|
+
@classmethod
|
|
154
|
+
def _nonempty(cls, value: dict[str, object]) -> dict[str, object]:
|
|
155
|
+
if not value:
|
|
156
|
+
raise ValueError("keys must be a non-empty object.")
|
|
157
|
+
return value
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class UpdateAgentRequest(BaseModel):
|
|
161
|
+
"""Body for ``PATCH /api/ui/agents`` — edit a local custom agent.
|
|
162
|
+
|
|
163
|
+
PATCH semantics: only fields the caller actually sends are applied. The
|
|
164
|
+
handler reads ``model_fields_set`` (via ``exclude_unset``) to tell "not sent"
|
|
165
|
+
apart from "sent as empty". ``name`` is the immutable identity.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
name: str = Field(..., description="Agent name (identity — immutable).")
|
|
169
|
+
description: str | None = None
|
|
170
|
+
prompt: str | None = None
|
|
171
|
+
allowed_tools: list[str] | None = None
|
|
172
|
+
status: str | None = None
|
|
173
|
+
|
|
174
|
+
@field_validator("name")
|
|
175
|
+
@classmethod
|
|
176
|
+
def _name(cls, value: str) -> str:
|
|
177
|
+
value = (value or "").strip()
|
|
178
|
+
if not value:
|
|
179
|
+
raise ValueError("name is required.")
|
|
180
|
+
return value
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class AgentChatRequest(BaseModel):
|
|
184
|
+
"""Body for ``POST /api/ui/agents/chat``."""
|
|
185
|
+
|
|
186
|
+
name: str = Field(..., description="Agent name.")
|
|
187
|
+
message: str = Field(..., description="User message.")
|
|
188
|
+
variables: dict = Field(default_factory=dict)
|
|
189
|
+
history: list = Field(default_factory=list)
|
|
190
|
+
department_id: str = ""
|
|
191
|
+
|
|
192
|
+
@field_validator("name", "message")
|
|
193
|
+
@classmethod
|
|
194
|
+
def _required(cls, value: str) -> str:
|
|
195
|
+
value = (value or "").strip()
|
|
196
|
+
if not value:
|
|
197
|
+
raise ValueError("name and message are required.")
|
|
198
|
+
return value
|
|
199
|
+
|
|
200
|
+
@field_validator("department_id")
|
|
201
|
+
@classmethod
|
|
202
|
+
def _strip(cls, value: str) -> str:
|
|
203
|
+
return (value or "").strip()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class AgentRunRequest(BaseModel):
|
|
207
|
+
"""Body for ``POST /api/ui/agents/run``.
|
|
208
|
+
|
|
209
|
+
``scope`` accepts a legacy ``source`` alias and defaults to ``"custom"``.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
name: str = Field(..., description="Agent name.")
|
|
213
|
+
variables: dict = Field(default_factory=dict)
|
|
214
|
+
department_id: str = ""
|
|
215
|
+
scope: str = "custom"
|
|
216
|
+
|
|
217
|
+
@model_validator(mode="before")
|
|
218
|
+
@classmethod
|
|
219
|
+
def _scope_from_source(cls, data: object) -> object:
|
|
220
|
+
if isinstance(data, dict):
|
|
221
|
+
scope = data.get("scope") or data.get("source") or "custom"
|
|
222
|
+
data = {**data, "scope": scope}
|
|
223
|
+
return data
|
|
224
|
+
|
|
225
|
+
@field_validator("name")
|
|
226
|
+
@classmethod
|
|
227
|
+
def _name(cls, value: str) -> str:
|
|
228
|
+
value = (value or "").strip()
|
|
229
|
+
if not value:
|
|
230
|
+
raise ValueError("name is required.")
|
|
231
|
+
return value
|
|
232
|
+
|
|
233
|
+
@field_validator("department_id")
|
|
234
|
+
@classmethod
|
|
235
|
+
def _strip_dept(cls, value: str) -> str:
|
|
236
|
+
return (value or "").strip()
|
|
237
|
+
|
|
238
|
+
@field_validator("scope")
|
|
239
|
+
@classmethod
|
|
240
|
+
def _scope(cls, value: str) -> str:
|
|
241
|
+
return (value or "").strip() or "custom"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# ── System assistant ─────────────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
class SystemChatRequest(BaseModel):
|
|
247
|
+
"""Body for ``POST /api/ui/system/chat``."""
|
|
248
|
+
|
|
249
|
+
message: str = Field(..., description="Question for the System Assistant.")
|
|
250
|
+
history: list = Field(default_factory=list)
|
|
251
|
+
|
|
252
|
+
@field_validator("message")
|
|
253
|
+
@classmethod
|
|
254
|
+
def _required(cls, value: str) -> str:
|
|
255
|
+
value = (value or "").strip()
|
|
256
|
+
if not value:
|
|
257
|
+
raise ValueError("message is required.")
|
|
258
|
+
return value
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ── Prompt Studio ─────────────────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
# The agent-prompt/* models below intentionally mirror the AI backend's request
|
|
264
|
+
# schemas field-for-field (routes/agent_prompt.py: Generate/Regenerate/Verify),
|
|
265
|
+
# so one JSON body works against both /api/v1/agent-prompt/* (backend) and
|
|
266
|
+
# /api/ui/agent-prompt/* (this SDK). ``engine`` is part of that shared contract;
|
|
267
|
+
# the SDK always runs ``claude_code`` locally, so it accepts the field for parity
|
|
268
|
+
# and ignores non-local values rather than 400ing.
|
|
269
|
+
_ENGINE_DESC = "'claude_code' (local SDK) or 'server' (backend LLM)."
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class SystemPromptRequest(BaseModel):
|
|
273
|
+
"""Body for the SDK-only Prompt Studio helpers (``/prompt/analyze``,
|
|
274
|
+
``/prompt/comments``) — no backend equivalent, so no ``engine`` field."""
|
|
275
|
+
|
|
276
|
+
system_prompt: str = Field(..., description="The system prompt to analyse.")
|
|
277
|
+
|
|
278
|
+
@field_validator("system_prompt")
|
|
279
|
+
@classmethod
|
|
280
|
+
def _min_len(cls, value: str) -> str:
|
|
281
|
+
value = (value or "").strip()
|
|
282
|
+
if len(value) < 10:
|
|
283
|
+
raise ValueError("Write a system prompt first (a bit longer).")
|
|
284
|
+
return value
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class PromptVerifyRequest(BaseModel):
|
|
288
|
+
"""Body for ``POST /api/ui/agent-prompt/verify`` — mirrors the backend's
|
|
289
|
+
``VerifyPromptRequest`` (``system_prompt`` + ``engine``)."""
|
|
290
|
+
|
|
291
|
+
system_prompt: str = Field(..., description="The user-written prompt to verify.")
|
|
292
|
+
engine: str = Field("claude_code", description=_ENGINE_DESC)
|
|
293
|
+
|
|
294
|
+
@field_validator("system_prompt")
|
|
295
|
+
@classmethod
|
|
296
|
+
def _min_len(cls, value: str) -> str:
|
|
297
|
+
value = (value or "").strip()
|
|
298
|
+
if len(value) < 10:
|
|
299
|
+
raise ValueError("Write a system prompt first (a bit longer).")
|
|
300
|
+
return value
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class PromptRunRequest(BaseModel):
|
|
304
|
+
"""Body for ``POST /api/ui/prompt/run``."""
|
|
305
|
+
|
|
306
|
+
system_prompt: str = Field(..., description="System prompt to run.")
|
|
307
|
+
inputs: dict = Field(default_factory=dict)
|
|
308
|
+
output_format: str = ""
|
|
309
|
+
tools: list[str] = Field(default_factory=list)
|
|
310
|
+
|
|
311
|
+
@field_validator("system_prompt")
|
|
312
|
+
@classmethod
|
|
313
|
+
def _required(cls, value: str) -> str:
|
|
314
|
+
value = (value or "").strip()
|
|
315
|
+
if not value:
|
|
316
|
+
raise ValueError("system_prompt is required.")
|
|
317
|
+
return value
|
|
318
|
+
|
|
319
|
+
@field_validator("output_format")
|
|
320
|
+
@classmethod
|
|
321
|
+
def _strip(cls, value: str) -> str:
|
|
322
|
+
return (value or "").strip()
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class PromptRegenerateRequest(BaseModel):
|
|
326
|
+
"""Body for ``POST /api/ui/agent-prompt/regenerate`` — mirrors the backend's
|
|
327
|
+
``RegeneratePromptRequest``."""
|
|
328
|
+
|
|
329
|
+
system_prompt: str = Field(..., description="The existing prompt to improve.")
|
|
330
|
+
feedback: str = Field("", description="Optional guidance on what to change.")
|
|
331
|
+
agent_name: str = Field("", description="Optional context: the agent's name.")
|
|
332
|
+
agent_type: str = Field("", description="Optional context: the agent's type.")
|
|
333
|
+
job: str = Field("", description="Optional context: the agent's main job.")
|
|
334
|
+
engine: str = Field("claude_code", description=_ENGINE_DESC)
|
|
335
|
+
|
|
336
|
+
@field_validator("system_prompt")
|
|
337
|
+
@classmethod
|
|
338
|
+
def _required(cls, value: str) -> str:
|
|
339
|
+
value = (value or "").strip()
|
|
340
|
+
if not value:
|
|
341
|
+
raise ValueError("system_prompt is required.")
|
|
342
|
+
return value
|
|
343
|
+
|
|
344
|
+
@field_validator("agent_name", "agent_type")
|
|
345
|
+
@classmethod
|
|
346
|
+
def _strip(cls, value: str) -> str:
|
|
347
|
+
return (value or "").strip()
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class AgentPromptGenerateRequest(BaseModel):
|
|
351
|
+
"""Body for ``POST /api/ui/agent-prompt/generate`` — mirrors the backend's
|
|
352
|
+
``GeneratePromptRequest``."""
|
|
353
|
+
|
|
354
|
+
agent_name: str = Field(..., description="The agent's name, e.g. 'Release Notes Writer'.")
|
|
355
|
+
agent_type: str = Field(..., description="The agent's type/category, e.g. 'documentation'.")
|
|
356
|
+
job: str = Field(..., description="The agent's main job, in plain language.")
|
|
357
|
+
context: str = Field("", description="Optional extra context to steer the prompt.")
|
|
358
|
+
engine: str = Field("claude_code", description=_ENGINE_DESC)
|
|
359
|
+
|
|
360
|
+
@field_validator("agent_name", "agent_type", "job")
|
|
361
|
+
@classmethod
|
|
362
|
+
def _required(cls, value: str) -> str:
|
|
363
|
+
value = (value or "").strip()
|
|
364
|
+
if not value:
|
|
365
|
+
raise ValueError("agent_name, agent_type, and job are required.")
|
|
366
|
+
return value
|
|
@@ -222,3 +222,38 @@ def status() -> str:
|
|
|
222
222
|
if _systemd_available():
|
|
223
223
|
return _status_systemd()
|
|
224
224
|
return "no supported service manager found."
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def restart() -> tuple[bool, str]:
|
|
228
|
+
"""Restart the installed agentos-ui service so it re-imports the on-disk code.
|
|
229
|
+
|
|
230
|
+
A running server holds the routes it imported at boot, so a package upgrade
|
|
231
|
+
(``agentos-auto update``) doesn't expose newly-added APIs until the process
|
|
232
|
+
restarts. This performs that restart via whatever manager installed it.
|
|
233
|
+
Best-effort: returns ``(False, msg)`` when no managed service is found, so a
|
|
234
|
+
manually-launched panel is left untouched (the operator restarts it himself).
|
|
235
|
+
"""
|
|
236
|
+
if IS_WINDOWS:
|
|
237
|
+
_run(["schtasks", "/End", "/TN", WIN_TASK])
|
|
238
|
+
rc, out = _run(["schtasks", "/Run", "/TN", WIN_TASK])
|
|
239
|
+
return (rc == 0), (f"restarted scheduled task '{WIN_TASK}'." if rc == 0 else out)
|
|
240
|
+
if IS_MAC:
|
|
241
|
+
path = _mac_plist_path()
|
|
242
|
+
if not path.exists():
|
|
243
|
+
return False, "no launchd agent installed — restart the panel manually."
|
|
244
|
+
_run(["launchctl", "unload", str(path)])
|
|
245
|
+
rc, out = _run(["launchctl", "load", "-w", str(path)])
|
|
246
|
+
return (rc == 0), (f"restarted launchd agent '{_MAC_LABEL}'." if rc == 0 else out)
|
|
247
|
+
if _systemd_available():
|
|
248
|
+
is_root = hasattr(os, "geteuid") and os.geteuid() == 0
|
|
249
|
+
sysctl = ["systemctl"] if is_root else ["systemctl", "--user"]
|
|
250
|
+
# Only restart a unit that actually exists, so we don't spam errors on a
|
|
251
|
+
# box where the panel runs under nohup/tmux instead of systemd.
|
|
252
|
+
rc, _ = _run([*sysctl, "list-unit-files", f"{SERVICE_NAME}.service"])
|
|
253
|
+
_, active = _run([*sysctl, "is-active", SERVICE_NAME])
|
|
254
|
+
if rc != 0 and active.strip() not in ("active", "activating"):
|
|
255
|
+
return False, f"no systemd unit '{SERVICE_NAME}' — restart the panel manually."
|
|
256
|
+
rc, out = _run([*sysctl, "restart", SERVICE_NAME])
|
|
257
|
+
return (rc == 0), (f"restarted systemd service '{SERVICE_NAME}'." if rc == 0
|
|
258
|
+
else out or f"systemctl restart {SERVICE_NAME} failed")
|
|
259
|
+
return False, "no supported service manager found — restart the panel manually (`agentos-ui`)."
|
|
File without changes
|
{devops_bot_sdk-1.4.126 → devops_bot_sdk-1.4.129}/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
|