devops-bot-sdk 1.4.116__tar.gz → 1.4.117__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.
Files changed (89) hide show
  1. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/devops_bot_sdk.egg-info/SOURCES.txt +1 -0
  4. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/pyproject.toml +1 -1
  5. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/__init__.py +2 -2
  6. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agentd.py +6 -0
  7. devops_bot_sdk-1.4.117/sdk/platform_compat.py +48 -0
  8. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/run_auto.py +6 -0
  9. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/app.js +52 -11
  10. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/prompt-studio.html +3 -2
  11. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/README.md +0 -0
  12. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  13. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  14. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/devops_bot_sdk.egg-info/requires.txt +0 -0
  15. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  16. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/__init__.py +0 -0
  17. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/builder.py +0 -0
  18. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/chat.py +0 -0
  19. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/connectors.py +0 -0
  20. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/engines/__init__.py +0 -0
  21. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/engines/claude_code.py +0 -0
  22. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/engines/http.py +0 -0
  23. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/engines/mcp.py +0 -0
  24. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/engines/n8n.py +0 -0
  25. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/engines/shell.py +0 -0
  26. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/integrations.py +0 -0
  27. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/inventory.py +0 -0
  28. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/library/__init__.py +0 -0
  29. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/manifest.py +0 -0
  30. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/prompt_studio.py +0 -0
  31. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/prompt_template.py +0 -0
  32. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/prompt_vars.py +0 -0
  33. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/run_log.py +0 -0
  34. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/secrets.py +0 -0
  35. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/usage.py +0 -0
  36. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/agents/varstore.py +0 -0
  37. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/cli.py +0 -0
  38. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/client.py +0 -0
  39. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/collectors/__init__.py +0 -0
  40. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/collectors/files.py +0 -0
  41. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/collectors/process.py +0 -0
  42. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/collectors/screenshot.py +0 -0
  43. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/config.py +0 -0
  44. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/crucial.py +0 -0
  45. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/design_verify/__init__.py +0 -0
  46. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/design_verify/assets.py +0 -0
  47. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/design_verify/bootstrap.py +0 -0
  48. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/design_verify/browser.py +0 -0
  49. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/design_verify/compare.py +0 -0
  50. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/design_verify/loop.py +0 -0
  51. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/exceptions.py +0 -0
  52. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/git_ops.py +0 -0
  53. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/graphify.py +0 -0
  54. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/hooks/__init__.py +0 -0
  55. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/hooks/crucial_guard.py +0 -0
  56. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ipc/__init__.py +0 -0
  57. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ipc/electron_bridge.py +0 -0
  58. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ipc/handlers.py +0 -0
  59. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/local_exec.py +0 -0
  60. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/models/__init__.py +0 -0
  61. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/models/envelope.py +0 -0
  62. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/models/requests.py +0 -0
  63. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/models/responses.py +0 -0
  64. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/models/snapshots.py +0 -0
  65. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/py.typed +0 -0
  66. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/sse.py +0 -0
  67. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/test.py +0 -0
  68. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/test_pipeline.py +0 -0
  69. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/__init__.py +0 -0
  70. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/api.py +0 -0
  71. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/dashboard.py +0 -0
  72. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/jobs.py +0 -0
  73. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/runner.py +0 -0
  74. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/security.py +0 -0
  75. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/server.py +0 -0
  76. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/service.py +0 -0
  77. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/setup_manager.py +0 -0
  78. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/agents.html +0 -0
  79. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/custom-agents.html +0 -0
  80. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/default-agents.html +0 -0
  81. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/index.html +0 -0
  82. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/runs.html +0 -0
  83. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/setup.html +0 -0
  84. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/styles.css +0 -0
  85. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/ui/static/tickets.html +0 -0
  86. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/sdk/updater.py +0 -0
  87. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/setup.cfg +0 -0
  88. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/tests/test_agent_chat_features.py +0 -0
  89. {devops_bot_sdk-1.4.116 → devops_bot_sdk-1.4.117}/tests/test_agents_connectors_builder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.116
3
+ Version: 1.4.117
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.4.116
3
+ Version: 1.4.117
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -16,6 +16,7 @@ sdk/exceptions.py
16
16
  sdk/git_ops.py
17
17
  sdk/graphify.py
18
18
  sdk/local_exec.py
19
+ sdk/platform_compat.py
19
20
  sdk/py.typed
20
21
  sdk/run_auto.py
21
22
  sdk/sse.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devops-bot-sdk"
7
- version = "1.4.116"
7
+ version = "1.4.117"
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.116
3
+ Version: 1.4.117
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.116"
33
+ __version__ = "1.4.117"
34
34
  __author__ = "AgentOS"
35
35
 
36
36
  from sdk.client import BackendClient
@@ -618,6 +618,12 @@ def status() -> None:
618
618
 
619
619
 
620
620
  def main() -> None:
621
+ # macOS: keep the background daemon from aborting ("Python quit unexpectedly")
622
+ # when it does networking after the double-fork. May re-exec once; must run
623
+ # before any fork or objc-linked import.
624
+ from sdk.platform_compat import ensure_macos_fork_safety
625
+ ensure_macos_fork_safety()
626
+
621
627
  argv = sys.argv[1:]
622
628
  if argv and argv[0] == "start":
623
629
  foreground = "--foreground" in argv[1:] or "-f" in argv[1:]
@@ -0,0 +1,48 @@
1
+ """Cross-platform runtime shims used by the CLI entry points.
2
+
3
+ Kept dependency-free and import-cheap: these run at the very top of ``main()``
4
+ BEFORE any networking/objc-linked module is imported, and one of them re-execs
5
+ the interpreter — so it must not drag in heavy imports of its own.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import sys
11
+
12
+
13
+ def ensure_macos_fork_safety() -> None:
14
+ """Stop macOS from killing our background daemons with a native abort.
15
+
16
+ ``agentos-auto`` / ``agentos-agentd`` double-fork (fork + setsid, no exec)
17
+ into a background daemon that then does networking (httpx → CFNetwork /
18
+ SystemConfiguration) and TLS. When the Objective-C runtime detects objc use
19
+ after a bare ``fork()`` with no ``exec()`` it calls ``abort()`` — which macOS
20
+ surfaces as the "Python quit unexpectedly" crash dialog. Exporting
21
+ ``OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`` disables that guard.
22
+
23
+ The objc runtime reads that variable when its dylib is first mapped (that is
24
+ interpreter startup on framework Python builds), so setting it via
25
+ ``os.environ`` mid-run is too late to help the CURRENT process. We set it and
26
+ re-exec a FRESH interpreter ONCE, so the replacement process — and every
27
+ daemon it later forks — starts with the variable already in place. We also
28
+ short-circuit the system-proxy (``_scproxy``) lookup that most often triggers
29
+ the objc path on the first outbound request.
30
+
31
+ No-op off macOS, and idempotent: the "already YES" check is the re-exec guard
32
+ that prevents an exec loop.
33
+ """
34
+ if sys.platform != "darwin":
35
+ return
36
+ if os.environ.get("OBJC_DISABLE_INITIALIZE_FORK_SAFETY") == "YES":
37
+ return # already applied on this process → don't re-exec again
38
+ os.environ["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
39
+ os.environ.setdefault("no_proxy", "*")
40
+ try:
41
+ # os.execv inherits the just-modified os.environ, so the fresh process
42
+ # sees the variable from its first instruction. argv[0] is a runnable
43
+ # Python file for both a console-script launch and `python -m ...`.
44
+ os.execv(sys.executable, [sys.executable, sys.argv[0], *sys.argv[1:]])
45
+ except Exception: # noqa: BLE001
46
+ # Re-exec failed (unusual argv[0], permissions). The env var is at least
47
+ # set for child processes we spawn — carry on rather than refuse to start.
48
+ pass
@@ -544,6 +544,12 @@ def _run_loop(workspace_root: str | None, only_keys: list[str] | None = None,
544
544
 
545
545
 
546
546
  def main() -> None:
547
+ # macOS: keep the background daemon from aborting ("Python quit unexpectedly")
548
+ # when it does networking after the double-fork. May re-exec once; must run
549
+ # before any fork or objc-linked import.
550
+ from sdk.platform_compat import ensure_macos_fork_safety
551
+ ensure_macos_fork_safety()
552
+
547
553
  argv = sys.argv[1:]
548
554
  cmd = argv[0] if argv and not argv[0].startswith("-") else "run"
549
555
 
@@ -225,7 +225,7 @@
225
225
  let toolsNeeded = []; // AI-assessed; the user never sees or sets these
226
226
  let lastInputs = []; // input DEFINITIONS from analyze/manual-add (for saving)
227
227
  let lastOutputs = [];
228
- let lastSecrets = []; // API-key names detected by analyze (values collected at save)
228
+ let lastSecrets = []; // API-key names (analyze-detected or hand-added; values collected at save)
229
229
  let studioJob = null; // id of the in-flight analyze/review/run job
230
230
 
231
231
  function showStop(on) {
@@ -276,17 +276,30 @@
276
276
  $("save-panel").style.display = "";
277
277
  }
278
278
 
279
- // Required API keys detected by analyze collected here, stored (encrypted)
280
- // when the agent is saved. Values are password inputs; names are fixed.
279
+ // Department defaults resolve their keys from the backend/host env, not a local
280
+ // store so the value inputs are irrelevant (and hidden) in that scope.
281
+ function secretsHidden() {
282
+ return $("sa-scope") && $("sa-scope-field").style.display !== "none"
283
+ && $("sa-scope").value === "department";
284
+ }
285
+ // One secret row: env-var name (+ optional description) + masked value + remove.
286
+ function secretRowHTML(s, value) {
287
+ const desc = s.description ? ` <span class="muted" style="font-weight:400">— ${esc(s.description)}</span>` : "";
288
+ return `<div class="field" data-secret-row="${esc(s.name)}"><label>${esc(s.name)}${desc}` +
289
+ ` <a href="#" class="secret-rm" data-rmsecret="${esc(s.name)}" style="margin-left:6px;font-weight:400;font-size:12px">remove</a></label>` +
290
+ `<input class="secret-in" type="password" data-secret="${esc(s.name)}" placeholder="paste value (optional — hidden)" autocomplete="off" value="${esc(value || "")}" /></div>`;
291
+ }
292
+ // API keys detected by analyze OR added by hand — collected here, stored
293
+ // (encrypted) when the agent is saved. Shown once analysis has run so the user
294
+ // can add a key even when the AI detected none.
281
295
  function renderSecrets() {
282
296
  const panel = $("studio-secrets-panel"), box = $("studio-secrets");
283
297
  if (!panel || !box) return;
284
- if (!lastSecrets.length) { panel.style.display = "none"; box.innerHTML = ""; return; }
298
+ if (secretsHidden()) { panel.style.display = "none"; return; }
285
299
  panel.style.display = "";
286
- box.innerHTML = lastSecrets.map(s =>
287
- `<div class="field"><label>${esc(s.name)}${s.description ? ` <span class="muted" style="font-weight:400">— ${esc(s.description)}</span>` : ""}</label>
288
- <input class="secret-in" type="password" data-secret="${esc(s.name)}" placeholder="paste value (optionalhidden)" autocomplete="off" /></div>`
289
- ).join("");
300
+ box.innerHTML = lastSecrets.length
301
+ ? lastSecrets.map(s => secretRowHTML(s, "")).join("")
302
+ : "<div class='muted' data-empty>No API keys detectedadd one below if this task calls an external service.</div>";
290
303
  }
291
304
 
292
305
  function collectSecrets() {
@@ -391,6 +404,35 @@
391
404
  $("studio-inputs").appendChild(wrap.firstChild);
392
405
  });
393
406
 
407
+ // Let the user add an API key the AI missed. Names are env-var style
408
+ // (UPPER_SNAKE); the value is optional here and can be filled in later.
409
+ $("sp-add-secret").addEventListener("click", () => {
410
+ const raw = (window.prompt("Environment-variable NAME for this key (e.g. OPENAI_API_KEY):") || "").trim();
411
+ if (!raw) return;
412
+ const name = raw.toUpperCase().replace(/[^A-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "");
413
+ if (!name) { toast("Use an env-var name like OPENAI_API_KEY", "err"); return; }
414
+ if (lastSecrets.some(s => s.name === name)) { toast("That secret already exists", "err"); return; }
415
+ const desc = (window.prompt("What is it for? (optional)") || "").trim();
416
+ lastSecrets.push({ name, description: desc });
417
+ const empty = $("studio-secrets").querySelector("[data-empty]");
418
+ if (empty) empty.remove();
419
+ const wrap = document.createElement("div");
420
+ wrap.innerHTML = secretRowHTML({ name, description: desc }, "");
421
+ $("studio-secrets").appendChild(wrap.firstChild);
422
+ });
423
+
424
+ // Drop a secret (AI-detected over-reach or one added by mistake).
425
+ $("studio-secrets").addEventListener("click", (e) => {
426
+ const a = e.target.closest(".secret-rm");
427
+ if (!a) return;
428
+ e.preventDefault();
429
+ const nm = a.dataset.rmsecret;
430
+ lastSecrets = lastSecrets.filter(s => s.name !== nm);
431
+ const row = $("studio-secrets").querySelector(`[data-secret-row="${nm}"]`);
432
+ if (row) row.remove();
433
+ if (!lastSecrets.length) renderSecrets(); // restore the "add one below" hint
434
+ });
435
+
394
436
  async function pollRunInto(promise) {
395
437
  $("sp-run-status").textContent = "Running… (safe to switch pages — it keeps running)";
396
438
  $("resp-panel").style.display = "";
@@ -440,9 +482,8 @@
440
482
  // when that scope is picked (they resolve keys from the backend/host env).
441
483
  if ($("sa-scope")) $("sa-scope").addEventListener("change", () => {
442
484
  const panel = $("studio-secrets-panel");
443
- if (panel && lastSecrets.length) {
444
- panel.style.display = $("sa-scope").value === "department" ? "none" : "";
445
- }
485
+ if (!panel || $("io-panel").style.display === "none") return; // only after analysis
486
+ panel.style.display = secretsHidden() ? "none" : "";
446
487
  });
447
488
 
448
489
  // Turn this Studio session into a reusable agent (custom or department default).
@@ -43,12 +43,13 @@
43
43
  <button class="btn" id="sp-add-input" type="button" style="margin-top:6px">+ Add input</button>
44
44
 
45
45
  <div id="studio-secrets-panel" style="display:none;margin-top:14px">
46
- <div style="font-weight:600;margin-bottom:2px">API keys required <span class="muted" style="font-weight:400">(detected by AI)</span></div>
46
+ <div style="font-weight:600;margin-bottom:2px">API keys &amp; secrets <span class="muted" style="font-weight:400">(detected by AI — add any it missed)</span></div>
47
47
  <p class="muted" style="margin:0 0 .5em;font-size:12px">
48
48
  Stored <b>encrypted on this machine</b> when you save the agent, reused every run.
49
- Leave blank to fill in later on the Custom Agents page.
49
+ Leave a value blank to fill in later on the Custom Agents page.
50
50
  </p>
51
51
  <div id="studio-secrets"></div>
52
+ <button class="btn" id="sp-add-secret" type="button" style="margin-top:6px">+ Add secret</button>
52
53
  </div>
53
54
  <div style="margin-top:10px">
54
55
  <button class="btn primary" id="sp-run">Run</button>