diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15ef419..941d2a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -213,4 +213,4 @@ jobs: magent tools doctor magent system security-report - name: Cross-platform contract tests - run: python -m pytest -q tests/unit/test_permissions_bypass.py tests/unit/test_safe_names.py tests/unit/test_durability_and_state.py tests/unit/test_auth_store.py tests/unit/test_tools_safety.py tests/unit/test_provider_support.py tests/unit/test_security_assurance.py -k "not document_tools_create_docx_and_pptx and not visual_artifact_tools_create_svg_diagram_and_image" + run: python -m pytest -q tests/unit/test_process_liveness.py tests/unit/test_permissions_bypass.py tests/unit/test_safe_names.py tests/unit/test_durability_and_state.py tests/unit/test_auth_store.py tests/unit/test_tools_safety.py tests/unit/test_provider_support.py tests/unit/test_security_assurance.py -k "not document_tools_create_docx_and_pptx and not visual_artifact_tools_create_svg_diagram_and_image" diff --git a/CHANGELOG.md b/CHANGELOG.md index ae9eea3..e6bb327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.3.0 — Unreleased + +See [release notes](docs/RELEASE_NOTES_1.3.0.md). + ## Unreleased ## 1.2.0 (2026-09-06) diff --git a/README.md b/README.md index ef14541..74c0799 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@
# 🐦⬛ MagAgent
diff --git a/docs/RELEASE_NOTES_1.3.0.md b/docs/RELEASE_NOTES_1.3.0.md
new file mode 100644
index 0000000..1b26a66
--- /dev/null
+++ b/docs/RELEASE_NOTES_1.3.0.md
@@ -0,0 +1,15 @@
+# MagAgent 1.3.0
+
+Release notes — September 12, 2026.
+
+- External approval decisions now wake the issuing broker; request creation and decisions share full storage transactions. Corruption and lock timeout fail closed. Windows locking uses an actual byte lock.
+- Stdio decisions validate their AAIS envelope and reviewed digest. The Web UI reports the authority outcome, retains dialog focus, and shows origin and expiry.
+- `magent capabilities --json` reports installed WebMCP prerequisites without a model call; the existing `magent tools doctor` remains available.
+- Run center exposes recorded file changes, checkpoints and final audit evidence before recovery, and recognizes the runtime's waiting state. Task status changes are explicitly distinguished from restarting a stopped harness.
+- Config safety and workbench maintenance now pass the type checker and were removed from its exclusion list.
+
+Approval and task records remain local to their existing authority. Inspect uncertain external effects before retrying; changing a task status is not a guarantee of execution resumption.
+
+AGS, OAP and AAIS document/wire formats remain unchanged. Local validation evidence and remaining platform gates are recorded in the ecosystem release report.
+
+Recovery ownership checks use a non-destructive Windows process query, with a regression test that verifies live child processes survive inspection.
diff --git a/pyproject.toml b/pyproject.toml
index 737cf05..85a03e8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mag-agent"
-version = "1.2.0"
+version = "1.3.0"
description = "MagAgent — CLI AI coding agent with persistent MagGraph memory. Like the Magpie: intelligent, tool-using, and never forgets."
readme = "README.md"
license = { text = "Apache-2.0" }
@@ -181,7 +181,6 @@ module = [
"magent.cli.commands.plugins",
"magent.cli.main",
"magent.config",
- "magent.config_safety",
"magent.gateway.adapters.slack",
"magent.goal_orchestrator",
"magent.model_capabilities",
@@ -190,7 +189,6 @@ module = [
"magent.tools.web",
"magent.utils",
"magent.workbench",
- "magent.workbench_maintenance",
]
ignore_errors = true
diff --git a/src/magent/__init__.py b/src/magent/__init__.py
index c2aa901..10149f1 100644
--- a/src/magent/__init__.py
+++ b/src/magent/__init__.py
@@ -1,4 +1,4 @@
"""MagAgent — CLI AI coding agent powered by MagGraph persistent memory."""
-__version__ = "1.2.0"
+__version__ = "1.3.0"
__author__ = "Alex Merced"
diff --git a/src/magent/approval_broker.py b/src/magent/approval_broker.py
index 5de0106..c64f435 100644
--- a/src/magent/approval_broker.py
+++ b/src/magent/approval_broker.py
@@ -10,9 +10,11 @@
import copy
import json
+import os
import signal
import sys
import threading
+import time
from collections.abc import Callable, Mapping
from contextlib import suppress
from dataclasses import dataclass
@@ -29,7 +31,7 @@
validate,
)
-from magent.workbench_store import WorkbenchStore
+from magent.workbench_store import WorkbenchStore, WorkbenchStoreError
Envelope = dict[str, Any]
Publisher = Callable[[Envelope], None]
@@ -109,8 +111,17 @@ def _empty() -> Envelope:
}
def _state(self) -> Envelope:
- state = self.store.read(self.STORE_NAME, self._empty())
- return state if isinstance(state, dict) else self._empty()
+ state = self.store.read(self.STORE_NAME, self._empty(), strict=True)
+ if (
+ not isinstance(state, dict)
+ or state.get("schema") != self._empty()["schema"]
+ or any(
+ key not in state or not isinstance(state[key], type(default))
+ for key, default in self._empty().items()
+ )
+ ):
+ raise WorkbenchStoreError("Invalid approval state; explicit recovery is required")
+ return state
def _next_sequence(self, state: Envelope, key: str = "sequence") -> int:
value = int(state.get(key, 0)) + 1
@@ -173,7 +184,7 @@ def request(
) -> str:
exact_action = copy.deepcopy(dict(action))
digest = action_digest(exact_action)
- with self._lock:
+ with self._lock, self.store.lock(self.STORE_NAME):
state = self._state()
remembered = next(
(
@@ -229,6 +240,7 @@ def request(
)
request_id = str(envelope["request"]["id"])
state.setdefault("pending", {})[request_id] = envelope
+ state.setdefault("owners", {})[request_id] = os.getpid()
state.setdefault("events", []).append(envelope)
state["events"] = state["events"][-1000:]
self.store.write(self.STORE_NAME, state)
@@ -240,18 +252,29 @@ def request(
self._waiters[request_id] = waiter
self._publishers[request_id] = publish
publish(copy.deepcopy(envelope))
- if not waiter.resolved.wait(timeout):
- with suppress(ConflictError, ValueError):
- self.decide(
- request_id,
- decision="deny",
- scope="once",
- actor={
- "id": "magent.timeout",
- "type": "policy",
- "authenticated_by": "authority",
- },
- )
+ deadline = time.monotonic() + timeout
+ while not waiter.resolved.wait(min(0.1, max(0, deadline - time.monotonic()))):
+ with self._lock, self.store.lock(self.STORE_NAME):
+ durable = self._state().get("resolutions", {}).get(request_id)
+ if durable:
+ waiter.resolution = durable
+ publish(copy.deepcopy(durable))
+ break
+ if time.monotonic() >= deadline:
+ with suppress(ConflictError, ValueError):
+ self.decide(
+ request_id,
+ decision="deny",
+ scope="once",
+ actor={
+ "id": "magent.timeout",
+ "type": "policy",
+ "authenticated_by": "authority",
+ },
+ )
+ with self._lock, self.store.lock(self.STORE_NAME):
+ waiter.resolution = self._state().get("resolutions", {}).get(request_id)
+ break
with self._lock:
resolution = waiter.resolution
self._waiters.pop(request_id, None)
@@ -259,6 +282,8 @@ def request(
if not resolution:
return "deny"
body = resolution["resolution"]
+ if body["outcome"] == "approved" and action_digest(waiter.current_action()) != digest:
+ return "deny"
return str(body.get("effective_scope", "deny")) if body["outcome"] == "approved" else "deny"
def decide(
@@ -269,6 +294,7 @@ def decide(
scope: str,
actor: Mapping[str, Any],
decision_id: str | None = None,
+ reviewed_digest: str | None = None,
) -> Envelope:
publisher: Publisher | None = None
waiter: _Waiter | None = None
@@ -286,6 +312,16 @@ def decide(
raise ConflictError(f"request {request_id} was already resolved")
if pending is None:
raise ValueError(f"unknown pending approval: {request_id}")
+ if (
+ reviewed_digest is not None
+ and reviewed_digest != pending["request"]["action_digest"]
+ ):
+ raise ConflictError("Decision digest does not match the reviewed action")
+ owner = state.get("owners", {}).get(request_id)
+ if decision == "approve" and owner is not None and not self._owner_alive(owner):
+ raise ConflictError(
+ "The issuing process stopped; inspect recovery before starting new work"
+ )
decided = create_decision(
pending,
decision=decision,
@@ -308,6 +344,7 @@ def decide(
state.setdefault("decisions", {})[request_id] = decided
state.setdefault("resolutions", {})[request_id] = resolution
state.setdefault("events", []).append(resolution)
+ state["events"] = state["events"][-1000:]
if resolution["resolution"]["outcome"] == "approved" and scope in {
"session",
"persistent",
@@ -375,12 +412,35 @@ def cancel_active(self) -> int:
)
return len(request_ids)
+ @staticmethod
+ def _owner_alive(pid: int | None) -> bool:
+ from magent.process_liveness import process_alive
+
+ return process_alive(pid)
+
+ def recovery(self) -> Envelope:
+ with self._lock, self.store.lock(self.STORE_NAME):
+ state = self._state()
+ owners = state.get("owners", {})
+ return {
+ "orphaned": [
+ key
+ for key in state["pending"]
+ if key in owners and not self._owner_alive(owners[key])
+ ],
+ "unknown_owner": [key for key in state["pending"] if key not in owners],
+ "receipts": list(state["resolutions"].values())[-100:],
+ "guidance": "Stopped owners are not restarted. Inspect completed effects before creating a new run.",
+ }
+
def snapshot(self) -> Envelope:
with self._lock:
state = self._state()
machine = ApprovalStore(last_sequence=int(state.get("sequence", 0)))
- for envelope in state.get("pending", {}).values():
- machine.add(validate(envelope))
+ for key, envelope in state.get("pending", {}).items():
+ owner = state.get("owners", {}).get(key)
+ if owner is None or self._owner_alive(owner):
+ machine.add(validate(envelope))
return machine.snapshot(stream=self.stream)
def events_after(self, sequence: int) -> list[Envelope]:
@@ -409,7 +469,7 @@ def read_decisions() -> None:
try:
for line in sys.stdin:
try:
- envelope = json.loads(line)
+ envelope = validate(json.loads(line))
if envelope.get("type") != "approval.decided":
continue
decision = envelope["decision"]
@@ -423,6 +483,7 @@ def read_decisions() -> None:
"authenticated_by": "aais-ndjson-stdio",
},
decision_id=str(decision.get("id") or "") or None,
+ reviewed_digest=str(decision["action_digest"]),
)
except Exception as error: # malformed input never grants authority
print(
diff --git a/src/magent/capability_readiness.py b/src/magent/capability_readiness.py
index 487e018..66e6461 100644
--- a/src/magent/capability_readiness.py
+++ b/src/magent/capability_readiness.py
@@ -3,7 +3,13 @@
from __future__ import annotations
import importlib.util
+import json
+import subprocess
from dataclasses import asdict, dataclass
+from pathlib import Path
+from typing import Any
+
+from magent import __version__
@dataclass(frozen=True)
@@ -50,3 +56,54 @@ def readiness_report() -> dict[str, object]:
"capabilities": capabilities,
"full_install": 'python -m pip install "mag-agent[full]"',
}
+
+
+def capability_report() -> dict[str, Any]:
+ reasons: list[str] = []
+ package = importlib.util.find_spec("playwright") is not None
+ browser = False
+ if package:
+ try:
+ from playwright._impl._driver import compute_driver_executable
+
+ node, cli = compute_driver_executable()
+ script = (
+ "process.stdout.write(require("
+ + json.dumps(str(Path(cli).parent))
+ + ").chromium.executablePath())"
+ )
+ result = subprocess.run(
+ [node, "-e", script], capture_output=True, text=True, timeout=3, check=True
+ )
+ browser = Path(result.stdout).is_file()
+ except Exception:
+ reasons.append("Browser driver could not be inspected.")
+ if not package:
+ reasons.append("Install the webmcp optional dependency pack.")
+ if not browser:
+ reasons.append("Install the Playwright Chromium browser.")
+ try:
+ from magent.browser import normalize_webmcp_origins
+ from magent.config import load_config
+
+ origins = normalize_webmcp_origins(load_config().get("webmcp", "origins", default=None))
+ except (ValueError, RuntimeError):
+ origins = ()
+ reasons.append("Configure at least one valid exact HTTPS origin.")
+ return {
+ "schema": "agent-runtime.capabilities.v1",
+ "harness": "magagent",
+ "version": __version__,
+ "supported": {"aais": True, "webmcp": True, "oap": "1.0", "ags": "1.0"},
+ "ready": {"webmcp": bool(package and browser and origins)},
+ "webmcp": {
+ "package_installed": package,
+ "browser_installed": browser,
+ "origins": list(origins),
+ "registry_status": "not_checked",
+ "reasons": reasons,
+ },
+ "limits": [
+ "Live registry, credentials and profile policy are checked by the harness at execution."
+ ],
+ }
diff --git a/src/magent/cli/main.py b/src/magent/cli/main.py
index 8fee734..d391418 100644
--- a/src/magent/cli/main.py
+++ b/src/magent/cli/main.py
@@ -410,6 +410,18 @@ def main(
_run_repl(username, config, main_provider, extract_provider, cwd, profile=effective_profile)
+@app.command("capabilities")
+def capabilities_cmd(json_output: bool = typer.Option(True, "--json/--no-json")) -> None:
+ """Inspect installed runtime capabilities without running a model or opening a site."""
+ from magent.capability_readiness import capability_report
+
+ report = capability_report()
+ if json_output:
+ console.print_json(data=report)
+ else:
+ console.print(report)
+
+
@app.command("ask", rich_help_panel="Everyday Agent Work")
def ask_cmd(
task: str = typer.Argument(..., help="One-shot task to run non-interactively"),
diff --git a/src/magent/config_safety.py b/src/magent/config_safety.py
index 3269573..99f9cf8 100644
--- a/src/magent/config_safety.py
+++ b/src/magent/config_safety.py
@@ -27,7 +27,7 @@ def config_paths(username: str | None = None) -> dict[str, str]:
def show_config(username: str | None = None) -> dict[str, Any]:
"""Return config file paths and text for global/current-user config."""
- result = {"ok": True, "files": {}}
+ result: dict[str, Any] = {"ok": True, "files": {}}
for key, raw_path in config_paths(username).items():
path = Path(raw_path)
text = path.read_text(encoding="utf-8") if path.exists() else ""
@@ -50,7 +50,7 @@ def redact_config_text(text: str) -> str:
key = stripped.split("=", 1)[0].strip().strip('"').strip("'").lower()
if any(secret in key for secret in SECRET_KEYS):
prefix = line.split("=", 1)[0].rstrip()
- lines.append(f"{prefix} = \"***\"")
+ lines.append(f'{prefix} = "***"')
else:
lines.append(line)
return "\n".join(lines) + ("\n" if text.endswith("\n") else "")
@@ -94,7 +94,11 @@ def diff_config(backup_id: str | None = None, username: str | None = None) -> di
current = Path(raw_path)
old = backup / f"{key}.toml"
before = old.read_text(encoding="utf-8").splitlines(keepends=True) if old.exists() else []
- after = current.read_text(encoding="utf-8").splitlines(keepends=True) if current.exists() else []
+ after = (
+ current.read_text(encoding="utf-8").splitlines(keepends=True)
+ if current.exists()
+ else []
+ )
diffs[key] = "".join(
difflib.unified_diff(
before,
diff --git a/src/magent/docs/command-reference.md b/src/magent/docs/command-reference.md
index 1d89510..a80cac5 100644
--- a/src/magent/docs/command-reference.md
+++ b/src/magent/docs/command-reference.md
@@ -110,6 +110,7 @@ Generated from the active Typer command tree.
- `magent auth`
- `magent browser`
- `magent cache`
+- `magent capabilities`
- `magent checkpoint`
- `magent ci`
- `magent code`
diff --git a/src/magent/process_liveness.py b/src/magent/process_liveness.py
new file mode 100644
index 0000000..4757e82
--- /dev/null
+++ b/src/magent/process_liveness.py
@@ -0,0 +1,33 @@
+"""Non-destructive process liveness checks for recovery ownership."""
+
+import os
+import sys
+
+
+def process_alive(pid: int | None) -> bool:
+ if not isinstance(pid, int) or isinstance(pid, bool) or pid <= 0:
+ return False
+ if sys.platform == "win32":
+ # os.kill(pid, 0) terminates a Windows process. Query its wait state instead.
+ import ctypes
+ from ctypes import wintypes
+
+ kernel = ctypes.WinDLL("kernel32", use_last_error=True)
+ kernel.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
+ kernel.OpenProcess.restype = wintypes.HANDLE
+ kernel.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD]
+ kernel.WaitForSingleObject.restype = wintypes.DWORD
+ kernel.CloseHandle.argtypes = [wintypes.HANDLE]
+ kernel.CloseHandle.restype = wintypes.BOOL
+ handle = kernel.OpenProcess(0x00100000, False, pid) # SYNCHRONIZE only
+ if not handle:
+ return False
+ try:
+ return kernel.WaitForSingleObject(handle, 0) == 0x00000102 # WAIT_TIMEOUT
+ finally:
+ kernel.CloseHandle(handle)
+ try:
+ os.kill(pid, 0)
+ return True
+ except (OSError, ValueError, OverflowError):
+ return False
diff --git a/src/magent/ui.py b/src/magent/ui.py
index c8eeff3..386afe2 100644
--- a/src/magent/ui.py
+++ b/src/magent/ui.py
@@ -1172,6 +1172,8 @@ def _dispatch(self, method: str) -> None:
)
elif parsed.path == "/api/approvals/snapshot":
self._json(approval_broker.snapshot())
+ elif parsed.path == "/api/approvals/recovery":
+ self._json(approval_broker.recovery())
elif parsed.path == "/api/approvals/events":
after = _int_or(query.get("after", ["0"])[0], 0)
self._json({"events": approval_broker.events_after(after)})
@@ -1188,6 +1190,7 @@ def _dispatch(self, method: str) -> None:
"authenticated_by": "magent-loopback-token",
},
decision_id=str(body.get("decision_id") or "") or None,
+ reviewed_digest=body.get("action_digest"),
)
except Exception as error: # AAIS conflicts are safe client races.
self._json({"ok": False, "error": str(error)}, status=409)
diff --git a/src/magent/webui/static/assets/index-Dzs2HP-v.js b/src/magent/webui/static/assets/index-BT5XGUGZ.js
similarity index 96%
rename from src/magent/webui/static/assets/index-Dzs2HP-v.js
rename to src/magent/webui/static/assets/index-BT5XGUGZ.js
index c9252b0..2bd7058 100644
--- a/src/magent/webui/static/assets/index-Dzs2HP-v.js
+++ b/src/magent/webui/static/assets/index-BT5XGUGZ.js
@@ -19,8 +19,8 @@ Error generating stack: `+e.message+`
fs:write:src/**
shell:exec:pytest*`,onChange:e=>F({...P,permissions:e.target.value})})]})]}),(0,N.jsx)(`p`,{className:`context-note`,children:`Saving updates the draft only. Use Save graph above to validate and write it.`}),(0,N.jsxs)(`div`,{className:`graph-card-dialog-actions`,children:[(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:Ke,children:`Delete card`}),(0,N.jsx)(`span`,{}),(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>F(null),children:`Cancel`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:`Apply changes`})]})]})})]})}function je(e){let t=String(e.source||``).toLowerCase(),n=String(e.trust||``).toLowerCase();return t===`managed`||n===`managed`?{key:`managed`,label:`Managed`}:t.includes(`/.agentprofiles/`)?{key:`universal`,label:`Universal`}:t.includes(`/.agents/`)?{key:`portable`,label:`Portable`}:t.includes(`/.magent/agents/`)?{key:`project`,label:`Project`}:t.includes(`/.config/magent/`)||t===`user`?{key:`user`,label:`User`}:{key:n||`project`,label:n||`Project`}}var Me={tools:[],skills:[],mcp_servers:[],permission_modes:[`paranoid`,`balanced`,`silent`,`yolo`],network_modes:[`none`,`ask`,`read`,`full`]};function Ne({profiles:e,refresh:t,setError:n,notify:r}){let[i,a]=(0,l.useState)(e[0]?.name??``),[o,s]=(0,l.useState)(null),[c,u]=(0,l.useState)(!1),[d,f]=(0,l.useState)(!1),[p,m]=(0,l.useState)(!1),[h,g]=(0,l.useState)(``),[y,b]=(0,l.useState)(``),[x,S]=(0,l.useState)(`project`),[C,w]=(0,l.useState)(!1),[T,E]=(0,l.useState)(0),[ee,D]=(0,l.useState)([]),[O,te]=(0,l.useState)(Me),[ne,re]=(0,l.useState)(``),[k,A]=(0,l.useState)({name:``,description:``,instructions:``,provider:``,model:``,tools:[],skills:[],mcp_servers:[],permission_mode:``,network_mode:``,scope:`project`}),ie=(0,l.useRef)(null);(0,l.useEffect)(()=>{if(!C){E(0);return}let e=Date.now(),t=window.setInterval(()=>E(Math.floor((Date.now()-e)/1e3)),1e3);return()=>window.clearInterval(t)},[C]),(0,l.useEffect)(()=>{_(`/api/onboarding/providers`).then(e=>D(e.providers||[])).catch(()=>D([]))},[]),(0,l.useEffect)(()=>{_(`/api/profiles/contract`).then(e=>te({...Me,...e.choices||{}})).catch(()=>te(Me))},[]);async function j(e){try{let t=await _(`/api/profiles/export?name=${encodeURIComponent(e)}`),n=URL.createObjectURL(new Blob([JSON.stringify(t.document,null,2)],{type:`application/json`})),r=window.document.createElement(`a`);r.href=n,r.download=t.filename.replace(/\.md$/,`.json`),r.click(),URL.revokeObjectURL(n)}catch(e){n(e.message)}}async function M(e){try{await v(`/api/profiles/import`,{document:JSON.parse(await e.text()),scope:`project`}),await t(),r(`Profile imported.`)}catch(e){n(String(e).includes(`JSON`)?`That file is not a JSON agent profile. Export one first to see the shape.`:e.message)}}(0,l.useEffect)(()=>{!i&&e[0]&&a(e[0].name)},[e,i]),(0,l.useEffect)(()=>{if(!i)return;let e=!1;return(async()=>{try{let t=await _(`/api/profile?name=${encodeURIComponent(i)}`);if(!e){let e=t.profile||{},r=t.effective_profile||{},i=e.document?.metadata||{};s({...e,...r,description:String(i.description||``)}),n(``)}}catch(t){e||n(t.message)}})(),()=>{e=!0}},[i,n]);async function ae(e){e.preventDefault();try{await v(`/api/profiles`,{...k,...k.provider||k.model?{model:{provider:k.provider||void 0,model:k.model||void 0}}:{},...d?{expected_digest:o?.profile_digest||o?.digest||``}:{},tools:k.tools,skills:k.skills,mcp_servers:k.mcp_servers,permission_mode:k.permission_mode||void 0,network_mode:k.network_mode||void 0,scope:k.scope}),u(!1),A({name:``,description:``,instructions:``,provider:``,model:``,tools:[],skills:[],mcp_servers:[],permission_mode:``,network_mode:``,scope:`project`}),await t(),r(`Profile ${k.name} ${d?`updated`:`created`}.`),a(k.name),f(!1)}catch(e){n(e.message)}}async function oe(e){if(e.preventDefault(),h.trim()){w(!0);try{let e=(await v(`/api/profiles/generate`,{prompt:h.trim(),name:y.trim()})).document||{},t=e.metadata||{},n=e.spec||{},i=n.tools||{},a=e=>(e||[]).map(e=>typeof e==`string`?e:e.name).filter(Boolean);A({name:String(t.name||y),description:String(t.description||``),instructions:String(n.role?.instructions||``),provider:String(n.model?.provider||``),model:String(n.model?.id||``),tools:(i.allow||[]).map(String),skills:a(i.skills||[]),mcp_servers:a(i.mcp_servers||[]),permission_mode:String({deny:`paranoid`,ask:`balanced`,allow:`silent`}[n.permissions?.default]||``),network_mode:String({deny:`none`,ask:`ask`,allow:`full`}[n.permissions?.network]||``),scope:x}),f(!1),m(!1),u(!0),r(`Generated a validated profile draft. Review it before creating the profile.`)}catch(e){n(e.message)}finally{w(!1)}}}let P=o?.permissions,F=!!(o&&o.source!==`managed`);async function I(){if(i)try{let e=(await _(`/api/profiles/export?name=${encodeURIComponent(i)}`)).document||{},t=e.metadata||{},n=e.spec||{},r=n.tools||{},a=e=>(e||[]).map(e=>typeof e==`string`?e:e.name).filter(Boolean);A({name:i,description:String(t.description||o?.description||``),instructions:String(n.role?.instructions||``),provider:String(n.model?.provider||``),model:String(n.model?.id||``),tools:(r.allow||[]).map(String),skills:a(r.skills||[]),mcp_servers:a(r.mcp_servers||[]),permission_mode:String({deny:`paranoid`,ask:`balanced`,allow:`silent`}[n.permissions?.default]||n.permissions?.default||``),network_mode:String({deny:`none`,allow:`read`}[n.permissions?.network]||n.permissions?.network||``),scope:je({source:o?.source,trust:o?.trust}).key===`portable`?`portable`:je({source:o?.source,trust:o?.trust}).key===`universal`?`universal`:je({source:o?.source,trust:o?.trust}).key===`user`?`user`:`project`}),f(!0),u(!0)}catch(e){n(e.message)}}async function se(e){if(e.preventDefault(),!(!i||!ne.trim()))try{await v(`/api/profiles/clone`,{source:i,name:ne.trim(),scope:`project`}),await t(),a(ne.trim()),re(``),r(`Created editable profile @${ne.trim()}.`)}catch(e){n(e.message)}}async function L(){if(!(!i||!o)&&window.confirm(`Delete profile @${i}? Existing conversations keep their transcript but can no longer run with it.`))try{await v(`/api/profiles/delete`,{name:i,expected_digest:o.profile_digest||o.digest||``}),a(``),s(null),await t(),r(`Profile ${i} deleted.`)}catch(e){n(e.message)}}return(0,N.jsxs)(`div`,{className:`page`,children:[(0,N.jsxs)(`div`,{className:`page-header`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`OPEN AGENT PROFILE`}),(0,N.jsx)(`h1`,{children:`Profiles`}),(0,N.jsx)(`p`,{children:`Inspect effective authority or create a reusable specialist.`})]}),(0,N.jsxs)(`div`,{className:`profile-actions`,children:[(0,N.jsx)(`input`,{ref:ie,type:`file`,accept:`.json,application/json`,hidden:!0,onChange:e=>{let t=e.target.files?.[0];t&&M(t),e.target.value=``}}),(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>ie.current?.click(),children:`↑ Import`}),(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,disabled:!i,onClick:()=>i&&void j(i),children:`↓ Export`}),(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>m(!0),children:`✦ Generate profile`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,onClick:()=>{f(!1),A({name:``,description:``,instructions:``,provider:``,model:``,tools:[],skills:[],mcp_servers:[],permission_mode:``,network_mode:``,scope:`project`}),u(!0)},children:`+ New profile`})]})]}),(0,N.jsxs)(`div`,{className:`profile-layout`,children:[(0,N.jsxs)(`div`,{className:`profile-list`,children:[(0,N.jsxs)(`div`,{className:`profile-origin-legend`,children:[(0,N.jsx)(`b`,{children:`Profile sources`}),(0,N.jsxs)(`span`,{children:[(0,N.jsx)(`i`,{className:`managed`}),` Managed: built-in, read-only`]}),(0,N.jsxs)(`span`,{children:[(0,N.jsx)(`i`,{className:`project`}),` Project: this workspace's .magent directory`]}),(0,N.jsxs)(`span`,{children:[(0,N.jsx)(`i`,{className:`portable`}),` Portable: this project's .agents directory`]}),(0,N.jsxs)(`span`,{children:[(0,N.jsx)(`i`,{className:`universal`}),` Universal: shared from ~/.agentprofiles`]}),(0,N.jsxs)(`span`,{children:[(0,N.jsx)(`i`,{className:`user`}),` User: MagAgent user configuration`]})]}),e.map(e=>(0,N.jsx)(`button`,{type:`button`,className:`profile-row ${i===e.name?`active`:``}`,onClick:()=>a(e.name),children:(()=>{let t=je(e);return(0,N.jsxs)(`span`,{className:`profile-row-heading`,children:[(0,N.jsxs)(`b`,{children:[`@`,e.name]}),(0,N.jsx)(`small`,{className:`profile-origin ${t.key}`,children:t.label})]})})()},e.name)),!e.length&&(0,N.jsx)(`div`,{className:`empty-small`,children:(0,N.jsx)(`b`,{children:`No profiles`})})]}),(0,N.jsx)(`div`,{className:`detail-card`,children:o?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`PROFILE DETAIL`}),(0,N.jsxs)(`h3`,{children:[`@`,o.name||i]}),(0,N.jsx)(`p`,{children:o.description||`No description.`}),(0,N.jsxs)(`dl`,{children:[(0,N.jsx)(`dt`,{children:`Provider`}),(0,N.jsx)(`dd`,{children:o.provider||`inherit`}),(0,N.jsx)(`dt`,{children:`Model`}),(0,N.jsx)(`dd`,{children:o.model||`inherit`}),(0,N.jsx)(`dt`,{children:`Permission mode`}),(0,N.jsx)(`dd`,{children:o.permission_mode||`inherit`}),(0,N.jsx)(`dt`,{children:`Network`}),(0,N.jsx)(`dd`,{children:o.network_access||`inherit`}),(0,N.jsx)(`dt`,{children:`Digest`}),(0,N.jsx)(`dd`,{className:`mono`,children:(o.profile_digest||o.digest||`—`).slice(0,24)})]}),o.tools?.length?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`TOOLS`}),(0,N.jsx)(`div`,{className:`chip-row`,children:o.tools.map(e=>(0,N.jsx)(`span`,{className:`chip`,children:e},e))})]}):null,o.skills?.length?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`SKILLS`}),(0,N.jsx)(`div`,{className:`chip-row`,children:o.skills.map(e=>(0,N.jsx)(`span`,{className:`chip`,children:e},e))})]}):null,o.mcp_servers?.length?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`MCP SERVERS`}),(0,N.jsx)(`div`,{className:`chip-row`,children:o.mcp_servers.map(e=>(0,N.jsx)(`span`,{className:`chip`,children:e},e))})]}):null,P&&(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`EFFECTIVE AUTHORITY`}),(0,N.jsx)(`div`,{className:`authority-grid`,children:Array.isArray(P)?P.map(e=>(0,N.jsx)(`div`,{children:(0,N.jsx)(`b`,{children:e})},e)):Object.entries(P).map(([e,t])=>(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`small`,{children:e}),(0,N.jsx)(`b`,{children:String(t)})]},e))})]}),(0,N.jsx)(`p`,{className:`context-note`,children:`A profile can narrow the configured tools and permissions, never widen them.`}),F?(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>void I(),children:`Edit profile`}),(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:()=>void L(),children:`Delete profile`})]}):(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`p`,{className:`context-note`,children:`Managed profiles are immutable OAP baselines. Make an editable project copy to choose its tools, skills, MCP servers, model, and narrower permissions.`}),(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>re(`${i}-custom`),children:`Customize as a copy`})]})]}):(0,N.jsx)(`div`,{className:`empty-panel`,children:(0,N.jsx)(`h2`,{children:`Select a profile`})})})]}),c&&(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`New profile`,onClick:e=>{e.target===e.currentTarget&&u(!1)},children:(0,N.jsxs)(`form`,{className:`modal`,onSubmit:ae,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:d?`EDIT PROFILE`:`NEW PROFILE`}),(0,N.jsx)(`h2`,{children:d?`Edit @${i}`:`Create a specialist`})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>{u(!1),f(!1)},"aria-label":`Close`,children:`×`})]}),(0,N.jsx)(`label`,{htmlFor:`profileName`,children:`Name`}),(0,N.jsx)(`input`,{id:`profileName`,required:!0,value:k.name,disabled:d,onChange:e=>A({...k,name:e.target.value})}),(0,N.jsx)(`label`,{htmlFor:`profileDescription`,children:`Description`}),(0,N.jsx)(`input`,{id:`profileDescription`,value:k.description,onChange:e=>A({...k,description:e.target.value})}),(0,N.jsx)(`label`,{htmlFor:`profileInstructions`,children:`Role instructions`}),(0,N.jsx)(`textarea`,{id:`profileInstructions`,rows:4,value:k.instructions,onChange:e=>A({...k,instructions:e.target.value})}),(0,N.jsx)(`label`,{htmlFor:`profileScope`,children:`Save location`}),(0,N.jsxs)(`select`,{id:`profileScope`,value:k.scope,disabled:d,onChange:e=>A({...k,scope:e.target.value}),children:[(0,N.jsx)(`option`,{value:`project`,children:`Project · .magent/agents`}),(0,N.jsx)(`option`,{value:`portable`,children:`Portable with project · .agents`}),(0,N.jsx)(`option`,{value:`universal`,children:`Universal across harnesses · ~/.agentprofiles`}),(0,N.jsx)(`option`,{value:`user`,children:`This MagAgent user · config agents`})]}),d&&(0,N.jsx)(`p`,{className:`context-note`,children:`Editing preserves the existing profile location. Export or clone it to move it to another scope.`}),(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`label`,{htmlFor:`profileProvider`,children:`Provider`}),(0,N.jsxs)(`select`,{id:`profileProvider`,value:k.provider,onChange:e=>{let t=ee.find(t=>(t.name||t.id)===e.target.value);A({...k,provider:e.target.value,model:t?.default_model||k.model})},children:[(0,N.jsx)(`option`,{value:``,children:`Inherit workspace provider`}),ee.map(e=>{let t=e.name||e.id||``;return(0,N.jsx)(`option`,{value:t,children:e.display_name||t},t)})]})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`label`,{htmlFor:`profileModel`,children:`Model`}),(0,N.jsx)(`input`,{id:`profileModel`,list:`profileModels`,placeholder:`inherit`,value:k.model,onChange:e=>A({...k,model:e.target.value})}),(0,N.jsx)(`datalist`,{id:`profileModels`,children:ee.filter(e=>!k.provider||(e.name||e.id)===k.provider).map(e=>(0,N.jsx)(`option`,{value:e.default_model||``},`${e.name}-model`))})]})]}),(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`label`,{htmlFor:`profilePermission`,children:`Permission ceiling`}),(0,N.jsxs)(`select`,{id:`profilePermission`,value:k.permission_mode,onChange:e=>A({...k,permission_mode:e.target.value}),children:[(0,N.jsx)(`option`,{value:``,children:`Inherit workspace mode`}),O.permission_modes.map(e=>(0,N.jsx)(`option`,{value:e,children:e},e))]})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`label`,{htmlFor:`profileNetwork`,children:`Network ceiling`}),(0,N.jsxs)(`select`,{id:`profileNetwork`,value:k.network_mode,onChange:e=>A({...k,network_mode:e.target.value}),children:[(0,N.jsx)(`option`,{value:``,children:`Inherit workspace network`}),O.network_modes.map(e=>(0,N.jsx)(`option`,{value:e,children:e},e))]})]})]}),(0,N.jsxs)(`fieldset`,{className:`capability-picker`,children:[(0,N.jsx)(`legend`,{children:`Allowed tools`}),(0,N.jsx)(`p`,{children:`Unchecked tools are unavailable to this profile. Selecting none inherits the harness tool set.`}),(0,N.jsx)(`div`,{children:O.tools.map(e=>(0,N.jsxs)(`label`,{children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:k.tools.includes(e.name),onChange:t=>A({...k,tools:t.target.checked?[...k.tools,e.name]:k.tools.filter(t=>t!==e.name)})}),(0,N.jsxs)(`span`,{children:[(0,N.jsx)(`b`,{children:e.name}),(0,N.jsx)(`small`,{children:e.description})]})]},e.name))})]}),(O.skills.length>0||O.mcp_servers.length>0)&&(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`fieldset`,{className:`compact-picker`,children:[(0,N.jsx)(`legend`,{children:`Skills`}),O.skills.map(e=>(0,N.jsxs)(`label`,{children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:k.skills.includes(e.name),onChange:t=>A({...k,skills:t.target.checked?[...k.skills,e.name]:k.skills.filter(t=>t!==e.name)})}),e.name]},e.name))]}),(0,N.jsxs)(`fieldset`,{className:`compact-picker`,children:[(0,N.jsx)(`legend`,{children:`MCP servers`}),O.mcp_servers.map(e=>(0,N.jsxs)(`label`,{children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:k.mcp_servers.includes(e),onChange:t=>A({...k,mcp_servers:t.target.checked?[...k.mcp_servers,e]:k.mcp_servers.filter(t=>t!==e)})}),e]},e))]})]}),(0,N.jsx)(`p`,{className:`context-note`,children:`Leave both blank to inherit the workspace route. A profile can narrow authority, never widen it.`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:d?`Save changes`:`Create profile`})]})}),p&&(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`Generate profile`,onClick:e=>{e.target===e.currentTarget&&!C&&m(!1)},children:(0,N.jsxs)(`form`,{className:`modal`,onSubmit:oe,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`OAP PROFILE AUTHOR`}),(0,N.jsx)(`h2`,{children:`Generate a specialist`})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,disabled:C,onClick:()=>m(!1),"aria-label":`Close`,children:`×`})]}),(0,N.jsx)(`p`,{children:`Describe who the agent should be, when to use it, and any limits it needs. MagAgent will only reference locally available capabilities and will open the validated result for review.`}),(0,N.jsx)(`label`,{htmlFor:`generationPrompt`,children:`What should this agent do?`}),(0,N.jsx)(`textarea`,{id:`generationPrompt`,required:!0,rows:7,value:h,disabled:C,onChange:e=>g(e.target.value),placeholder:`Create a cautious accessibility reviewer that inspects frontend changes, explains WCAG issues, and never edits files without asking…`}),(0,N.jsxs)(`label`,{htmlFor:`generationName`,children:[`Preferred name `,(0,N.jsx)(`small`,{children:`(optional)`})]}),(0,N.jsx)(`input`,{id:`generationName`,pattern:`[a-z0-9][a-z0-9._-]*`,value:y,disabled:C,onChange:e=>b(e.target.value),placeholder:`accessibility-reviewer`}),(0,N.jsx)(`label`,{htmlFor:`generationScope`,children:`Where should it be saved after review?`}),(0,N.jsxs)(`select`,{id:`generationScope`,value:x,disabled:C,onChange:e=>S(e.target.value),children:[(0,N.jsx)(`option`,{value:`project`,children:`Project · .magent/agents`}),(0,N.jsx)(`option`,{value:`portable`,children:`Portable with project · .agents`}),(0,N.jsx)(`option`,{value:`universal`,children:`Universal across harnesses · ~/.agentprofiles`}),(0,N.jsx)(`option`,{value:`user`,children:`This MagAgent user`})]}),(0,N.jsx)(`p`,{className:`context-note`,children:`Generation creates a draft, not authority. You review the complete OAP profile and effective permissions before it is saved.`}),C&&(0,N.jsxs)(`div`,{className:`operation-health`,role:`status`,"aria-live":`polite`,children:[(0,N.jsx)(`span`,{className:`operation-spinner`,"aria-hidden":`true`}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`b`,{children:`Authoring and validating the profile`}),(0,N.jsx)(`p`,{children:`The model is matching your prompt to locally available tools, skills, and policy.`})]}),(0,N.jsxs)(`span`,{children:[T,`s`]})]}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,disabled:C,children:C?`Generating validated draft…`:`Generate validated draft`})]})}),ne&&(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`Customize managed profile`,onClick:e=>{e.target===e.currentTarget&&re(``)},children:(0,N.jsxs)(`form`,{className:`modal`,onSubmit:se,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`EDITABLE COPY`}),(0,N.jsxs)(`h2`,{children:[`Customize @`,i]})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>re(``),children:`×`})]}),(0,N.jsx)(`p`,{children:`The managed original stays intact. This project copy starts with the same OAP policy and can be narrowed independently.`}),(0,N.jsxs)(`label`,{children:[`New profile name`,(0,N.jsx)(`input`,{required:!0,pattern:`[a-z0-9][a-z0-9._-]*`,value:ne,onChange:e=>re(e.target.value)})]}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:`Create editable copy`})]})})]})}var Pe=[`keyword`,`hybrid`,`semantic`];function Fe(e){let t=Number(e||0);if(!t)return`0 B`;let n=[`B`,`KB`,`MB`],r=Math.min(n.length-1,Math.floor(Math.log(t)/Math.log(1024)));return`${(t/1024**r).toFixed(+!!r)} ${n[r]}`}function Ie({setError:e,notify:t}){let[n,r]=(0,l.useState)(null),[i,a]=(0,l.useState)(``),[o,s]=(0,l.useState)(`keyword`),[c,u]=(0,l.useState)(null),[d,f]=(0,l.useState)(!1),[p,m]=(0,l.useState)(null),[h,g]=(0,l.useState)(!1),[y,b]=(0,l.useState)({id:``,type:`fact`,body:``,links:``}),x=(0,l.useCallback)(async()=>{try{r(await _(`/api/memory/overview`))}catch(t){e(t.message)}},[e]);(0,l.useEffect)(()=>{x()},[x]);async function S(){if(!i.trim()){u(null);return}f(!0);try{let e=await _(`/api/memory/search?q=${encodeURIComponent(i)}&mode=${o}`);u(e.results||[])}catch(t){e(t.message)}finally{f(!1)}}async function C(t){try{let n=await _(`/api/memory/node?id=${encodeURIComponent(t)}`);if(!n.ok){e(n.error||`That memory node could not be read.`);return}m(n)}catch(t){e(t.message)}}function w(){b({id:``,type:`fact`,body:``,links:``}),g(!0)}function T(){p&&(b({id:p.id||``,type:p.type||`fact`,body:p.body||``,links:(p.links||[]).join(`, `)}),g(!0))}async function E(n){n.preventDefault();let r=!!(p&&p.id===y.id);try{let e=await v(`/api/memory/nodes`,{action:r?`update`:`create`,id:y.id,type:y.type,body:y.body,links:y.links.split(`,`).map(e=>e.trim()).filter(Boolean)});g(!1),m(e),await x(),t(`Memory ${r?`updated`:`created`}.`)}catch(t){e(t.message)}}async function ee(){if(!(!p?.id||!window.confirm(`Delete memory “${p.id}”? This cannot be undone.`)))try{await v(`/api/memory/nodes`,{action:`delete`,id:p.id}),m(null),await x(),t(`Memory deleted.`)}catch(t){e(t.message)}}let D=c??n?.nodes??[],O=n?.stats||{};return(0,N.jsxs)(`div`,{className:`page-view view active memory-page`,children:[(0,N.jsxs)(`div`,{className:`page-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`span`,{className:`eyebrow`,children:`KNOWLEDGE`}),(0,N.jsx)(`h1`,{children:`Memory`}),(0,N.jsx)(`p`,{children:`Create, review, and maintain the durable facts that shape agent replies.`})]}),(0,N.jsxs)(`div`,{className:`header-actions`,children:[(0,N.jsx)(`button`,{className:`secondary-button`,type:`button`,onClick:()=>void x(),children:`Refresh`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,onClick:w,children:`+ New memory`})]})]}),n?.error&&(0,N.jsx)(`div`,{className:`graph-error`,role:`alert`,children:n.error}),n&&n.available===!1?(0,N.jsx)(`p`,{className:`memory-empty`,children:n.note}):(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`div`,{className:`memory-stats`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`span`,{children:`NODES`}),(0,N.jsx)(`strong`,{children:String(O.nodes??`—`)})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`span`,{children:`LINKS`}),(0,N.jsx)(`strong`,{children:String(O.edges_total??`—`)})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`span`,{children:`ON DISK`}),(0,N.jsx)(`strong`,{children:Fe(O.disk_bytes)})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`span`,{children:`DUPLICATE GROUPS`}),(0,N.jsx)(`strong`,{children:String(n?.quality?.duplicate_groups??`—`)})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`span`,{children:`SUPPRESSED`}),(0,N.jsx)(`strong`,{children:String(n?.quality?.suppressed?.length??0)})]})]}),(0,N.jsxs)(`form`,{className:`memory-search`,onSubmit:e=>{e.preventDefault(),S()},children:[(0,N.jsx)(`label`,{className:`sr-only`,htmlFor:`memoryQuery`,children:`Search memory`}),(0,N.jsx)(`input`,{id:`memoryQuery`,value:i,placeholder:`Search what the agent remembers…`,onChange:e=>a(e.target.value)}),(0,N.jsx)(`label`,{className:`sr-only`,htmlFor:`memoryMode`,children:`Search mode`}),(0,N.jsx)(`select`,{id:`memoryMode`,value:o,onChange:e=>s(e.target.value),children:Pe.map(e=>(0,N.jsx)(`option`,{value:e,children:e},e))}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,disabled:d,children:d?`Searching…`:`Search`}),c&&(0,N.jsx)(`button`,{className:`secondary-button`,type:`button`,onClick:()=>{u(null),a(``)},children:`Clear`})]}),(0,N.jsx)(`p`,{className:`memory-count`,children:c?`${c.length} match${c.length===1?``:`es`} for “${i}”`:n?.note||`${D.length} of ${n?.total??D.length} nodes`}),(0,N.jsxs)(`div`,{className:`memory-layout`,children:[(0,N.jsxs)(`div`,{className:`memory-list`,children:[D.length===0&&(0,N.jsx)(`p`,{className:`memory-empty`,children:`Nothing to show.`}),D.map(e=>(0,N.jsxs)(`button`,{type:`button`,className:`memory-row ${p?.id===e.id?`active`:``}`,onClick:()=>void C(e.id),children:[(0,N.jsx)(`strong`,{children:e.id}),(0,N.jsx)(`span`,{className:`tag`,children:e.type||`note`}),(0,N.jsx)(`small`,{children:e.excerpt})]},e.id))]}),(0,N.jsx)(`div`,{className:`memory-detail`,children:p?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`span`,{className:`eyebrow`,children:p.type||`note`}),(0,N.jsx)(`h3`,{children:p.id}),(0,N.jsx)(`p`,{className:`memory-path`,children:p.path}),(0,N.jsx)(`pre`,{children:p.body}),(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:T,children:`Edit`}),(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:()=>void ee(),children:`Delete`})]}),p.truncated&&(0,N.jsx)(`p`,{className:`memory-empty`,children:`This note is longer than the browser shows. Read the file for the rest.`}),(0,N.jsxs)(`div`,{className:`memory-links`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`span`,{className:`eyebrow`,children:`LINKS OUT`}),p.links?.length?p.links.map(e=>(0,N.jsx)(`button`,{type:`button`,className:`link-chip`,onClick:()=>void C(e),children:e},e)):(0,N.jsx)(`p`,{className:`memory-empty`,children:`None.`})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`span`,{className:`eyebrow`,children:`LINKS IN`}),p.backlinks?.length?p.backlinks.map(e=>(0,N.jsx)(`button`,{type:`button`,className:`link-chip`,onClick:()=>void C(e),children:e},e)):(0,N.jsx)(`p`,{className:`memory-empty`,children:`Nothing refers to this note.`})]})]})]}):(0,N.jsx)(`p`,{className:`memory-empty`,children:`Choose a note to read it and see what links to it.`})})]})]}),h&&(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`Memory editor`,onClick:e=>{e.target===e.currentTarget&&g(!1)},children:(0,N.jsxs)(`form`,{className:`modal`,onSubmit:E,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`DURABLE MEMORY`}),(0,N.jsx)(`h2`,{children:p?.id===y.id&&y.id?`Edit memory`:`Create memory`})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>g(!1),children:`×`})]}),(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`label`,{htmlFor:`memoryId`,children:`ID`}),(0,N.jsx)(`input`,{id:`memoryId`,required:!0,disabled:p?.id===y.id&&!!y.id,value:y.id,onChange:e=>b({...y,id:e.target.value})})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`label`,{htmlFor:`memoryType`,children:`Type`}),(0,N.jsxs)(`select`,{id:`memoryType`,value:y.type,onChange:e=>b({...y,type:e.target.value}),children:[(0,N.jsx)(`option`,{value:`fact`,children:`Fact`}),(0,N.jsx)(`option`,{value:`preference`,children:`Preference`}),(0,N.jsx)(`option`,{value:`project`,children:`Project`}),(0,N.jsx)(`option`,{value:`decision`,children:`Decision`}),(0,N.jsx)(`option`,{value:`bookmark`,children:`Bookmark`})]})]})]}),(0,N.jsx)(`label`,{htmlFor:`memoryBody`,children:`Body`}),(0,N.jsx)(`textarea`,{id:`memoryBody`,rows:9,required:!0,value:y.body,onChange:e=>b({...y,body:e.target.value})}),(0,N.jsxs)(`label`,{htmlFor:`memoryLinks`,children:[`Links `,(0,N.jsx)(`small`,{children:`comma-separated memory IDs`})]}),(0,N.jsx)(`input`,{id:`memoryLinks`,value:y.links,onChange:e=>b({...y,links:e.target.value})}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:`Save memory`})]})})]})}function Le({fields:e,profiles:t,refresh:n,setError:r,notify:i}){let[a,o]=(0,l.useState)(``),[s,c]=(0,l.useState)([]),[u,d]=(0,l.useState)(``),[f,p]=(0,l.useState)(``),[m,h]=(0,l.useState)(``),[g,y]=(0,l.useState)(`keyring`),[b,x]=(0,l.useState)([]);(0,l.useEffect)(()=>{_(`/api/onboarding/providers`).then(e=>{c(e.providers||[]),d(e.default_provider||``),p(e.default_model||``),x(e.image_models||[])}).catch(()=>c([]))},[]);async function S(e){if(e.preventDefault(),u){o(`provider-setup`);try{await v(`/api/onboarding/configure`,{provider:u,model:f,credential:m,credential_storage:g}),h(``),await n(),i(`Provider configuration saved.`)}catch(e){r(e.message)}finally{o(``)}}}async function C(e,t){o(e.path);try{await v(`/api/settings`,{path:e.path,value:t}),await n(),i(`${e.label} saved.`)}catch(e){r(e.message)}finally{o(``)}}return(0,N.jsxs)(`div`,{className:`page`,children:[(0,N.jsx)(`div`,{className:`page-header`,children:(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`GUIDED CONFIGURATION`}),(0,N.jsx)(`h1`,{children:`Settings`}),(0,N.jsx)(`p`,{children:`Safe defaults only. Credentials stay in your environment or the OS keyring.`})]})}),(0,N.jsxs)(`form`,{className:`provider-setup-card`,onSubmit:S,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`PROVIDER ONBOARDING`}),(0,N.jsx)(`h2`,{children:`Configure model access`}),(0,N.jsx)(`p`,{children:`Choose the workspace default and optionally store its credential. Existing credentials are never displayed.`})]}),(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`label`,{children:[`Provider`,(0,N.jsxs)(`select`,{value:u,onChange:e=>{let t=e.target.value,n=s.find(e=>e.name===t);d(t),p(n?.default_model||``)},children:[(0,N.jsx)(`option`,{value:``,children:`Choose a provider…`}),s.map(e=>(0,N.jsxs)(`option`,{value:e.name,children:[e.display_name||e.name,e.configured?` · configured`:``]},e.name))]})]}),(0,N.jsxs)(`label`,{children:[`Default model`,(0,N.jsx)(`input`,{value:f,onChange:e=>p(e.target.value),placeholder:s.find(e=>e.name===u)?.default_model||`provider default`})]})]}),u&&s.find(e=>e.name===u)?.needs_key&&(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`label`,{children:[`API key `,(0,N.jsx)(`small`,{children:`(leave blank to keep the existing credential)`}),(0,N.jsx)(`input`,{type:`password`,autoComplete:`off`,value:m,onChange:e=>h(e.target.value),placeholder:s.find(e=>e.name===u)?.credential_ready?`Credential already configured`:`Paste provider API key`})]}),(0,N.jsxs)(`label`,{children:[`Credential storage`,(0,N.jsxs)(`select`,{value:g,onChange:e=>y(e.target.value),children:[(0,N.jsx)(`option`,{value:`keyring`,children:`OS keyring (recommended)`}),(0,N.jsx)(`option`,{value:`config`,children:`MagAgent config file`})]})]}),g===`config`&&(0,N.jsx)(`p`,{className:`credential-warning`,children:`The key will be written to MagAgent's user config. File permissions are tightened, but the OS keyring is safer when available.`})]}),(0,N.jsxs)(`div`,{className:`provider-setup-actions`,children:[(0,N.jsx)(`span`,{children:s.find(e=>e.name===u)?.credential_reason||`Select a provider to inspect readiness.`}),(0,N.jsx)(`button`,{className:`primary-button`,disabled:!u||a===`provider-setup`,children:a===`provider-setup`?`Saving…`:`Save provider`})]})]}),e.length?(0,N.jsx)(`div`,{className:`setting-list`,children:e.map(e=>(0,N.jsxs)(`article`,{className:`setting-card`,children:[(0,N.jsxs)(`label`,{htmlFor:`setting-${e.path}`,children:[(0,N.jsx)(`b`,{children:e.label}),e.description&&(0,N.jsx)(`small`,{children:e.description})]}),e.path===`models.image_maker`?(0,N.jsxs)(`select`,{id:`setting-${e.path}`,value:String(e.value??``),disabled:a===e.path,onChange:t=>void C(e,t.target.value),children:[e.value&&!b.some(t=>t.value===e.value)&&(0,N.jsxs)(`option`,{value:String(e.value),children:[String(e.value),` · current custom route`]}),b.some(e=>e.available)?(0,N.jsx)(`option`,{value:``,children:`Not configured`}):(0,N.jsx)(`option`,{value:``,children:`No image models detected — configure a supported provider`}),b.map(e=>(0,N.jsxs)(`option`,{value:e.value,disabled:!e.available,children:[e.label,e.available?``:` · provider unavailable`]},e.id))]}):e.path===`defaults.provider`?(0,N.jsxs)(`select`,{id:`setting-${e.path}`,value:String(e.value??``),disabled:a===e.path,onChange:t=>void C(e,t.target.value),children:[e.value&&!s.some(t=>t.name===e.value)&&(0,N.jsx)(`option`,{value:String(e.value),children:String(e.value)}),(0,N.jsx)(`option`,{value:``,children:`Choose a provider…`}),s.map(e=>(0,N.jsx)(`option`,{value:e.name,children:e.display_name||e.name},e.name))]}):e.path===`defaults.model`?(0,N.jsxs)(`select`,{id:`setting-${e.path}`,value:String(e.value??``),disabled:a===e.path,onChange:t=>void C(e,t.target.value),children:[e.value&&!s.some(t=>t.default_model===e.value)&&(0,N.jsx)(`option`,{value:String(e.value),children:String(e.value)}),(0,N.jsx)(`option`,{value:``,children:`Use provider default`}),Array.from(new Set(s.map(e=>e.default_model).filter(Boolean))).map(e=>(0,N.jsx)(`option`,{value:e,children:e},e))]}):e.path===`agent_profiles.default_profile`?(0,N.jsxs)(`select`,{id:`setting-${e.path}`,value:String(e.value??``),disabled:a===e.path,onChange:t=>void C(e,t.target.value),children:[(0,N.jsx)(`option`,{value:``,children:`No default profile`}),t.map(e=>(0,N.jsxs)(`option`,{value:e.name,children:[`@`,e.name]},e.name))]}):e.choices?.length?(0,N.jsx)(`select`,{id:`setting-${e.path}`,value:String(e.value??``),disabled:a===e.path,onChange:t=>void C(e,t.target.value),children:e.choices.map(e=>(0,N.jsx)(`option`,{value:e,children:e},e))}):e.type===`boolean`?(0,N.jsx)(`input`,{id:`setting-${e.path}`,type:`checkbox`,checked:!!e.value,disabled:a===e.path,onChange:t=>void C(e,t.target.checked)}):(0,N.jsx)(`input`,{id:`setting-${e.path}`,type:e.type===`integer`?`number`:`text`,defaultValue:String(e.value??``),disabled:a===e.path,onBlur:t=>{t.target.value!==String(e.value??``)&&C(e,e.type===`integer`?Number(t.target.value):t.target.value)}})]},e.path))}):(0,N.jsxs)(`div`,{className:`empty-panel`,children:[(0,N.jsx)(`h2`,{children:`Nothing editable here`}),(0,N.jsxs)(`p`,{children:[`Run `,(0,N.jsx)(`code`,{children:`magent configure`}),` for the full wizard.`]})]})]})}function Re({title:e,value:t}){let n=t==null?`—`:typeof t==`object`?JSON.stringify(t,null,2):String(t),r=n.includes(`
`)||n.length>60;return(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:e}),r?(0,N.jsx)(`pre`,{children:n}):(0,N.jsx)(`b`,{children:n})]})}function ze({setError:e}){let[t,n]=(0,l.useState)(null),[r,i]=(0,l.useState)(!0);async function a(){i(!0);try{let[e,t]=await Promise.all([_(`/api/cockpit`),_(`/api/state`)]);n({...t,...e})}catch(t){e(t.message)}finally{i(!1)}}(0,l.useEffect)(()=>{a()},[]);let o=Object.entries(t||{}).filter(([e])=>e!==`ok`&&e!==`error`);return(0,N.jsxs)(`div`,{className:`page`,children:[(0,N.jsxs)(`div`,{className:`page-header`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`LOCAL DIAGNOSTICS`}),(0,N.jsx)(`h1`,{children:`Operations`}),(0,N.jsx)(`p`,{children:`Workspace health, plans, patches, models, and recent activity.`})]}),(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>void a(),disabled:r,children:r?`Refreshing…`:`Refresh`})]}),o.length?(0,N.jsx)(`div`,{className:`ops-grid`,children:o.map(([e,t])=>(0,N.jsx)(Re,{title:e.replace(/_/g,` `),value:t},e))}):(0,N.jsx)(`div`,{className:`empty-panel`,children:(0,N.jsx)(`h2`,{children:r?`Loading…`:`Nothing to report`})})]})}function Be({selected:e,setSelected:t,activeConversation:n,setError:r,notify:i}){let[a,o]=(0,l.useState)([]),[s,c]=(0,l.useState)(``),[u,d]=(0,l.useState)(null),[f,p]=(0,l.useState)({}),[m,h]=(0,l.useState)(``),[g,y]=(0,l.useState)(`git status --short`),[b,x]=(0,l.useState)(``),[S,C]=(0,l.useState)(!1),[w,T]=(0,l.useState)(``),[E,ee]=(0,l.useState)(``);async function D(){try{let[e,t]=await Promise.all([_(`/api/workspace/files?q=${encodeURIComponent(s)}`),_(`/api/workspace/git`)]);o(e.files||[]),p(t)}catch(e){r(e.message)}}(0,l.useEffect)(()=>{D()},[]);let O=(0,l.useMemo)(()=>a.filter(e=>e.artifact),[a]);async function te(e){try{d(await _(`/api/workspace/file?path=${encodeURIComponent(e)}`))}catch(e){r(e.message)}}async function ne(e){try{let t=await _(`/api/workspace/diff?staged=${e}`);h(t.diff||`No changes in this view.`)}catch(e){r(e.message)}}async function re(a){if(a.size>5242880){r(`Uploads are limited to 5 MB.`);return}let o=await new Promise((e,t)=>{let n=new FileReader;n.onerror=()=>t(n.error),n.onload=()=>e(String(n.result).split(`,`,2)[1]||``),n.readAsDataURL(a)});try{let r=await v(`/api/workspace/upload`,{name:a.name,data:o,conversation_id:n||`shared`});t([...new Set([...e,r.file.path])].slice(0,20)),i(`${a.name} attached`),await D()}catch(e){r(e.message)}}async function k(){C(!0);try{let e=await v(`/api/workspace/terminal`,{command:g});x(`${e.stdout||``}${e.stderr||``}`||`Exited ${e.returncode}`)}catch(e){r(e.message)}finally{C(!1)}}async function A(e,t){if(!(e===`discard`&&!window.confirm(`Discard uncommitted changes in ${t}? This cannot be undone.`)))try{await v(`/api/workspace/git`,{action:e,path:t}),i(`${e} completed for ${t}`),await D()}catch(e){r(e.message)}}async function ie(){try{await v(`/api/workspace/worktrees`,{action:`create`,branch:w,directory:E,create_branch:!0}),i(`Worktree created for ${w}`),T(``),ee(``),await D()}catch(e){r(e.message)}}async function j(e){if(window.confirm(`Remove the Git worktree at ${e}? Uncommitted worktrees are refused by Git.`))try{await v(`/api/workspace/worktrees`,{action:`remove`,directory:e}),i(`Worktree removed`),await D()}catch(e){r(e.message)}}function M(e){let t=e.slice(3).trim();return t.includes(` -> `)&&t.split(` -> `).pop()||t}return(0,N.jsxs)(`section`,{className:`view active page-view`,children:[(0,N.jsxs)(`div`,{className:`page-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`PROJECT CONTEXT`}),(0,N.jsx)(`h1`,{children:`Workspace`}),(0,N.jsx)(`p`,{children:`Inspect files and artifacts, attach context, review Git, and run explicit local commands.`})]}),(0,N.jsxs)(`label`,{className:`upload-button`,children:[`Attach file`,(0,N.jsx)(`input`,{type:`file`,onChange:e=>e.target.files?.[0]&&void re(e.target.files[0])})]})]}),(0,N.jsxs)(`div`,{className:`workspace-tools`,children:[(0,N.jsxs)(`div`,{className:`detail-card workspace-browser`,children:[(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[(0,N.jsx)(`input`,{"aria-label":`Search workspace files`,value:s,onChange:e=>c(e.target.value),placeholder:`Filter files`}),(0,N.jsx)(`button`,{className:`secondary-button`,type:`button`,onClick:()=>void D(),children:`Search`})]}),(0,N.jsxs)(`p`,{className:`selection-note`,children:[e.length,`/20 files attached to the next message`]}),(0,N.jsx)(`div`,{className:`file-list`,children:a.map(n=>(0,N.jsxs)(`div`,{className:`file-row`,children:[(0,N.jsx)(`input`,{type:`checkbox`,"aria-label":`Attach ${n.path}`,checked:e.includes(n.path),onChange:()=>t(e.includes(n.path)?e.filter(e=>e!==n.path):[...e,n.path].slice(0,20))}),(0,N.jsx)(`button`,{type:`button`,onClick:()=>void te(n.path),children:n.path}),(0,N.jsxs)(`small`,{children:[Math.ceil(n.size/1024),` KB`]})]},n.path))})]}),(0,N.jsxs)(`article`,{className:`detail-card file-preview`,children:[(0,N.jsx)(`h2`,{children:u?.path||`Preview`}),!u&&(0,N.jsx)(`p`,{children:`Select a file to inspect it without leaving MagAgent.`}),u?.text&&(0,N.jsx)(`pre`,{children:u.content}),u?.data_url&&u.mime.startsWith(`image/`)&&(0,N.jsx)(`img`,{src:u.data_url,alt:u.path}),u?.data_url&&!u.mime.startsWith(`image/`)&&(0,N.jsxs)(`p`,{children:[`Binary preview ready (`,u.mime,`).`]})]})]}),(0,N.jsxs)(`div`,{className:`ops-grid`,children:[(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:`Git status`}),(0,N.jsx)(`div`,{className:`git-file-list`,children:(f.status||[]).filter(e=>!e.startsWith(`##`)).map(e=>{let t=M(e);return(0,N.jsxs)(`div`,{className:`git-file-row`,children:[(0,N.jsx)(`code`,{children:e}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`button`,{type:`button`,onClick:()=>void A(`stage`,t),children:`Stage`}),(0,N.jsx)(`button`,{type:`button`,onClick:()=>void A(`unstage`,t),children:`Unstage`}),(0,N.jsx)(`button`,{className:`danger-link`,type:`button`,onClick:()=>void A(`discard`,t),children:`Discard`})]})]},e)})}),!(f.status||[]).filter(e=>!e.startsWith(`##`)).length&&(0,N.jsx)(`p`,{children:`Clean or not a Git repository.`}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[(0,N.jsx)(`button`,{className:`secondary-button`,type:`button`,onClick:()=>void ne(!1),children:`Working diff`}),(0,N.jsx)(`button`,{className:`secondary-button`,type:`button`,onClick:()=>void ne(!0),children:`Staged diff`})]})]}),(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:`Branches & worktrees`}),(0,N.jsx)(`pre`,{children:(f.branches||[]).join(`
-`)}),(f.worktrees||[]).map(e=>(0,N.jsxs)(`div`,{className:`git-file-row`,children:[(0,N.jsxs)(`code`,{children:[e.worktree,` `,e.branch||``]}),e.worktree&&e.current!==`true`&&(0,N.jsx)(`div`,{children:(0,N.jsx)(`button`,{className:`danger-link`,type:`button`,onClick:()=>void j(e.worktree),children:`Remove`})})]},e.worktree)),(0,N.jsxs)(`div`,{className:`worktree-form`,children:[(0,N.jsx)(`input`,{"aria-label":`New worktree branch`,value:w,onChange:e=>T(e.target.value),placeholder:`feature/name`}),(0,N.jsx)(`input`,{"aria-label":`New worktree directory`,value:E,onChange:e=>ee(e.target.value),placeholder:`../project-feature`}),(0,N.jsx)(`button`,{className:`secondary-button`,type:`button`,disabled:!w||!E,onClick:()=>void ie(),children:`Create branch + worktree`})]})]}),(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:`Artifacts`}),(0,N.jsxs)(`p`,{children:[O.length,` previewable project files discovered.`]}),(0,N.jsx)(`div`,{className:`artifact-links`,children:O.slice(0,12).map(e=>(0,N.jsx)(`button`,{type:`button`,onClick:()=>void te(e.path),children:e.name},e.path))})]})]}),m&&(0,N.jsx)(`pre`,{className:`diff-view`,children:m}),(0,N.jsxs)(`article`,{className:`detail-card terminal-card`,children:[(0,N.jsx)(`h2`,{children:`Command console`}),(0,N.jsx)(`p`,{children:`Commands run as an argument list in the selected workspace, with no shell expansion and a 60-second limit.`}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[(0,N.jsx)(`input`,{"aria-label":`Workspace command`,value:g,onChange:e=>y(e.target.value),onKeyDown:e=>{e.key===`Enter`&&k()}}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,disabled:S,onClick:()=>void k(),children:S?`Running…`:`Run`})]}),b&&(0,N.jsx)(`pre`,{children:b})]})]})}function Ve({setError:e,notify:t}){let[n,r]=(0,l.useState)({}),[i,a]=(0,l.useState)(``),[o,s]=(0,l.useState)(60),[c,u]=(0,l.useState)(()=>localStorage.getItem(`magent-notifications`)===`on`),[d,f]=(0,l.useState)([]),p=(0,l.useRef)({});async function m(){try{let[e,t]=await Promise.all([_(`/api/run-center`),_(`/api/tasks`)]);if(r(e),f(t.tasks||[]),c&&`Notification`in window&&Notification.permission===`granted`){let n=[...e.chat_runs||[],...e.graph_runs||[],...t.tasks||[]];for(let e of n){let t=e,n=String(t.id||t.run_id||t.job_id||t.task_id||``),r=String(t.state||t.status||``),i=p.current[n];n&&i&&i!==r&&[`succeeded`,`failed`,`cancelled`,`completed`].includes(r)&&new Notification(`MagAgent ${r}`,{body:String(t.title||t.summary||n)}),n&&(p.current[n]=r)}}}catch(t){e(t.message)}}(0,l.useEffect)(()=>{m();let e=window.setInterval(()=>void m(),5e3);return()=>window.clearInterval(e)},[c]);async function h(){try{await v(`/api/schedules`,{path:i,interval_minutes:o}),a(``),t(`Graph schedule created`),await m()}catch(t){e(t.message)}}async function g(t,n){try{await v(`/api/schedules/action`,{id:t,action:n}),await m()}catch(t){e(t.message)}}async function y(n,r){let i=n.id||n.task_id||``;try{await v(`/api/tasks/action`,{task_id:i,action:r}),t(`Task ${r} requested`),await m()}catch(t){e(t.message)}}async function b(){if(!(`Notification`in window)){e(`This browser does not support desktop notifications.`);return}let t=!c&&await Notification.requestPermission()===`granted`;u(t),localStorage.setItem(`magent-notifications`,t?`on`:`off`)}return(0,N.jsxs)(`section`,{className:`view active page-view`,children:[(0,N.jsxs)(`div`,{className:`page-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`BACKGROUND WORK`}),(0,N.jsx)(`h1`,{children:`Run center`}),(0,N.jsx)(`p`,{children:`Monitor chat and graph execution, manage durable tasks, and schedule governed graphs.`})]}),(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>void b(),children:c?`Notifications on`:`Enable notifications`})]}),(0,N.jsxs)(`div`,{className:`ops-grid`,children:[(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:`Chat runs`}),(0,N.jsx)(`pre`,{children:JSON.stringify(n.chat_runs||[],null,2)})]}),(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:`Graph runs`}),(0,N.jsx)(`pre`,{children:JSON.stringify(n.graph_runs||[],null,2)})]})]}),(0,N.jsxs)(`article`,{className:`detail-card schedule-card`,children:[(0,N.jsx)(`h2`,{children:`Durable tasks`}),!d.length&&(0,N.jsx)(`p`,{children:`No durable execution tasks yet.`}),(0,N.jsx)(`div`,{className:`schedule-list`,children:d.map(e=>{let t=e.id||e.task_id||``,n=e.state||e.status||`unknown`;return(0,N.jsxs)(`div`,{className:`schedule-row`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`b`,{children:e.title||t}),(0,N.jsxs)(`small`,{children:[n,e.summary?` · ${e.summary}`:``]})]}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[n===`running`&&(0,N.jsx)(`button`,{type:`button`,onClick:()=>void y(e,`pause`),children:`Pause`}),n===`paused`&&(0,N.jsx)(`button`,{type:`button`,onClick:()=>void y(e,`resume`),children:`Resume`}),![`succeeded`,`cancelled`].includes(n)&&(0,N.jsx)(`button`,{type:`button`,onClick:()=>void y(e,`cancel`),children:`Cancel`}),[`failed`,`cancelled`].includes(n)&&(0,N.jsx)(`button`,{type:`button`,onClick:()=>void y(e,`retry`),children:`Retry`})]})]},t)})})]}),(0,N.jsxs)(`article`,{className:`detail-card schedule-card`,children:[(0,N.jsx)(`h2`,{children:`Schedule a graph`}),(0,N.jsx)(`p`,{children:`Schedules run only while this local UI server is active and still pass graph validation and human-gate checks.`}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[(0,N.jsx)(`input`,{"aria-label":`Graph path`,value:i,onChange:e=>a(e.target.value),placeholder:`workflows/review.agraph.yaml`}),(0,N.jsx)(`input`,{"aria-label":`Interval minutes`,type:`number`,min:1,value:o,onChange:e=>s(Number(e.target.value))}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,disabled:!i,onClick:()=>void h(),children:`Schedule`})]}),(0,N.jsx)(`div`,{className:`schedule-list`,children:(n.schedules||[]).map(e=>(0,N.jsxs)(`div`,{className:`schedule-row`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`b`,{children:e.path}),(0,N.jsxs)(`small`,{children:[`Every `,e.interval_minutes,` min · `,e.status,e.last_error?` · ${e.last_error}`:``]})]}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[(0,N.jsx)(`button`,{type:`button`,onClick:()=>void g(e.id,`run`),children:`Run now`}),(0,N.jsx)(`button`,{type:`button`,onClick:()=>void g(e.id,e.status===`active`?`pause`:`resume`),children:e.status===`active`?`Pause`:`Resume`}),(0,N.jsx)(`button`,{type:`button`,onClick:()=>void g(e.id,`delete`),children:`Delete`})]})]},e.id))})]})]})}var He=e=>({kind:e,name:``,source:``,description:``,body:``,transport:`stdio`,protocol_mode:`auto`,command:``,args:``,cwd:``,env:``,url:``,headers:``,credential_env:``,timeout:30,allow_deprecated_transport:!1,enabled:!0});function Ue(e,t=!1){return Object.entries(e||{}).filter(([e])=>!(t&&e.toLowerCase()===`authorization`)).map(([e,t])=>`${e}=${t}`).join(`
+`)}),(f.worktrees||[]).map(e=>(0,N.jsxs)(`div`,{className:`git-file-row`,children:[(0,N.jsxs)(`code`,{children:[e.worktree,` `,e.branch||``]}),e.worktree&&e.current!==`true`&&(0,N.jsx)(`div`,{children:(0,N.jsx)(`button`,{className:`danger-link`,type:`button`,onClick:()=>void j(e.worktree),children:`Remove`})})]},e.worktree)),(0,N.jsxs)(`div`,{className:`worktree-form`,children:[(0,N.jsx)(`input`,{"aria-label":`New worktree branch`,value:w,onChange:e=>T(e.target.value),placeholder:`feature/name`}),(0,N.jsx)(`input`,{"aria-label":`New worktree directory`,value:E,onChange:e=>ee(e.target.value),placeholder:`../project-feature`}),(0,N.jsx)(`button`,{className:`secondary-button`,type:`button`,disabled:!w||!E,onClick:()=>void ie(),children:`Create branch + worktree`})]})]}),(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:`Artifacts`}),(0,N.jsxs)(`p`,{children:[O.length,` previewable project files discovered.`]}),(0,N.jsx)(`div`,{className:`artifact-links`,children:O.slice(0,12).map(e=>(0,N.jsx)(`button`,{type:`button`,onClick:()=>void te(e.path),children:e.name},e.path))})]})]}),m&&(0,N.jsx)(`pre`,{className:`diff-view`,children:m}),(0,N.jsxs)(`article`,{className:`detail-card terminal-card`,children:[(0,N.jsx)(`h2`,{children:`Command console`}),(0,N.jsx)(`p`,{children:`Commands run as an argument list in the selected workspace, with no shell expansion and a 60-second limit.`}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[(0,N.jsx)(`input`,{"aria-label":`Workspace command`,value:g,onChange:e=>y(e.target.value),onKeyDown:e=>{e.key===`Enter`&&k()}}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,disabled:S,onClick:()=>void k(),children:S?`Running…`:`Run`})]}),b&&(0,N.jsx)(`pre`,{children:b})]})]})}function Ve({setError:e,notify:t}){let[n,r]=(0,l.useState)({}),[i,a]=(0,l.useState)(``),[o,s]=(0,l.useState)(60),[c,u]=(0,l.useState)(()=>localStorage.getItem(`magent-notifications`)===`on`),[d,f]=(0,l.useState)([]),p=(0,l.useRef)({});async function m(){try{let[e,t]=await Promise.all([_(`/api/run-center`),_(`/api/tasks`)]);if(r(e),f(t.tasks||[]),c&&`Notification`in window&&Notification.permission===`granted`){let n=[...e.chat_runs||[],...e.graph_runs||[],...t.tasks||[]];for(let e of n){let t=e,n=String(t.id||t.run_id||t.job_id||t.task_id||``),r=String(t.state||t.status||``),i=p.current[n];n&&i&&i!==r&&[`succeeded`,`failed`,`cancelled`,`completed`].includes(r)&&new Notification(`MagAgent ${r}`,{body:String(t.title||t.summary||n)}),n&&(p.current[n]=r)}}}catch(t){e(t.message)}}(0,l.useEffect)(()=>{m();let e=window.setInterval(()=>void m(),5e3);return()=>window.clearInterval(e)},[c]);async function h(){try{await v(`/api/schedules`,{path:i,interval_minutes:o}),a(``),t(`Graph schedule created`),await m()}catch(t){e(t.message)}}async function g(t,n){try{await v(`/api/schedules/action`,{id:t,action:n}),await m()}catch(t){e(t.message)}}async function y(n,r){let i=n.id||n.task_id||``;try{await v(`/api/tasks/action`,{task_id:i,action:r}),t(`Task ${r} requested`),await m()}catch(t){e(t.message)}}async function b(){if(!(`Notification`in window)){e(`This browser does not support desktop notifications.`);return}let t=!c&&await Notification.requestPermission()===`granted`;u(t),localStorage.setItem(`magent-notifications`,t?`on`:`off`)}return(0,N.jsxs)(`section`,{className:`view active page-view`,children:[(0,N.jsxs)(`div`,{className:`page-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`BACKGROUND WORK`}),(0,N.jsx)(`h1`,{children:`Run center`}),(0,N.jsx)(`p`,{children:`Monitor chat and graph execution, manage durable tasks, and schedule governed graphs.`})]}),(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>void b(),children:c?`Notifications on`:`Enable notifications`})]}),(0,N.jsxs)(`div`,{className:`ops-grid`,children:[(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:`Chat runs`}),(0,N.jsx)(`pre`,{children:JSON.stringify(n.chat_runs||[],null,2)})]}),(0,N.jsxs)(`article`,{className:`ops-card`,children:[(0,N.jsx)(`h3`,{children:`Graph runs`}),(0,N.jsx)(`pre`,{children:JSON.stringify(n.graph_runs||[],null,2)})]})]}),(0,N.jsxs)(`article`,{className:`detail-card schedule-card`,children:[(0,N.jsx)(`h2`,{children:`Durable tasks`}),!d.length&&(0,N.jsx)(`p`,{children:`No durable execution tasks yet.`}),(0,N.jsx)(`div`,{className:`schedule-list`,children:d.map(e=>{let t=e.id||e.task_id||``,n=e.state||e.status||`unknown`;return(0,N.jsxs)(`div`,{className:`schedule-row`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`b`,{children:e.title||t}),(0,N.jsxs)(`small`,{children:[n,e.summary?` · ${e.summary}`:``]}),(0,N.jsxs)(`details`,{children:[(0,N.jsx)(`summary`,{children:`Review recovery evidence`}),(0,N.jsx)(`p`,{children:`Inspect completed effects before retrying. Changing task status alone does not restart a stopped harness.`}),(0,N.jsx)(`pre`,{children:JSON.stringify({files_changed:e.files_changed||[],checkpoints:e.checkpoints||[],final_audit:e.final_audit||{},metadata:e.metadata||{}},null,2)})]})]}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[n===`running`&&(0,N.jsx)(`button`,{type:`button`,onClick:()=>void y(e,`pause`),children:`Pause`}),[`paused`,`waiting`].includes(n)&&(0,N.jsx)(`button`,{type:`button`,onClick:()=>void y(e,`resume`),children:`Resume`}),![`succeeded`,`cancelled`].includes(n)&&(0,N.jsx)(`button`,{type:`button`,onClick:()=>void y(e,`cancel`),children:`Cancel`}),[`failed`,`cancelled`].includes(n)&&(0,N.jsx)(`button`,{type:`button`,onClick:()=>void y(e,`retry`),children:`Retry`})]})]},t)})})]}),(0,N.jsxs)(`article`,{className:`detail-card schedule-card`,children:[(0,N.jsx)(`h2`,{children:`Schedule a graph`}),(0,N.jsx)(`p`,{children:`Schedules run only while this local UI server is active and still pass graph validation and human-gate checks.`}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[(0,N.jsx)(`input`,{"aria-label":`Graph path`,value:i,onChange:e=>a(e.target.value),placeholder:`workflows/review.agraph.yaml`}),(0,N.jsx)(`input`,{"aria-label":`Interval minutes`,type:`number`,min:1,value:o,onChange:e=>s(Number(e.target.value))}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,disabled:!i,onClick:()=>void h(),children:`Schedule`})]}),(0,N.jsx)(`div`,{className:`schedule-list`,children:(n.schedules||[]).map(e=>(0,N.jsxs)(`div`,{className:`schedule-row`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`b`,{children:e.path}),(0,N.jsxs)(`small`,{children:[`Every `,e.interval_minutes,` min · `,e.status,e.last_error?` · ${e.last_error}`:``]})]}),(0,N.jsxs)(`div`,{className:`toolbar-row`,children:[(0,N.jsx)(`button`,{type:`button`,onClick:()=>void g(e.id,`run`),children:`Run now`}),(0,N.jsx)(`button`,{type:`button`,onClick:()=>void g(e.id,e.status===`active`?`pause`:`resume`),children:e.status===`active`?`Pause`:`Resume`}),(0,N.jsx)(`button`,{type:`button`,onClick:()=>void g(e.id,`delete`),children:`Delete`})]})]},e.id))})]})]})}var He=e=>({kind:e,name:``,source:``,description:``,body:``,transport:`stdio`,protocol_mode:`auto`,command:``,args:``,cwd:``,env:``,url:``,headers:``,credential_env:``,timeout:30,allow_deprecated_transport:!1,enabled:!0});function Ue(e,t=!1){return Object.entries(e||{}).filter(([e])=>!(t&&e.toLowerCase()===`authorization`)).map(([e,t])=>`${e}=${t}`).join(`
`)}function We(e,t){let n={};for(let r of e.split(`
`).map(e=>e.trim()).filter(Boolean)){let e=r.indexOf(`=`);if(e<1||!r.slice(e+1).trim())throw Error(`${t} must use NAME=value, one entry per line.`);n[r.slice(0,e).trim()]=r.slice(e+1).trim()}return n}function Ge(e){let t=(e.headers?.Authorization||e.headers?.authorization||``).match(/^Bearer \$\{([^}]+)\}$/i)?.[1]||``;return{...He(`mcp`),original:e.name,name:e.name,enabled:e.enabled,transport:e.transport||(e.url?`streamable-http`:`stdio`),protocol_mode:e.protocol_mode||`auto`,command:e.command||``,args:(e.args||[]).join(`
`),cwd:e.cwd||``,env:Ue(e.env),url:e.url||``,headers:Ue(e.headers,!!t),credential_env:t,timeout:e.timeout||30,allow_deprecated_transport:!!e.allow_deprecated_transport}}function Ke(e){let t=We(e.headers,`Headers`);if(e.credential_env.trim()){let n=e.credential_env.trim();if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(n))throw Error(`Credential environment variable must be a valid environment name.`);t.Authorization=`Bearer \${${n}}`}return{kind:`mcp`,action:`save`,name:e.name,transport:e.transport,protocol_mode:e.protocol_mode,command:e.command,args:e.args.split(`
-`).map(e=>e.trim()).filter(Boolean),cwd:e.cwd,env:We(e.env,`Environment variables`),url:e.url,headers:t,timeout:e.timeout,allow_deprecated_transport:e.allow_deprecated_transport,enabled:e.enabled}}function qe({setError:e,notify:t}){let[n,r]=(0,l.useState)({}),[i,a]=(0,l.useState)(null),[o,s]=(0,l.useState)(!1),[c,u]=(0,l.useState)(null),[d,f]=(0,l.useState)(``),[p,m]=(0,l.useState)([]),[h,g]=(0,l.useState)(``);async function y(){try{let[e,t]=await Promise.all([_(`/api/extensions`),_(`/api/onboarding/providers`)]);r(e),m(t.image_models||[])}catch(t){e(t.message)}}(0,l.useEffect)(()=>{y()},[]);async function b(n,r){try{await v(`/api/extensions/plugins`,{name:n,enabled:r}),t(`${n} ${r?`enabled`:`disabled`}`),await y()}catch(t){e(t.message)}}async function x(n){if(n.preventDefault(),i)try{await v(`/api/extensions/manage`,i.kind===`mcp`?Ke(i):{kind:i.kind,action:i.kind===`plugin`?`install`:`save`,name:i.name,source:i.source,description:i.description,body:i.body}),t(`${i.kind} saved.`),a(null),await y()}catch(t){e(t.message)}}async function S(){if(!(!i||i.kind!==`mcp`)){s(!0);try{t((await v(`/api/extensions/mcp/test`,Ke(i))).message||`MCP connection succeeded.`)}catch(t){e(t.message)}finally{s(!1)}}}async function C(n,r){if(window.confirm(`Delete ${n} “${r}”?`))try{await v(`/api/extensions/manage`,{kind:n,action:`delete`,name:r}),t(`${n} deleted.`),await y()}catch(t){e(t.message)}}async function w(n){if(n.preventDefault(),c)try{await v(`/api/settings`,{path:c.id===`image`?`models.image_maker`:`tools.browser_enabled`,value:c.id===`image`?(d===`__custom__`?h:d).trim():!c.enabled,scope:`global`}),t(`${c.label} updated.`),u(null),await y()}catch(t){e(t.message)}}return(0,N.jsxs)(`section`,{className:`view active page-view extensions-page`,children:[(0,N.jsx)(`div`,{className:`page-head`,children:(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`TOOLS & INTEGRATIONS`}),(0,N.jsx)(`h1`,{children:`Extensions`}),(0,N.jsx)(`p`,{children:`Install local plugins and manage project skills and MCP connections. Credentials are referenced by environment variable and are never revealed here.`})]})}),(0,N.jsx)(`div`,{className:`card-grid`,children:(n.capabilities||[]).map(e=>(0,N.jsxs)(`article`,{className:`detail-card`,children:[(0,N.jsx)(`h3`,{children:e.label}),(0,N.jsx)(`p`,{children:e.description}),(0,N.jsx)(`span`,{className:`tag`,children:e.enabled?`Ready`:`Not configured`}),typeof e.configured==`string`&&e.configured&&(0,N.jsx)(`p`,{className:`mono`,children:e.configured}),[`image`,`browser`].includes(e.id)&&(0,N.jsx)(`div`,{className:`detail-actions`,children:(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>{u(e);let t=typeof e.configured==`string`?e.configured:``,n=p.some(e=>e.value===t);f(t&&!n?`__custom__`:t),g(t&&!n?t:``)},children:e.id===`browser`?e.enabled?`Disable`:`Enable`:e.enabled?`Edit configuration`:`Configure`})})]},e.id))}),(0,N.jsxs)(`div`,{className:`section-heading`,children:[(0,N.jsx)(`h2`,{children:`Plugins`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,onClick:()=>a(He(`plugin`)),children:`+ Install plugin`})]}),(0,N.jsxs)(`div`,{className:`card-grid`,children:[(n.plugins||[]).map(e=>(0,N.jsxs)(`article`,{className:`detail-card`,children:[(0,N.jsx)(`h3`,{children:e.name}),(0,N.jsxs)(`p`,{children:[`Version `,e.version||`unknown`,` · integrity `,e.integrity||`unrecorded`]}),(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`secondary-button`,disabled:e.valid===!1&&!e.enabled,type:`button`,onClick:()=>void b(e.name,!e.enabled),children:e.enabled?`Disable`:`Enable`}),(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:()=>void C(`plugin`,e.name),children:`Uninstall`})]})]},e.name)),!(n.plugins||[]).length&&(0,N.jsx)(`p`,{children:`No plugins installed.`})]}),(0,N.jsxs)(`div`,{className:`section-heading`,children:[(0,N.jsx)(`h2`,{children:`Project skills`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,onClick:()=>a(He(`skill`)),children:`+ New skill`})]}),(0,N.jsx)(`div`,{className:`card-grid`,children:(n.skills||[]).map(e=>(0,N.jsxs)(`article`,{className:`detail-card`,children:[(0,N.jsx)(`h3`,{children:e.name}),(0,N.jsx)(`p`,{children:e.description}),(0,N.jsxs)(`span`,{className:`tag`,children:[`v`,e.version]}),e.editable&&(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>a({...He(`skill`),original:e.name,name:e.name,description:e.description||``,body:String(e.body||``).replace(/^---[\s\S]*?---\s*/,``)}),children:`Edit`}),(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:()=>void C(`skill`,e.name),children:`Delete`})]})]},`${e.name}-${e.version}`))}),(0,N.jsxs)(`div`,{className:`section-heading`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`h2`,{children:`MCP servers`}),(0,N.jsx)(`p`,{className:`context-note`,children:`Auto-negotiation supports modern MCP and legacy-compatible servers. Transport and protocol version are separate choices.`})]}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,onClick:()=>a(He(`mcp`)),children:`+ New server`})]}),(0,N.jsx)(`div`,{className:`card-grid`,children:(n.mcp_servers||[]).map(e=>(0,N.jsxs)(`article`,{className:`detail-card`,children:[(0,N.jsx)(`h3`,{children:e.name}),(0,N.jsx)(`span`,{className:`tag`,children:e.enabled?`Enabled`:`Disabled`}),(0,N.jsx)(`span`,{className:`tag`,children:e.transport||`stdio`}),(0,N.jsxs)(`span`,{className:`tag`,children:[`protocol `,e.protocol_mode||`auto`]}),(0,N.jsx)(`p`,{children:e.command||e.url||`No launch target`}),(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>a(Ge(e)),children:`Edit`}),(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:()=>void C(`mcp`,e.name),children:`Delete`})]})]},e.name))}),i&&(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`${i.kind} editor`,onClick:e=>{e.target===e.currentTarget&&a(null)},children:(0,N.jsxs)(`form`,{className:`modal`,onSubmit:x,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:i.kind.toUpperCase()}),(0,N.jsxs)(`h2`,{children:[i.original?`Edit`:`Create or install`,` `,i.kind]})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>a(null),children:`×`})]}),(0,N.jsxs)(`label`,{children:[`Name`,(0,N.jsx)(`input`,{required:!0,value:i.name,disabled:!!i.original,onChange:e=>a({...i,name:e.target.value})})]}),i.kind===`plugin`&&(0,N.jsxs)(`label`,{children:[`Local source folder`,(0,N.jsx)(`input`,{required:!0,placeholder:`/path/to/plugin`,value:i.source,onChange:e=>a({...i,source:e.target.value})})]}),i.kind===`skill`&&(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`label`,{children:[`Description`,(0,N.jsx)(`input`,{required:!0,value:i.description,onChange:e=>a({...i,description:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Instructions`,(0,N.jsx)(`textarea`,{required:!0,rows:10,value:i.body,onChange:e=>a({...i,body:e.target.value})})]})]}),i.kind===`mcp`&&(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`label`,{children:[`Transport`,(0,N.jsxs)(`select`,{value:i.transport,onChange:e=>a({...i,transport:e.target.value}),children:[(0,N.jsx)(`option`,{value:`stdio`,children:`Local process (stdio)`}),(0,N.jsx)(`option`,{value:`streamable-http`,children:`Remote Streamable HTTP`}),(0,N.jsx)(`option`,{value:`legacy-sse`,children:`Legacy SSE (deprecated)`})]})]}),(0,N.jsxs)(`label`,{children:[`Protocol compatibility`,(0,N.jsxs)(`select`,{value:i.protocol_mode,onChange:e=>a({...i,protocol_mode:e.target.value}),children:[(0,N.jsx)(`option`,{value:`auto`,children:`Auto-negotiate (recommended)`}),(0,N.jsx)(`option`,{value:`modern`,children:`Modern MCP only`}),(0,N.jsx)(`option`,{value:`legacy`,children:`Legacy MCP only`})]})]})]}),i.transport===`stdio`?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`label`,{children:[`Command`,(0,N.jsx)(`input`,{required:!0,placeholder:`npx`,value:i.command,onChange:e=>a({...i,command:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Arguments `,(0,N.jsx)(`small`,{children:`one per line`}),(0,N.jsx)(`textarea`,{rows:4,value:i.args,onChange:e=>a({...i,args:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Working directory `,(0,N.jsx)(`small`,{children:`optional`}),(0,N.jsx)(`input`,{placeholder:`/path/to/server`,value:i.cwd,onChange:e=>a({...i,cwd:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Environment mapping `,(0,N.jsxs)(`small`,{children:[`NAME=value, one per line; use $`,"${ENV_VAR}",` for credentials`]}),(0,N.jsx)(`textarea`,{rows:4,placeholder:"API_TOKEN=${API_TOKEN}",value:i.env,onChange:e=>a({...i,env:e.target.value})})]})]}):(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`label`,{children:[`Server URL`,(0,N.jsx)(`input`,{required:!0,type:`url`,placeholder:`https://example.com/mcp`,value:i.url,onChange:e=>a({...i,url:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Bearer credential environment variable `,(0,N.jsx)(`small`,{children:`optional`}),(0,N.jsx)(`input`,{placeholder:`MCP_ACCESS_TOKEN`,value:i.credential_env,onChange:e=>a({...i,credential_env:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Additional headers `,(0,N.jsx)(`small`,{children:`NAME=value, one per line; environment references supported`}),(0,N.jsx)(`textarea`,{rows:4,placeholder:"X-Tenant=${MCP_TENANT}",value:i.headers,onChange:e=>a({...i,headers:e.target.value})})]})]}),(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`label`,{children:[`Connection timeout (seconds)`,(0,N.jsx)(`input`,{type:`number`,min:1,max:300,value:i.timeout,onChange:e=>a({...i,timeout:Number(e.target.value)})})]}),(0,N.jsxs)(`label`,{className:`checkbox-row`,children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:i.enabled,onChange:e=>a({...i,enabled:e.target.checked})}),` Enable after saving`]})]}),i.transport===`legacy-sse`&&(0,N.jsxs)(`label`,{className:`checkbox-row warning-choice`,children:[(0,N.jsx)(`input`,{type:`checkbox`,required:!0,checked:i.allow_deprecated_transport,onChange:e=>a({...i,allow_deprecated_transport:e.target.checked})}),` I understand legacy SSE is deprecated and explicitly allow it.`]}),(0,N.jsx)(`p`,{className:`context-note`,children:`“Modern” and “legacy” select protocol eras; the exact server version is negotiated during connection. Use Auto unless the server documents a fixed era.`}),(0,N.jsxs)(`div`,{className:`mcp-test-row`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,disabled:o,onClick:()=>void S(),children:o?`Testing…`:`Test connection`}),(0,N.jsx)(`span`,{children:`Testing may start the local command or contact the remote URL, but does not save it.`})]})]}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:`Save`})]})}),c&&(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`Configure ${c.label}`,onClick:e=>{e.target===e.currentTarget&&u(null)},children:(0,N.jsxs)(`form`,{className:`modal`,onSubmit:w,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`CAPABILITY`}),(0,N.jsx)(`h2`,{children:c.id===`browser`?`${c.enabled?`Disable`:`Enable`} browser automation`:`Configure image generation`})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>u(null),children:`×`})]}),c.id===`image`?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`p`,{children:`Select an image model whose provider is available on this machine. Configure missing provider access in Settings first.`}),(0,N.jsxs)(`label`,{children:[`Image model`,(0,N.jsxs)(`select`,{value:d,onChange:e=>f(e.target.value),children:[(0,N.jsx)(`option`,{value:``,children:`Not configured`}),p.filter(e=>e.id!==`custom`).map(e=>(0,N.jsxs)(`option`,{value:e.value,disabled:!e.available,children:[e.label,e.available?``:` · configure provider first`]},e.id)),(0,N.jsx)(`option`,{value:`__custom__`,children:`Custom provider/model…`})]})]}),d===`__custom__`&&(0,N.jsxs)(`label`,{children:[`Custom route`,(0,N.jsx)(`input`,{required:!0,placeholder:`provider/model`,value:h,onChange:e=>g(e.target.value)})]}),(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>{f(``),g(``)},children:`Clear`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:`Save configuration`})]})]}):(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`p`,{children:`Profile and permission ceilings still apply when browser automation is enabled.`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:c.enabled?`Disable`:`Enable`})]})]})})]})}function Je({rows:e,onClose:t}){return(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`Keyboard shortcuts`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,N.jsxs)(`div`,{className:`modal shortcuts-sheet`,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`KEYBOARD`}),(0,N.jsx)(`h2`,{children:`Shortcuts`})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:t,"aria-label":`Close`,children:`×`})]}),(0,N.jsx)(`ul`,{children:e.map(e=>(0,N.jsxs)(`li`,{children:[(0,N.jsx)(`span`,{children:e.describe}),(0,N.jsx)(`kbd`,{children:e.keys})]},`${e.describe}-${e.keys}`))})]})})}function Ye({setError:e,notify:t}){let[n,r]=(0,l.useState)([]),[i,a]=(0,l.useState)(!1),o=(0,l.useCallback)(async()=>{try{let e=await _(`/api/approvals/snapshot`);r(e.snapshot?.pending??[])}catch{}},[]);(0,l.useEffect)(()=>{o();let e=window.setInterval(()=>void o(),800);return()=>window.clearInterval(e)},[o]);let s=(0,l.useCallback)(async(n,r)=>{a(!0);try{let e=await v(`/api/approvals/decide`,{request_id:n.id,decision:r.decision,scope:r.scope,decision_id:`dec_web_${crypto.randomUUID().replaceAll(`-`,``)}`});if(!e.ok)throw Error(e.error||`The approval could not be resolved.`);t(r.decision===`approve`?`Permission approved.`:`Permission denied.`),await o()}catch(t){e(t.message),await o()}finally{a(!1)}},[t,o,e]),c=n[0];return c?(0,N.jsx)(`div`,{className:`modal-backdrop approval-backdrop`,role:`presentation`,children:(0,N.jsxs)(`section`,{className:`modal approval-modal`,role:`alertdialog`,"aria-modal":`true`,"aria-label":`Permission required`,children:[(0,N.jsxs)(`small`,{children:[`PERMISSION REQUIRED · `,n.length,` PENDING`]}),(0,N.jsx)(`h2`,{children:c.action.summary}),(0,N.jsxs)(`div`,{className:`approval-risk ${c.risk.level}`,children:[(0,N.jsxs)(`b`,{children:[c.risk.level,` risk`]}),c.risk.reasons.map(e=>(0,N.jsx)(`span`,{children:e},e))]}),(0,N.jsxs)(`dl`,{className:`approval-detail`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Action`}),(0,N.jsx)(`dd`,{children:c.action.name})]}),c.action.working_directory&&(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Project`}),(0,N.jsx)(`dd`,{children:c.action.working_directory})]}),c.origin.node_id&&(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Graph card`}),(0,N.jsx)(`dd`,{children:c.origin.node_id})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Exact arguments`}),(0,N.jsx)(`dd`,{children:(0,N.jsx)(`pre`,{children:JSON.stringify(c.action.arguments,null,2)})})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Digest`}),(0,N.jsx)(`dd`,{children:(0,N.jsx)(`code`,{children:c.action_digest})})]})]}),!!c.action.effects?.length&&(0,N.jsx)(`ul`,{children:c.action.effects.map(e=>(0,N.jsx)(`li`,{children:e},e))}),(0,N.jsx)(`div`,{className:`approval-actions`,children:c.choices.map(e=>(0,N.jsx)(`button`,{className:e.decision===`approve`?`primary-button`:`secondary-button`,disabled:i,type:`button`,onClick:()=>void s(c,e),children:e.label},`${e.decision}:${e.scope}`))}),(0,N.jsx)(`small`,{children:`MagAgent will revalidate the exact action and current policy before execution.`})]})}):null}var Xe=[{id:`chat`,glyph:`✦`,label:`Chats`},{id:`bots`,glyph:`◉`,label:`Bots`},{id:`graphs`,glyph:`⌘`,label:`Graphs`},{id:`profiles`,glyph:`◇`,label:`Profiles`},{id:`memory`,glyph:`❖`,label:`Memory`},{id:`workspace`,glyph:`▤`,label:`Files`},{id:`runs`,glyph:`▶`,label:`Runs`},{id:`extensions`,glyph:`⬡`,label:`Tools`},{id:`settings`,glyph:`⚙`,label:`Settings`},{id:`operations`,glyph:`⌁`,label:`Ops`}];function Ze(){let[e,t]=(0,l.useState)(M),[n,r]=(0,l.useState)(`chat`),[i,a]=(0,l.useState)(null),[o,s]=(0,l.useState)([]),[c,u]=(0,l.useState)(null),[d,f]=(0,l.useState)([]),[p,h]=(0,l.useState)([]),[g,y]=(0,l.useState)(``),[b,x]=(0,l.useState)(``),[S,C]=(0,l.useState)(!1),[w,T]=(0,l.useState)(!1),[D,O]=(0,l.useState)(!1),[k,ae]=(0,l.useState)(!1),[P,F]=(0,l.useState)(!0),[I,se]=(0,l.useState)([]),L=(0,l.useCallback)(e=>{x(e),window.setTimeout(()=>x(``),2600)},[]),R=(0,l.useCallback)(async()=>{let e=await _(`/api/conversations`);s(e.conversations||[]),u(t=>{let n=e.conversations||[];return t&&n.some(e=>e.id===t)?t:n[0]?.id||null})},[]),ce=(0,l.useCallback)(async()=>{let e=await _(`/api/bootstrap`);a(e),s(e.conversations||[]),f(e.profiles?.profiles||[]),h(e.settings?.fields||[]),u(t=>{let n=e.conversations||[];return t&&n.some(e=>e.id===t)?t:n[0]?.id||null})},[]);(0,l.useEffect)(()=>{m(),(async()=>{try{if((await _(`/api/onboarding/readiness`).catch(()=>({ready:!0}))).ready===!1){O(!0);return}await ce()}catch(e){y(e.message)}finally{T(!0)}})()},[ce]);let le=(0,l.useMemo)(()=>[{key:`k`,mod:!0,describe:`Focus the message box`,run:()=>{r(`chat`),window.setTimeout(()=>document.querySelector(`.composer textarea`)?.focus(),0)}},{key:`f`,mod:!0,describe:`Search conversations`,run:()=>{r(`chat`),window.setTimeout(()=>document.querySelector(`#conversationSearch`)?.focus(),0)}},{key:`n`,mod:!0,shift:!0,describe:`New chat`,run:()=>{r(`chat`),window.dispatchEvent(new CustomEvent(`magent:new-conversation`))}},...Xe.map((e,t)=>({key:String(t+1),mod:!0,describe:`Go to ${e.label}`,run:()=>r(e.id)})),{key:`/`,describe:`Show keyboard shortcuts`,run:()=>C(!0)},{key:`Escape`,describe:`Close a dialog or clear an error`,run:()=>{C(!1),y(``)}}],[]);(0,l.useEffect)(()=>E(le),[le]);let ue=(0,l.useCallback)(async(e,t=[],n=i?.project||`.`,a=``,o=i?.permission_mode||`balanced`)=>{try{let i=await v(`/api/conversations`,{kind:e,profiles:t,project:n,coordinator:a,permission_mode:o,title:e===`bot`&&t[0]?`Chat with ${t[0]}`:`New conversation`});await R(),u(i.conversation.id),ae(!1),r(`chat`)}catch(e){y(e.message)}},[i?.project,i?.permission_mode,R]),fe=(0,l.useCallback)(async e=>{try{await v(`/api/conversations/delete`,{conversation_id:e.id}),await R(),L(`Conversation deleted.`)}catch(e){y(e.message)}},[L,R]),me=(0,l.useCallback)(async(e,t)=>{try{await v(`/api/conversations/update`,{conversation_id:e.id,project:t}),await R(),L(`Conversation project updated.`)}catch(e){y(e.message)}},[L,R]),ge=(0,l.useCallback)(async(e,t)=>{try{await v(`/api/conversations/update`,{conversation_id:e.id,permission_mode:t}),await R(),L(`Permission mode changed to ${t}.`)}catch(e){y(e.message)}},[L,R]),_e=o.find(e=>e.id===c)||null;return w?D?(0,N.jsx)(oe,{setError:y,onReady:()=>{O(!1),ce()}}):(0,N.jsxs)(`div`,{className:`app-shell`,children:[(0,N.jsxs)(`aside`,{className:`rail`,"aria-label":`Primary navigation`,children:[(0,N.jsx)(`div`,{className:`brand-mark`,children:`M`}),(0,N.jsx)(`nav`,{children:Xe.map(e=>(0,N.jsxs)(`button`,{type:`button`,className:`rail-button ${n===e.id?`active`:``}`,"data-view":e.id,title:e.label,"aria-current":n===e.id?`page`:void 0,onClick:()=>r(e.id),children:[e.glyph,(0,N.jsx)(`span`,{children:e.label})]},e.id))}),(0,N.jsxs)(`button`,{type:`button`,className:`rail-button theme-toggle`,onClick:()=>{let n=j(e);t(n),A(n),ie(n)},"aria-label":`Theme: ${te[e]}. Activate to change.`,title:`Theme: ${te[e]}`,children:[(0,N.jsx)(`span`,{"aria-hidden":`true`,children:re[e]}),(0,N.jsx)(`i`,{children:ne[e]})]}),(0,N.jsx)(`div`,{className:`connection-dot`,title:`Local connection`})]}),(0,N.jsx)(pe,{conversations:o,activeId:c,setActiveId:u,profiles:d,boot:i,onCreate:ue,onDelete:fe,onProjectChange:me,setError:y,open:k,setOpen:ae}),(0,N.jsxs)(`main`,{className:`workspace`,children:[g&&(0,N.jsxs)(`div`,{className:`error-banner`,role:`alert`,children:[g,(0,N.jsx)(`button`,{type:`button`,onClick:()=>y(``),"aria-label":`Dismiss`,children:`×`})]}),(0,N.jsxs)(`div`,{className:`workspace-head`,children:[(0,N.jsx)(`button`,{className:`icon-button sidebar-open`,type:`button`,onClick:()=>ae(!0),"aria-label":`Show conversations`,children:`☰`}),(0,N.jsx)(`div`,{className:`workspace-title`,children:n===`chat`?(0,N.jsx)(`h1`,{children:_e?.title||`New conversation`}):(0,N.jsx)(`b`,{})}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>F(e=>!e),"aria-label":`Toggle conversation details`,"aria-expanded":P,children:`☷`})]}),n===`chat`&&(0,N.jsx)(de,{active:_e,refresh:R,setError:y,notify:L,context:I,clearContext:()=>se([])}),n===`bots`&&(0,N.jsx)(ve,{profiles:d,setError:y,onStart:()=>r(`chat`),refresh:R,setActiveId:u}),(0,N.jsx)(`div`,{className:`persistent-view ${n===`graphs`?`active`:``}`,"aria-hidden":n!==`graphs`,children:(0,N.jsx)(Ae,{profiles:d,setError:y,notify:L})}),(0,N.jsx)(`div`,{className:`persistent-view ${n===`profiles`?`active`:``}`,"aria-hidden":n!==`profiles`,children:(0,N.jsx)(Ne,{profiles:d,refresh:ce,setError:y,notify:L})}),n===`memory`&&(0,N.jsx)(Ie,{setError:y,notify:L}),n===`workspace`&&(0,N.jsx)(Be,{selected:I,setSelected:se,activeConversation:c,setError:y,notify:L}),n===`runs`&&(0,N.jsx)(Ve,{setError:y,notify:L}),n===`extensions`&&(0,N.jsx)(qe,{setError:y,notify:L}),n===`settings`&&(0,N.jsx)(Le,{fields:p,profiles:d,refresh:ce,setError:y,notify:L}),n===`operations`&&(0,N.jsx)(ze,{setError:y})]}),P&&(0,N.jsx)(he,{active:_e,boot:i,onPermissionChange:ge,onClose:()=>F(!1)}),S&&(0,N.jsx)(Je,{rows:[...le.map(e=>({describe:e.describe,keys:ee(e)})),{describe:`Send the message`,keys:`Enter`},{describe:`Newline in the message box`,keys:`Shift+Enter`}],onClose:()=>C(!1)}),(0,N.jsx)(Ye,{setError:y,notify:L}),b&&(0,N.jsx)(`div`,{className:`toast`,role:`status`,children:b})]}):(0,N.jsxs)(`div`,{className:`splash`,children:[(0,N.jsx)(`div`,{className:`brand-mark`,children:`M`}),(0,N.jsx)(`p`,{children:`Opening the workspace…`})]})}(0,u.createRoot)(document.getElementById(`root`)).render((0,N.jsx)(l.StrictMode,{children:(0,N.jsx)(Ze,{})}));
\ No newline at end of file
+`).map(e=>e.trim()).filter(Boolean),cwd:e.cwd,env:We(e.env,`Environment variables`),url:e.url,headers:t,timeout:e.timeout,allow_deprecated_transport:e.allow_deprecated_transport,enabled:e.enabled}}function qe({setError:e,notify:t}){let[n,r]=(0,l.useState)({}),[i,a]=(0,l.useState)(null),[o,s]=(0,l.useState)(!1),[c,u]=(0,l.useState)(null),[d,f]=(0,l.useState)(``),[p,m]=(0,l.useState)([]),[h,g]=(0,l.useState)(``);async function y(){try{let[e,t]=await Promise.all([_(`/api/extensions`),_(`/api/onboarding/providers`)]);r(e),m(t.image_models||[])}catch(t){e(t.message)}}(0,l.useEffect)(()=>{y()},[]);async function b(n,r){try{await v(`/api/extensions/plugins`,{name:n,enabled:r}),t(`${n} ${r?`enabled`:`disabled`}`),await y()}catch(t){e(t.message)}}async function x(n){if(n.preventDefault(),i)try{await v(`/api/extensions/manage`,i.kind===`mcp`?Ke(i):{kind:i.kind,action:i.kind===`plugin`?`install`:`save`,name:i.name,source:i.source,description:i.description,body:i.body}),t(`${i.kind} saved.`),a(null),await y()}catch(t){e(t.message)}}async function S(){if(!(!i||i.kind!==`mcp`)){s(!0);try{t((await v(`/api/extensions/mcp/test`,Ke(i))).message||`MCP connection succeeded.`)}catch(t){e(t.message)}finally{s(!1)}}}async function C(n,r){if(window.confirm(`Delete ${n} “${r}”?`))try{await v(`/api/extensions/manage`,{kind:n,action:`delete`,name:r}),t(`${n} deleted.`),await y()}catch(t){e(t.message)}}async function w(n){if(n.preventDefault(),c)try{await v(`/api/settings`,{path:c.id===`image`?`models.image_maker`:`tools.browser_enabled`,value:c.id===`image`?(d===`__custom__`?h:d).trim():!c.enabled,scope:`global`}),t(`${c.label} updated.`),u(null),await y()}catch(t){e(t.message)}}return(0,N.jsxs)(`section`,{className:`view active page-view extensions-page`,children:[(0,N.jsx)(`div`,{className:`page-head`,children:(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`TOOLS & INTEGRATIONS`}),(0,N.jsx)(`h1`,{children:`Extensions`}),(0,N.jsx)(`p`,{children:`Install local plugins and manage project skills and MCP connections. Credentials are referenced by environment variable and are never revealed here.`})]})}),(0,N.jsx)(`div`,{className:`card-grid`,children:(n.capabilities||[]).map(e=>(0,N.jsxs)(`article`,{className:`detail-card`,children:[(0,N.jsx)(`h3`,{children:e.label}),(0,N.jsx)(`p`,{children:e.description}),(0,N.jsx)(`span`,{className:`tag`,children:e.enabled?`Ready`:`Not configured`}),typeof e.configured==`string`&&e.configured&&(0,N.jsx)(`p`,{className:`mono`,children:e.configured}),[`image`,`browser`].includes(e.id)&&(0,N.jsx)(`div`,{className:`detail-actions`,children:(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>{u(e);let t=typeof e.configured==`string`?e.configured:``,n=p.some(e=>e.value===t);f(t&&!n?`__custom__`:t),g(t&&!n?t:``)},children:e.id===`browser`?e.enabled?`Disable`:`Enable`:e.enabled?`Edit configuration`:`Configure`})})]},e.id))}),(0,N.jsxs)(`div`,{className:`section-heading`,children:[(0,N.jsx)(`h2`,{children:`Plugins`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,onClick:()=>a(He(`plugin`)),children:`+ Install plugin`})]}),(0,N.jsxs)(`div`,{className:`card-grid`,children:[(n.plugins||[]).map(e=>(0,N.jsxs)(`article`,{className:`detail-card`,children:[(0,N.jsx)(`h3`,{children:e.name}),(0,N.jsxs)(`p`,{children:[`Version `,e.version||`unknown`,` · integrity `,e.integrity||`unrecorded`]}),(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`secondary-button`,disabled:e.valid===!1&&!e.enabled,type:`button`,onClick:()=>void b(e.name,!e.enabled),children:e.enabled?`Disable`:`Enable`}),(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:()=>void C(`plugin`,e.name),children:`Uninstall`})]})]},e.name)),!(n.plugins||[]).length&&(0,N.jsx)(`p`,{children:`No plugins installed.`})]}),(0,N.jsxs)(`div`,{className:`section-heading`,children:[(0,N.jsx)(`h2`,{children:`Project skills`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,onClick:()=>a(He(`skill`)),children:`+ New skill`})]}),(0,N.jsx)(`div`,{className:`card-grid`,children:(n.skills||[]).map(e=>(0,N.jsxs)(`article`,{className:`detail-card`,children:[(0,N.jsx)(`h3`,{children:e.name}),(0,N.jsx)(`p`,{children:e.description}),(0,N.jsxs)(`span`,{className:`tag`,children:[`v`,e.version]}),e.editable&&(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>a({...He(`skill`),original:e.name,name:e.name,description:e.description||``,body:String(e.body||``).replace(/^---[\s\S]*?---\s*/,``)}),children:`Edit`}),(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:()=>void C(`skill`,e.name),children:`Delete`})]})]},`${e.name}-${e.version}`))}),(0,N.jsxs)(`div`,{className:`section-heading`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`h2`,{children:`MCP servers`}),(0,N.jsx)(`p`,{className:`context-note`,children:`Auto-negotiation supports modern MCP and legacy-compatible servers. Transport and protocol version are separate choices.`})]}),(0,N.jsx)(`button`,{className:`primary-button`,type:`button`,onClick:()=>a(He(`mcp`)),children:`+ New server`})]}),(0,N.jsx)(`div`,{className:`card-grid`,children:(n.mcp_servers||[]).map(e=>(0,N.jsxs)(`article`,{className:`detail-card`,children:[(0,N.jsx)(`h3`,{children:e.name}),(0,N.jsx)(`span`,{className:`tag`,children:e.enabled?`Enabled`:`Disabled`}),(0,N.jsx)(`span`,{className:`tag`,children:e.transport||`stdio`}),(0,N.jsxs)(`span`,{className:`tag`,children:[`protocol `,e.protocol_mode||`auto`]}),(0,N.jsx)(`p`,{children:e.command||e.url||`No launch target`}),(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>a(Ge(e)),children:`Edit`}),(0,N.jsx)(`button`,{className:`danger-button`,type:`button`,onClick:()=>void C(`mcp`,e.name),children:`Delete`})]})]},e.name))}),i&&(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`${i.kind} editor`,onClick:e=>{e.target===e.currentTarget&&a(null)},children:(0,N.jsxs)(`form`,{className:`modal`,onSubmit:x,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:i.kind.toUpperCase()}),(0,N.jsxs)(`h2`,{children:[i.original?`Edit`:`Create or install`,` `,i.kind]})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>a(null),children:`×`})]}),(0,N.jsxs)(`label`,{children:[`Name`,(0,N.jsx)(`input`,{required:!0,value:i.name,disabled:!!i.original,onChange:e=>a({...i,name:e.target.value})})]}),i.kind===`plugin`&&(0,N.jsxs)(`label`,{children:[`Local source folder`,(0,N.jsx)(`input`,{required:!0,placeholder:`/path/to/plugin`,value:i.source,onChange:e=>a({...i,source:e.target.value})})]}),i.kind===`skill`&&(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`label`,{children:[`Description`,(0,N.jsx)(`input`,{required:!0,value:i.description,onChange:e=>a({...i,description:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Instructions`,(0,N.jsx)(`textarea`,{required:!0,rows:10,value:i.body,onChange:e=>a({...i,body:e.target.value})})]})]}),i.kind===`mcp`&&(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`label`,{children:[`Transport`,(0,N.jsxs)(`select`,{value:i.transport,onChange:e=>a({...i,transport:e.target.value}),children:[(0,N.jsx)(`option`,{value:`stdio`,children:`Local process (stdio)`}),(0,N.jsx)(`option`,{value:`streamable-http`,children:`Remote Streamable HTTP`}),(0,N.jsx)(`option`,{value:`legacy-sse`,children:`Legacy SSE (deprecated)`})]})]}),(0,N.jsxs)(`label`,{children:[`Protocol compatibility`,(0,N.jsxs)(`select`,{value:i.protocol_mode,onChange:e=>a({...i,protocol_mode:e.target.value}),children:[(0,N.jsx)(`option`,{value:`auto`,children:`Auto-negotiate (recommended)`}),(0,N.jsx)(`option`,{value:`modern`,children:`Modern MCP only`}),(0,N.jsx)(`option`,{value:`legacy`,children:`Legacy MCP only`})]})]})]}),i.transport===`stdio`?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`label`,{children:[`Command`,(0,N.jsx)(`input`,{required:!0,placeholder:`npx`,value:i.command,onChange:e=>a({...i,command:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Arguments `,(0,N.jsx)(`small`,{children:`one per line`}),(0,N.jsx)(`textarea`,{rows:4,value:i.args,onChange:e=>a({...i,args:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Working directory `,(0,N.jsx)(`small`,{children:`optional`}),(0,N.jsx)(`input`,{placeholder:`/path/to/server`,value:i.cwd,onChange:e=>a({...i,cwd:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Environment mapping `,(0,N.jsxs)(`small`,{children:[`NAME=value, one per line; use $`,"${ENV_VAR}",` for credentials`]}),(0,N.jsx)(`textarea`,{rows:4,placeholder:"API_TOKEN=${API_TOKEN}",value:i.env,onChange:e=>a({...i,env:e.target.value})})]})]}):(0,N.jsxs)(N.Fragment,{children:[(0,N.jsxs)(`label`,{children:[`Server URL`,(0,N.jsx)(`input`,{required:!0,type:`url`,placeholder:`https://example.com/mcp`,value:i.url,onChange:e=>a({...i,url:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Bearer credential environment variable `,(0,N.jsx)(`small`,{children:`optional`}),(0,N.jsx)(`input`,{placeholder:`MCP_ACCESS_TOKEN`,value:i.credential_env,onChange:e=>a({...i,credential_env:e.target.value})})]}),(0,N.jsxs)(`label`,{children:[`Additional headers `,(0,N.jsx)(`small`,{children:`NAME=value, one per line; environment references supported`}),(0,N.jsx)(`textarea`,{rows:4,placeholder:"X-Tenant=${MCP_TENANT}",value:i.headers,onChange:e=>a({...i,headers:e.target.value})})]})]}),(0,N.jsxs)(`div`,{className:`form-grid`,children:[(0,N.jsxs)(`label`,{children:[`Connection timeout (seconds)`,(0,N.jsx)(`input`,{type:`number`,min:1,max:300,value:i.timeout,onChange:e=>a({...i,timeout:Number(e.target.value)})})]}),(0,N.jsxs)(`label`,{className:`checkbox-row`,children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:i.enabled,onChange:e=>a({...i,enabled:e.target.checked})}),` Enable after saving`]})]}),i.transport===`legacy-sse`&&(0,N.jsxs)(`label`,{className:`checkbox-row warning-choice`,children:[(0,N.jsx)(`input`,{type:`checkbox`,required:!0,checked:i.allow_deprecated_transport,onChange:e=>a({...i,allow_deprecated_transport:e.target.checked})}),` I understand legacy SSE is deprecated and explicitly allow it.`]}),(0,N.jsx)(`p`,{className:`context-note`,children:`“Modern” and “legacy” select protocol eras; the exact server version is negotiated during connection. Use Auto unless the server documents a fixed era.`}),(0,N.jsxs)(`div`,{className:`mcp-test-row`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,disabled:o,onClick:()=>void S(),children:o?`Testing…`:`Test connection`}),(0,N.jsx)(`span`,{children:`Testing may start the local command or contact the remote URL, but does not save it.`})]})]}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:`Save`})]})}),c&&(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`Configure ${c.label}`,onClick:e=>{e.target===e.currentTarget&&u(null)},children:(0,N.jsxs)(`form`,{className:`modal`,onSubmit:w,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`CAPABILITY`}),(0,N.jsx)(`h2`,{children:c.id===`browser`?`${c.enabled?`Disable`:`Enable`} browser automation`:`Configure image generation`})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>u(null),children:`×`})]}),c.id===`image`?(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`p`,{children:`Select an image model whose provider is available on this machine. Configure missing provider access in Settings first.`}),(0,N.jsxs)(`label`,{children:[`Image model`,(0,N.jsxs)(`select`,{value:d,onChange:e=>f(e.target.value),children:[(0,N.jsx)(`option`,{value:``,children:`Not configured`}),p.filter(e=>e.id!==`custom`).map(e=>(0,N.jsxs)(`option`,{value:e.value,disabled:!e.available,children:[e.label,e.available?``:` · configure provider first`]},e.id)),(0,N.jsx)(`option`,{value:`__custom__`,children:`Custom provider/model…`})]})]}),d===`__custom__`&&(0,N.jsxs)(`label`,{children:[`Custom route`,(0,N.jsx)(`input`,{required:!0,placeholder:`provider/model`,value:h,onChange:e=>g(e.target.value)})]}),(0,N.jsxs)(`div`,{className:`detail-actions`,children:[(0,N.jsx)(`button`,{className:`ghost-button`,type:`button`,onClick:()=>{f(``),g(``)},children:`Clear`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:`Save configuration`})]})]}):(0,N.jsxs)(N.Fragment,{children:[(0,N.jsx)(`p`,{children:`Profile and permission ceilings still apply when browser automation is enabled.`}),(0,N.jsx)(`button`,{className:`primary-button`,type:`submit`,children:c.enabled?`Disable`:`Enable`})]})]})})]})}function Je({rows:e,onClose:t}){return(0,N.jsx)(`div`,{className:`modal-backdrop`,role:`dialog`,"aria-modal":`true`,"aria-label":`Keyboard shortcuts`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,N.jsxs)(`div`,{className:`modal shortcuts-sheet`,children:[(0,N.jsxs)(`div`,{className:`dialog-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`div`,{className:`eyebrow`,children:`KEYBOARD`}),(0,N.jsx)(`h2`,{children:`Shortcuts`})]}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:t,"aria-label":`Close`,children:`×`})]}),(0,N.jsx)(`ul`,{children:e.map(e=>(0,N.jsxs)(`li`,{children:[(0,N.jsx)(`span`,{children:e.describe}),(0,N.jsx)(`kbd`,{children:e.keys})]},`${e.describe}-${e.keys}`))})]})})}function Ye(e,t){(0,l.useEffect)(()=>{let n=e.current;if(!n||!t)return;let r=document.activeElement,i=()=>Array.from(n.querySelectorAll(`button:not(:disabled), input, select, textarea, a[href], summary, [tabindex="0"]`));(i()[0]??n).focus();let a=e=>{if(e.key!==`Tab`)return;let t=i();if(!t.length){e.preventDefault(),n.focus();return}let r=t[0],a=t[t.length-1];e.shiftKey&&(document.activeElement===r||!n.contains(document.activeElement))?(e.preventDefault(),a.focus()):!e.shiftKey&&(document.activeElement===a||!n.contains(document.activeElement))&&(e.preventDefault(),r.focus())},o=e=>{n.contains(e.target)||(i()[0]??n).focus()};return document.addEventListener(`keydown`,a),document.addEventListener(`focusin`,o),()=>{document.removeEventListener(`keydown`,a),document.removeEventListener(`focusin`,o),r?.isConnected&&r.focus()}},[e,t])}function Xe({setError:e,notify:t}){let[n,r]=(0,l.useState)([]),[i,a]=(0,l.useState)(!1),o=(0,l.useCallback)(async()=>{try{let e=await _(`/api/approvals/snapshot`);r(e.snapshot?.pending??[])}catch{}},[]);(0,l.useEffect)(()=>{o();let e=window.setInterval(()=>void o(),800);return()=>window.clearInterval(e)},[o]);let s=(0,l.useCallback)(async(n,r)=>{a(!0);try{let e=await v(`/api/approvals/decide`,{request_id:n.id,action_digest:n.action_digest,decision:r.decision,scope:r.scope,decision_id:`dec_web_${crypto.randomUUID().replaceAll(`-`,``)}`});if(!e.ok)throw Error(e.error||`The approval could not be resolved.`);t(`Harness outcome: ${e.resolution?.resolution.outcome??`unknown`}`),await o()}catch(t){e(t.message),await o()}finally{a(!1)}},[t,o,e]),c=n[0],u=(0,l.useRef)(null);return Ye(u,c?.id),c?(0,N.jsx)(`div`,{className:`modal-backdrop approval-backdrop`,role:`presentation`,children:(0,N.jsxs)(`section`,{ref:u,tabIndex:-1,className:`modal approval-modal`,role:`alertdialog`,"aria-modal":`true`,"aria-label":`Permission required`,children:[(0,N.jsxs)(`small`,{children:[`PERMISSION REQUIRED · `,n.length,` PENDING`]}),(0,N.jsx)(`h2`,{children:c.action.summary}),(0,N.jsxs)(`p`,{children:[`From `,Object.values(c.origin).join(` · `)]}),c.expires_at&&(0,N.jsxs)(`p`,{children:[`Expires `,new Date(c.expires_at).toLocaleString()]}),(0,N.jsxs)(`div`,{className:`approval-risk ${c.risk.level}`,children:[(0,N.jsxs)(`b`,{children:[c.risk.level,` risk`]}),c.risk.reasons.map(e=>(0,N.jsx)(`span`,{children:e},e))]}),(0,N.jsxs)(`dl`,{className:`approval-detail`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Action`}),(0,N.jsx)(`dd`,{children:c.action.name})]}),c.action.working_directory&&(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Project`}),(0,N.jsx)(`dd`,{children:c.action.working_directory})]}),c.origin.node_id&&(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Graph card`}),(0,N.jsx)(`dd`,{children:c.origin.node_id})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Exact arguments`}),(0,N.jsx)(`dd`,{children:(0,N.jsx)(`pre`,{children:JSON.stringify(c.action.arguments,null,2)})})]}),(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`dt`,{children:`Digest`}),(0,N.jsx)(`dd`,{children:(0,N.jsx)(`code`,{children:c.action_digest})})]})]}),!!c.action.effects?.length&&(0,N.jsx)(`ul`,{children:c.action.effects.map(e=>(0,N.jsx)(`li`,{children:e},e))}),(0,N.jsx)(`div`,{className:`approval-actions`,children:c.choices.map(e=>(0,N.jsx)(`button`,{className:e.decision===`approve`?`primary-button`:`secondary-button`,disabled:i||!!(c.expires_at&&Date.parse(c.expires_at)<=Date.now()),type:`button`,onClick:()=>void s(c,e),children:e.label},`${e.decision}:${e.scope}`))}),(0,N.jsx)(`small`,{children:`MagAgent will revalidate the exact action and current policy before execution.`})]})}):null}var Ze=[{id:`chat`,glyph:`✦`,label:`Chats`},{id:`bots`,glyph:`◉`,label:`Bots`},{id:`graphs`,glyph:`⌘`,label:`Graphs`},{id:`profiles`,glyph:`◇`,label:`Profiles`},{id:`memory`,glyph:`❖`,label:`Memory`},{id:`workspace`,glyph:`▤`,label:`Files`},{id:`runs`,glyph:`▶`,label:`Runs`},{id:`extensions`,glyph:`⬡`,label:`Tools`},{id:`settings`,glyph:`⚙`,label:`Settings`},{id:`operations`,glyph:`⌁`,label:`Ops`}];function Qe(){let[e,t]=(0,l.useState)(M),[n,r]=(0,l.useState)(`chat`),[i,a]=(0,l.useState)(null),[o,s]=(0,l.useState)([]),[c,u]=(0,l.useState)(null),[d,f]=(0,l.useState)([]),[p,h]=(0,l.useState)([]),[g,y]=(0,l.useState)(``),[b,x]=(0,l.useState)(``),[S,C]=(0,l.useState)(!1),[w,T]=(0,l.useState)(!1),[D,O]=(0,l.useState)(!1),[k,ae]=(0,l.useState)(!1),[P,F]=(0,l.useState)(!0),[I,se]=(0,l.useState)([]),L=(0,l.useCallback)(e=>{x(e),window.setTimeout(()=>x(``),2600)},[]),R=(0,l.useCallback)(async()=>{let e=await _(`/api/conversations`);s(e.conversations||[]),u(t=>{let n=e.conversations||[];return t&&n.some(e=>e.id===t)?t:n[0]?.id||null})},[]),ce=(0,l.useCallback)(async()=>{let e=await _(`/api/bootstrap`);a(e),s(e.conversations||[]),f(e.profiles?.profiles||[]),h(e.settings?.fields||[]),u(t=>{let n=e.conversations||[];return t&&n.some(e=>e.id===t)?t:n[0]?.id||null})},[]);(0,l.useEffect)(()=>{m(),(async()=>{try{if((await _(`/api/onboarding/readiness`).catch(()=>({ready:!0}))).ready===!1){O(!0);return}await ce()}catch(e){y(e.message)}finally{T(!0)}})()},[ce]);let le=(0,l.useMemo)(()=>[{key:`k`,mod:!0,describe:`Focus the message box`,run:()=>{r(`chat`),window.setTimeout(()=>document.querySelector(`.composer textarea`)?.focus(),0)}},{key:`f`,mod:!0,describe:`Search conversations`,run:()=>{r(`chat`),window.setTimeout(()=>document.querySelector(`#conversationSearch`)?.focus(),0)}},{key:`n`,mod:!0,shift:!0,describe:`New chat`,run:()=>{r(`chat`),window.dispatchEvent(new CustomEvent(`magent:new-conversation`))}},...Ze.map((e,t)=>({key:String(t+1),mod:!0,describe:`Go to ${e.label}`,run:()=>r(e.id)})),{key:`/`,describe:`Show keyboard shortcuts`,run:()=>C(!0)},{key:`Escape`,describe:`Close a dialog or clear an error`,run:()=>{C(!1),y(``)}}],[]);(0,l.useEffect)(()=>E(le),[le]);let ue=(0,l.useCallback)(async(e,t=[],n=i?.project||`.`,a=``,o=i?.permission_mode||`balanced`)=>{try{let i=await v(`/api/conversations`,{kind:e,profiles:t,project:n,coordinator:a,permission_mode:o,title:e===`bot`&&t[0]?`Chat with ${t[0]}`:`New conversation`});await R(),u(i.conversation.id),ae(!1),r(`chat`)}catch(e){y(e.message)}},[i?.project,i?.permission_mode,R]),fe=(0,l.useCallback)(async e=>{try{await v(`/api/conversations/delete`,{conversation_id:e.id}),await R(),L(`Conversation deleted.`)}catch(e){y(e.message)}},[L,R]),me=(0,l.useCallback)(async(e,t)=>{try{await v(`/api/conversations/update`,{conversation_id:e.id,project:t}),await R(),L(`Conversation project updated.`)}catch(e){y(e.message)}},[L,R]),ge=(0,l.useCallback)(async(e,t)=>{try{await v(`/api/conversations/update`,{conversation_id:e.id,permission_mode:t}),await R(),L(`Permission mode changed to ${t}.`)}catch(e){y(e.message)}},[L,R]),_e=o.find(e=>e.id===c)||null;return w?D?(0,N.jsx)(oe,{setError:y,onReady:()=>{O(!1),ce()}}):(0,N.jsxs)(`div`,{className:`app-shell`,children:[(0,N.jsxs)(`aside`,{className:`rail`,"aria-label":`Primary navigation`,children:[(0,N.jsx)(`div`,{className:`brand-mark`,children:`M`}),(0,N.jsx)(`nav`,{children:Ze.map(e=>(0,N.jsxs)(`button`,{type:`button`,className:`rail-button ${n===e.id?`active`:``}`,"data-view":e.id,title:e.label,"aria-current":n===e.id?`page`:void 0,onClick:()=>r(e.id),children:[e.glyph,(0,N.jsx)(`span`,{children:e.label})]},e.id))}),(0,N.jsxs)(`button`,{type:`button`,className:`rail-button theme-toggle`,onClick:()=>{let n=j(e);t(n),A(n),ie(n)},"aria-label":`Theme: ${te[e]}. Activate to change.`,title:`Theme: ${te[e]}`,children:[(0,N.jsx)(`span`,{"aria-hidden":`true`,children:re[e]}),(0,N.jsx)(`i`,{children:ne[e]})]}),(0,N.jsx)(`div`,{className:`connection-dot`,title:`Local connection`})]}),(0,N.jsx)(pe,{conversations:o,activeId:c,setActiveId:u,profiles:d,boot:i,onCreate:ue,onDelete:fe,onProjectChange:me,setError:y,open:k,setOpen:ae}),(0,N.jsxs)(`main`,{className:`workspace`,children:[g&&(0,N.jsxs)(`div`,{className:`error-banner`,role:`alert`,children:[g,(0,N.jsx)(`button`,{type:`button`,onClick:()=>y(``),"aria-label":`Dismiss`,children:`×`})]}),(0,N.jsxs)(`div`,{className:`workspace-head`,children:[(0,N.jsx)(`button`,{className:`icon-button sidebar-open`,type:`button`,onClick:()=>ae(!0),"aria-label":`Show conversations`,children:`☰`}),(0,N.jsx)(`div`,{className:`workspace-title`,children:n===`chat`?(0,N.jsx)(`h1`,{children:_e?.title||`New conversation`}):(0,N.jsx)(`b`,{})}),(0,N.jsx)(`button`,{className:`icon-button`,type:`button`,onClick:()=>F(e=>!e),"aria-label":`Toggle conversation details`,"aria-expanded":P,children:`☷`})]}),n===`chat`&&(0,N.jsx)(de,{active:_e,refresh:R,setError:y,notify:L,context:I,clearContext:()=>se([])}),n===`bots`&&(0,N.jsx)(ve,{profiles:d,setError:y,onStart:()=>r(`chat`),refresh:R,setActiveId:u}),(0,N.jsx)(`div`,{className:`persistent-view ${n===`graphs`?`active`:``}`,"aria-hidden":n!==`graphs`,children:(0,N.jsx)(Ae,{profiles:d,setError:y,notify:L})}),(0,N.jsx)(`div`,{className:`persistent-view ${n===`profiles`?`active`:``}`,"aria-hidden":n!==`profiles`,children:(0,N.jsx)(Ne,{profiles:d,refresh:ce,setError:y,notify:L})}),n===`memory`&&(0,N.jsx)(Ie,{setError:y,notify:L}),n===`workspace`&&(0,N.jsx)(Be,{selected:I,setSelected:se,activeConversation:c,setError:y,notify:L}),n===`runs`&&(0,N.jsx)(Ve,{setError:y,notify:L}),n===`extensions`&&(0,N.jsx)(qe,{setError:y,notify:L}),n===`settings`&&(0,N.jsx)(Le,{fields:p,profiles:d,refresh:ce,setError:y,notify:L}),n===`operations`&&(0,N.jsx)(ze,{setError:y})]}),P&&(0,N.jsx)(he,{active:_e,boot:i,onPermissionChange:ge,onClose:()=>F(!1)}),S&&(0,N.jsx)(Je,{rows:[...le.map(e=>({describe:e.describe,keys:ee(e)})),{describe:`Send the message`,keys:`Enter`},{describe:`Newline in the message box`,keys:`Shift+Enter`}],onClose:()=>C(!1)}),(0,N.jsx)(Xe,{setError:y,notify:L}),b&&(0,N.jsx)(`div`,{className:`toast`,role:`status`,children:b})]}):(0,N.jsxs)(`div`,{className:`splash`,children:[(0,N.jsx)(`div`,{className:`brand-mark`,children:`M`}),(0,N.jsx)(`p`,{children:`Opening the workspace…`})]})}(0,u.createRoot)(document.getElementById(`root`)).render((0,N.jsx)(l.StrictMode,{children:(0,N.jsx)(Qe,{})}));
\ No newline at end of file
diff --git a/src/magent/webui/static/index.html b/src/magent/webui/static/index.html
index a3d33a7..6e1e09b 100644
--- a/src/magent/webui/static/index.html
+++ b/src/magent/webui/static/index.html
@@ -5,7 +5,7 @@
From {Object.values(approval.origin).join(" · ")}
+ {approval.expires_at &&Expires {new Date(approval.expires_at).toLocaleString()}
}