+ View ${R.length} pending change${R.length!==1?"s":""} + +
+${JSON.stringify(e.issues,null,2)}
+ diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
new file mode 100644
index 000000000..aa175961d
--- /dev/null
+++ b/.github/workflows/docker-release.yml
@@ -0,0 +1,143 @@
+name: Docker Release
+
+on:
+ push:
+ branches:
+ - main
+ tags:
+ - "v*"
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+ # Build amd64 image
+ build-amd64:
+ runs-on: ubuntu-latest
+ permissions:
+ packages: write
+ contents: read
+ outputs:
+ image-digest: ${{ steps.build.outputs.digest }}
+ image-metadata: ${{ steps.meta.outputs.json }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=semver,pattern={{version}}
+ type=semver,pattern={{version}},suffix=-amd64
+ type=semver,pattern={{version}},suffix=-arm64
+ type=ref,event=branch,suffix=-amd64
+ type=ref,event=branch,suffix=-arm64
+
+ - name: Build and push amd64 image
+ id: build
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ platforms: linux/amd64
+ labels: ${{ steps.meta.outputs.labels }}
+ tags: ${{ steps.meta.outputs.tags }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+ provenance: false
+ push: true
+
+ # Build arm64 image
+ build-arm64:
+ runs-on: ubuntu-24.04-arm
+ permissions:
+ packages: write
+ contents: read
+ outputs:
+ image-digest: ${{ steps.build.outputs.digest }}
+ image-metadata: ${{ steps.meta.outputs.json }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=semver,pattern={{version}}
+ type=semver,pattern={{version}},suffix=-amd64
+ type=semver,pattern={{version}},suffix=-arm64
+ type=ref,event=branch,suffix=-amd64
+ type=ref,event=branch,suffix=-arm64
+
+ - name: Build and push arm64 image
+ id: build
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ platforms: linux/arm64
+ labels: ${{ steps.meta.outputs.labels }}
+ tags: ${{ steps.meta.outputs.tags }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+ provenance: false
+ push: true
+
+ # Create multi-platform manifest
+ create-manifest:
+ runs-on: ubuntu-latest
+ permissions:
+ packages: write
+ contents: read
+ needs: [build-amd64, build-arm64]
+ steps:
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata for manifest
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=semver,pattern={{version}}
+
+ - name: Create and push manifest
+ run: |
+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+ ${{ needs.build-amd64.outputs.image-digest }} \
+ ${{ needs.build-arm64.outputs.image-digest }}
+ env:
+ DOCKER_METADATA_OUTPUT_JSON: ${{ steps.meta.outputs.json }}
diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml
index b7e8e274e..84d1b7f32 100644
--- a/.github/workflows/install-smoke.yml
+++ b/.github/workflows/install-smoke.yml
@@ -29,5 +29,6 @@ jobs:
CLAWDBOT_INSTALL_CLI_URL: https://clawd.bot/install-cli.sh
CLAWDBOT_NO_ONBOARD: "1"
CLAWDBOT_INSTALL_SMOKE_SKIP_CLI: "1"
+ CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }}
CLAWDBOT_INSTALL_SMOKE_PREVIOUS: "2026.1.11-4"
run: pnpm test:install:smoke
diff --git a/AGENTS.md b/AGENTS.md
index 285630310..fbf1ecf79 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -7,6 +7,7 @@
- Tests: colocated `*.test.ts`.
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`.
- Plugins/extensions: live under `extensions/*` (workspace packages). Keep plugin-only deps in the extension `package.json`; do not add them to the root `package.json` unless core uses them.
+- Plugins: install runs `npm install --omit=dev` in plugin dir; runtime deps must live in `dependencies`. Avoid `workspace:*` in `dependencies` (npm install breaks); put `clawdbot` in `devDependencies` or `peerDependencies` instead (runtime resolves `clawdbot/plugin-sdk` via jiti alias).
- Installers served from `https://clawd.bot/*`: live in the sibling repo `../clawd.bot` (`public/install.sh`, `public/install-cli.sh`, `public/install.ps1`).
- Messaging channels: always consider **all** built-in + extension channels when refactoring shared logic (routing, allowlists, pairing, command gating, onboarding, docs).
- Core channel docs: `docs/channels/`
@@ -22,6 +23,16 @@
- README (GitHub): keep absolute docs URLs (`https://docs.clawd.bot/...`) so links work on GitHub.
- Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”.
+## exe.dev VM ops (general)
+- Access: stable path is `ssh exe.dev` then `ssh vm-name` (assume SSH key already set).
+- SSH flaky: use exe.dev web terminal or Shelley (web agent); keep a tmux session for long ops.
+- Update: `sudo npm i -g clawdbot@latest` (global install needs root on `/usr/lib/node_modules`).
+- Config: use `clawdbot config set ...`; ensure `gateway.mode=local` is set.
+- Discord: store raw token only (no `DISCORD_BOT_TOKEN=` prefix).
+- Restart: stop old gateway and run:
+ `pkill -9 -f clawdbot-gateway || true; nohup clawdbot gateway run --bind loopback --port 18789 --force > /tmp/clawdbot-gateway.log 2>&1 &`
+- Verify: `clawdbot channels status --probe`, `ss -ltnp | rg 18789`, `tail -n 120 /tmp/clawdbot-gateway.log`.
+
## Build, Test, and Development Commands
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
- Install deps: `pnpm install`
@@ -51,6 +62,7 @@
- Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements).
- Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`.
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
+- Do not set test workers above 16; tried already.
- Live tests (real keys): `CLAWDBOT_LIVE_TEST=1 pnpm test:live` (Clawdbot-only) or `LIVE=1 pnpm test:live` (includes provider live tests). Docker: `pnpm test:docker:live-models`, `pnpm test:docker:live-gateway`. Onboarding Docker E2E: `pnpm test:docker:onboard`.
- Full kit + what’s covered: `docs/testing.md`.
- Pure test additions/fixes generally do **not** need a changelog entry unless they alter user-facing behavior or the user asks for one.
@@ -65,6 +77,7 @@
- PR review flow: when given a PR link, review via `gh pr view`/`gh pr diff` and do **not** change branches.
- PR review calls: prefer a single `gh pr view --json ...` to batch metadata/comments; run `gh pr diff` only when needed.
- Before starting a review when a GH Issue/PR is pasted: run `git pull`; if there are local changes or unpushed commits, stop and alert the user before reviewing.
+- Goal: merge PRs. Prefer **rebase** when commits are clean; **squash** when history is messy.
- PR merge flow: create a temp branch from `main`, merge the PR branch into it (prefer squash unless commit history is important; use rebase/merge when it is). Always try to merge the PR unless it’s truly difficult, then use another approach. If we squash, add the PR author as a co-contributor. Apply fixes, add changelog entry (include PR # + thanks), run full gate before the final commit, commit, merge back to `main`, delete the temp branch, and end on `main`.
- If you review a PR and later do work on it, land via merge/squash (no direct-main commits) and always add the PR author as a co-contributor.
- When working on a PR: add a changelog entry with the PR number and thank the contributor.
@@ -93,6 +106,7 @@
## Agent-Specific Notes
- Vocabulary: "makeup" = "mac app".
- Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`.
+- Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/clawdbot && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`.
- When working on a GitHub Issue or PR, print the full URL at the end of the task.
- When answering questions, respond with high-confidence answers only: verify in code; do not guess.
- Never update the Carbon dependency.
@@ -118,6 +132,10 @@
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
- **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session.
- **Multi-agent safety:** when you see unrecognized files, keep going; focus on your changes and commit only those.
+- Lint/format churn:
+ - If staged+unstaged diffs are formatting-only, auto-resolve without asking.
+ - If commit/push already requested, auto-stage and include formatting-only follow-ups in the same commit (or a tiny follow-up commit if needed), no extra confirmation.
+ - Only ask when changes are semantic (logic/data/behavior).
- Lobster seam: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e0cc4b88c..6661a5a3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,45 +2,139 @@
Docs: https://docs.clawd.bot
+## 2026.1.24
+
+### Highlights
+- Ollama: provider discovery + docs. (#1606) Thanks @abhaymundhara. https://docs.clawd.bot/providers/ollama
+- Venius (Venice AI): highlight provider guide + cross-links + expanded guidance. https://docs.clawd.bot/providers/venice
+
+### Changes
+- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts
+- Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
+- Docs: add verbose installer troubleshooting guidance.
+- Docs: update Fly.io guide notes.
+- Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.clawd.bot/bedrock
+- Exec approvals: forward approval prompts to chat with `/approve` for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands
+- Gateway: expose config.patch in the gateway tool with safe partial updates + restart sentinel. (#1653) Thanks @Glucksberg.
+
+### Fixes
+- BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.
+- Web UI: hide internal `message_id` hints in chat bubbles.
+- Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.
+- Heartbeat: normalize target identifiers for consistent routing.
+- TUI: reload history after gateway reconnect to restore session state. (#1663)
+- Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)
+- Exec: keep approvals for elevated ask unless full mode. (#1616) Thanks @ivancasco.
+- Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.
+- Agents: use the active auth profile for auto-compaction recovery.
+- Models: default missing custom provider fields so minimal configs are accepted.
+- Gateway: honor trusted proxy client IPs for local pairing + HTTP checks. (#1654) Thanks @ndbroadbent.
+- Gateway: reduce log noise for late invokes + remote node probes; debounce skills refresh. (#1607) Thanks @petter-b.
+- macOS: default direct-transport `ws://` URLs to port 18789; document `gateway.remote.transport`. (#1603) Thanks @ngutman.
+- Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634)
+- Google Chat: tighten email allowlist matching, typing cleanup, media caps, and onboarding/docs/tests. (#1635) Thanks @iHildy.
+- Google Chat: normalize space targets without double `spaces/` prefix.
+
+## 2026.1.23-1
+
+### Fixes
+- Packaging: include dist/tts output in npm tarball (fixes missing dist/tts/tts.js).
+
+## 2026.1.23
+
+### Highlights
+- TTS: move Telegram TTS into core + enable model-driven TTS tags by default for expressive audio replies. (#1559) Thanks @Glucksberg. https://docs.clawd.bot/tts
+- Gateway: add `/tools/invoke` HTTP endpoint for direct tool calls (auth + tool policy enforced). (#1575) Thanks @vignesh07. https://docs.clawd.bot/gateway/tools-invoke-http-api
+- Heartbeat: per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer. https://docs.clawd.bot/gateway/heartbeat
+- Deploy: add Fly.io deployment support + guide. (#1570) https://docs.clawd.bot/platforms/fly
+- Channels: add Tlon/Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a. https://docs.clawd.bot/channels/tlon
+
+### Changes
+- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.clawd.bot/multi-agent-sandbox-tools
+- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.clawd.bot/bedrock
+- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.clawd.bot/cli/system
+- CLI: add live auth probes to `clawdbot models status` for per-profile verification. (commit 40181afde) https://docs.clawd.bot/cli/models
+- CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it. (commit 2c85b1b40)
+- Browser: add node-host proxy auto-routing for remote gateways (configurable per gateway/node). (commit c3cb26f7c)
+- Plugins: add optional `llm-task` JSON-only tool for workflows. (#1498) Thanks @vignesh07. https://docs.clawd.bot/tools/llm-task
+- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
+- Agents: keep system prompt time zone-only and move current time to `session_status` for better cache hits. (commit 66eec295b)
+- Agents: remove redundant bash tool alias from tool registration/display. (#1571) Thanks @Takhoffman.
+- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc. https://docs.clawd.bot/automation/cron-vs-heartbeat
+- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc. https://docs.clawd.bot/gateway/heartbeat
+
+### Fixes
+- Sessions: accept non-UUID sessionIds for history/send/status while preserving agent scoping. (#1518)
+- Heartbeat: accept plugin channel ids for heartbeat target validation + UI hints.
+- Messaging/Sessions: mirror outbound sends into target session keys (threads + dmScope), create session entries on send, and normalize session key casing. (#1520, commit 4b6cdd1d3)
+- Sessions: reject array-backed session stores to prevent silent wipes. (#1469)
+- Gateway: compare Linux process start time to avoid PID recycling lock loops; keep locks unless stale. (#1572) Thanks @steipete.
+- Gateway: accept null optional fields in exec approval requests. (#1511) Thanks @pvoo.
+- Exec approvals: persist allowlist entry ids to keep macOS allowlist rows stable. (#1521) Thanks @ngutman.
+- Exec: honor tools.exec ask/security defaults for elevated approvals (avoid unwanted prompts). (commit 5662a9cdf)
+- Daemon: use platform PATH delimiters when building minimal service paths. (commit a4e57d3ac)
+- Linux: include env-configured user bin roots in systemd PATH and align PATH audits. (#1512) Thanks @robbyczgw-cla.
+- Tailscale: retry serve/funnel with sudo only for permission errors and keep original failure details. (#1551) Thanks @sweepies.
+- Docker: update gateway command in docker-compose and Hetzner guide. (#1514)
+- Agents: show tool error fallback when the last assistant turn only invoked tools (prevents silent stops). (commit 8ea8801d0)
+- Agents: ignore IDENTITY.md template placeholders when parsing identity. (#1556)
+- Agents: drop orphaned OpenAI Responses reasoning blocks on model switches. (#1562) Thanks @roshanasingh4.
+- Agents: add CLI log hint to "agent failed before reply" messages. (#1550) Thanks @sweepies.
+- Agents: warn and ignore tool allowlists that only reference unknown or unloaded plugin tools. (#1566)
+- Agents: treat plugin-only tool allowlists as opt-ins; keep core tools enabled. (#1467)
+- Agents: honor enqueue overrides for embedded runs to avoid queue deadlocks in tests. (commit 084002998)
+- Slack: honor open groupPolicy for unlisted channels in message + slash gating. (#1563) Thanks @itsjaydesu.
+- Discord: limit autoThread mention bypass to bot-owned threads; keep ack reactions mention-gated. (#1511) Thanks @pvoo.
+- Discord: retry rate-limited allowlist resolution + command deploy to avoid gateway crashes. (commit f70ac0c7c)
+- Mentions: ignore mentionPattern matches when another explicit mention is present in group chats (Slack/Discord/Telegram/WhatsApp). (commit d905ca0e0)
+- Telegram: render markdown in media captions. (#1478)
+- MS Teams: remove `.default` suffix from Graph scopes and Bot Framework probe scopes. (#1507, #1574) Thanks @Evizero.
+- Browser: keep extension relay tabs controllable when the extension reuses a session id after switching tabs. (#1160)
+- Voice wake: auto-save wake words on blur/submit across iOS/Android and align limits with macOS. (commit 69f645c66)
+- UI: keep the Control UI sidebar visible while scrolling long pages. (#1515) Thanks @pookNast.
+- UI: cache Control UI markdown rendering + memoize chat text extraction to reduce Safari typing jank. (commit d57cb2e1a)
+- TUI: forward unknown slash commands, include Gateway commands in autocomplete, and render slash replies as system output. (commit 1af227b61, commit 8195497ce, commit 6fba598ea)
+- CLI: auth probe output polish (table output, inline errors, reduced noise, and wrap fixes in `clawdbot models status`). (commit da3f2b489, commit 00ae21bed, commit 31e59cd58, commit f7dc27f2d, commit 438e782f8, commit 886752217, commit aabe0bed3, commit 81535d512, commit c63144ab1)
+- Media: only parse `MEDIA:` tags when they start the line to avoid stripping prose mentions. (#1206)
+- Media: preserve PNG alpha when possible; fall back to JPEG when still over size cap. (#1491) Thanks @robbyczgw-cla.
+- Skills: gate bird Homebrew install to macOS. (#1569) Thanks @bradleypriest.
+
## 2026.1.22
### Changes
-- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster
-- Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.clawd.bot/tools/lobster
-- Agents: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer.
-- Memory: prevent CLI hangs by deferring vector probes, adding sqlite-vec/embedding timeouts, and showing sync progress early.
-- Docs: add troubleshooting entry for gateway.mode blocking gateway start. https://docs.clawd.bot/gateway/troubleshooting
-- Docs: add /model allowlist troubleshooting note. (#1405)
-- Docs: add per-message Gmail search example for gog. (#1220) Thanks @mbelinky.
-- UI: show per-session assistant identity in the Control UI. (#1420) Thanks @robbyczgw-cla.
+- Highlight: Compaction safeguard now uses adaptive chunking, progressive fallback, and UI status + retries. (#1466) Thanks @dlauer.
+- Providers: add Antigravity usage tracking to status output. (#1490) Thanks @patelhiren.
+- Slack: add chat-type reply threading overrides via `replyToModeByChatType`. (#1442) Thanks @stefangalescu.
+- BlueBubbles: add `asVoice` support for MP3/CAF voice memos in sendAttachment. (#1477, #1482) Thanks @Nicell.
- Onboarding: add hatch choice (TUI/Web/Later), token explainer, background dashboard seed on macOS, and showcase link.
-- Onboarding: remove the run setup-token auth option (paste setup-token or reuse CLI creds instead).
-- Signal: add typing indicators and DM read receipts via signal-cli.
-- MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero.
-- CLI: add `clawdbot update wizard` for interactive channel selection and restart prompts. https://docs.clawd.bot/cli/update
-
-### Breaking
-- **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert.
### Fixes
- BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell.
- Message tool: keep path/filePath as-is for send; hydrate buffers only for sendAttachment. (#1444) Thanks @hopyky.
- Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla.
- Control UI: resolve local avatar URLs with basePath across injection + identity RPC. (#1457) Thanks @dlauer.
+- Agents: sanitize assistant history text to strip tool-call markers. (#1456) Thanks @zerone0x.
+- Discord: clarify Message Content Intent onboarding hint. (#1487) Thanks @kyleok.
+- Gateway: stop the service before uninstalling and fail if it remains loaded.
- Agents: surface concrete API error details instead of generic AI service errors.
+- Exec: fall back to non-PTY when PTY spawn fails (EBADF). (#1484)
- Exec approvals: allow per-segment allowlists for chained shell commands on gateway + node hosts. (#1458) Thanks @czekaj.
- Agents: make OpenAI sessions image-sanitize-only; gate tool-id/repair sanitization by provider.
- Doctor: honor CLAWDBOT_GATEWAY_TOKEN for auth checks and security audit token reuse. (#1448) Thanks @azade-c.
- Agents: make tool summaries more readable and only show optional params when set.
+- Agents: honor SOUL.md guidance even when the file is nested or path-qualified. (#1434) Thanks @neooriginal.
+- Matrix (plugin): persist m.direct for resolved DMs and harden room fallback. (#1436, #1486) Thanks @sibbl.
- CLI: prefer `~` for home paths in output.
- Mattermost (plugin): enforce pairing/allowlist gating, keep @username targets, and clarify plugin-only docs. (#1428) Thanks @damoahdominic.
- Agents: centralize transcript sanitization in the runner; keep
` (then the sender is added to a local allowlist store).
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`).
@@ -116,7 +116,7 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies.
## Highlights
- **[Local-first Gateway](https://docs.clawd.bot/gateway)** — single control plane for sessions, channels, tools, and events.
-- **[Multi-channel inbox](https://docs.clawd.bot/channels)** — WhatsApp, Telegram, Slack, Discord, Signal, iMessage, BlueBubbles, Microsoft Teams, Matrix, Zalo, Zalo Personal, WebChat, macOS, iOS/Android.
+- **[Multi-channel inbox](https://docs.clawd.bot/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, Microsoft Teams, Matrix, Zalo, Zalo Personal, WebChat, macOS, iOS/Android.
- **[Multi-agent routing](https://docs.clawd.bot/gateway/configuration)** — route inbound channels/accounts/peers to isolated agents (workspaces + per-agent sessions).
- **[Voice Wake](https://docs.clawd.bot/nodes/voicewake) + [Talk Mode](https://docs.clawd.bot/nodes/talk)** — always-on speech for macOS/iOS/Android with ElevenLabs.
- **[Live Canvas](https://docs.clawd.bot/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.clawd.bot/platforms/mac/canvas#canvas-a2ui).
@@ -138,7 +138,7 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies.
- [Media pipeline](https://docs.clawd.bot/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.clawd.bot/nodes/audio).
### Channels
-- [Channels](https://docs.clawd.bot/channels): [WhatsApp](https://docs.clawd.bot/channels/whatsapp) (Baileys), [Telegram](https://docs.clawd.bot/channels/telegram) (grammY), [Slack](https://docs.clawd.bot/channels/slack) (Bolt), [Discord](https://docs.clawd.bot/channels/discord) (discord.js), [Signal](https://docs.clawd.bot/channels/signal) (signal-cli), [iMessage](https://docs.clawd.bot/channels/imessage) (imsg), [BlueBubbles](https://docs.clawd.bot/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.clawd.bot/channels/msteams) (extension), [Matrix](https://docs.clawd.bot/channels/matrix) (extension), [Zalo](https://docs.clawd.bot/channels/zalo) (extension), [Zalo Personal](https://docs.clawd.bot/channels/zalouser) (extension), [WebChat](https://docs.clawd.bot/web/webchat).
+- [Channels](https://docs.clawd.bot/channels): [WhatsApp](https://docs.clawd.bot/channels/whatsapp) (Baileys), [Telegram](https://docs.clawd.bot/channels/telegram) (grammY), [Slack](https://docs.clawd.bot/channels/slack) (Bolt), [Discord](https://docs.clawd.bot/channels/discord) (discord.js), [Google Chat](https://docs.clawd.bot/channels/googlechat) (Chat API), [Signal](https://docs.clawd.bot/channels/signal) (signal-cli), [iMessage](https://docs.clawd.bot/channels/imessage) (imsg), [BlueBubbles](https://docs.clawd.bot/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.clawd.bot/channels/msteams) (extension), [Matrix](https://docs.clawd.bot/channels/matrix) (extension), [Zalo](https://docs.clawd.bot/channels/zalo) (extension), [Zalo Personal](https://docs.clawd.bot/channels/zalouser) (extension), [WebChat](https://docs.clawd.bot/web/webchat).
- [Group routing](https://docs.clawd.bot/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.clawd.bot/channels).
### Apps + nodes
@@ -169,7 +169,7 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies.
## How it works (short)
```
-WhatsApp / Telegram / Slack / Discord / Signal / iMessage / BlueBubbles / Microsoft Teams / Matrix / Zalo / Zalo Personal / WebChat
+WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBubbles / Microsoft Teams / Matrix / Zalo / Zalo Personal / WebChat
│
▼
┌───────────────────────────────┐
@@ -252,7 +252,7 @@ ClawdHub is a minimal skill registry. With ClawdHub enabled, the agent can searc
## Chat commands
-Send these in WhatsApp/Telegram/Slack/Microsoft Teams/WebChat (group commands are owner-only):
+Send these in WhatsApp/Telegram/Slack/Google Chat/Microsoft Teams/WebChat (group commands are owner-only):
- `/status` — compact session status (model + tokens, cost when available)
- `/new` or `/reset` — reset the session
@@ -478,28 +478,30 @@ Thanks to all clawtributors:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/appcast.xml b/appcast.xml
index a5cc610f6..bed929dfb 100644
--- a/appcast.xml
+++ b/appcast.xml
@@ -2,6 +2,141 @@
Clawdbot
+ -
+
2026.1.23
+ Sat, 24 Jan 2026 13:02:18 +0000
+ https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
+ 7750
+ 2026.1.23
+ 15.0
+ Clawdbot 2026.1.23
+Highlights
+
+- TTS: allow model-driven TTS tags by default for expressive audio replies (laughter, singing cues, etc.).
+
+Changes
+
+- Gateway: add /tools/invoke HTTP endpoint for direct tool calls and document it. (#1575) Thanks @vignesh07.
+- Agents: keep system prompt time zone-only and move current time to
session_status for better cache hits.
+- Agents: remove redundant bash tool alias from tool registration/display. (#1571) Thanks @Takhoffman.
+- Browser: add node-host proxy auto-routing for remote gateways (configurable per gateway/node).
+- Heartbeat: add per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer.
+- Plugins: add optional llm-task JSON-only tool for workflows. (#1498) Thanks @vignesh07.
+- CLI: restart the gateway by default after
clawdbot update; add --no-restart to skip it.
+- CLI: add live auth probes to
clawdbot models status for per-profile verification.
+- CLI: add
clawdbot system for system events + heartbeat controls; remove standalone wake.
+- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3.
+- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc.
+- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc.
+- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
+- Tlon: add Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a.
+- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt.
+- TTS: move Telegram TTS into core with auto-replies, commands, and gateway methods. (#1559) Thanks @Glucksberg.
+
+Fixes
+
+- Sessions: accept non-UUID sessionIds for history/send/status while preserving agent scoping. (#1518)
+- Gateway: compare Linux process start time to avoid PID recycling lock loops; keep locks unless stale. (#1572) Thanks @steipete.
+- Messaging: mirror outbound sends into target session keys (threads + dmScope) and create session entries on send. (#1520)
+- Sessions: normalize session key casing to lowercase for consistent routing.
+- BlueBubbles: normalize group session keys for outbound mirroring. (#1520)
+- Skills: gate bird Homebrew install to macOS. (#1569) Thanks @bradleypriest.
+- Slack: honor open groupPolicy for unlisted channels in message + slash gating. (#1563) Thanks @itsjaydesu.
+- Agents: show tool error fallback when the last assistant turn only invoked tools (prevents silent stops).
+- Agents: ignore IDENTITY.md template placeholders when parsing identity to avoid placeholder replies. (#1556)
+- Agents: drop orphaned OpenAI Responses reasoning blocks on model switches. (#1562) Thanks @roshanasingh4.
+- Docker: update gateway command in docker-compose and Hetzner guide. (#1514)
+- Sessions: reject array-backed session stores to prevent silent wipes. (#1469)
+- Voice wake: auto-save wake words on blur/submit across iOS/Android and align limits with macOS.
+- UI: keep the Control UI sidebar visible while scrolling long pages. (#1515) Thanks @pookNast.
+- UI: cache Control UI markdown rendering + memoize chat text extraction to reduce Safari typing jank.
+- Tailscale: retry serve/funnel with sudo only for permission errors and keep original failure details. (#1551) Thanks @sweepies.
+- Agents: add CLI log hint to "agent failed before reply" messages. (#1550) Thanks @sweepies.
+- Discord: limit autoThread mention bypass to bot-owned threads; keep ack reactions mention-gated. (#1511) Thanks @pvoo.
+- Discord: retry rate-limited allowlist resolution + command deploy to avoid gateway crashes.
+- Mentions: ignore mentionPattern matches when another explicit mention is present in group chats (Slack/Discord/Telegram/WhatsApp).
+- Gateway: accept null optional fields in exec approval requests. (#1511) Thanks @pvoo.
+- Exec: honor tools.exec ask/security defaults for elevated approvals (avoid unwanted prompts).
+- TUI: forward unknown slash commands (for example,
/context) to the Gateway.
+- TUI: include Gateway slash commands in autocomplete and
/help.
+- CLI: skip usage lines in
clawdbot models status when provider usage is unavailable.
+- CLI: suppress diagnostic session/run noise during auth probes.
+- CLI: hide auth probe timeout warnings from embedded runs.
+- CLI: render auth probe results as a table in
clawdbot models status.
+- CLI: suppress probe-only embedded logs unless
--verbose is set.
+- CLI: move auth probe errors below the table to reduce wrapping.
+- CLI: prevent ANSI color bleed when table cells wrap.
+- CLI: explain when auth profiles are excluded by auth.order in probe details.
+- CLI: drop the em dash when the banner tagline wraps to a second line.
+- CLI: inline auth probe errors in status rows to reduce wrapping.
+- Telegram: render markdown in media captions. (#1478)
+- Agents: honor enqueue overrides for embedded runs to avoid queue deadlocks in tests.
+- Agents: trigger model fallback when auth profiles are all in cooldown or unavailable. (#1522)
+- Daemon: use platform PATH delimiters when building minimal service paths.
+- Tests: skip embedded runner ordering assertion on Windows to avoid CI timeouts.
+- Linux: include env-configured user bin roots in systemd PATH and align PATH audits. (#1512) Thanks @robbyczgw-cla.
+- TUI: render Gateway slash-command replies as system output (for example,
/context).
+- Media: only parse
MEDIA: tags when they start the line to avoid stripping prose mentions. (#1206)
+- Media: preserve PNG alpha when possible; fall back to JPEG when still over size cap. (#1491) Thanks @robbyczgw-cla.
+- Agents: treat plugin-only tool allowlists as opt-ins; keep core tools enabled. (#1467)
+- Exec approvals: persist allowlist entry ids to keep macOS allowlist rows stable. (#1521) Thanks @ngutman.
+- MS Teams (plugin): remove
.default suffix from Graph scopes to avoid double-appending. (#1507) Thanks @Evizero.
+- MS Teams (plugin): remove
.default suffix from Bot Framework probe scope to avoid double-appending. (#1574) Thanks @Evizero.
+- Browser: keep extension relay tabs controllable when the extension reuses a session id after switching tabs. (#1160)
+- Agents: warn and ignore tool allowlists that only reference unknown or unloaded plugin tools. (#1566)
+
+
+]]>
+
+
+ -
+
2026.1.22
+ Fri, 23 Jan 2026 08:58:14 +0000
+ https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
+ 7530
+ 2026.1.22
+ 15.0
+ Clawdbot 2026.1.22
+Changes
+
+- Highlight: Compaction safeguard now uses adaptive chunking, progressive fallback, and UI status + retries. (#1466) Thanks @dlauer.
+- Providers: add Antigravity usage tracking to status output. (#1490) Thanks @patelhiren.
+- Slack: add chat-type reply threading overrides via
replyToModeByChatType. (#1442) Thanks @stefangalescu.
+- BlueBubbles: add
asVoice support for MP3/CAF voice memos in sendAttachment. (#1477, #1482) Thanks @Nicell.
+- Onboarding: add hatch choice (TUI/Web/Later), token explainer, background dashboard seed on macOS, and showcase link.
+
+Fixes
+
+- BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell.
+- Message tool: keep path/filePath as-is for send; hydrate buffers only for sendAttachment. (#1444) Thanks @hopyky.
+- Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla.
+- Control UI: resolve local avatar URLs with basePath across injection + identity RPC. (#1457) Thanks @dlauer.
+- Agents: sanitize assistant history text to strip tool-call markers. (#1456) Thanks @zerone0x.
+- Discord: clarify Message Content Intent onboarding hint. (#1487) Thanks @kyleok.
+- Gateway: stop the service before uninstalling and fail if it remains loaded.
+- Agents: surface concrete API error details instead of generic AI service errors.
+- Exec: fall back to non-PTY when PTY spawn fails (EBADF). (#1484)
+- Exec approvals: allow per-segment allowlists for chained shell commands on gateway + node hosts. (#1458) Thanks @czekaj.
+- Agents: make OpenAI sessions image-sanitize-only; gate tool-id/repair sanitization by provider.
+- Doctor: honor CLAWDBOT_GATEWAY_TOKEN for auth checks and security audit token reuse. (#1448) Thanks @azade-c.
+- Agents: make tool summaries more readable and only show optional params when set.
+- Agents: honor SOUL.md guidance even when the file is nested or path-qualified. (#1434) Thanks @neooriginal.
+- Matrix (plugin): persist m.direct for resolved DMs and harden room fallback. (#1436, #1486) Thanks @sibbl.
+- CLI: prefer
~ for home paths in output.
+- Mattermost (plugin): enforce pairing/allowlist gating, keep @username targets, and clarify plugin-only docs. (#1428) Thanks @damoahdominic.
+- Agents: centralize transcript sanitization in the runner; keep
tags and error turns intact.
+- Auth: skip auth profiles in cooldown during initial selection and rotation. (#1316) Thanks @odrobnik.
+- Agents/TUI: honor user-pinned auth profiles during cooldown and preserve search picker ranking. (#1432) Thanks @tobiasbischoff.
+- Docs: fix gog auth services example to include docs scope. (#1454) Thanks @zerone0x.
+- Slack: reduce WebClient retries to avoid duplicate sends. (#1481)
+- Slack: read thread replies for message reads when threadId is provided (replies-only). (#1450) Thanks @rodrigouroz.
+- macOS: prefer linked channels in gateway summary to avoid false “not linked” status.
+- macOS/tests: fix gateway summary lookup after guard unwrap; prevent browser opens during tests. (ECID-1483)
+
+
+]]>
+
+
-
2026.1.21
Thu, 22 Jan 2026 12:22:35 +0000
@@ -76,211 +211,5 @@
]]>
- -
-
2026.1.21
- Wed, 21 Jan 2026 08:18:22 +0000
- https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
- 7116
- 2026.1.21
- 15.0
- Clawdbot 2026.1.21
-Changes
-
-- Control UI: add copy-as-markdown with error feedback. (#1345) https://docs.clawd.bot/web/control-ui
-- Control UI: drop the legacy list view. (#1345) https://docs.clawd.bot/web/control-ui
-- TUI: add syntax highlighting for code blocks. (#1200) https://docs.clawd.bot/tui
-- TUI: session picker shows derived titles, fuzzy search, relative times, and last message preview. (#1271) https://docs.clawd.bot/tui
-- TUI: add a searchable model picker for quicker model selection. (#1198) https://docs.clawd.bot/tui
-- TUI: add input history (up/down) for submitted messages. (#1348) https://docs.clawd.bot/tui
-- ACP: add
clawdbot acp for IDE integrations. https://docs.clawd.bot/cli/acp
-- ACP: add
clawdbot acp client interactive harness for debugging. https://docs.clawd.bot/cli/acp
-- Skills: add download installs with OS-filtered options. https://docs.clawd.bot/tools/skills
-- Skills: add the local sherpa-onnx-tts skill. https://docs.clawd.bot/tools/skills
-- Memory: add hybrid BM25 + vector search (FTS5) with weighted merging and fallback. https://docs.clawd.bot/concepts/memory
-- Memory: add SQLite embedding cache to speed up reindexing and frequent updates. https://docs.clawd.bot/concepts/memory
-- Memory: add OpenAI batch indexing for embeddings when configured. https://docs.clawd.bot/concepts/memory
-- Memory: enable OpenAI batch indexing by default for OpenAI embeddings. https://docs.clawd.bot/concepts/memory
-- Memory: allow parallel OpenAI batch indexing jobs (default concurrency: 2). https://docs.clawd.bot/concepts/memory
-- Memory: render progress immediately, color batch statuses in verbose logs, and poll OpenAI batch status every 2s by default. https://docs.clawd.bot/concepts/memory
-- Memory: add
--verbose logging for memory status + batch indexing details. https://docs.clawd.bot/concepts/memory
-- Memory: add native Gemini embeddings provider for memory search. (#1151) https://docs.clawd.bot/concepts/memory
-- Browser: allow config defaults for efficient snapshots in the tool/CLI. (#1336) https://docs.clawd.bot/tools/browser
-- Nostr: add the Nostr channel plugin with profile management + onboarding defaults. (#1323) https://docs.clawd.bot/channels/nostr
-- Matrix: migrate to matrix-bot-sdk with E2EE support, location handling, and group allowlist upgrades. (#1298) https://docs.clawd.bot/channels/matrix
-- Slack: add HTTP webhook mode via Bolt HTTP receiver. (#1143) https://docs.clawd.bot/channels/slack
-- Telegram: enrich forwarded-message context with normalized origin details + legacy fallback. (#1090) https://docs.clawd.bot/channels/telegram
-- Discord: fall back to
/skill when native command limits are exceeded. (#1287)
-- Discord: expose
/skill globally. (#1287)
-- Zalouser: add channel dock metadata, config schema, setup wiring, probe, and status issues. (#1219) https://docs.clawd.bot/plugins/zalouser
-- Plugins: require manifest-embedded config schemas with preflight validation warnings. (#1272) https://docs.clawd.bot/plugins/manifest
-- Plugins: move channel catalog metadata into plugin manifests. (#1290) https://docs.clawd.bot/plugins/manifest
-- Plugins: align Nextcloud Talk policy helpers with core patterns. (#1290) https://docs.clawd.bot/plugins/manifest
-- Plugins/UI: let channel plugin metadata drive UI labels/icons and cron channel options. (#1306) https://docs.clawd.bot/web/control-ui
-- Plugins: add plugin slots with a dedicated memory slot selector. https://docs.clawd.bot/plugins/agent-tools
-- Plugins: ship the bundled BlueBubbles channel plugin (disabled by default). https://docs.clawd.bot/channels/bluebubbles
-- Plugins: migrate bundled messaging extensions to the plugin SDK and resolve plugin-sdk imports in the loader.
-- Plugins: migrate the Zalo plugin to the shared plugin SDK runtime. https://docs.clawd.bot/channels/zalo
-- Plugins: migrate the Zalo Personal plugin to the shared plugin SDK runtime. https://docs.clawd.bot/plugins/zalouser
-- Plugins: allow optional agent tools with explicit allowlists and add the plugin tool authoring guide. https://docs.clawd.bot/plugins/agent-tools
-- Plugins: auto-enable bundled channel/provider plugins when configuration is present.
-- Plugins: sync plugin sources on channel switches and update npm-installed plugins during
clawdbot update.
-- Plugins: share npm plugin update logic between
clawdbot update and clawdbot plugins update.
-- Gateway/API: add
/v1/responses (OpenResponses) with item-based input + semantic streaming events. (#1229)
-- Gateway/API: expand
/v1/responses to support file/image inputs, tool_choice, usage, and output limits. (#1229)
-- Usage: add
/usage cost summaries and macOS menu cost charts. https://docs.clawd.bot/reference/api-usage-costs
-- Security: warn when <=300B models run without sandboxing while web tools are enabled. https://docs.clawd.bot/cli/security
-- Exec: add host/security/ask routing for gateway + node exec. https://docs.clawd.bot/tools/exec
-- Exec: add
/exec directive for per-session exec defaults (host/security/ask/node). https://docs.clawd.bot/tools/exec
-- Exec approvals: migrate approvals to
~/.clawdbot/exec-approvals.json with per-agent allowlists + skill auto-allow toggle, and add approvals UI + node exec lifecycle events. https://docs.clawd.bot/tools/exec-approvals
-- Nodes: add headless node host (
clawdbot node start) for system.run/system.which. https://docs.clawd.bot/cli/node
-- Nodes: add node daemon service install/status/start/stop/restart. https://docs.clawd.bot/cli/node
-- Bridge: add
skills.bins RPC to support node host auto-allow skill bins.
-- Sessions: add daily reset policy with per-type overrides and idle windows (default 4am local), preserving legacy idle-only configs. (#1146) https://docs.clawd.bot/concepts/session
-- Sessions: allow
sessions_spawn to override thinking level for sub-agent runs. https://docs.clawd.bot/tools/subagents
-- Channels: unify thread/topic allowlist matching + command/mention gating helpers across core providers. https://docs.clawd.bot/concepts/groups
-- Models: add Qwen Portal OAuth provider support. (#1120) https://docs.clawd.bot/providers/qwen
-- Onboarding: add allowlist prompts and username-to-id resolution across core and extension channels. https://docs.clawd.bot/start/onboarding
-- Docs: clarify allowlist input types and onboarding behavior for messaging channels. https://docs.clawd.bot/start/onboarding
-- Docs: refresh Android node discovery docs for the Gateway WS service type. https://docs.clawd.bot/platforms/android
-- Docs: surface Amazon Bedrock in provider lists and clarify Bedrock auth env vars. (#1289) https://docs.clawd.bot/bedrock
-- Docs: clarify WhatsApp voice notes. https://docs.clawd.bot/channels/whatsapp
-- Docs: clarify Windows WSL portproxy LAN access notes. https://docs.clawd.bot/platforms/windows
-- Docs: refresh bird skill install metadata and usage notes. (#1302) https://docs.clawd.bot/tools/browser-login
-- Agents: add local docs path resolution and include docs/mirror/source/community pointers in the system prompt.
-- Agents: clarify node_modules read-only guidance in agent instructions.
-- Config: stamp last-touched metadata on write and warn if the config is newer than the running build.
-- macOS: hide usage section when usage is unavailable instead of showing provider errors.
-- Android: migrate node transport to the Gateway WebSocket protocol with TLS pinning support + gateway discovery naming.
-- Android: send structured payloads in node events/invokes and include user-agent metadata in gateway connects.
-- Android: remove legacy bridge transport code now that nodes use the gateway protocol.
-- Android: bump okhttp + dnsjava to satisfy lint dependency checks.
-- Build: update workspace + core/plugin deps.
-- Build: use tsgo for dev/watch builds by default (opt out with
CLAWDBOT_TS_COMPILER=tsc).
-- Repo: remove the Peekaboo git submodule now that the SPM release is used.
-- macOS: switch PeekabooBridge integration to the tagged Swift Package Manager release.
-- macOS: stop syncing Peekaboo in postinstall.
-- Swabble: use the tagged Commander Swift package release.
-
-Breaking
-
-- BREAKING: Reject invalid/unknown config entries and refuse to start the gateway for safety. Run
clawdbot doctor --fix to repair, then update plugins (clawdbot plugins update) if you use any.
-
-Fixes
-
-- Discovery: shorten Bonjour DNS-SD service type to
_clawdbot-gw._tcp and update discovery clients/docs.
-- Diagnostics: export OTLP logs, correct queue depth tracking, and document message-flow telemetry.
-- Diagnostics: emit message-flow diagnostics across channels via shared dispatch. (#1244)
-- Diagnostics: gate heartbeat/webhook logging. (#1244)
-- Gateway: strip inbound envelope headers from chat history messages to keep clients clean.
-- Gateway: clarify unauthorized handshake responses with token/password mismatch guidance.
-- Gateway: allow mobile node client ids for iOS + Android handshake validation. (#1354)
-- Gateway: clarify connect/validation errors for gateway params. (#1347)
-- Gateway: preserve restart wake routing + thread replies across restarts. (#1337)
-- Gateway: reschedule per-agent heartbeats on config hot reload without restarting the runner.
-- Gateway: require authorized restarts for SIGUSR1 (restart/apply/update) so config gating can't be bypassed.
-- Cron: auto-deliver isolated agent output to explicit targets without tool calls. (#1285)
-- Agents: preserve subagent announce thread/topic routing + queued replies across channels. (#1241)
-- Agents: propagate accountId into embedded runs so sub-agent announce routing honors the originating account. (#1058)
-- Agents: avoid treating timeout errors with "aborted" messages as user aborts, so model fallback still runs. (#1137)
-- Agents: sanitize oversized image payloads before send and surface image-dimension errors.
-- Sessions: fall back to session labels when listing display names. (#1124)
-- Compaction: include tool failure summaries in safeguard compaction to prevent retry loops. (#1084)
-- Config: log invalid config issues once per run and keep invalid-config errors stackless.
-- Config: allow Perplexity as a web_search provider in config validation. (#1230)
-- Config: allow custom fields under
skills.entries..config for skill credentials/config. (#1226)
-- Doctor: clarify plugin auto-enable hint text in the startup banner.
-- Doctor: canonicalize legacy session keys in session stores to prevent stale metadata. (#1169)
-- Docs: make docs:list fail fast with a clear error if the docs directory is missing.
-- Plugins: add Nextcloud Talk manifest for plugin config validation. (#1297)
-- Plugins: surface plugin load/register/config errors in gateway logs with plugin/source context.
-- CLI: preserve cron delivery settings when editing message payloads. (#1322)
-- CLI: keep
clawdbot logs output resilient to broken pipes while preserving progress output.
-- CLI: avoid duplicating --profile/--dev flags when formatting commands.
-- CLI: centralize CLI command registration to keep fast-path routing and program wiring in sync. (#1207)
-- CLI: keep banners on routed commands, restore config guarding outside fast-path routing, and tighten fast-path flag parsing while skipping console capture for extra speed. (#1195)
-- CLI: skip runner rebuilds when dist is fresh. (#1231)
-- CLI: add WSL2/systemd unavailable hints in daemon status/doctor output.
-- Status: route native
/status to the active agent so model selection reflects the correct profile. (#1301)
-- Status: show both usage windows with reset hints when usage data is available. (#1101)
-- UI: keep config form enums typed, preserve empty strings, protect sensitive defaults, and deepen config search. (#1315)
-- UI: preserve ordered list numbering in chat markdown. (#1341)
-- UI: allow Control UI to read gatewayUrl from URL params for remote WebSocket targets. (#1342)
-- UI: prevent double-scroll in Control UI chat by locking chat layout to the viewport. (#1283)
-- UI: enable shell mode for sync Windows spawns to avoid
pnpm ui:build EINVAL. (#1212)
-- TUI: keep thinking blocks ordered before content during streaming and isolate per-run assembly. (#1202)
-- TUI: align custom editor initialization with the latest pi-tui API. (#1298)
-- TUI: show generic empty-state text for searchable pickers. (#1201)
-- TUI: highlight model search matches and stabilize search ordering.
-- Configure: hide OpenRouter auto routing model from the model picker. (#1182)
-- Memory: show total file counts + scan issues in
clawdbot memory status.
-- Memory: fall back to non-batch embeddings after repeated batch failures.
-- Memory: apply OpenAI batch defaults even without explicit remote config.
-- Memory: index atomically so failed reindex preserves the previous memory database. (#1151)
-- Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151)
-- Memory: retry transient 5xx errors (Cloudflare) during embedding indexing.
-- Memory: parallelize embedding indexing with rate-limit retries.
-- Memory: split overly long lines to keep embeddings under token limits.
-- Memory: skip empty chunks to avoid invalid embedding inputs.
-- Memory: split embedding batches to avoid OpenAI token limits during indexing.
-- Memory: probe sqlite-vec availability in
clawdbot memory status.
-- Exec approvals: enforce allowlist when ask is off.
-- Exec approvals: prefer raw command for node approvals/events.
-- Tools: show exec elevated flag before the command and keep it outside markdown in tool summaries.
-- Tools: return a companion-app-required message when node exec is requested with no paired node.
-- Tools: return a companion-app-required message when
system.run is requested without a supporting node.
-- Exec: default gateway/node exec security to allowlist when unset (sandbox stays deny).
-- Exec: prefer bash when fish is default shell, falling back to sh if bash is missing. (#1297)
-- Exec: merge login-shell PATH for host=gateway exec while keeping daemon PATH minimal. (#1304)
-- Streaming: emit assistant deltas for OpenAI-compatible SSE chunks. (#1147)
-- Discord: make resolve warnings avoid raw JSON payloads on rate limits.
-- Discord: process message handlers in parallel across sessions to avoid event queue blocking. (#1295)
-- Discord: stop reconnecting the gateway after aborts to prevent duplicate listeners.
-- Discord: only emit slow listener warnings after 30s.
-- Discord: inherit parent channel allowlists for thread slash commands and reactions. (#1123)
-- Telegram: honor pairing allowlists for native slash commands.
-- Telegram: preserve hidden text_link URLs by expanding entities in inbound text. (#1118)
-- Slack: resolve Bolt import interop for Bun + Node. (#1191)
-- Web search: infer Perplexity base URL from API key source (direct vs OpenRouter).
-- Web fetch: harden SSRF protection with shared hostname checks and redirect limits. (#1346)
-- Browser: register AI snapshot refs for act commands. (#1282)
-- Voice call: include request query in Twilio webhook verification when publicUrl is set. (#864)
-- Anthropic: default API prompt caching to 1h with configurable TTL override.
-- Anthropic: ignore TTL for OAuth.
-- Auth profiles: keep auto-pinned preference while allowing rotation on failover. (#1138)
-- Auth profiles: user pins stay locked. (#1138)
-- Model catalog: avoid caching import failures, log transient discovery errors, and keep partial results. (#1332)
-- Tests: stabilize Windows gateway/CLI tests by skipping sidecars, normalizing argv, and extending timeouts.
-- Tests: stabilize plugin SDK resolution and embedded agent timeouts.
-- Windows: install gateway scheduled task as the current user.
-- Windows: show friendly guidance instead of failing on access denied.
-- macOS: load menu session previews asynchronously so items populate while the menu is open.
-- macOS: use label colors for session preview text so previews render in menu subviews.
-- macOS: suppress usage error text in the menubar cost view.
-- macOS: Doctor repairs LaunchAgent bootstrap issues for Gateway + Node when listed but not loaded. (#1166)
-- macOS: avoid touching launchd in Remote over SSH so quitting the app no longer disables the remote gateway. (#1105)
-- macOS: bundle Textual resources in packaged app builds to avoid code block crashes. (#1006)
-- Daemon: include HOME in service environments to avoid missing HOME errors. (#1214)
-
-Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @NicholaiVogel, @RyanLisse, @ThePickle31, @VACInc, @Whoaa512, @YuriNachos, @aaronveklabs, @abdaraxus, @alauppe, @ameno-, @artuskg, @austinm911, @bradleypriest, @cheeeee, @dougvk, @fogboots, @gnarco, @gumadeiras, @jdrhyne, @joelklabo, @longmaba, @mukhtharcm, @odysseus0, @oscargavin, @rhjoh, @sebslight, @sibbl, @sleontenko, @steipete, @suminhthanh, @thewilloftheshadow, @tyler6204, @vignesh07, @visionik, @ysqander, @zerone0x.
-
-]]>
-
-
- -
-
2026.1.16-2
- Sat, 17 Jan 2026 12:46:22 +0000
- https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
- 6273
- 2026.1.16-2
- 15.0
- Clawdbot 2026.1.16-2
-Changes
-
-- CLI: stamp build commit into dist metadata so banners show the commit in npm installs.
-
-
-]]>
-
-
\ No newline at end of file
diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts
index 7a99b672a..d8d77ebe1 100644
--- a/apps/android/app/build.gradle.kts
+++ b/apps/android/app/build.gradle.kts
@@ -21,8 +21,8 @@ android {
applicationId = "com.clawdbot.android"
minSdk = 31
targetSdk = 36
- versionCode = 202601210
- versionName = "2026.1.21"
+ versionCode = 202601240
+ versionName = "2026.1.24"
}
buildTypes {
diff --git a/apps/android/app/src/main/java/com/clawdbot/android/WakeWords.kt b/apps/android/app/src/main/java/com/clawdbot/android/WakeWords.kt
index 855a0de7c..d54ed1e08 100644
--- a/apps/android/app/src/main/java/com/clawdbot/android/WakeWords.kt
+++ b/apps/android/app/src/main/java/com/clawdbot/android/WakeWords.kt
@@ -8,10 +8,14 @@ object WakeWords {
return input.split(",").map { it.trim() }.filter { it.isNotEmpty() }
}
+ fun parseIfChanged(input: String, current: List): List? {
+ val parsed = parseCommaSeparated(input)
+ return if (parsed == current) null else parsed
+ }
+
fun sanitize(words: List, defaults: List): List {
val cleaned =
words.map { it.trim() }.filter { it.isNotEmpty() }.take(maxWords).map { it.take(maxWordLength) }
return cleaned.ifEmpty { defaults }
}
}
-
diff --git a/apps/android/app/src/main/java/com/clawdbot/android/ui/SettingsSheet.kt b/apps/android/app/src/main/java/com/clawdbot/android/ui/SettingsSheet.kt
index aee1059bd..e3a9b3ecb 100644
--- a/apps/android/app/src/main/java/com/clawdbot/android/ui/SettingsSheet.kt
+++ b/apps/android/app/src/main/java/com/clawdbot/android/ui/SettingsSheet.kt
@@ -28,6 +28,8 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
@@ -49,7 +51,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
@@ -58,6 +63,7 @@ import com.clawdbot.android.LocationMode
import com.clawdbot.android.MainViewModel
import com.clawdbot.android.NodeForegroundService
import com.clawdbot.android.VoiceWakeMode
+import com.clawdbot.android.WakeWords
@Composable
fun SettingsSheet(viewModel: MainViewModel) {
@@ -86,6 +92,8 @@ fun SettingsSheet(viewModel: MainViewModel) {
val listState = rememberLazyListState()
val (wakeWordsText, setWakeWordsText) = remember { mutableStateOf("") }
val (advancedExpanded, setAdvancedExpanded) = remember { mutableStateOf(false) }
+ val focusManager = LocalFocusManager.current
+ var wakeWordsHadFocus by remember { mutableStateOf(false) }
val deviceModel =
remember {
listOfNotNull(Build.MANUFACTURER, Build.MODEL)
@@ -104,6 +112,12 @@ fun SettingsSheet(viewModel: MainViewModel) {
}
LaunchedEffect(wakeWords) { setWakeWordsText(wakeWords.joinToString(", ")) }
+ val commitWakeWords = {
+ val parsed = WakeWords.parseIfChanged(wakeWordsText, wakeWords)
+ if (parsed != null) {
+ viewModel.setWakeWords(parsed)
+ }
+ }
val permissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { perms ->
@@ -481,25 +495,27 @@ fun SettingsSheet(viewModel: MainViewModel) {
value = wakeWordsText,
onValueChange = setWakeWordsText,
label = { Text("Wake Words (comma-separated)") },
- modifier = Modifier.fillMaxWidth(),
+ modifier =
+ Modifier.fillMaxWidth().onFocusChanged { focusState ->
+ if (focusState.isFocused) {
+ wakeWordsHadFocus = true
+ } else if (wakeWordsHadFocus) {
+ wakeWordsHadFocus = false
+ commitWakeWords()
+ }
+ },
singleLine = true,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
+ keyboardActions =
+ KeyboardActions(
+ onDone = {
+ commitWakeWords()
+ focusManager.clearFocus()
+ },
+ ),
)
}
- item {
- Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
- Button(
- onClick = {
- val parsed = com.clawdbot.android.WakeWords.parseCommaSeparated(wakeWordsText)
- viewModel.setWakeWords(parsed)
- },
- enabled = isConnected,
- ) {
- Text("Save + Sync")
- }
-
- Button(onClick = viewModel::resetWakeWordsDefaults) { Text("Reset defaults") }
- }
- }
+ item { Button(onClick = viewModel::resetWakeWordsDefaults) { Text("Reset defaults") } }
item {
Text(
if (isConnected) {
diff --git a/apps/android/app/src/test/java/com/clawdbot/android/WakeWordsTest.kt b/apps/android/app/src/test/java/com/clawdbot/android/WakeWordsTest.kt
index 1d61383e8..9363e810c 100644
--- a/apps/android/app/src/test/java/com/clawdbot/android/WakeWordsTest.kt
+++ b/apps/android/app/src/test/java/com/clawdbot/android/WakeWordsTest.kt
@@ -1,6 +1,7 @@
package com.clawdbot.android
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
import org.junit.Test
class WakeWordsTest {
@@ -32,5 +33,18 @@ class WakeWordsTest {
assertEquals("w1", sanitized.first())
assertEquals("w${WakeWords.maxWords}", sanitized.last())
}
-}
+ @Test
+ fun parseIfChangedSkipsWhenUnchanged() {
+ val current = listOf("clawd", "claude")
+ val parsed = WakeWords.parseIfChanged(" clawd , claude ", current)
+ assertNull(parsed)
+ }
+
+ @Test
+ fun parseIfChangedReturnsUpdatedList() {
+ val current = listOf("clawd")
+ val parsed = WakeWords.parseIfChanged(" clawd , jarvis ", current)
+ assertEquals(listOf("clawd", "jarvis"), parsed)
+ }
+}
diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist
index 1b7b5b3d5..9dd7a0315 100644
--- a/apps/ios/Sources/Info.plist
+++ b/apps/ios/Sources/Info.plist
@@ -19,9 +19,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2026.1.21
+ 2026.1.24
CFBundleVersion
- 20260121
+ 20260124
NSAppTransportSecurity
NSAllowsArbitraryLoadsInWebContent
diff --git a/apps/ios/Sources/Settings/VoiceWakeWordsSettingsView.swift b/apps/ios/Sources/Settings/VoiceWakeWordsSettingsView.swift
index d13edafe2..5aef87b0c 100644
--- a/apps/ios/Sources/Settings/VoiceWakeWordsSettingsView.swift
+++ b/apps/ios/Sources/Settings/VoiceWakeWordsSettingsView.swift
@@ -1,8 +1,10 @@
import SwiftUI
+import Combine
struct VoiceWakeWordsSettingsView: View {
@Environment(NodeAppModel.self) private var appModel
@State private var triggerWords: [String] = VoiceWakePreferences.loadTriggerWords()
+ @FocusState private var focusedTriggerIndex: Int?
@State private var syncTask: Task?
var body: some View {
@@ -12,6 +14,10 @@ struct VoiceWakeWordsSettingsView: View {
TextField("Wake word", text: self.binding(for: index))
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
+ .focused(self.$focusedTriggerIndex, equals: index)
+ .onSubmit {
+ self.commitTriggerWords()
+ }
}
.onDelete(perform: self.removeWords)
@@ -39,17 +45,18 @@ struct VoiceWakeWordsSettingsView: View {
.onAppear {
if self.triggerWords.isEmpty {
self.triggerWords = VoiceWakePreferences.defaultTriggerWords
+ self.commitTriggerWords()
}
}
- .onChange(of: self.triggerWords) { _, newValue in
- // Keep local voice wake responsive even if the gateway isn't connected yet.
- VoiceWakePreferences.saveTriggerWords(newValue)
-
- let snapshot = VoiceWakePreferences.sanitizeTriggerWords(newValue)
- self.syncTask?.cancel()
- self.syncTask = Task { [snapshot, weak appModel = self.appModel] in
- try? await Task.sleep(nanoseconds: 650_000_000)
- await appModel?.setGlobalWakeWords(snapshot)
+ .onChange(of: self.focusedTriggerIndex) { oldValue, newValue in
+ guard oldValue != nil, oldValue != newValue else { return }
+ self.commitTriggerWords()
+ }
+ .onReceive(NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)) { _ in
+ guard self.focusedTriggerIndex == nil else { return }
+ let updated = VoiceWakePreferences.loadTriggerWords()
+ if updated != self.triggerWords {
+ self.triggerWords = updated
}
}
}
@@ -63,6 +70,7 @@ struct VoiceWakeWordsSettingsView: View {
if self.triggerWords.isEmpty {
self.triggerWords = VoiceWakePreferences.defaultTriggerWords
}
+ self.commitTriggerWords()
}
private func binding(for index: Int) -> Binding {
@@ -76,4 +84,15 @@ struct VoiceWakeWordsSettingsView: View {
self.triggerWords[index] = newValue
})
}
+
+ private func commitTriggerWords() {
+ VoiceWakePreferences.saveTriggerWords(self.triggerWords)
+
+ let snapshot = VoiceWakePreferences.sanitizeTriggerWords(self.triggerWords)
+ self.syncTask?.cancel()
+ self.syncTask = Task { [snapshot, weak appModel = self.appModel] in
+ try? await Task.sleep(nanoseconds: 650_000_000)
+ await appModel?.setGlobalWakeWords(snapshot)
+ }
+ }
}
diff --git a/apps/ios/Sources/Voice/VoiceWakePreferences.swift b/apps/ios/Sources/Voice/VoiceWakePreferences.swift
index 96f46518e..4c75c22a6 100644
--- a/apps/ios/Sources/Voice/VoiceWakePreferences.swift
+++ b/apps/ios/Sources/Voice/VoiceWakePreferences.swift
@@ -6,6 +6,8 @@ enum VoiceWakePreferences {
// Keep defaults aligned with the mac app.
static let defaultTriggerWords: [String] = ["clawd", "claude"]
+ static let maxWords = 32
+ static let maxWordLength = 64
static func decodeGatewayTriggers(from payloadJSON: String) -> [String]? {
guard let data = payloadJSON.data(using: .utf8) else { return nil }
@@ -30,6 +32,8 @@ enum VoiceWakePreferences {
let cleaned = words
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
+ .prefix(Self.maxWords)
+ .map { String($0.prefix(Self.maxWordLength)) }
return cleaned.isEmpty ? Self.defaultTriggerWords : cleaned
}
diff --git a/apps/ios/Tests/Info.plist b/apps/ios/Tests/Info.plist
index e0351f399..798a77421 100644
--- a/apps/ios/Tests/Info.plist
+++ b/apps/ios/Tests/Info.plist
@@ -17,8 +17,8 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 2026.1.21
+ 2026.1.24
CFBundleVersion
- 20260121
+ 20260124
diff --git a/apps/ios/Tests/VoiceWakePreferencesTests.swift b/apps/ios/Tests/VoiceWakePreferencesTests.swift
index acf501654..ec4a63afa 100644
--- a/apps/ios/Tests/VoiceWakePreferencesTests.swift
+++ b/apps/ios/Tests/VoiceWakePreferencesTests.swift
@@ -11,6 +11,18 @@ import Testing
#expect(VoiceWakePreferences.sanitizeTriggerWords(["", " "]) == VoiceWakePreferences.defaultTriggerWords)
}
+ @Test func sanitizeTriggerWordsLimitsWordLength() {
+ let long = String(repeating: "x", count: VoiceWakePreferences.maxWordLength + 5)
+ let cleaned = VoiceWakePreferences.sanitizeTriggerWords(["ok", long])
+ #expect(cleaned[1].count == VoiceWakePreferences.maxWordLength)
+ }
+
+ @Test func sanitizeTriggerWordsLimitsWordCount() {
+ let words = (1...VoiceWakePreferences.maxWords + 3).map { "w\($0)" }
+ let cleaned = VoiceWakePreferences.sanitizeTriggerWords(words)
+ #expect(cleaned.count == VoiceWakePreferences.maxWords)
+ }
+
@Test func displayStringUsesSanitizedWords() {
#expect(VoiceWakePreferences.displayString(for: ["", " "]) == "clawd, claude")
}
diff --git a/apps/ios/project.yml b/apps/ios/project.yml
index ea6519001..52faeb9d0 100644
--- a/apps/ios/project.yml
+++ b/apps/ios/project.yml
@@ -81,8 +81,8 @@ targets:
properties:
CFBundleDisplayName: Clawdbot
CFBundleIconName: AppIcon
- CFBundleShortVersionString: "2026.1.21"
- CFBundleVersion: "20260121"
+ CFBundleShortVersionString: "2026.1.24"
+ CFBundleVersion: "20260124"
UILaunchScreen: {}
UIApplicationSceneManifest:
UIApplicationSupportsMultipleScenes: false
@@ -130,5 +130,5 @@ targets:
path: Tests/Info.plist
properties:
CFBundleDisplayName: ClawdbotTests
- CFBundleShortVersionString: "2026.1.21"
- CFBundleVersion: "20260121"
+ CFBundleShortVersionString: "2026.1.24"
+ CFBundleVersion: "20260124"
diff --git a/apps/macos/Sources/Clawdbot/AppState.swift b/apps/macos/Sources/Clawdbot/AppState.swift
index fdb702f17..eeaf034d0 100644
--- a/apps/macos/Sources/Clawdbot/AppState.swift
+++ b/apps/macos/Sources/Clawdbot/AppState.swift
@@ -24,6 +24,11 @@ final class AppState {
case remote
}
+ enum RemoteTransport: String {
+ case ssh
+ case direct
+ }
+
var isPaused: Bool {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.isPaused, forKey: pauseDefaultsKey) } }
}
@@ -166,6 +171,10 @@ final class AppState {
}
}
+ var remoteTransport: RemoteTransport {
+ didSet { self.syncGatewayConfigIfNeeded() }
+ }
+
var canvasEnabled: Bool {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.canvasEnabled, forKey: canvasEnabledKey) } }
}
@@ -200,6 +209,10 @@ final class AppState {
}
}
+ var remoteUrl: String {
+ didSet { self.syncGatewayConfigIfNeeded() }
+ }
+
var remoteIdentity: String {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.remoteIdentity, forKey: remoteIdentityKey) } }
}
@@ -263,13 +276,15 @@ final class AppState {
}
let configRoot = ClawdbotConfigFile.loadDict()
- let configGateway = configRoot["gateway"] as? [String: Any]
- let configRemoteUrl = (configGateway?["remote"] as? [String: Any])?["url"] as? String
+ let configRemoteUrl = GatewayRemoteConfig.resolveUrlString(root: configRoot)
+ let configRemoteTransport = GatewayRemoteConfig.resolveTransport(root: configRoot)
let resolvedConnectionMode = ConnectionModeResolver.resolve(root: configRoot).mode
+ self.remoteTransport = configRemoteTransport
self.connectionMode = resolvedConnectionMode
let storedRemoteTarget = UserDefaults.standard.string(forKey: remoteTargetKey) ?? ""
if resolvedConnectionMode == .remote,
+ configRemoteTransport != .direct,
storedRemoteTarget.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
let host = AppState.remoteHost(from: configRemoteUrl)
{
@@ -277,6 +292,7 @@ final class AppState {
} else {
self.remoteTarget = storedRemoteTarget
}
+ self.remoteUrl = configRemoteUrl ?? ""
self.remoteIdentity = UserDefaults.standard.string(forKey: remoteIdentityKey) ?? ""
self.remoteProjectRoot = UserDefaults.standard.string(forKey: remoteProjectRootKey) ?? ""
self.remoteCliPath = UserDefaults.standard.string(forKey: remoteCliPathKey) ?? ""
@@ -354,10 +370,11 @@ final class AppState {
private func applyConfigOverrides(_ root: [String: Any]) {
let gateway = root["gateway"] as? [String: Any]
let modeRaw = (gateway?["mode"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
- let remoteUrl = (gateway?["remote"] as? [String: Any])?["url"] as? String
+ let remoteUrl = GatewayRemoteConfig.resolveUrlString(root: root)
let hasRemoteUrl = !(remoteUrl?
.trimmingCharacters(in: .whitespacesAndNewlines)
.isEmpty ?? true)
+ let remoteTransport = GatewayRemoteConfig.resolveTransport(root: root)
let desiredMode: ConnectionMode? = switch modeRaw {
case "local":
@@ -378,8 +395,17 @@ final class AppState {
self.connectionMode = .remote
}
+ if remoteTransport != self.remoteTransport {
+ self.remoteTransport = remoteTransport
+ }
+ let remoteUrlText = remoteUrl ?? ""
+ if remoteUrlText != self.remoteUrl {
+ self.remoteUrl = remoteUrlText
+ }
+
let targetMode = desiredMode ?? self.connectionMode
if targetMode == .remote,
+ remoteTransport != .direct,
let host = AppState.remoteHost(from: remoteUrl)
{
self.updateRemoteTarget(host: host)
@@ -402,6 +428,8 @@ final class AppState {
let connectionMode = self.connectionMode
let remoteTarget = self.remoteTarget
let remoteIdentity = self.remoteIdentity
+ let remoteTransport = self.remoteTransport
+ let remoteUrl = self.remoteUrl
let desiredMode: String? = switch connectionMode {
case .local:
"local"
@@ -435,39 +463,63 @@ final class AppState {
var remote = gateway["remote"] as? [String: Any] ?? [:]
var remoteChanged = false
- if let host = remoteHost {
- let existingUrl = (remote["url"] as? String)?
- .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
- let parsedExisting = existingUrl.isEmpty ? nil : URL(string: existingUrl)
- let scheme = parsedExisting?.scheme?.isEmpty == false ? parsedExisting?.scheme : "ws"
- let port = parsedExisting?.port ?? 18789
- let desiredUrl = "\(scheme ?? "ws")://\(host):\(port)"
- if existingUrl != desiredUrl {
- remote["url"] = desiredUrl
+ if remoteTransport == .direct {
+ let trimmedUrl = remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines)
+ if trimmedUrl.isEmpty {
+ if remote["url"] != nil {
+ remote.removeValue(forKey: "url")
+ remoteChanged = true
+ }
+ } else {
+ let normalizedUrl = GatewayRemoteConfig.normalizeGatewayUrlString(trimmedUrl) ?? trimmedUrl
+ if (remote["url"] as? String) != normalizedUrl {
+ remote["url"] = normalizedUrl
+ remoteChanged = true
+ }
+ }
+ if (remote["transport"] as? String) != RemoteTransport.direct.rawValue {
+ remote["transport"] = RemoteTransport.direct.rawValue
remoteChanged = true
}
- }
+ } else {
+ if remote["transport"] != nil {
+ remote.removeValue(forKey: "transport")
+ remoteChanged = true
+ }
+ if let host = remoteHost {
+ let existingUrl = (remote["url"] as? String)?
+ .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ let parsedExisting = existingUrl.isEmpty ? nil : URL(string: existingUrl)
+ let scheme = parsedExisting?.scheme?.isEmpty == false ? parsedExisting?.scheme : "ws"
+ let port = parsedExisting?.port ?? 18789
+ let desiredUrl = "\(scheme ?? "ws")://\(host):\(port)"
+ if existingUrl != desiredUrl {
+ remote["url"] = desiredUrl
+ remoteChanged = true
+ }
+ }
- let sanitizedTarget = Self.sanitizeSSHTarget(remoteTarget)
- if !sanitizedTarget.isEmpty {
- if (remote["sshTarget"] as? String) != sanitizedTarget {
- remote["sshTarget"] = sanitizedTarget
+ let sanitizedTarget = Self.sanitizeSSHTarget(remoteTarget)
+ if !sanitizedTarget.isEmpty {
+ if (remote["sshTarget"] as? String) != sanitizedTarget {
+ remote["sshTarget"] = sanitizedTarget
+ remoteChanged = true
+ }
+ } else if remote["sshTarget"] != nil {
+ remote.removeValue(forKey: "sshTarget")
remoteChanged = true
}
- } else if remote["sshTarget"] != nil {
- remote.removeValue(forKey: "sshTarget")
- remoteChanged = true
- }
- let trimmedIdentity = remoteIdentity.trimmingCharacters(in: .whitespacesAndNewlines)
- if !trimmedIdentity.isEmpty {
- if (remote["sshIdentity"] as? String) != trimmedIdentity {
- remote["sshIdentity"] = trimmedIdentity
+ let trimmedIdentity = remoteIdentity.trimmingCharacters(in: .whitespacesAndNewlines)
+ if !trimmedIdentity.isEmpty {
+ if (remote["sshIdentity"] as? String) != trimmedIdentity {
+ remote["sshIdentity"] = trimmedIdentity
+ remoteChanged = true
+ }
+ } else if remote["sshIdentity"] != nil {
+ remote.removeValue(forKey: "sshIdentity")
remoteChanged = true
}
- } else if remote["sshIdentity"] != nil {
- remote.removeValue(forKey: "sshIdentity")
- remoteChanged = true
}
if remoteChanged {
@@ -621,8 +673,10 @@ extension AppState {
state.iconOverride = .system
state.heartbeatsEnabled = true
state.connectionMode = .local
+ state.remoteTransport = .ssh
state.canvasEnabled = true
state.remoteTarget = "user@example.com"
+ state.remoteUrl = "wss://gateway.example.ts.net"
state.remoteIdentity = "~/.ssh/id_ed25519"
state.remoteProjectRoot = "~/Projects/clawdbot"
state.remoteCliPath = ""
diff --git a/apps/macos/Sources/Clawdbot/ChannelsSettings+ChannelState.swift b/apps/macos/Sources/Clawdbot/ChannelsSettings+ChannelState.swift
index 79dd97cf9..efce42781 100644
--- a/apps/macos/Sources/Clawdbot/ChannelsSettings+ChannelState.swift
+++ b/apps/macos/Sources/Clawdbot/ChannelsSettings+ChannelState.swift
@@ -40,6 +40,16 @@ extension ChannelsSettings {
return .orange
}
+ var googlechatTint: Color {
+ guard let status = self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)
+ else { return .secondary }
+ if !status.configured { return .secondary }
+ if status.lastError != nil { return .orange }
+ if status.probe?.ok == false { return .orange }
+ if status.running { return .green }
+ return .orange
+ }
+
var signalTint: Color {
guard let status = self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)
else { return .secondary }
@@ -85,6 +95,14 @@ extension ChannelsSettings {
return "Configured"
}
+ var googlechatSummary: String {
+ guard let status = self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)
+ else { return "Checking…" }
+ if !status.configured { return "Not configured" }
+ if status.running { return "Running" }
+ return "Configured"
+ }
+
var signalSummary: String {
guard let status = self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)
else { return "Checking…" }
@@ -193,6 +211,37 @@ extension ChannelsSettings {
return lines.isEmpty ? nil : lines.joined(separator: " · ")
}
+ var googlechatDetails: String? {
+ guard let status = self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)
+ else { return nil }
+ var lines: [String] = []
+ if let source = status.credentialSource {
+ lines.append("Credential: \(source)")
+ }
+ if let audienceType = status.audienceType {
+ let audience = status.audience ?? ""
+ let label = audience.isEmpty ? audienceType : "\(audienceType) \(audience)"
+ lines.append("Audience: \(label)")
+ }
+ if let probe = status.probe {
+ if probe.ok {
+ if let elapsed = probe.elapsedMs {
+ lines.append("Probe \(Int(elapsed))ms")
+ }
+ } else {
+ let code = probe.status.map { String($0) } ?? "unknown"
+ lines.append("Probe failed (\(code))")
+ }
+ }
+ if let last = self.date(fromMs: status.lastProbeAt) {
+ lines.append("Last probe \(relativeAge(from: last))")
+ }
+ if let err = status.lastError, !err.isEmpty {
+ lines.append("Error: \(err)")
+ }
+ return lines.isEmpty ? nil : lines.joined(separator: " · ")
+ }
+
var signalDetails: String? {
guard let status = self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)
else { return nil }
@@ -244,7 +293,7 @@ extension ChannelsSettings {
}
var orderedChannels: [ChannelItem] {
- let fallback = ["whatsapp", "telegram", "discord", "slack", "signal", "imessage"]
+ let fallback = ["whatsapp", "telegram", "discord", "googlechat", "slack", "signal", "imessage"]
let order = self.store.snapshot?.channelOrder ?? fallback
let channels = order.enumerated().map { index, id in
ChannelItem(
@@ -307,6 +356,8 @@ extension ChannelsSettings {
return self.telegramTint
case "discord":
return self.discordTint
+ case "googlechat":
+ return self.googlechatTint
case "signal":
return self.signalTint
case "imessage":
@@ -326,6 +377,8 @@ extension ChannelsSettings {
return self.telegramSummary
case "discord":
return self.discordSummary
+ case "googlechat":
+ return self.googlechatSummary
case "signal":
return self.signalSummary
case "imessage":
@@ -345,6 +398,8 @@ extension ChannelsSettings {
return self.telegramDetails
case "discord":
return self.discordDetails
+ case "googlechat":
+ return self.googlechatDetails
case "signal":
return self.signalDetails
case "imessage":
@@ -377,6 +432,10 @@ extension ChannelsSettings {
return self
.date(fromMs: self.channelStatus("discord", as: ChannelsStatusSnapshot.DiscordStatus.self)?
.lastProbeAt)
+ case "googlechat":
+ return self
+ .date(fromMs: self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)?
+ .lastProbeAt)
case "signal":
return self
.date(fromMs: self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)?.lastProbeAt)
@@ -411,6 +470,10 @@ extension ChannelsSettings {
guard let status = self.channelStatus("discord", as: ChannelsStatusSnapshot.DiscordStatus.self)
else { return false }
return status.lastError?.isEmpty == false || status.probe?.ok == false
+ case "googlechat":
+ guard let status = self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)
+ else { return false }
+ return status.lastError?.isEmpty == false || status.probe?.ok == false
case "signal":
guard let status = self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)
else { return false }
diff --git a/apps/macos/Sources/Clawdbot/ChannelsStore.swift b/apps/macos/Sources/Clawdbot/ChannelsStore.swift
index e62e737a4..1cbeca381 100644
--- a/apps/macos/Sources/Clawdbot/ChannelsStore.swift
+++ b/apps/macos/Sources/Clawdbot/ChannelsStore.swift
@@ -85,6 +85,28 @@ struct ChannelsStatusSnapshot: Codable {
let lastProbeAt: Double?
}
+ struct GoogleChatProbe: Codable {
+ let ok: Bool
+ let status: Int?
+ let error: String?
+ let elapsedMs: Double?
+ }
+
+ struct GoogleChatStatus: Codable {
+ let configured: Bool
+ let credentialSource: String?
+ let audienceType: String?
+ let audience: String?
+ let webhookPath: String?
+ let webhookUrl: String?
+ let running: Bool
+ let lastStartAt: Double?
+ let lastStopAt: Double?
+ let lastError: String?
+ let probe: GoogleChatProbe?
+ let lastProbeAt: Double?
+ }
+
struct SignalProbe: Codable {
let ok: Bool
let status: Int?
diff --git a/apps/macos/Sources/Clawdbot/Constants.swift b/apps/macos/Sources/Clawdbot/Constants.swift
index 25f2589e3..b55bd6d20 100644
--- a/apps/macos/Sources/Clawdbot/Constants.swift
+++ b/apps/macos/Sources/Clawdbot/Constants.swift
@@ -12,6 +12,8 @@ let voiceWakeTriggerChimeKey = "clawdbot.voiceWakeTriggerChime"
let voiceWakeSendChimeKey = "clawdbot.voiceWakeSendChime"
let showDockIconKey = "clawdbot.showDockIcon"
let defaultVoiceWakeTriggers = ["clawd", "claude"]
+let voiceWakeMaxWords = 32
+let voiceWakeMaxWordLength = 64
let voiceWakeMicKey = "clawdbot.voiceWakeMicID"
let voiceWakeMicNameKey = "clawdbot.voiceWakeMicName"
let voiceWakeLocaleKey = "clawdbot.voiceWakeLocaleID"
diff --git a/apps/macos/Sources/Clawdbot/DebugSettings.swift b/apps/macos/Sources/Clawdbot/DebugSettings.swift
index 51886bc5d..ec313b2a0 100644
--- a/apps/macos/Sources/Clawdbot/DebugSettings.swift
+++ b/apps/macos/Sources/Clawdbot/DebugSettings.swift
@@ -102,7 +102,9 @@ struct DebugSettings: View {
}
}
- Text("When enabled, Clawdbot won't install or manage \(gatewayLaunchdLabel). It will only attach to an existing Gateway.")
+ Text(
+ "When enabled, Clawdbot won't install or manage \(gatewayLaunchdLabel). " +
+ "It will only attach to an existing Gateway.")
.font(.caption)
.foregroundStyle(.secondary)
diff --git a/apps/macos/Sources/Clawdbot/ExecApprovals.swift b/apps/macos/Sources/Clawdbot/ExecApprovals.swift
index 537ceeaad..c6f413922 100644
--- a/apps/macos/Sources/Clawdbot/ExecApprovals.swift
+++ b/apps/macos/Sources/Clawdbot/ExecApprovals.swift
@@ -84,11 +84,52 @@ enum ExecApprovalDecision: String, Codable, Sendable {
case deny
}
-struct ExecAllowlistEntry: Codable, Hashable {
+struct ExecAllowlistEntry: Codable, Hashable, Identifiable {
+ var id: UUID
var pattern: String
var lastUsedAt: Double?
var lastUsedCommand: String?
var lastResolvedPath: String?
+
+ init(
+ id: UUID = UUID(),
+ pattern: String,
+ lastUsedAt: Double? = nil,
+ lastUsedCommand: String? = nil,
+ lastResolvedPath: String? = nil)
+ {
+ self.id = id
+ self.pattern = pattern
+ self.lastUsedAt = lastUsedAt
+ self.lastUsedCommand = lastUsedCommand
+ self.lastResolvedPath = lastResolvedPath
+ }
+
+ private enum CodingKeys: String, CodingKey {
+ case id
+ case pattern
+ case lastUsedAt
+ case lastUsedCommand
+ case lastResolvedPath
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ self.id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
+ self.pattern = try container.decode(String.self, forKey: .pattern)
+ self.lastUsedAt = try container.decodeIfPresent(Double.self, forKey: .lastUsedAt)
+ self.lastUsedCommand = try container.decodeIfPresent(String.self, forKey: .lastUsedCommand)
+ self.lastResolvedPath = try container.decodeIfPresent(String.self, forKey: .lastResolvedPath)
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(self.id, forKey: .id)
+ try container.encode(self.pattern, forKey: .pattern)
+ try container.encodeIfPresent(self.lastUsedAt, forKey: .lastUsedAt)
+ try container.encodeIfPresent(self.lastUsedCommand, forKey: .lastUsedCommand)
+ try container.encodeIfPresent(self.lastResolvedPath, forKey: .lastResolvedPath)
+ }
}
struct ExecApprovalsDefaults: Codable {
@@ -295,6 +336,7 @@ enum ExecApprovalsStore {
let allowlist = ((wildcardEntry.allowlist ?? []) + (agentEntry.allowlist ?? []))
.map { entry in
ExecAllowlistEntry(
+ id: entry.id,
pattern: entry.pattern.trimmingCharacters(in: .whitespacesAndNewlines),
lastUsedAt: entry.lastUsedAt,
lastUsedCommand: entry.lastUsedCommand,
@@ -379,6 +421,7 @@ enum ExecApprovalsStore {
let allowlist = (entry.allowlist ?? []).map { item -> ExecAllowlistEntry in
guard item.pattern == pattern else { return item }
return ExecAllowlistEntry(
+ id: item.id,
pattern: item.pattern,
lastUsedAt: Date().timeIntervalSince1970 * 1000,
lastUsedCommand: command,
@@ -398,6 +441,7 @@ enum ExecApprovalsStore {
let cleaned = allowlist
.map { item in
ExecAllowlistEntry(
+ id: item.id,
pattern: item.pattern.trimmingCharacters(in: .whitespacesAndNewlines),
lastUsedAt: item.lastUsedAt,
lastUsedCommand: item.lastUsedCommand,
diff --git a/apps/macos/Sources/Clawdbot/GatewayConnection.swift b/apps/macos/Sources/Clawdbot/GatewayConnection.swift
index 9feb98ba9..7facc6d61 100644
--- a/apps/macos/Sources/Clawdbot/GatewayConnection.swift
+++ b/apps/macos/Sources/Clawdbot/GatewayConnection.swift
@@ -11,6 +11,7 @@ enum GatewayAgentChannel: String, Codable, CaseIterable, Sendable {
case whatsapp
case telegram
case discord
+ case googlechat
case slack
case signal
case imessage
diff --git a/apps/macos/Sources/Clawdbot/GatewayDiscoveryHelpers.swift b/apps/macos/Sources/Clawdbot/GatewayDiscoveryHelpers.swift
new file mode 100644
index 000000000..2bb0aaf15
--- /dev/null
+++ b/apps/macos/Sources/Clawdbot/GatewayDiscoveryHelpers.swift
@@ -0,0 +1,47 @@
+import ClawdbotDiscovery
+import Foundation
+
+enum GatewayDiscoveryHelpers {
+ static func sshTarget(for gateway: GatewayDiscoveryModel.DiscoveredGateway) -> String? {
+ let host = self.sanitizedTailnetHost(gateway.tailnetDns) ?? gateway.lanHost
+ guard let host = self.trimmed(host), !host.isEmpty else { return nil }
+ let user = NSUserName()
+ var target = "\(user)@\(host)"
+ if gateway.sshPort != 22 {
+ target += ":\(gateway.sshPort)"
+ }
+ return target
+ }
+
+ static func directUrl(for gateway: GatewayDiscoveryModel.DiscoveredGateway) -> String? {
+ self.directGatewayUrl(
+ tailnetDns: gateway.tailnetDns,
+ lanHost: gateway.lanHost,
+ gatewayPort: gateway.gatewayPort)
+ }
+
+ static func directGatewayUrl(
+ tailnetDns: String?,
+ lanHost: String?,
+ gatewayPort: Int?) -> String?
+ {
+ if let tailnetDns = self.sanitizedTailnetHost(tailnetDns) {
+ return "wss://\(tailnetDns)"
+ }
+ guard let lanHost = self.trimmed(lanHost), !lanHost.isEmpty else { return nil }
+ let port = gatewayPort ?? 18789
+ return "ws://\(lanHost):\(port)"
+ }
+
+ static func sanitizedTailnetHost(_ host: String?) -> String? {
+ guard let host = self.trimmed(host), !host.isEmpty else { return nil }
+ if host.hasSuffix(".internal.") || host.hasSuffix(".internal") {
+ return nil
+ }
+ return host
+ }
+
+ private static func trimmed(_ value: String?) -> String? {
+ value?.trimmingCharacters(in: .whitespacesAndNewlines)
+ }
+}
diff --git a/apps/macos/Sources/Clawdbot/GatewayDiscoveryMenu.swift b/apps/macos/Sources/Clawdbot/GatewayDiscoveryMenu.swift
index a064b788a..2a4c571eb 100644
--- a/apps/macos/Sources/Clawdbot/GatewayDiscoveryMenu.swift
+++ b/apps/macos/Sources/Clawdbot/GatewayDiscoveryMenu.swift
@@ -4,6 +4,8 @@ import SwiftUI
struct GatewayDiscoveryInlineList: View {
var discovery: GatewayDiscoveryModel
var currentTarget: String?
+ var currentUrl: String?
+ var transport: AppState.RemoteTransport
var onSelect: (GatewayDiscoveryModel.DiscoveredGateway) -> Void
@State private var hoveredGatewayID: GatewayDiscoveryModel.DiscoveredGateway.ID?
@@ -25,9 +27,8 @@ struct GatewayDiscoveryInlineList: View {
} else {
VStack(alignment: .leading, spacing: 6) {
ForEach(self.discovery.gateways.prefix(6)) { gateway in
- let target = self.suggestedSSHTarget(gateway)
- let selected = (target != nil && self.currentTarget?
- .trimmingCharacters(in: .whitespacesAndNewlines) == target)
+ let display = self.displayInfo(for: gateway)
+ let selected = display.selected
Button {
withAnimation(.spring(response: 0.25, dampingFraction: 0.9)) {
@@ -40,7 +41,7 @@ struct GatewayDiscoveryInlineList: View {
.font(.callout.weight(.semibold))
.lineLimit(1)
.truncationMode(.tail)
- Text(target ?? "Gateway pairing only")
+ Text(display.label)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.lineLimit(1)
@@ -83,27 +84,26 @@ struct GatewayDiscoveryInlineList: View {
.fill(Color(NSColor.controlBackgroundColor)))
}
}
- .help("Click a discovered gateway to fill the SSH target.")
+ .help(self.transport == .direct
+ ? "Click a discovered gateway to fill the gateway URL."
+ : "Click a discovered gateway to fill the SSH target.")
}
- private func suggestedSSHTarget(_ gateway: GatewayDiscoveryModel.DiscoveredGateway) -> String? {
- let host = self.sanitizedTailnetHost(gateway.tailnetDns) ?? gateway.lanHost
- guard let host else { return nil }
- let user = NSUserName()
- return GatewayDiscoveryModel.buildSSHTarget(
- user: user,
- host: host,
- port: gateway.sshPort)
- }
-
- private func sanitizedTailnetHost(_ host: String?) -> String? {
- guard let host else { return nil }
- let trimmed = host.trimmingCharacters(in: .whitespacesAndNewlines)
- if trimmed.isEmpty { return nil }
- if trimmed.hasSuffix(".internal.") || trimmed.hasSuffix(".internal") {
- return nil
+ private func displayInfo(
+ for gateway: GatewayDiscoveryModel.DiscoveredGateway) -> (label: String, selected: Bool)
+ {
+ switch self.transport {
+ case .direct:
+ let url = GatewayDiscoveryHelpers.directUrl(for: gateway)
+ let label = url ?? "Gateway pairing only"
+ let selected = url != nil && self.trimmed(self.currentUrl) == url
+ return (label, selected)
+ case .ssh:
+ let target = GatewayDiscoveryHelpers.sshTarget(for: gateway)
+ let label = target ?? "Gateway pairing only"
+ let selected = target != nil && self.trimmed(self.currentTarget) == target
+ return (label, selected)
}
- return trimmed
}
private func rowBackground(selected: Bool, hovered: Bool) -> Color {
@@ -111,6 +111,10 @@ struct GatewayDiscoveryInlineList: View {
if hovered { return Color.secondary.opacity(0.08) }
return Color.clear
}
+
+ private func trimmed(_ value: String?) -> String {
+ value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ }
}
struct GatewayDiscoveryMenu: View {
diff --git a/apps/macos/Sources/Clawdbot/GatewayEndpointStore.swift b/apps/macos/Sources/Clawdbot/GatewayEndpointStore.swift
index 633b7d872..d9385bc79 100644
--- a/apps/macos/Sources/Clawdbot/GatewayEndpointStore.swift
+++ b/apps/macos/Sources/Clawdbot/GatewayEndpointStore.swift
@@ -311,6 +311,19 @@ actor GatewayEndpointStore {
token: token,
password: password))
case .remote:
+ let root = ClawdbotConfigFile.loadDict()
+ if GatewayRemoteConfig.resolveTransport(root: root) == .direct {
+ guard let url = GatewayRemoteConfig.resolveGatewayUrl(root: root) else {
+ self.cancelRemoteEnsure()
+ self.setState(.unavailable(
+ mode: .remote,
+ reason: "gateway.remote.url missing or invalid for direct transport"))
+ return
+ }
+ self.cancelRemoteEnsure()
+ self.setState(.ready(mode: .remote, url: url, token: token, password: password))
+ return
+ }
let port = await self.deps.remotePortIfRunning()
guard let port else {
self.setState(.connecting(mode: .remote, detail: Self.remoteConnectingDetail))
@@ -341,6 +354,25 @@ actor GatewayEndpointStore {
code: 1,
userInfo: [NSLocalizedDescriptionKey: "Remote mode is not enabled"])
}
+ let root = ClawdbotConfigFile.loadDict()
+ if GatewayRemoteConfig.resolveTransport(root: root) == .direct {
+ guard let url = GatewayRemoteConfig.resolveGatewayUrl(root: root) else {
+ throw NSError(
+ domain: "GatewayEndpoint",
+ code: 1,
+ userInfo: [NSLocalizedDescriptionKey: "gateway.remote.url missing or invalid"])
+ }
+ guard let port = GatewayRemoteConfig.defaultPort(for: url),
+ let portInt = UInt16(exactly: port)
+ else {
+ throw NSError(
+ domain: "GatewayEndpoint",
+ code: 1,
+ userInfo: [NSLocalizedDescriptionKey: "Invalid gateway.remote.url port"])
+ }
+ self.logger.info("remote transport direct; skipping SSH tunnel")
+ return portInt
+ }
let config = try await self.ensureRemoteConfig(detail: Self.remoteConnectingDetail)
guard let portInt = config.0.port, let port = UInt16(exactly: portInt) else {
throw NSError(
@@ -401,6 +433,21 @@ actor GatewayEndpointStore {
userInfo: [NSLocalizedDescriptionKey: "Remote mode is not enabled"])
}
+ let root = ClawdbotConfigFile.loadDict()
+ if GatewayRemoteConfig.resolveTransport(root: root) == .direct {
+ guard let url = GatewayRemoteConfig.resolveGatewayUrl(root: root) else {
+ throw NSError(
+ domain: "GatewayEndpoint",
+ code: 1,
+ userInfo: [NSLocalizedDescriptionKey: "gateway.remote.url missing or invalid"])
+ }
+ let token = self.deps.token()
+ let password = self.deps.password()
+ self.cancelRemoteEnsure()
+ self.setState(.ready(mode: .remote, url: url, token: token, password: password))
+ return (url, token, password)
+ }
+
self.kickRemoteEnsureIfNeeded(detail: detail)
guard let ensure = self.remoteEnsure else {
throw NSError(domain: "GatewayEndpoint", code: 1, userInfo: [NSLocalizedDescriptionKey: "Connecting…"])
diff --git a/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift b/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift
index 4dc02593b..60964fa39 100644
--- a/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift
+++ b/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift
@@ -248,12 +248,11 @@ final class GatewayProcessManager {
guard let linkId else {
return "port \(port), health probe succeeded, \(instanceText)"
}
- let linked = linkId.flatMap { snap.channels[$0]?.linked } ?? false
- let authAge = linkId.flatMap { snap.channels[$0]?.authAgeMs }.flatMap(msToAge) ?? "unknown age"
+ let linked = snap.channels[linkId]?.linked ?? false
+ let authAge = snap.channels[linkId]?.authAgeMs.flatMap(msToAge) ?? "unknown age"
let label =
- linkId.flatMap { snap.channelLabels?[$0] } ??
- linkId?.capitalized ??
- "channel"
+ snap.channelLabels?[linkId] ??
+ linkId.capitalized
let linkText = linked ? "linked" : "not linked"
return "port \(port), \(label) \(linkText), auth \(authAge), \(instanceText)"
}
diff --git a/apps/macos/Sources/Clawdbot/GatewayRemoteConfig.swift b/apps/macos/Sources/Clawdbot/GatewayRemoteConfig.swift
new file mode 100644
index 000000000..0b8ab3515
--- /dev/null
+++ b/apps/macos/Sources/Clawdbot/GatewayRemoteConfig.swift
@@ -0,0 +1,64 @@
+import Foundation
+
+enum GatewayRemoteConfig {
+ static func resolveTransport(root: [String: Any]) -> AppState.RemoteTransport {
+ guard let gateway = root["gateway"] as? [String: Any],
+ let remote = gateway["remote"] as? [String: Any],
+ let raw = remote["transport"] as? String
+ else {
+ return .ssh
+ }
+ let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
+ return trimmed == AppState.RemoteTransport.direct.rawValue ? .direct : .ssh
+ }
+
+ static func resolveUrlString(root: [String: Any]) -> String? {
+ guard let gateway = root["gateway"] as? [String: Any],
+ let remote = gateway["remote"] as? [String: Any],
+ let urlRaw = remote["url"] as? String
+ else {
+ return nil
+ }
+ let trimmed = urlRaw.trimmingCharacters(in: .whitespacesAndNewlines)
+ return trimmed.isEmpty ? nil : trimmed
+ }
+
+ static func resolveGatewayUrl(root: [String: Any]) -> URL? {
+ guard let raw = self.resolveUrlString(root: root) else { return nil }
+ return self.normalizeGatewayUrl(raw)
+ }
+
+ static func normalizeGatewayUrlString(_ raw: String) -> String? {
+ self.normalizeGatewayUrl(raw)?.absoluteString
+ }
+
+ static func normalizeGatewayUrl(_ raw: String) -> URL? {
+ let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !trimmed.isEmpty, let url = URL(string: trimmed) else { return nil }
+ let scheme = url.scheme?.lowercased() ?? ""
+ guard scheme == "ws" || scheme == "wss" else { return nil }
+ let host = url.host?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ guard !host.isEmpty else { return nil }
+ if scheme == "ws", url.port == nil {
+ guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
+ return url
+ }
+ components.port = 18789
+ return components.url
+ }
+ return url
+ }
+
+ static func defaultPort(for url: URL) -> Int? {
+ if let port = url.port { return port }
+ let scheme = url.scheme?.lowercased() ?? ""
+ switch scheme {
+ case "wss":
+ return 443
+ case "ws":
+ return 18789
+ default:
+ return nil
+ }
+ }
+}
diff --git a/apps/macos/Sources/Clawdbot/GeneralSettings.swift b/apps/macos/Sources/Clawdbot/GeneralSettings.swift
index bffd75b3c..18dd423a2 100644
--- a/apps/macos/Sources/Clawdbot/GeneralSettings.swift
+++ b/apps/macos/Sources/Clawdbot/GeneralSettings.swift
@@ -17,6 +17,7 @@ struct GeneralSettings: View {
@State private var showRemoteAdvanced = false
private let isPreview = ProcessInfo.processInfo.isPreview
private var isNixMode: Bool { ProcessInfo.processInfo.isNixMode }
+ private var remoteLabelWidth: CGFloat { 88 }
var body: some View {
ScrollView(.vertical) {
@@ -104,7 +105,7 @@ struct GeneralSettings: View {
Picker("Mode", selection: self.$state.connectionMode) {
Text("Not configured").tag(AppState.ConnectionMode.unconfigured)
Text("Local (this Mac)").tag(AppState.ConnectionMode.local)
- Text("Remote over SSH").tag(AppState.ConnectionMode.remote)
+ Text("Remote (another host)").tag(AppState.ConnectionMode.remote)
}
.pickerStyle(.menu)
.labelsHidden()
@@ -136,60 +137,51 @@ struct GeneralSettings: View {
private var remoteCard: some View {
VStack(alignment: .leading, spacing: 10) {
- HStack(alignment: .center, spacing: 10) {
- Text("SSH")
- .font(.callout.weight(.semibold))
- .frame(width: 48, alignment: .leading)
- TextField("user@host[:22]", text: self.$state.remoteTarget)
- .textFieldStyle(.roundedBorder)
- .frame(maxWidth: .infinity)
- Button {
- Task { await self.testRemote() }
- } label: {
- if self.remoteStatus == .checking {
- ProgressView().controlSize(.small)
- } else {
- Text("Test remote")
- }
- }
- .buttonStyle(.borderedProminent)
- .disabled(self.remoteStatus == .checking || self.state.remoteTarget
- .trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
+ self.remoteTransportRow
+
+ if self.state.remoteTransport == .ssh {
+ self.remoteSshRow
+ } else {
+ self.remoteDirectRow
}
GatewayDiscoveryInlineList(
discovery: self.gatewayDiscovery,
- currentTarget: self.state.remoteTarget)
+ currentTarget: self.state.remoteTarget,
+ currentUrl: self.state.remoteUrl,
+ transport: self.state.remoteTransport)
{ gateway in
self.applyDiscoveredGateway(gateway)
}
- .padding(.leading, 58)
+ .padding(.leading, self.remoteLabelWidth + 10)
self.remoteStatusView
- .padding(.leading, 58)
+ .padding(.leading, self.remoteLabelWidth + 10)
- DisclosureGroup(isExpanded: self.$showRemoteAdvanced) {
- VStack(alignment: .leading, spacing: 8) {
- LabeledContent("Identity file") {
- TextField("/Users/you/.ssh/id_ed25519", text: self.$state.remoteIdentity)
- .textFieldStyle(.roundedBorder)
- .frame(width: 280)
- }
- LabeledContent("Project root") {
- TextField("/home/you/Projects/clawdbot", text: self.$state.remoteProjectRoot)
- .textFieldStyle(.roundedBorder)
- .frame(width: 280)
- }
- LabeledContent("CLI path") {
- TextField("/Applications/Clawdbot.app/.../clawdbot", text: self.$state.remoteCliPath)
- .textFieldStyle(.roundedBorder)
- .frame(width: 280)
+ if self.state.remoteTransport == .ssh {
+ DisclosureGroup(isExpanded: self.$showRemoteAdvanced) {
+ VStack(alignment: .leading, spacing: 8) {
+ LabeledContent("Identity file") {
+ TextField("/Users/you/.ssh/id_ed25519", text: self.$state.remoteIdentity)
+ .textFieldStyle(.roundedBorder)
+ .frame(width: 280)
+ }
+ LabeledContent("Project root") {
+ TextField("/home/you/Projects/clawdbot", text: self.$state.remoteProjectRoot)
+ .textFieldStyle(.roundedBorder)
+ .frame(width: 280)
+ }
+ LabeledContent("CLI path") {
+ TextField("/Applications/Clawdbot.app/.../clawdbot", text: self.$state.remoteCliPath)
+ .textFieldStyle(.roundedBorder)
+ .frame(width: 280)
+ }
}
+ .padding(.top, 4)
+ } label: {
+ Text("Advanced")
+ .font(.callout.weight(.semibold))
}
- .padding(.top, 4)
- } label: {
- Text("Advanced")
- .font(.callout.weight(.semibold))
}
// Diagnostics
@@ -219,16 +211,89 @@ struct GeneralSettings: View {
}
}
- Text("Tip: enable Tailscale for stable remote access.")
- .font(.footnote)
- .foregroundStyle(.secondary)
- .lineLimit(1)
+ if self.state.remoteTransport == .ssh {
+ Text("Tip: enable Tailscale for stable remote access.")
+ .font(.footnote)
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ } else {
+ Text("Tip: use Tailscale Serve so the gateway has a valid HTTPS cert.")
+ .font(.footnote)
+ .foregroundStyle(.secondary)
+ .lineLimit(2)
+ }
}
.transition(.opacity)
.onAppear { self.gatewayDiscovery.start() }
.onDisappear { self.gatewayDiscovery.stop() }
}
+ private var remoteTransportRow: some View {
+ HStack(alignment: .center, spacing: 10) {
+ Text("Transport")
+ .font(.callout.weight(.semibold))
+ .frame(width: self.remoteLabelWidth, alignment: .leading)
+ Picker("Transport", selection: self.$state.remoteTransport) {
+ Text("SSH tunnel").tag(AppState.RemoteTransport.ssh)
+ Text("Direct (ws/wss)").tag(AppState.RemoteTransport.direct)
+ }
+ .pickerStyle(.segmented)
+ .frame(maxWidth: 320)
+ }
+ }
+
+ private var remoteSshRow: some View {
+ HStack(alignment: .center, spacing: 10) {
+ Text("SSH target")
+ .font(.callout.weight(.semibold))
+ .frame(width: self.remoteLabelWidth, alignment: .leading)
+ TextField("user@host[:22]", text: self.$state.remoteTarget)
+ .textFieldStyle(.roundedBorder)
+ .frame(maxWidth: .infinity)
+ Button {
+ Task { await self.testRemote() }
+ } label: {
+ if self.remoteStatus == .checking {
+ ProgressView().controlSize(.small)
+ } else {
+ Text("Test remote")
+ }
+ }
+ .buttonStyle(.borderedProminent)
+ .disabled(self.remoteStatus == .checking || self.state.remoteTarget
+ .trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
+ }
+ }
+
+ private var remoteDirectRow: some View {
+ VStack(alignment: .leading, spacing: 6) {
+ HStack(alignment: .center, spacing: 10) {
+ Text("Gateway")
+ .font(.callout.weight(.semibold))
+ .frame(width: self.remoteLabelWidth, alignment: .leading)
+ TextField("wss://gateway.example.ts.net", text: self.$state.remoteUrl)
+ .textFieldStyle(.roundedBorder)
+ .frame(maxWidth: .infinity)
+ Button {
+ Task { await self.testRemote() }
+ } label: {
+ if self.remoteStatus == .checking {
+ ProgressView().controlSize(.small)
+ } else {
+ Text("Test remote")
+ }
+ }
+ .buttonStyle(.borderedProminent)
+ .disabled(self.remoteStatus == .checking || self.state.remoteUrl
+ .trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
+ }
+ Text("Direct mode requires a ws:// or wss:// URL (Tailscale Serve uses wss://).")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .padding(.leading, self.remoteLabelWidth + 10)
+ }
+ }
+
private var controlStatusLine: String {
switch ControlChannel.shared.state {
case .connected: "Connected"
@@ -458,24 +523,36 @@ extension GeneralSettings {
func testRemote() async {
self.remoteStatus = .checking
let settings = CommandResolver.connectionSettings()
- guard !settings.target.isEmpty else {
- self.remoteStatus = .failed("Set an SSH target first")
- return
+ if self.state.remoteTransport == .direct {
+ let trimmedUrl = self.state.remoteUrl.trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !trimmedUrl.isEmpty else {
+ self.remoteStatus = .failed("Set a gateway URL first")
+ return
+ }
+ guard Self.isValidWsUrl(trimmedUrl) else {
+ self.remoteStatus = .failed("Gateway URL must start with ws:// or wss://")
+ return
+ }
+ } else {
+ guard !settings.target.isEmpty else {
+ self.remoteStatus = .failed("Set an SSH target first")
+ return
+ }
+
+ // Step 1: basic SSH reachability check
+ let sshResult = await ShellExecutor.run(
+ command: Self.sshCheckCommand(target: settings.target, identity: settings.identity),
+ cwd: nil,
+ env: nil,
+ timeout: 8)
+
+ guard sshResult.ok else {
+ self.remoteStatus = .failed(self.formatSSHFailure(sshResult, target: settings.target))
+ return
+ }
}
- // Step 1: basic SSH reachability check
- let sshResult = await ShellExecutor.run(
- command: Self.sshCheckCommand(target: settings.target, identity: settings.identity),
- cwd: nil,
- env: nil,
- timeout: 8)
-
- guard sshResult.ok else {
- self.remoteStatus = .failed(self.formatSSHFailure(sshResult, target: settings.target))
- return
- }
-
- // Step 2: control channel health over tunnel
+ // Step 2: control channel health check
let originalMode = AppStateStore.shared.connectionMode
do {
try await ControlChannel.shared.configure(mode: .remote(
@@ -502,6 +579,14 @@ extension GeneralSettings {
}
}
+ private static func isValidWsUrl(_ raw: String) -> Bool {
+ guard let url = URL(string: raw.trimmingCharacters(in: .whitespacesAndNewlines)) else { return false }
+ let scheme = url.scheme?.lowercased() ?? ""
+ guard scheme == "ws" || scheme == "wss" else { return false }
+ let host = url.host?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ return !host.isEmpty
+ }
+
private static func sshCheckCommand(target: String, identity: String) -> [String] {
var args: [String] = [
"/usr/bin/ssh",
@@ -570,12 +655,18 @@ extension GeneralSettings {
let host = gateway.tailnetDns ?? gateway.lanHost
guard let host else { return }
let user = NSUserName()
- self.state.remoteTarget = GatewayDiscoveryModel.buildSSHTarget(
- user: user,
- host: host,
- port: gateway.sshPort)
- self.state.remoteCliPath = gateway.cliPath ?? ""
- ClawdbotConfigFile.setRemoteGatewayUrl(host: host, port: gateway.gatewayPort)
+ if self.state.remoteTransport == .direct {
+ if let url = GatewayDiscoveryHelpers.directUrl(for: gateway) {
+ self.state.remoteUrl = url
+ }
+ } else {
+ self.state.remoteTarget = GatewayDiscoveryModel.buildSSHTarget(
+ user: user,
+ host: host,
+ port: gateway.sshPort)
+ self.state.remoteCliPath = gateway.cliPath ?? ""
+ ClawdbotConfigFile.setRemoteGatewayUrl(host: host, port: gateway.gatewayPort)
+ }
}
}
@@ -598,7 +689,9 @@ extension GeneralSettings {
static func exerciseForTesting() {
let state = AppState(preview: true)
state.connectionMode = .remote
+ state.remoteTransport = .ssh
state.remoteTarget = "user@host:2222"
+ state.remoteUrl = "wss://gateway.example.ts.net"
state.remoteIdentity = "/tmp/id_ed25519"
state.remoteProjectRoot = "/tmp/clawdbot"
state.remoteCliPath = "/tmp/clawdbot"
diff --git a/apps/macos/Sources/Clawdbot/MenuSessionsInjector.swift b/apps/macos/Sources/Clawdbot/MenuSessionsInjector.swift
index 4b8854cda..7ab9a64ca 100644
--- a/apps/macos/Sources/Clawdbot/MenuSessionsInjector.swift
+++ b/apps/macos/Sources/Clawdbot/MenuSessionsInjector.swift
@@ -1,4 +1,5 @@
import AppKit
+import Foundation
import Observation
import SwiftUI
@@ -517,11 +518,25 @@ extension MenuSessionsInjector {
switch mode {
case .remote:
platform = "remote"
- let target = AppStateStore.shared.remoteTarget
- if let parsed = CommandResolver.parseSSHTarget(target) {
- host = parsed.port == 22 ? parsed.host : "\(parsed.host):\(parsed.port)"
+ if AppStateStore.shared.remoteTransport == .direct {
+ let trimmedUrl = AppStateStore.shared.remoteUrl
+ .trimmingCharacters(in: .whitespacesAndNewlines)
+ if let url = URL(string: trimmedUrl), let urlHost = url.host, !urlHost.isEmpty {
+ if let port = url.port {
+ host = "\(urlHost):\(port)"
+ } else {
+ host = urlHost
+ }
+ } else {
+ host = trimmedUrl.nonEmpty
+ }
} else {
- host = target.nonEmpty
+ let target = AppStateStore.shared.remoteTarget
+ if let parsed = CommandResolver.parseSSHTarget(target) {
+ host = parsed.port == 22 ? parsed.host : "\(parsed.host):\(parsed.port)"
+ } else {
+ host = target.nonEmpty
+ }
}
case .local:
platform = "local"
diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Actions.swift b/apps/macos/Sources/Clawdbot/OnboardingView+Actions.swift
index 5942be760..874ae28a2 100644
--- a/apps/macos/Sources/Clawdbot/OnboardingView+Actions.swift
+++ b/apps/macos/Sources/Clawdbot/OnboardingView+Actions.swift
@@ -25,7 +25,11 @@ extension OnboardingView {
self.preferredGatewayID = gateway.stableID
GatewayDiscoveryPreferences.setPreferredStableID(gateway.stableID)
- if let host = gateway.tailnetDns ?? gateway.lanHost {
+ if self.state.remoteTransport == .direct {
+ if let url = GatewayDiscoveryHelpers.directUrl(for: gateway) {
+ self.state.remoteUrl = url
+ }
+ } else if let host = GatewayDiscoveryHelpers.sanitizedTailnetHost(gateway.tailnetDns) ?? gateway.lanHost {
let user = NSUserName()
self.state.remoteTarget = GatewayDiscoveryModel.buildSSHTarget(
user: user,
diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift b/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift
index e32252e81..5c5eead34 100644
--- a/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift
+++ b/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift
@@ -177,42 +177,67 @@ extension OnboardingView {
VStack(alignment: .leading, spacing: 10) {
Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 8) {
GridRow {
- Text("SSH target")
+ Text("Transport")
.font(.callout.weight(.semibold))
.frame(width: labelWidth, alignment: .leading)
- TextField("user@host[:port]", text: self.$state.remoteTarget)
- .textFieldStyle(.roundedBorder)
- .frame(width: fieldWidth)
+ Picker("Transport", selection: self.$state.remoteTransport) {
+ Text("SSH tunnel").tag(AppState.RemoteTransport.ssh)
+ Text("Direct (ws/wss)").tag(AppState.RemoteTransport.direct)
+ }
+ .pickerStyle(.segmented)
+ .frame(width: fieldWidth)
}
- GridRow {
- Text("Identity file")
- .font(.callout.weight(.semibold))
- .frame(width: labelWidth, alignment: .leading)
- TextField("/Users/you/.ssh/id_ed25519", text: self.$state.remoteIdentity)
- .textFieldStyle(.roundedBorder)
- .frame(width: fieldWidth)
+ if self.state.remoteTransport == .direct {
+ GridRow {
+ Text("Gateway URL")
+ .font(.callout.weight(.semibold))
+ .frame(width: labelWidth, alignment: .leading)
+ TextField("wss://gateway.example.ts.net", text: self.$state.remoteUrl)
+ .textFieldStyle(.roundedBorder)
+ .frame(width: fieldWidth)
+ }
}
- GridRow {
- Text("Project root")
- .font(.callout.weight(.semibold))
- .frame(width: labelWidth, alignment: .leading)
- TextField("/home/you/Projects/clawdbot", text: self.$state.remoteProjectRoot)
- .textFieldStyle(.roundedBorder)
- .frame(width: fieldWidth)
- }
- GridRow {
- Text("CLI path")
- .font(.callout.weight(.semibold))
- .frame(width: labelWidth, alignment: .leading)
- TextField(
- "/Applications/Clawdbot.app/.../clawdbot",
- text: self.$state.remoteCliPath)
- .textFieldStyle(.roundedBorder)
- .frame(width: fieldWidth)
+ if self.state.remoteTransport == .ssh {
+ GridRow {
+ Text("SSH target")
+ .font(.callout.weight(.semibold))
+ .frame(width: labelWidth, alignment: .leading)
+ TextField("user@host[:port]", text: self.$state.remoteTarget)
+ .textFieldStyle(.roundedBorder)
+ .frame(width: fieldWidth)
+ }
+ GridRow {
+ Text("Identity file")
+ .font(.callout.weight(.semibold))
+ .frame(width: labelWidth, alignment: .leading)
+ TextField("/Users/you/.ssh/id_ed25519", text: self.$state.remoteIdentity)
+ .textFieldStyle(.roundedBorder)
+ .frame(width: fieldWidth)
+ }
+ GridRow {
+ Text("Project root")
+ .font(.callout.weight(.semibold))
+ .frame(width: labelWidth, alignment: .leading)
+ TextField("/home/you/Projects/clawdbot", text: self.$state.remoteProjectRoot)
+ .textFieldStyle(.roundedBorder)
+ .frame(width: fieldWidth)
+ }
+ GridRow {
+ Text("CLI path")
+ .font(.callout.weight(.semibold))
+ .frame(width: labelWidth, alignment: .leading)
+ TextField(
+ "/Applications/Clawdbot.app/.../clawdbot",
+ text: self.$state.remoteCliPath)
+ .textFieldStyle(.roundedBorder)
+ .frame(width: fieldWidth)
+ }
}
}
- Text("Tip: keep Tailscale enabled so your gateway stays reachable.")
+ Text(self.state.remoteTransport == .direct
+ ? "Tip: use Tailscale Serve so the gateway has a valid HTTPS cert."
+ : "Tip: keep Tailscale enabled so your gateway stays reachable.")
.font(.footnote)
.foregroundStyle(.secondary)
.lineLimit(1)
@@ -225,7 +250,10 @@ extension OnboardingView {
}
func gatewaySubtitle(for gateway: GatewayDiscoveryModel.DiscoveredGateway) -> String? {
- if let host = gateway.tailnetDns ?? gateway.lanHost {
+ if self.state.remoteTransport == .direct {
+ return GatewayDiscoveryHelpers.directUrl(for: gateway) ?? "Gateway pairing only"
+ }
+ if let host = GatewayDiscoveryHelpers.sanitizedTailnetHost(gateway.tailnetDns) ?? gateway.lanHost {
let portSuffix = gateway.sshPort != 22 ? " · ssh \(gateway.sshPort)" : ""
return "\(host)\(portSuffix)"
}
diff --git a/apps/macos/Sources/Clawdbot/Resources/Info.plist b/apps/macos/Sources/Clawdbot/Resources/Info.plist
index 283901c0f..1c7d9619f 100644
--- a/apps/macos/Sources/Clawdbot/Resources/Info.plist
+++ b/apps/macos/Sources/Clawdbot/Resources/Info.plist
@@ -15,9 +15,9 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 2026.1.21
+ 2026.1.24
CFBundleVersion
- 202601210
+ 202601240
CFBundleIconFile
Clawdbot
CFBundleURLTypes
diff --git a/apps/macos/Sources/Clawdbot/SystemRunSettingsView.swift b/apps/macos/Sources/Clawdbot/SystemRunSettingsView.swift
index 0ac799e6d..eef826c3f 100644
--- a/apps/macos/Sources/Clawdbot/SystemRunSettingsView.swift
+++ b/apps/macos/Sources/Clawdbot/SystemRunSettingsView.swift
@@ -123,12 +123,12 @@ struct SystemRunSettingsView: View {
.foregroundStyle(.secondary)
} else {
VStack(alignment: .leading, spacing: 8) {
- ForEach(Array(self.model.entries.enumerated()), id: \.offset) { index, _ in
+ ForEach(self.model.entries, id: \.id) { entry in
ExecAllowlistRow(
entry: Binding(
- get: { self.model.entries[index] },
- set: { self.model.updateEntry($0, at: index) }),
- onRemove: { self.model.removeEntry(at: index) })
+ get: { self.model.entry(for: entry.id) ?? entry },
+ set: { self.model.updateEntry($0, id: entry.id) }),
+ onRemove: { self.model.removeEntry(id: entry.id) })
}
}
}
@@ -373,20 +373,24 @@ final class ExecApprovalsSettingsModel {
ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
}
- func updateEntry(_ entry: ExecAllowlistEntry, at index: Int) {
+ func updateEntry(_ entry: ExecAllowlistEntry, id: UUID) {
guard !self.isDefaultsScope else { return }
- guard self.entries.indices.contains(index) else { return }
+ guard let index = self.entries.firstIndex(where: { $0.id == id }) else { return }
self.entries[index] = entry
ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
}
- func removeEntry(at index: Int) {
+ func removeEntry(id: UUID) {
guard !self.isDefaultsScope else { return }
- guard self.entries.indices.contains(index) else { return }
+ guard let index = self.entries.firstIndex(where: { $0.id == id }) else { return }
self.entries.remove(at: index)
ExecApprovalsStore.updateAllowlist(agentId: self.selectedAgentId, allowlist: self.entries)
}
+ func entry(for id: UUID) -> ExecAllowlistEntry? {
+ self.entries.first(where: { $0.id == id })
+ }
+
func refreshSkillBins(force: Bool = false) async {
guard self.autoAllowSkills else {
self.skillBins = []
diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeHelpers.swift b/apps/macos/Sources/Clawdbot/VoiceWakeHelpers.swift
index a60aa7d7c..98cdc0cb5 100644
--- a/apps/macos/Sources/Clawdbot/VoiceWakeHelpers.swift
+++ b/apps/macos/Sources/Clawdbot/VoiceWakeHelpers.swift
@@ -4,6 +4,8 @@ func sanitizeVoiceWakeTriggers(_ words: [String]) -> [String] {
let cleaned = words
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
+ .prefix(voiceWakeMaxWords)
+ .map { String($0.prefix(voiceWakeMaxWordLength)) }
return cleaned.isEmpty ? defaultVoiceWakeTriggers : cleaned
}
diff --git a/apps/macos/Sources/Clawdbot/VoiceWakeSettings.swift b/apps/macos/Sources/Clawdbot/VoiceWakeSettings.swift
index 176980cc5..a41e8bb1f 100644
--- a/apps/macos/Sources/Clawdbot/VoiceWakeSettings.swift
+++ b/apps/macos/Sources/Clawdbot/VoiceWakeSettings.swift
@@ -21,6 +21,7 @@ struct VoiceWakeSettings: View {
@State private var micObserver = AudioInputDeviceObserver()
@State private var micRefreshTask: Task?
@State private var availableLocales: [Locale] = []
+ @State private var triggerEntries: [TriggerEntry] = []
private let fieldLabelWidth: CGFloat = 140
private let controlWidth: CGFloat = 240
private let isPreview = ProcessInfo.processInfo.isPreview
@@ -31,9 +32,9 @@ struct VoiceWakeSettings: View {
var id: String { self.uid }
}
- private struct IndexedWord: Identifiable {
- let id: Int
- let value: String
+ private struct TriggerEntry: Identifiable {
+ let id: UUID
+ var value: String
}
private var voiceWakeBinding: Binding {
@@ -105,6 +106,7 @@ struct VoiceWakeSettings: View {
.onAppear {
guard !self.isPreview else { return }
self.startMicObserver()
+ self.loadTriggerEntries()
}
.onChange(of: self.state.voiceWakeMicID) { _, _ in
guard !self.isPreview else { return }
@@ -122,8 +124,10 @@ struct VoiceWakeSettings: View {
self.micRefreshTask = nil
Task { await self.meter.stop() }
self.micObserver.stop()
+ self.syncTriggerEntriesToState()
} else {
self.startMicObserver()
+ self.loadTriggerEntries()
}
}
.onDisappear {
@@ -136,11 +140,16 @@ struct VoiceWakeSettings: View {
self.micRefreshTask = nil
self.micObserver.stop()
Task { await self.meter.stop() }
+ self.syncTriggerEntriesToState()
}
}
- private var indexedWords: [IndexedWord] {
- self.state.swabbleTriggerWords.enumerated().map { IndexedWord(id: $0.offset, value: $0.element) }
+ private func loadTriggerEntries() {
+ self.triggerEntries = self.state.swabbleTriggerWords.map { TriggerEntry(id: UUID(), value: $0) }
+ }
+
+ private func syncTriggerEntriesToState() {
+ self.state.swabbleTriggerWords = self.triggerEntries.map(\.value)
}
private var triggerTable: some View {
@@ -154,29 +163,42 @@ struct VoiceWakeSettings: View {
} label: {
Label("Add word", systemImage: "plus")
}
- .disabled(self.state.swabbleTriggerWords
- .contains(where: { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }))
+ .disabled(self.triggerEntries
+ .contains(where: { $0.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }))
- Button("Reset defaults") { self.state.swabbleTriggerWords = defaultVoiceWakeTriggers }
+ Button("Reset defaults") {
+ self.triggerEntries = defaultVoiceWakeTriggers.map { TriggerEntry(id: UUID(), value: $0) }
+ self.syncTriggerEntriesToState()
+ }
}
- Table(self.indexedWords) {
- TableColumn("Word") { row in
- TextField("Wake word", text: self.binding(for: row.id))
- .textFieldStyle(.roundedBorder)
- }
- TableColumn("") { row in
- Button {
- self.removeWord(at: row.id)
- } label: {
- Image(systemName: "trash")
+ VStack(spacing: 0) {
+ ForEach(self.$triggerEntries) { $entry in
+ HStack(spacing: 8) {
+ TextField("Wake word", text: $entry.value)
+ .textFieldStyle(.roundedBorder)
+ .onSubmit {
+ self.syncTriggerEntriesToState()
+ }
+
+ Button {
+ self.removeWord(id: entry.id)
+ } label: {
+ Image(systemName: "trash")
+ }
+ .buttonStyle(.borderless)
+ .help("Remove trigger word")
+ .frame(width: 24)
+ }
+ .padding(8)
+
+ if entry.id != self.triggerEntries.last?.id {
+ Divider()
}
- .buttonStyle(.borderless)
- .help("Remove trigger word")
}
- .width(36)
}
- .frame(minHeight: 180)
+ .frame(maxWidth: .infinity, minHeight: 180, alignment: .topLeading)
+ .background(Color(nsColor: .textBackgroundColor))
.clipShape(RoundedRectangle(cornerRadius: 6))
.overlay(
RoundedRectangle(cornerRadius: 6)
@@ -211,24 +233,12 @@ struct VoiceWakeSettings: View {
}
private func addWord() {
- self.state.swabbleTriggerWords.append("")
+ self.triggerEntries.append(TriggerEntry(id: UUID(), value: ""))
}
- private func removeWord(at index: Int) {
- guard self.state.swabbleTriggerWords.indices.contains(index) else { return }
- self.state.swabbleTriggerWords.remove(at: index)
- }
-
- private func binding(for index: Int) -> Binding {
- Binding(
- get: {
- guard self.state.swabbleTriggerWords.indices.contains(index) else { return "" }
- return self.state.swabbleTriggerWords[index]
- },
- set: { newValue in
- guard self.state.swabbleTriggerWords.indices.contains(index) else { return }
- self.state.swabbleTriggerWords[index] = newValue
- })
+ private func removeWord(id: UUID) {
+ self.triggerEntries.removeAll { $0.id == id }
+ self.syncTriggerEntriesToState()
}
private func toggleTest() {
@@ -638,13 +648,14 @@ extension VoiceWakeSettings {
state.voicePushToTalkEnabled = true
state.swabbleTriggerWords = ["Claude", "Hey"]
- let view = VoiceWakeSettings(state: state, isActive: true)
+ var view = VoiceWakeSettings(state: state, isActive: true)
view.availableMics = [AudioInputDevice(uid: "mic-1", name: "Built-in")]
view.availableLocales = [Locale(identifier: "en_US")]
view.meterLevel = 0.42
view.meterError = "No input"
view.testState = .detected("ok")
view.isTesting = true
+ view.triggerEntries = [TriggerEntry(id: UUID(), value: "Claude")]
_ = view.body
_ = view.localePicker
@@ -654,8 +665,9 @@ extension VoiceWakeSettings {
_ = view.chimeSection
view.addWord()
- _ = view.binding(for: 0).wrappedValue
- view.removeWord(at: 0)
+ if let entryId = view.triggerEntries.first?.id {
+ view.removeWord(id: entryId)
+ }
}
}
#endif
diff --git a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift
index 4b80bf3f6..9d2ca5ed4 100644
--- a/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift
+++ b/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift
@@ -385,6 +385,7 @@ public struct SendParams: Codable, Sendable {
public let to: String
public let message: String
public let mediaurl: String?
+ public let mediaurls: [String]?
public let gifplayback: Bool?
public let channel: String?
public let accountid: String?
@@ -395,6 +396,7 @@ public struct SendParams: Codable, Sendable {
to: String,
message: String,
mediaurl: String?,
+ mediaurls: [String]?,
gifplayback: Bool?,
channel: String?,
accountid: String?,
@@ -404,6 +406,7 @@ public struct SendParams: Codable, Sendable {
self.to = to
self.message = message
self.mediaurl = mediaurl
+ self.mediaurls = mediaurls
self.gifplayback = gifplayback
self.channel = channel
self.accountid = accountid
@@ -414,6 +417,7 @@ public struct SendParams: Codable, Sendable {
case to
case message
case mediaurl = "mediaUrl"
+ case mediaurls = "mediaUrls"
case gifplayback = "gifPlayback"
case channel
case accountid = "accountId"
@@ -478,6 +482,9 @@ public struct AgentParams: Codable, Sendable {
public let accountid: String?
public let replyaccountid: String?
public let threadid: String?
+ public let groupid: String?
+ public let groupchannel: String?
+ public let groupspace: String?
public let timeout: Int?
public let lane: String?
public let extrasystemprompt: String?
@@ -500,6 +507,9 @@ public struct AgentParams: Codable, Sendable {
accountid: String?,
replyaccountid: String?,
threadid: String?,
+ groupid: String?,
+ groupchannel: String?,
+ groupspace: String?,
timeout: Int?,
lane: String?,
extrasystemprompt: String?,
@@ -521,6 +531,9 @@ public struct AgentParams: Codable, Sendable {
self.accountid = accountid
self.replyaccountid = replyaccountid
self.threadid = threadid
+ self.groupid = groupid
+ self.groupchannel = groupchannel
+ self.groupspace = groupspace
self.timeout = timeout
self.lane = lane
self.extrasystemprompt = extrasystemprompt
@@ -543,6 +556,9 @@ public struct AgentParams: Codable, Sendable {
case accountid = "accountId"
case replyaccountid = "replyAccountId"
case threadid = "threadId"
+ case groupid = "groupId"
+ case groupchannel = "groupChannel"
+ case groupspace = "groupSpace"
case timeout
case lane
case extrasystemprompt = "extraSystemPrompt"
@@ -948,6 +964,7 @@ public struct SessionsPreviewParams: Codable, Sendable {
public struct SessionsResolveParams: Codable, Sendable {
public let key: String?
+ public let sessionid: String?
public let label: String?
public let agentid: String?
public let spawnedby: String?
@@ -956,6 +973,7 @@ public struct SessionsResolveParams: Codable, Sendable {
public init(
key: String?,
+ sessionid: String?,
label: String?,
agentid: String?,
spawnedby: String?,
@@ -963,6 +981,7 @@ public struct SessionsResolveParams: Codable, Sendable {
includeunknown: Bool?
) {
self.key = key
+ self.sessionid = sessionid
self.label = label
self.agentid = agentid
self.spawnedby = spawnedby
@@ -971,6 +990,7 @@ public struct SessionsResolveParams: Codable, Sendable {
}
private enum CodingKeys: String, CodingKey {
case key
+ case sessionid = "sessionId"
case label
case agentid = "agentId"
case spawnedby = "spawnedBy"
@@ -1147,17 +1167,29 @@ public struct ConfigApplyParams: Codable, Sendable {
public struct ConfigPatchParams: Codable, Sendable {
public let raw: String
public let basehash: String?
+ public let sessionkey: String?
+ public let note: String?
+ public let restartdelayms: Int?
public init(
raw: String,
- basehash: String?
+ basehash: String?,
+ sessionkey: String?,
+ note: String?,
+ restartdelayms: Int?
) {
self.raw = raw
self.basehash = basehash
+ self.sessionkey = sessionkey
+ self.note = note
+ self.restartdelayms = restartdelayms
}
private enum CodingKeys: String, CodingKey {
case raw
case basehash = "baseHash"
+ case sessionkey = "sessionKey"
+ case note
+ case restartdelayms = "restartDelayMs"
}
}
@@ -1973,25 +2005,25 @@ public struct ExecApprovalsSnapshot: Codable, Sendable {
public struct ExecApprovalRequestParams: Codable, Sendable {
public let id: String?
public let command: String
- public let cwd: String?
- public let host: String?
- public let security: String?
- public let ask: String?
- public let agentid: String?
- public let resolvedpath: String?
- public let sessionkey: String?
+ public let cwd: AnyCodable?
+ public let host: AnyCodable?
+ public let security: AnyCodable?
+ public let ask: AnyCodable?
+ public let agentid: AnyCodable?
+ public let resolvedpath: AnyCodable?
+ public let sessionkey: AnyCodable?
public let timeoutms: Int?
public init(
id: String?,
command: String,
- cwd: String?,
- host: String?,
- security: String?,
- ask: String?,
- agentid: String?,
- resolvedpath: String?,
- sessionkey: String?,
+ cwd: AnyCodable?,
+ host: AnyCodable?,
+ security: AnyCodable?,
+ ask: AnyCodable?,
+ agentid: AnyCodable?,
+ resolvedpath: AnyCodable?,
+ sessionkey: AnyCodable?,
timeoutms: Int?
) {
self.id = id
diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayAgentChannelTests.swift b/apps/macos/Tests/ClawdbotIPCTests/GatewayAgentChannelTests.swift
index bf72af7e5..5db5476cb 100644
--- a/apps/macos/Tests/ClawdbotIPCTests/GatewayAgentChannelTests.swift
+++ b/apps/macos/Tests/ClawdbotIPCTests/GatewayAgentChannelTests.swift
@@ -11,6 +11,7 @@ import Testing
#expect(GatewayAgentChannel.last.shouldDeliver(true) == true)
#expect(GatewayAgentChannel.whatsapp.shouldDeliver(true) == true)
#expect(GatewayAgentChannel.telegram.shouldDeliver(true) == true)
+ #expect(GatewayAgentChannel.googlechat.shouldDeliver(true) == true)
#expect(GatewayAgentChannel.bluebubbles.shouldDeliver(true) == true)
#expect(GatewayAgentChannel.last.shouldDeliver(false) == false)
}
@@ -19,6 +20,7 @@ import Testing
#expect(GatewayAgentChannel(raw: nil) == .last)
#expect(GatewayAgentChannel(raw: " ") == .last)
#expect(GatewayAgentChannel(raw: "WEBCHAT") == .webchat)
+ #expect(GatewayAgentChannel(raw: "googlechat") == .googlechat)
#expect(GatewayAgentChannel(raw: "BLUEBUBBLES") == .bluebubbles)
#expect(GatewayAgentChannel(raw: "unknown") == .last)
}
diff --git a/apps/macos/Tests/ClawdbotIPCTests/GatewayEndpointStoreTests.swift b/apps/macos/Tests/ClawdbotIPCTests/GatewayEndpointStoreTests.swift
index 3513388a2..8cefe75e2 100644
--- a/apps/macos/Tests/ClawdbotIPCTests/GatewayEndpointStoreTests.swift
+++ b/apps/macos/Tests/ClawdbotIPCTests/GatewayEndpointStoreTests.swift
@@ -175,4 +175,10 @@ import Testing
customBindHost: "192.168.1.10")
#expect(host == "192.168.1.10")
}
+
+ @Test func normalizeGatewayUrlAddsDefaultPortForWs() {
+ let url = GatewayRemoteConfig.normalizeGatewayUrl("ws://gateway")
+ #expect(url?.port == 18789)
+ #expect(url?.absoluteString == "ws://gateway:18789")
+ }
}
diff --git a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeHelpersTests.swift b/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeHelpersTests.swift
index e7f7e06fc..49ad5a124 100644
--- a/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeHelpersTests.swift
+++ b/apps/macos/Tests/ClawdbotIPCTests/VoiceWakeHelpersTests.swift
@@ -12,6 +12,18 @@ struct VoiceWakeHelpersTests {
#expect(cleaned == defaultVoiceWakeTriggers)
}
+ @Test func sanitizeTriggersLimitsWordLength() {
+ let long = String(repeating: "x", count: voiceWakeMaxWordLength + 5)
+ let cleaned = sanitizeVoiceWakeTriggers(["ok", long])
+ #expect(cleaned[1].count == voiceWakeMaxWordLength)
+ }
+
+ @Test func sanitizeTriggersLimitsWordCount() {
+ let words = (1...voiceWakeMaxWords + 3).map { "w\($0)" }
+ let cleaned = sanitizeVoiceWakeTriggers(words)
+ #expect(cleaned.count == voiceWakeMaxWords)
+ }
+
@Test func normalizeLocaleStripsCollation() {
#expect(normalizeLocaleIdentifier("en_US@collation=phonebook") == "en_US")
}
diff --git a/apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/GatewayModels.swift b/apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/GatewayModels.swift
index 4b80bf3f6..9d2ca5ed4 100644
--- a/apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/GatewayModels.swift
+++ b/apps/shared/ClawdbotKit/Sources/ClawdbotProtocol/GatewayModels.swift
@@ -385,6 +385,7 @@ public struct SendParams: Codable, Sendable {
public let to: String
public let message: String
public let mediaurl: String?
+ public let mediaurls: [String]?
public let gifplayback: Bool?
public let channel: String?
public let accountid: String?
@@ -395,6 +396,7 @@ public struct SendParams: Codable, Sendable {
to: String,
message: String,
mediaurl: String?,
+ mediaurls: [String]?,
gifplayback: Bool?,
channel: String?,
accountid: String?,
@@ -404,6 +406,7 @@ public struct SendParams: Codable, Sendable {
self.to = to
self.message = message
self.mediaurl = mediaurl
+ self.mediaurls = mediaurls
self.gifplayback = gifplayback
self.channel = channel
self.accountid = accountid
@@ -414,6 +417,7 @@ public struct SendParams: Codable, Sendable {
case to
case message
case mediaurl = "mediaUrl"
+ case mediaurls = "mediaUrls"
case gifplayback = "gifPlayback"
case channel
case accountid = "accountId"
@@ -478,6 +482,9 @@ public struct AgentParams: Codable, Sendable {
public let accountid: String?
public let replyaccountid: String?
public let threadid: String?
+ public let groupid: String?
+ public let groupchannel: String?
+ public let groupspace: String?
public let timeout: Int?
public let lane: String?
public let extrasystemprompt: String?
@@ -500,6 +507,9 @@ public struct AgentParams: Codable, Sendable {
accountid: String?,
replyaccountid: String?,
threadid: String?,
+ groupid: String?,
+ groupchannel: String?,
+ groupspace: String?,
timeout: Int?,
lane: String?,
extrasystemprompt: String?,
@@ -521,6 +531,9 @@ public struct AgentParams: Codable, Sendable {
self.accountid = accountid
self.replyaccountid = replyaccountid
self.threadid = threadid
+ self.groupid = groupid
+ self.groupchannel = groupchannel
+ self.groupspace = groupspace
self.timeout = timeout
self.lane = lane
self.extrasystemprompt = extrasystemprompt
@@ -543,6 +556,9 @@ public struct AgentParams: Codable, Sendable {
case accountid = "accountId"
case replyaccountid = "replyAccountId"
case threadid = "threadId"
+ case groupid = "groupId"
+ case groupchannel = "groupChannel"
+ case groupspace = "groupSpace"
case timeout
case lane
case extrasystemprompt = "extraSystemPrompt"
@@ -948,6 +964,7 @@ public struct SessionsPreviewParams: Codable, Sendable {
public struct SessionsResolveParams: Codable, Sendable {
public let key: String?
+ public let sessionid: String?
public let label: String?
public let agentid: String?
public let spawnedby: String?
@@ -956,6 +973,7 @@ public struct SessionsResolveParams: Codable, Sendable {
public init(
key: String?,
+ sessionid: String?,
label: String?,
agentid: String?,
spawnedby: String?,
@@ -963,6 +981,7 @@ public struct SessionsResolveParams: Codable, Sendable {
includeunknown: Bool?
) {
self.key = key
+ self.sessionid = sessionid
self.label = label
self.agentid = agentid
self.spawnedby = spawnedby
@@ -971,6 +990,7 @@ public struct SessionsResolveParams: Codable, Sendable {
}
private enum CodingKeys: String, CodingKey {
case key
+ case sessionid = "sessionId"
case label
case agentid = "agentId"
case spawnedby = "spawnedBy"
@@ -1147,17 +1167,29 @@ public struct ConfigApplyParams: Codable, Sendable {
public struct ConfigPatchParams: Codable, Sendable {
public let raw: String
public let basehash: String?
+ public let sessionkey: String?
+ public let note: String?
+ public let restartdelayms: Int?
public init(
raw: String,
- basehash: String?
+ basehash: String?,
+ sessionkey: String?,
+ note: String?,
+ restartdelayms: Int?
) {
self.raw = raw
self.basehash = basehash
+ self.sessionkey = sessionkey
+ self.note = note
+ self.restartdelayms = restartdelayms
}
private enum CodingKeys: String, CodingKey {
case raw
case basehash = "baseHash"
+ case sessionkey = "sessionKey"
+ case note
+ case restartdelayms = "restartDelayMs"
}
}
@@ -1973,25 +2005,25 @@ public struct ExecApprovalsSnapshot: Codable, Sendable {
public struct ExecApprovalRequestParams: Codable, Sendable {
public let id: String?
public let command: String
- public let cwd: String?
- public let host: String?
- public let security: String?
- public let ask: String?
- public let agentid: String?
- public let resolvedpath: String?
- public let sessionkey: String?
+ public let cwd: AnyCodable?
+ public let host: AnyCodable?
+ public let security: AnyCodable?
+ public let ask: AnyCodable?
+ public let agentid: AnyCodable?
+ public let resolvedpath: AnyCodable?
+ public let sessionkey: AnyCodable?
public let timeoutms: Int?
public init(
id: String?,
command: String,
- cwd: String?,
- host: String?,
- security: String?,
- ask: String?,
- agentid: String?,
- resolvedpath: String?,
- sessionkey: String?,
+ cwd: AnyCodable?,
+ host: AnyCodable?,
+ security: AnyCodable?,
+ ask: AnyCodable?,
+ agentid: AnyCodable?,
+ resolvedpath: AnyCodable?,
+ sessionkey: AnyCodable?,
timeoutms: Int?
) {
self.id = id
diff --git a/dist/control-ui/assets/index-BPDeGGxb.css b/dist/control-ui/assets/index-BPDeGGxb.css
deleted file mode 100644
index 4fa216699..000000000
--- a/dist/control-ui/assets/index-BPDeGGxb.css
+++ /dev/null
@@ -1 +0,0 @@
-@import"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Unbounded:wght@400;500;600&family=Work+Sans:wght@400;500;600;700&display=swap";:root{--bg: #0a0f14;--bg-accent: #111826;--bg-grad-1: #162031;--bg-grad-2: #1f2a22;--bg-overlay: rgba(255, 255, 255, .05);--bg-glow: rgba(245, 159, 74, .12);--panel: rgba(14, 20, 30, .88);--panel-strong: rgba(18, 26, 38, .96);--chrome: rgba(9, 14, 20, .72);--chrome-strong: rgba(9, 14, 20, .86);--text: rgba(244, 246, 251, .96);--chat-text: rgba(231, 237, 244, .92);--muted: rgba(156, 169, 189, .72);--border: rgba(255, 255, 255, .09);--border-strong: rgba(255, 255, 255, .16);--accent: #f59f4a;--accent-2: #34c7b7;--ok: #2bd97f;--warn: #f2c94c;--danger: #ff6b6b;--focus: rgba(245, 159, 74, .35);--grid-line: rgba(255, 255, 255, .04);--theme-switch-x: 50%;--theme-switch-y: 50%;--mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--font-body: "Work Sans", system-ui, sans-serif;--font-display: "Unbounded", "Times New Roman", serif;color-scheme:dark}:root[data-theme=light]{--bg: #f5f1ea;--bg-accent: #ffffff;--bg-grad-1: #f1e6d6;--bg-grad-2: #e5eef4;--bg-overlay: rgba(28, 32, 46, .05);--bg-glow: rgba(52, 199, 183, .14);--panel: rgba(255, 255, 255, .9);--panel-strong: rgba(255, 255, 255, .97);--chrome: rgba(255, 255, 255, .75);--chrome-strong: rgba(255, 255, 255, .88);--text: rgba(27, 36, 50, .98);--chat-text: rgba(36, 48, 66, .9);--muted: rgba(80, 94, 114, .7);--border: rgba(18, 24, 40, .12);--border-strong: rgba(18, 24, 40, .2);--accent: #e28a3f;--accent-2: #1ba99d;--ok: #1aa86c;--warn: #b3771c;--danger: #d44848;--focus: rgba(226, 138, 63, .35);--grid-line: rgba(18, 24, 40, .06);color-scheme:light}*{box-sizing:border-box}html,body{height:100%}body{margin:0;font:15px/1.5 var(--font-body);background:radial-gradient(1200px 900px at 15% -10%,var(--bg-grad-1) 0%,transparent 55%) fixed,radial-gradient(900px 700px at 80% 10%,var(--bg-grad-2) 0%,transparent 60%) fixed,linear-gradient(160deg,var(--bg) 0%,var(--bg-accent) 100%) fixed;color:var(--text)}body:before{content:"";position:fixed;inset:0;background:linear-gradient(140deg,var(--bg-overlay) 0%,rgba(255,255,255,0) 40%),radial-gradient(620px 420px at 75% 75%,var(--bg-glow),transparent 60%);pointer-events:none;z-index:0}@keyframes theme-circle-transition{0%{clip-path:circle(0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%))}to{clip-path:circle(150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%))}}html.theme-transition{view-transition-name:theme}html.theme-transition::view-transition-old(theme){mix-blend-mode:normal;animation:none;z-index:1}html.theme-transition::view-transition-new(theme){mix-blend-mode:normal;z-index:2;animation:theme-circle-transition .45s ease-out forwards}@media(prefers-reduced-motion:reduce){html.theme-transition::view-transition-old(theme),html.theme-transition::view-transition-new(theme){animation:none!important}}clawdbot-app{display:block;position:relative;z-index:1;min-height:100vh}a{color:inherit}button,input,textarea,select{font:inherit;color:inherit}@keyframes rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes dashboard-enter{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.shell{--shell-pad: 16px;--shell-gap: 16px;--shell-nav-width: 220px;--shell-topbar-height: 56px;--shell-focus-duration: .22s;--shell-focus-ease: cubic-bezier(.2, .85, .25, 1);min-height:100vh;display:grid;grid-template-columns:var(--shell-nav-width) minmax(0,1fr);grid-template-rows:var(--shell-topbar-height) 1fr;grid-template-areas:"topbar topbar" "nav content";gap:0;animation:dashboard-enter .6s ease-out;transition:grid-template-columns var(--shell-focus-duration) var(--shell-focus-ease)}.shell--chat{min-height:100vh;height:100vh;overflow:hidden}@supports (height: 100dvh){.shell--chat{height:100dvh}}.shell--nav-collapsed,.shell--chat-focus{grid-template-columns:0px minmax(0,1fr)}.shell--onboarding{grid-template-rows:0 1fr}.shell--onboarding .topbar{display:none}.shell--onboarding .content{padding-top:0}.shell--chat-focus .content{padding-top:0;gap:0}.topbar{grid-area:topbar;position:sticky;top:0;z-index:40;display:flex;justify-content:space-between;align-items:center;gap:16px;padding:0 20px;height:var(--shell-topbar-height);border-bottom:1px solid var(--border);background:var(--panel);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.topbar-left{display:flex;align-items:center;gap:12px}.topbar .nav-collapse-toggle{width:44px;height:44px;margin-bottom:0}.topbar .nav-collapse-toggle__icon{font-size:22px}.brand{display:flex;flex-direction:column;gap:2px}.brand-title{font-family:var(--font-display);font-size:16px;letter-spacing:1px;text-transform:uppercase;font-weight:600;line-height:1.1}.brand-sub{font-size:10px;color:var(--muted);letter-spacing:.8px;text-transform:uppercase;line-height:1}.topbar-status{display:flex;align-items:center;gap:8px}.topbar-status .pill{padding:4px 10px;gap:6px;font-size:11px}.topbar-status .statusDot{width:6px;height:6px}.topbar-status .theme-toggle{--theme-item: 22px;--theme-gap: 4px;--theme-pad: 4px}.topbar-status .theme-icon{width:12px;height:12px}.nav{grid-area:nav;overflow-y:auto;overflow-x:hidden;padding:16px;border-right:1px solid var(--border);background:var(--panel);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);transition:width var(--shell-focus-duration) var(--shell-focus-ease),padding var(--shell-focus-duration) var(--shell-focus-ease)}.shell--chat-focus .nav{width:0;padding:0;border-width:0;overflow:hidden;pointer-events:none}.nav--collapsed{width:0;min-width:0;padding:0;overflow:hidden;border:none;opacity:0;pointer-events:none}.nav-collapse-toggle{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:1px solid transparent;border-radius:6px;cursor:pointer;transition:background .15s ease,border-color .15s ease;margin-bottom:16px}.nav-collapse-toggle:hover{background:#ffffff14;border-color:var(--border)}:root[data-theme=light] .nav-collapse-toggle:hover{background:#0000000f}.nav-collapse-toggle__icon{font-size:16px;color:var(--muted)}.nav-group{margin-bottom:18px;display:grid;gap:6px;padding-bottom:12px;border-bottom:1px dashed rgba(255,255,255,.08)}.nav-group:last-child{margin-bottom:0;padding-bottom:0;border-bottom:none}.nav-group__items{display:grid;gap:4px}.nav-group--collapsed .nav-group__items{display:none}.nav-label{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:4px 0;font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:1.4px;color:var(--text);opacity:.7;margin-bottom:4px;background:transparent;border:none;cursor:pointer;text-align:left}.nav-label:hover{opacity:1}.nav-label--static{cursor:default}.nav-label--static:hover{opacity:.7}.nav-label__text{flex:1}.nav-label__chevron{font-size:12px;opacity:.6}.nav-item{position:relative;display:flex;align-items:center;justify-content:flex-start;gap:8px;padding:10px 12px 10px 14px;border-radius:12px;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;text-decoration:none;transition:border-color .16s ease,background .16s ease,color .16s ease}.nav-item__icon{font-size:16px;width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.nav-item__text{font-size:13px;white-space:nowrap}.nav-item:hover{color:var(--text);border-color:#ffffff1f;background:#ffffff0f}.nav-item:before{content:"";position:absolute;left:0;top:50%;width:4px;height:60%;border-radius:0 999px 999px 0;transform:translateY(-50%);background:transparent}.nav-item.active{color:var(--text);border-color:#f59f4a73;background:#f59f4a1f}.nav-item.active:before{background:var(--accent);box-shadow:0 0 12px #f59f4a66}.content{grid-area:content;padding:8px 6px 20px;display:flex;flex-direction:column;gap:20px;min-height:0;overflow-y:auto;overflow-x:hidden}.content--chat{overflow:hidden}.content-header{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;padding:0 6px;overflow:hidden;transform-origin:top center;transition:opacity var(--shell-focus-duration) var(--shell-focus-ease),transform var(--shell-focus-duration) var(--shell-focus-ease),max-height var(--shell-focus-duration) var(--shell-focus-ease),padding var(--shell-focus-duration) var(--shell-focus-ease);max-height:90px}.shell--chat-focus .content-header{opacity:0;transform:translateY(-10px);max-height:0px;padding:0;pointer-events:none}.page-title{font-family:var(--font-display);font-size:26px;letter-spacing:.6px}.page-sub{color:var(--muted);font-size:12px;letter-spacing:.4px}.page-meta{display:flex;gap:10px}.content--chat .content-header{flex-direction:row;align-items:center;justify-content:space-between;gap:16px}.content--chat .content-header>div:first-child{text-align:left}.content--chat .page-meta{justify-content:flex-start}.content--chat .chat-controls{flex-shrink:0}.grid{display:grid;gap:18px}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.stat-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}.note-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.row{display:flex;gap:12px;align-items:center}.stack{display:grid;gap:14px}.filters{display:flex;flex-wrap:wrap;gap:10px;align-items:center}@media(max-width:1100px){.shell{--shell-pad: 12px;--shell-gap: 12px;--shell-nav-col: 1fr;grid-template-columns:1fr;grid-template-rows:auto auto 1fr;grid-template-areas:"topbar" "nav" "content"}.nav{position:static;max-height:none;display:flex;gap:16px;overflow-x:auto;border-right:none;padding:12px}.nav-group{grid-auto-flow:column;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));border-bottom:none;padding-bottom:0}.grid-cols-2,.grid-cols-3{grid-template-columns:1fr}.topbar{position:static;flex-direction:column;align-items:flex-start;gap:12px}.topbar-status{width:100%;flex-wrap:wrap}.table-head,.table-row,.list-item{grid-template-columns:1fr}}@media(max-width:1100px){.nav{display:flex;flex-direction:row;flex-wrap:nowrap;gap:6px;padding:10px 12px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}.nav::-webkit-scrollbar{display:none}.nav-group,.nav-group__items{display:contents}.nav-label{display:none}.nav-group--collapsed .nav-group__items{display:contents}.nav-item{padding:8px 14px;font-size:13px;border-radius:10px;white-space:nowrap;flex-shrink:0}.nav-item:before{display:none}}@media(max-width:600px){.shell{--shell-pad: 8px;--shell-gap: 8px}.topbar{padding:10px 12px;border-radius:12px;gap:8px;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center}.brand{flex:1;min-width:0}.brand-title{font-size:15px;letter-spacing:.3px}.brand-sub{display:none}.topbar-status{gap:6px;width:auto;flex-wrap:nowrap}.topbar-status .pill{padding:4px 8px;font-size:11px;gap:4px}.topbar-status .pill .mono{display:none}.topbar-status .pill span:nth-child(2){display:none}.nav{padding:8px;border-radius:12px;gap:8px;-webkit-overflow-scrolling:touch;scrollbar-width:none}.nav::-webkit-scrollbar{display:none}.nav-group{display:contents}.nav-label{display:none}.nav-item{padding:7px 10px;font-size:12px;border-radius:8px;white-space:nowrap;flex-shrink:0}.nav-item:before{display:none}.content-header{display:none}.content{padding:4px 4px 16px;gap:12px}.card{padding:12px;border-radius:12px}.card-title{font-size:14px}.stat-grid{gap:8px;grid-template-columns:repeat(2,1fr)}.stat{padding:10px;border-radius:10px}.stat-label{font-size:10px}.stat-value{font-size:16px}.note-grid,.form-grid{grid-template-columns:1fr;gap:10px}.field input,.field textarea,.field select{padding:8px 10px;border-radius:10px;font-size:14px}.btn{padding:8px 12px;font-size:13px}.pill{padding:4px 10px;font-size:12px}.chat-header{flex-direction:column;align-items:stretch;gap:8px}.chat-header__left{flex-direction:column;align-items:stretch}.chat-header__right{justify-content:space-between}.chat-session{min-width:unset;width:100%}.chat-thread{margin-top:8px;padding:10px 8px;border-radius:12px}.chat-msg{max-width:92%}.chat-bubble{padding:8px 10px;border-radius:12px}.chat-compose{gap:8px}.chat-compose__field textarea{min-height:60px;padding:8px 10px;border-radius:12px;font-size:14px}.log-stream{border-radius:10px;max-height:400px}.log-row{grid-template-columns:1fr;gap:4px;padding:8px}.log-time{font-size:10px}.log-level{font-size:9px}.log-subsystem{font-size:11px}.log-message{font-size:12px}.list-item{padding:10px;border-radius:10px}.list-title{font-size:14px}.list-sub{font-size:11px}.code-block{padding:8px;border-radius:10px;font-size:11px}.theme-toggle{--theme-item: 24px;--theme-gap: 4px;--theme-pad: 4px}.theme-icon{width:14px;height:14px}}.chat{position:relative;display:flex;flex-direction:column;flex:1 1 0;height:100%;min-height:0;overflow:hidden;background:transparent!important;border:none!important;box-shadow:none!important}.chat-header{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:nowrap;flex-shrink:0;padding-bottom:12px;margin-bottom:12px;background:transparent}.chat-header__left{display:flex;align-items:center;gap:12px;flex-wrap:wrap;min-width:0}.chat-header__right{display:flex;align-items:center;gap:8px}.chat-session{min-width:180px}.chat-thread{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding:12px;margin:0 -12px;min-height:0;border-radius:12px;background:transparent}.chat-focus-exit{position:absolute;top:12px;right:12px;z-index:100;width:32px;height:32px;border-radius:50%;border:1px solid var(--border);background:var(--panel);color:var(--muted);font-size:20px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease-out,color .15s ease-out,border-color .15s ease-out;box-shadow:0 4px 12px #0003}.chat-focus-exit:hover{background:var(--panel-strong);color:var(--text);border-color:var(--accent)}.chat-compose{position:sticky;bottom:0;flex-shrink:0;display:flex;align-items:flex-end;gap:12px;margin-top:auto;padding:16px 0 4px;background:linear-gradient(to bottom,transparent,var(--bg) 20%);z-index:10}.chat-compose__field{flex:1 1 auto;min-width:0}.chat-compose__field>span{display:none}.chat-compose .chat-compose__field textarea{width:100%;min-height:36px;max-height:150px;padding:8px 12px;border-radius:10px;resize:vertical;white-space:pre-wrap;font-family:var(--font-body);font-size:14px;line-height:1.45}.chat-compose__actions{flex-shrink:0;display:flex;align-items:stretch}.chat-compose .chat-compose__actions .btn{padding:8px 16px;font-size:13px;min-height:36px;white-space:nowrap}.chat-controls{display:flex;align-items:center;justify-content:flex-start;gap:12px;flex-wrap:wrap}.chat-controls__session{min-width:140px}.chat-controls__thinking{display:flex;align-items:center;gap:6px;font-size:13px}.btn--icon{padding:8px!important;min-width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--border);background:#ffffff0f}.chat-controls__separator{color:#fff6;font-size:18px;margin:0 8px;font-weight:300}:root[data-theme=light] .chat-controls__separator{color:#1018284d}.btn--icon:hover{background:#ffffff1f;border-color:#fff3}:root[data-theme=light] .btn--icon{background:#ffffffe6;border-color:#10182833;box-shadow:0 1px 2px #1018280d;color:#101828b3}:root[data-theme=light] .btn--icon:hover{background:#fff;border-color:#1018284d;color:#101828e6}.btn--icon svg{display:block}.chat-controls__session select{padding:6px 10px;font-size:13px}.chat-controls__thinking{display:flex;align-items:center;gap:4px;font-size:12px;padding:4px 10px;background:#ffffff0a;border-radius:6px;border:1px solid var(--border)}:root[data-theme=light] .chat-controls__thinking{background:#ffffffe6;border-color:#10182826}@media(max-width:640px){.chat-session{min-width:140px}.chat-compose{grid-template-columns:1fr}.chat-controls{flex-wrap:wrap;gap:8px}.chat-controls__session{min-width:120px}}.chat-thinking{margin-bottom:10px;padding:10px 12px;border-radius:10px;border:1px dashed rgba(255,255,255,.18);background:#ffffff0a;color:var(--muted);font-size:12px;line-height:1.4}:root[data-theme=light] .chat-thinking{border-color:#1018282e;background:#10182808}.chat-text{font-size:14px;line-height:1.5;word-wrap:break-word;overflow-wrap:break-word}.chat-text :where(p+p,p+ul,p+ol,p+pre,p+blockquote){margin-top:.75em}.chat-text :where(ul,ol){padding-left:1.5em}.chat-text :where(a){color:var(--accent);text-decoration:underline;text-underline-offset:2px}.chat-text :where(a:hover){opacity:.8}.chat-text :where(code){font-family:var(--mono);font-size:.9em}.chat-text :where(:not(pre)>code){background:#00000026;padding:.15em .4em;border-radius:4px}.chat-text :where(pre){background:#00000026;border-radius:6px;padding:10px 12px;overflow-x:auto}.chat-text :where(pre code){background:none;padding:0}.chat-text :where(blockquote){border-left:3px solid var(--border);padding-left:12px;color:var(--muted)}.chat-text :where(hr){border:none;border-top:1px solid var(--border);margin:1em 0}.chat-group{display:flex;gap:12px;align-items:flex-start;margin-bottom:16px;margin-left:16px;margin-right:16px}.chat-group.user{flex-direction:row-reverse;justify-content:flex-start}.chat-group-messages{display:flex;flex-direction:column;gap:2px;max-width:min(900px,calc(100% - 60px))}.chat-group.user .chat-group-messages{align-items:flex-end}.chat-group.user .chat-group-footer{justify-content:flex-end}.chat-group-footer{display:flex;gap:8px;align-items:baseline;margin-top:6px}.chat-sender-name{font-weight:500;font-size:12px;color:var(--muted)}.chat-group-timestamp{font-size:11px;color:var(--muted);opacity:.7}.chat-avatar{width:40px;height:40px;border-radius:8px;background:var(--panel-strong);display:grid;place-items:center;font-weight:600;font-size:14px;flex-shrink:0;align-self:flex-end;margin-bottom:4px}.chat-avatar.user{background:#f59f4a33;color:#f59f4a}.chat-avatar.assistant{background:#34c7b733;color:#34c7b7}.chat-avatar.other{background:#96969633;color:#969696}.chat-avatar.tool{background:#868e9633;color:#868e96}img.chat-avatar{display:block;object-fit:cover;object-position:center}.chat-bubble{position:relative;display:inline-block;border:1px solid var(--border);background:#0000001f;border-radius:12px;padding:10px 14px;box-shadow:none;transition:background .15s ease-out,border-color .15s ease-out;max-width:100%;word-wrap:break-word}.chat-bubble.has-copy{padding-right:36px}.chat-copy-btn{position:absolute;top:6px;right:8px;border:1px solid var(--border);background:#00000038;color:var(--muted);border-radius:8px;padding:4px 6px;font-size:14px;line-height:1;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .12s ease-out,background .12s ease-out}.chat-copy-btn__icon{display:inline-block;width:1em;text-align:center}.chat-bubble:hover .chat-copy-btn{opacity:1;pointer-events:auto}.chat-copy-btn:hover{background:#0000004d}.chat-copy-btn[data-copying="1"]{opacity:0;pointer-events:none}.chat-copy-btn[data-error="1"]{opacity:1;pointer-events:auto;border-color:#ff453acc;background:#ff453a2e;color:#ff453a}.chat-copy-btn[data-copied="1"]{opacity:1;pointer-events:auto;border-color:#34c7b7cc;background:#34c7b72e;color:#34c7b7}.chat-copy-btn:focus-visible{opacity:1;pointer-events:auto;outline:2px solid var(--accent);outline-offset:2px}@media(hover:none){.chat-copy-btn{opacity:1;pointer-events:auto}}.chat-bubble:hover{background:#0000002e}.chat-group.user .chat-bubble{background:#f59f4a26;border-color:#f59f4a4d}.chat-group.user .chat-bubble:hover{background:#f59f4a38}.chat-bubble.streaming{animation:pulsing-border 1.5s ease-out infinite}@keyframes pulsing-border{0%,to{border-color:var(--border)}50%{border-color:var(--accent)}}.chat-bubble.fade-in{animation:fade-in .2s ease-out}@keyframes fade-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.chat-tool-card{border:1px solid var(--border);border-radius:8px;padding:12px;margin-top:8px;transition:border-color .15s ease-out,background .15s ease-out;max-height:120px;overflow:hidden}.chat-tool-card:hover{border-color:var(--accent);background:#0000000f}.chat-tool-card:first-child{margin-top:0}.chat-tool-card--clickable{cursor:pointer}.chat-tool-card--clickable:focus{outline:2px solid var(--accent);outline-offset:2px}.chat-tool-card__header{display:flex;justify-content:space-between;align-items:center;gap:8px}.chat-tool-card__title{display:inline-flex;align-items:center;gap:6px;font-weight:600;font-size:13px;line-height:1.2}.chat-tool-card__icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;font-size:14px;line-height:1;font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif;vertical-align:middle;flex-shrink:0}.chat-tool-card__action{font-size:12px;color:var(--accent);opacity:.8;transition:opacity .15s ease-out}.chat-tool-card--clickable:hover .chat-tool-card__action{opacity:1}.chat-tool-card__status{font-size:14px;color:var(--ok)}.chat-tool-card__status-text{font-size:11px;margin-top:4px}.chat-tool-card__detail{font-size:12px;color:var(--muted);margin-top:4px}.chat-tool-card__preview{font-size:11px;color:var(--muted);margin-top:8px;padding:8px 10px;background:#00000014;border-radius:6px;white-space:pre-wrap;overflow:hidden;max-height:44px;line-height:1.4;border:1px solid rgba(255,255,255,.04)}.chat-tool-card--clickable:hover .chat-tool-card__preview{background:#0000001f;border-color:#ffffff14}.chat-tool-card__inline{font-size:11px;color:var(--text);margin-top:6px;padding:6px 8px;background:#0000000f;border-radius:4px;white-space:pre-wrap;word-break:break-word}.chat-reading-indicator{background:transparent;border:1px solid var(--border);padding:12px;display:inline-flex}.chat-reading-indicator__dots{display:flex;gap:6px;align-items:center}.chat-reading-indicator__dots span{width:6px;height:6px;border-radius:50%;background:var(--muted);animation:reading-pulse 1.4s ease-in-out infinite}.chat-reading-indicator__dots span:nth-child(1){animation-delay:0s}.chat-reading-indicator__dots span:nth-child(2){animation-delay:.2s}.chat-reading-indicator__dots span:nth-child(3){animation-delay:.4s}@keyframes reading-pulse{0%,60%,to{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.chat-split-container{display:flex;gap:0;flex:1;min-height:0;height:100%}.chat-main{min-width:400px;display:flex;flex-direction:column;overflow:hidden;transition:flex .25s ease-out}.chat-sidebar{flex:1;min-width:300px;border-left:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;animation:slide-in .2s ease-out}@keyframes slide-in{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.sidebar-panel{display:flex;flex-direction:column;height:100%;background:var(--panel)}.sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--border);flex-shrink:0;position:sticky;top:0;z-index:10;background:var(--panel)}.sidebar-header .btn{padding:4px 8px;font-size:14px;min-width:auto;line-height:1}.sidebar-title{font-weight:600;font-size:14px}.sidebar-content{flex:1;overflow:auto;padding:16px}.sidebar-markdown{font-size:14px;line-height:1.5}.sidebar-markdown pre{background:#0000001f;border-radius:4px;padding:12px;overflow-x:auto}.sidebar-markdown code{font-family:var(--mono);font-size:13px}@media(max-width:768px){.chat-split-container--open{position:fixed;inset:0;z-index:1000}.chat-split-container--open .chat-main{display:none}.chat-split-container--open .chat-sidebar{width:100%;min-width:0;border-left:none}}.card{border:1px solid var(--border);background:linear-gradient(160deg,rgba(255,255,255,.04),transparent 65%),var(--panel);border-radius:16px;padding:16px;box-shadow:0 18px 36px #00000047;animation:rise .4s ease}.card-title{font-family:var(--font-display);font-size:16px;letter-spacing:.6px;text-transform:uppercase}.card-sub{color:var(--muted);font-size:12px}.stat{background:linear-gradient(140deg,rgba(255,255,255,.04),transparent 70%),var(--panel-strong);border-radius:14px;padding:12px;border:1px solid var(--border-strong)}.stat-label{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:1px}.stat-value{font-size:18px;margin-top:6px}.stat-value.ok{color:var(--ok)}.stat-value.warn{color:var(--warn)}.stat-card{display:grid;gap:6px}.note-title{font-weight:600;letter-spacing:.2px}.status-list{display:grid;gap:8px}.status-list div{display:flex;justify-content:space-between;gap:12px;padding:6px 0;border-bottom:1px dashed rgba(255,255,255,.06)}.status-list div:last-child{border-bottom:none}.account-count{margin-top:8px;font-size:12px;font-weight:600;letter-spacing:.4px;color:var(--muted)}.account-card-list{margin-top:16px;display:grid;gap:10px}.account-card{border:1px solid var(--border);border-radius:10px;padding:12px;background:linear-gradient(160deg,rgba(255,255,255,.06),transparent),#ffffff08}.account-card-header{display:flex;justify-content:space-between;align-items:baseline;gap:12px}.account-card-title{font-weight:600}.account-card-id{font-family:var(--mono);font-size:12px;color:var(--muted)}.account-card-status{margin-top:8px;font-size:13px}.account-card-status div{padding:4px 0}.account-card-error{margin-top:6px;color:var(--danger);font-size:12px}.label{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.9px}.pill{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--border-strong);padding:6px 12px;border-radius:999px;background:linear-gradient(160deg,rgba(255,255,255,.06),transparent),var(--panel)}.theme-toggle{--theme-item: 28px;--theme-gap: 6px;--theme-pad: 6px;position:relative}.theme-toggle__track{position:relative;display:grid;grid-template-columns:repeat(3,var(--theme-item));gap:var(--theme-gap);padding:var(--theme-pad);border-radius:999px;border:1px solid var(--border-strong);background:#ffffff0a}.theme-toggle__indicator{position:absolute;top:50%;left:var(--theme-pad);width:var(--theme-item);height:var(--theme-item);border-radius:999px;transform:translateY(-50%) translate(calc(var(--theme-index, 0) * (var(--theme-item) + var(--theme-gap))));background:linear-gradient(160deg,rgba(255,255,255,.12),transparent),var(--panel-strong);border:1px solid var(--border-strong);box-shadow:0 8px 16px #00000040;transition:transform .18s ease-out,background .18s ease-out,box-shadow .18s ease-out;z-index:0}.theme-toggle__button{height:var(--theme-item);width:var(--theme-item);display:grid;place-items:center;border:0;border-radius:999px;background:transparent;color:var(--muted);cursor:pointer;position:relative;z-index:1;transition:color .15s ease-out,background .15s ease-out}.theme-toggle__button:hover{color:var(--text);background:#ffffff14}.theme-toggle__button.active{color:var(--text)}.theme-icon{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75px;stroke-linecap:round;stroke-linejoin:round}.pill.danger{border-color:#ff5c5c80;color:var(--danger)}.statusDot{width:8px;height:8px;border-radius:999px;background:var(--danger);box-shadow:0 0 0 2px #00000040}.statusDot.ok{background:var(--ok);box-shadow:0 0 0 2px #00000040,0 0 10px #2bd97f66}.btn{border:1px solid var(--border-strong);background:#ffffff0a;padding:8px 14px;border-radius:999px;cursor:pointer;transition:transform .15s ease,border-color .15s ease,background .15s ease}.btn:hover{background:#ffffff1a;transform:translateY(-1px)}.btn.primary{border-color:#f59f4a73;background:#f59f4a33}.btn.active{border-color:#f59f4a8c;background:#f59f4a29}.btn.danger{border-color:#ff6b6b73;background:#ff6b6b2e}.btn--sm{padding:5px 10px;font-size:12px}.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.field{display:grid;gap:6px}.field.full{grid-column:1 / -1}.field span{color:var(--muted);font-size:11px;letter-spacing:.4px}.field input,.field textarea,.field select{border:1px solid var(--border-strong);background:#00000038;border-radius:12px;padding:9px 11px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus);background:#00000047}.field select{appearance:none;padding-right:38px;background-color:var(--panel-strong);background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),linear-gradient(135deg,var(--muted) 50%,transparent 50%),linear-gradient(to right,transparent,transparent);background-position:calc(100% - 18px) 50%,calc(100% - 12px) 50%,calc(100% - 38px) 50%;background-size:6px 6px,6px 6px,1px 60%;background-repeat:no-repeat;box-shadow:inset 0 1px #ffffff0a}.field textarea{font-family:var(--mono);min-height:180px;resize:vertical;white-space:pre}.field textarea:focus{background:#00000052}.field.checkbox{grid-template-columns:auto 1fr;align-items:center}.config-form .field.checkbox{grid-template-columns:18px minmax(0,1fr);column-gap:10px}.config-form .field.checkbox input[type=checkbox]{margin:0}.form-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}:root[data-theme=light] .field input,:root[data-theme=light] .field textarea,:root[data-theme=light] .field select{background:#fff;border-color:#10182840;box-shadow:0 1px 2px #1018280f}:root[data-theme=light] .field input:focus,:root[data-theme=light] .field textarea:focus,:root[data-theme=light] .field select:focus{background:#fff}:root[data-theme=light] .btn{background:#ffffffe6;border-color:#10182833;box-shadow:0 1px 2px #1018280d}:root[data-theme=light] .btn:hover{background:#fff;border-color:#1018284d}:root[data-theme=light] .btn.primary{background:#f59f4a26}:root[data-theme=light] .btn.active{background:#f59f4a1f}.muted{color:var(--muted)}.mono{font-family:var(--mono)}.callout{padding:10px 12px;border-radius:14px;background:linear-gradient(160deg,rgba(255,255,255,.06),transparent),#ffffff08;border:1px solid var(--border)}.callout.danger{border-color:#ff5c5c66;color:var(--danger)}.code-block{font-family:var(--mono);font-size:12px;background:#00000059;padding:10px;border-radius:12px;border:1px solid var(--border);max-height:360px;overflow:auto}:root[data-theme=light] .code-block,:root[data-theme=light] .list-item,:root[data-theme=light] .table-row,:root[data-theme=light] .chip{background:#ffffffd9}.list{display:grid;gap:12px;container-type:inline-size}.list-item{display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,260px);gap:14px;align-items:start;border:1px solid var(--border);border-radius:14px;padding:12px;background:#0003}.list-item-clickable{cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.list-item-clickable:hover{border-color:var(--border-strong)}.list-item-selected{border-color:var(--accent);box-shadow:0 0 0 1px var(--focus)}.list-main{display:grid;gap:6px;min-width:0}.list-title{font-weight:600}.list-sub{color:var(--muted);font-size:12px}.list-meta{text-align:right;color:var(--muted);font-size:11px;display:grid;gap:4px;min-width:220px}.list-meta .btn{padding:6px 10px}.list-meta .field input,.list-meta .field textarea,.list-meta .field select{width:100%}@container (max-width: 560px){.list-item{grid-template-columns:1fr}.list-meta{min-width:0;text-align:left}}.chip-row{display:flex;flex-wrap:wrap;gap:6px}.chip{font-size:11px;border:1px solid var(--border);border-radius:999px;padding:4px 8px;color:var(--muted);background:#0003}.chip input{margin-right:6px}.chip-ok{color:var(--ok);border-color:#1bd98a66}.chip-warn{color:var(--warn);border-color:#f2c94c66}.table{display:grid;gap:8px}.table-head,.table-row{display:grid;grid-template-columns:1.4fr 1fr .8fr .7fr .8fr .8fr .8fr .8fr .6fr;gap:12px;align-items:center}.table-head{font-size:11px;text-transform:uppercase;letter-spacing:.8px;color:var(--muted)}.table-row{border:1px solid var(--border);padding:10px;border-radius:12px;background:#0003}.session-link{text-decoration:none;color:var(--accent)}.session-link:hover{text-decoration:underline}.log-stream{border:1px solid var(--border);border-radius:14px;background:#0003;max-height:520px;overflow:auto;container-type:inline-size}.log-row{display:grid;grid-template-columns:90px 70px minmax(140px,200px) minmax(0,1fr);gap:12px;align-items:start;padding:6px 10px;border-bottom:1px solid var(--border);font-size:12px}.log-row:last-child{border-bottom:none}.log-time{color:var(--muted)}.log-level{text-transform:uppercase;font-size:10px;font-weight:600;border:1px solid var(--border);border-radius:999px;padding:2px 6px;width:fit-content}.log-level.trace,.log-level.debug{color:var(--muted)}.log-level.info{color:var(--info);border-color:#4c96f266}.log-level.warn{color:var(--warn);border-color:#f2c94c66}.log-level.error,.log-level.fatal{color:var(--danger);border-color:#ff5c5c66}.log-chip.trace,.log-chip.debug{color:var(--muted)}.log-chip.info{color:var(--info);border-color:#4c96f266}.log-chip.warn{color:var(--warn);border-color:#f2c94c66}.log-chip.error,.log-chip.fatal{color:var(--danger);border-color:#ff5c5c66}.log-subsystem{color:var(--muted)}.log-message{white-space:pre-wrap;word-break:break-word}@container (max-width: 620px){.log-row{grid-template-columns:70px 60px minmax(0,1fr)}.log-subsystem{display:none}}.chat{display:flex;flex-direction:column;min-height:0}.shell--chat .chat{flex:1}.chat-header{display:flex;justify-content:space-between;align-items:flex-end;gap:12px;flex-wrap:wrap}.chat-header__left{display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap;min-width:0}.chat-header__right{display:flex;align-items:center;gap:10px}.chat-session{min-width:240px}.chat-thread{margin-top:12px;display:flex;flex-direction:column;gap:12px;flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:14px 12px;min-width:0;border-radius:0;border:none;background:transparent}:root[data-theme=light] .chat-thread{background:transparent}.chat-queue{margin-top:12px;padding:10px 12px;border-radius:16px;border:1px solid var(--border);background:#0000002e;display:grid;gap:8px}:root[data-theme=light] .chat-queue{background:#1018280a}.chat-queue__title{font-family:var(--font-mono);font-size:12px;color:var(--muted)}.chat-queue__list{display:grid;gap:8px}.chat-queue__item{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:10px;padding:8px 10px;border-radius:12px;border:1px dashed var(--border);background:#0003}:root[data-theme=light] .chat-queue__item{background:#1018280d}.chat-queue__text{color:var(--chat-text);font-size:13px;line-height:1.4;white-space:pre-wrap;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}.chat-queue__remove{align-self:start;padding:4px 10px;font-size:12px;line-height:1}.chat-line{display:flex}.chat-line.user{justify-content:flex-end}.chat-line.assistant,.chat-line.other{justify-content:flex-start}.chat-msg{display:grid;gap:6px;max-width:min(720px,82%)}.chat-line.user .chat-msg{justify-items:end}.chat-bubble{border:1px solid var(--border);background:#0000003d;border-radius:16px;padding:10px 12px;min-width:0;box-shadow:0 12px 22px #0000003d}:root[data-theme=light] .chat-bubble{background:#ffffffd9;box-shadow:0 12px 26px #10182814}.chat-line.user .chat-bubble{border-color:#f59f4a73;background:linear-gradient(135deg,#f59f4a42,#f59f4a1f)}.chat-line.assistant .chat-bubble{border-color:#34c7b733;background:linear-gradient(135deg,#34c7b71f,#0000003d)}:root[data-theme=light] .chat-line.assistant .chat-bubble{background:linear-gradient(135deg,#1bb9b11f,#ffffffd9)}@keyframes chatStreamPulse{0%{box-shadow:0 12px 22px #0000003d,0 0 #34c7b700}60%{box-shadow:0 12px 22px #0000003d,0 0 0 6px #34c7b714}to{box-shadow:0 12px 22px #0000003d,0 0 #34c7b700}}.chat-bubble.streaming{border-color:#34c7b766;animation:chatStreamPulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.chat-bubble.streaming{animation:none}}.chat-bubble.chat-reading-indicator{width:fit-content;padding:10px 14px}.chat-reading-indicator__dots{display:inline-flex;align-items:center;gap:6px;height:10px}.chat-reading-indicator__dots>span{display:inline-block;width:6px;height:6px;border-radius:999px;background:var(--chat-text);opacity:.55;transform:translateY(0);animation:chatReadingDot 1.1s ease-in-out infinite;will-change:transform,opacity}.chat-reading-indicator__dots>span:nth-child(2){animation-delay:.12s}.chat-reading-indicator__dots>span:nth-child(3){animation-delay:.24s}@keyframes chatReadingDot{0%,80%,to{opacity:.38;transform:translateY(0) scale(.92)}40%{opacity:1;transform:translateY(-3px) scale(1.18)}}@media(prefers-reduced-motion:reduce){.chat-reading-indicator__dots>span{animation:none;opacity:.75}}.chat-text{overflow-wrap:anywhere;word-break:break-word;color:var(--chat-text);line-height:1.5}.chat-text :where(p,ul,ol,pre,blockquote,table){margin:0}.chat-text :where(p+p,p+ul,p+ol,p+pre,p+blockquote,p+table){margin-top:.75em}.chat-text :where(ul,ol){padding-left:1.1em}.chat-text :where(li+li){margin-top:.25em}.chat-text :where(a){color:var(--accent);text-decoration-thickness:2px;text-underline-offset:2px}.chat-text :where(a:hover){text-decoration-thickness:3px}.chat-text :where(blockquote){border-left:2px solid rgba(255,255,255,.14);padding-left:12px;color:var(--muted)}:root[data-theme=light] .chat-text :where(blockquote){border-left-color:#10182829}.chat-text :where(hr){border:0;border-top:1px solid var(--border);opacity:.6;margin:.9em 0}.chat-text :where(code){font-family:var(--font-mono);font-size:.92em}.chat-text :where(:not(pre)>code){padding:.15em .35em;border-radius:8px;border:1px solid var(--border);background:#0003}:root[data-theme=light] .chat-text :where(:not(pre)>code){background:#1018280d}.chat-text :where(pre){margin-top:.75em;padding:10px 12px;border-radius:14px;border:1px solid var(--border);background:#00000038;overflow:auto}:root[data-theme=light] .chat-text :where(pre){background:#1018280a}.chat-text :where(pre code){font-size:12px;white-space:pre}.chat-text :where(table){margin-top:.75em;border-collapse:collapse;width:100%;font-size:12px}.chat-text :where(th,td){border:1px solid var(--border);padding:6px 8px;vertical-align:top}.chat-text :where(th){font-family:var(--font-mono);font-weight:600;color:var(--muted)}.chat-tool-card{margin-top:8px;padding:8px 10px;border-radius:12px;border:1px solid var(--border);background:#00000038;display:grid;gap:4px}:root[data-theme=light] .chat-tool-card{background:#ffffffb3}.chat-tool-card__title{font-family:var(--font-mono);font-size:12px;color:var(--chat-text)}.chat-tool-card__detail{font-family:var(--font-mono);font-size:11px;color:var(--muted)}.chat-tool-card__details{margin-top:6px}.chat-tool-card__summary{font-family:var(--font-mono);font-size:11px;color:var(--muted);cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px}.chat-tool-card__summary::-webkit-details-marker{display:none}.chat-tool-card__summary-meta{color:var(--muted);opacity:.8}.chat-tool-card__details[open] .chat-tool-card__summary{color:var(--chat-text)}.chat-tool-card__output{margin-top:6px;font-family:var(--font-mono);font-size:11px;line-height:1.45;white-space:pre-wrap;color:var(--chat-text);padding:8px;border-radius:10px;border:1px solid var(--border);background:#0003}:root[data-theme=light] .chat-tool-card__output{background:#1018280d}.chat-stamp{font-size:11px;color:var(--muted)}.chat-line.user .chat-stamp{text-align:right}.chat-compose{margin-top:12px;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:10px}.shell--chat .chat-compose{position:sticky;bottom:0;z-index:5;margin-top:0;padding-top:12px;background:linear-gradient(180deg,rgba(0,0,0,0) 0%,var(--panel) 35%)}.shell--chat-focus .chat-compose{bottom:calc(var(--shell-pad) + 8px);padding-bottom:calc(14px + env(safe-area-inset-bottom,0px));border-bottom-left-radius:18px;border-bottom-right-radius:18px}.chat-compose__field{gap:4px}.chat-compose__field textarea{min-height:72px;padding:10px 12px;border-radius:16px;resize:vertical;white-space:pre-wrap;font-family:var(--font-body);line-height:1.45}.chat-compose__field textarea:disabled{opacity:.7;cursor:not-allowed}.chat-compose__actions{justify-content:flex-end;align-self:end}@media(max-width:900px){.chat-session{min-width:200px}.chat-compose{grid-template-columns:1fr}}.qr-wrap{margin-top:12px;border-radius:14px;background:#0003;border:1px dashed rgba(255,255,255,.18);padding:12px;display:inline-flex}.qr-wrap img{width:180px;height:180px;border-radius:10px;image-rendering:pixelated}.exec-approval-overlay{position:fixed;inset:0;background:#080c12b3;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px;z-index:200}.exec-approval-card{width:min(560px,100%);background:var(--panel-strong);border:1px solid var(--border-strong);border-radius:18px;padding:20px;box-shadow:0 28px 60px #00000059;animation:rise .25s ease}.exec-approval-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.exec-approval-title{font-family:var(--font-display);font-size:14px;letter-spacing:.8px;text-transform:uppercase}.exec-approval-sub{color:var(--muted);font-size:12px}.exec-approval-queue{font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--muted);border:1px solid var(--border);border-radius:999px;padding:4px 10px}.exec-approval-command{margin-top:12px;padding:10px 12px;background:#00000040;border:1px solid var(--border);border-radius:12px;word-break:break-word;white-space:pre-wrap}.exec-approval-meta{margin-top:12px;display:grid;gap:6px;font-size:12px;color:var(--muted)}.exec-approval-meta-row{display:flex;justify-content:space-between;gap:12px}.exec-approval-meta-row span:last-child{color:var(--text);font-family:var(--mono)}.exec-approval-error{margin-top:10px;font-size:12px;color:var(--danger)}.exec-approval-actions{margin-top:16px;display:flex;flex-wrap:wrap;gap:10px}.config-layout{display:grid;grid-template-columns:240px minmax(0,1fr);gap:0;min-height:calc(100vh - 140px);margin:-16px;border-radius:16px;overflow:hidden;border:1px solid var(--border);background:var(--panel)}.config-sidebar{display:flex;flex-direction:column;background:#0003;border-right:1px solid var(--border)}:root[data-theme=light] .config-sidebar{background:#00000008}.config-sidebar__header{display:flex;align-items:center;justify-content:space-between;padding:16px;border-bottom:1px solid var(--border)}.config-sidebar__title{font-weight:600;font-size:14px;letter-spacing:.3px}.config-sidebar__footer{margin-top:auto;padding:12px;border-top:1px solid var(--border)}.config-search{position:relative;padding:12px;border-bottom:1px solid var(--border)}.config-search__icon{position:absolute;left:24px;top:50%;transform:translateY(-50%);width:16px;height:16px;color:var(--muted);pointer-events:none}.config-search__input{width:100%;padding:10px 32px 10px 40px;border:1px solid var(--border);border-radius:8px;background:#00000026;font-size:13px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.config-search__input::placeholder{color:var(--muted)}.config-search__input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus);background:#0003}:root[data-theme=light] .config-search__input{background:#fffc}:root[data-theme=light] .config-search__input:focus{background:#fff}.config-search__clear{position:absolute;right:20px;top:50%;transform:translateY(-50%);width:20px;height:20px;border:none;border-radius:50%;background:#ffffff1a;color:var(--muted);font-size:16px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease,color .15s ease}.config-search__clear:hover{background:#fff3;color:var(--text)}.config-nav{flex:1;overflow-y:auto;padding:8px}.config-nav__item{display:flex;align-items:center;gap:12px;width:100%;padding:10px 12px;border:none;border-radius:8px;background:transparent;color:var(--muted);font-size:13px;font-weight:500;text-align:left;cursor:pointer;transition:background .15s ease,color .15s ease}.config-nav__item:hover{background:#ffffff0d;color:var(--text)}:root[data-theme=light] .config-nav__item:hover{background:#0000000d}.config-nav__item.active{background:#f59f4a1f;color:var(--accent)}.config-nav__icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:14px}.config-nav__icon svg{width:18px;height:18px;stroke:currentColor;fill:none}.config-nav__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.config-mode-toggle{display:flex;padding:3px;background:#0003;border-radius:8px;border:1px solid var(--border)}:root[data-theme=light] .config-mode-toggle{background:#0000000f}.config-mode-toggle__btn{flex:1;padding:8px 12px;border:none;border-radius:6px;background:transparent;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}.config-mode-toggle__btn:hover{color:var(--text)}.config-mode-toggle__btn.active{background:#ffffff1a;color:var(--text);box-shadow:0 1px 3px #0003}:root[data-theme=light] .config-mode-toggle__btn.active{background:#fff;box-shadow:0 1px 3px #0000001a}.config-main{display:flex;flex-direction:column;min-width:0;background:var(--panel)}.config-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 20px;background:#00000014;border-bottom:1px solid var(--border)}:root[data-theme=light] .config-actions{background:#00000005}.config-actions__left,.config-actions__right{display:flex;align-items:center;gap:8px}.config-changes-badge{padding:5px 12px;border-radius:999px;background:#f59f4a26;border:1px solid rgba(245,159,74,.3);color:var(--accent);font-size:12px;font-weight:600}.config-status{font-size:13px;color:var(--muted)}.config-diff{margin:16px 20px 0;border:1px solid rgba(245,159,74,.3);border-radius:10px;background:#f59f4a0d;overflow:hidden}.config-diff__summary{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;cursor:pointer;font-size:13px;font-weight:600;color:var(--accent);list-style:none}.config-diff__summary::-webkit-details-marker{display:none}.config-diff__chevron{width:16px;height:16px;transition:transform .2s ease}.config-diff__chevron svg{width:100%;height:100%}.config-diff[open] .config-diff__chevron{transform:rotate(180deg)}.config-diff__content{padding:0 16px 16px;display:grid;gap:8px}.config-diff__item{display:flex;align-items:baseline;gap:12px;padding:8px 12px;border-radius:6px;background:#0000001a;font-size:12px;font-family:var(--mono)}:root[data-theme=light] .config-diff__item{background:#fff9}.config-diff__path{font-weight:600;color:var(--text);flex-shrink:0}.config-diff__values{display:flex;align-items:baseline;gap:8px;min-width:0;flex-wrap:wrap}.config-diff__from{color:var(--danger);opacity:.8}.config-diff__arrow{color:var(--muted)}.config-diff__to{color:var(--ok)}.config-section-hero{display:flex;align-items:center;gap:14px;padding:14px 20px;border-bottom:1px solid var(--border);background:#0000000a}:root[data-theme=light] .config-section-hero{background:#00000004}.config-section-hero__icon{width:28px;height:28px;color:var(--accent);display:flex;align-items:center;justify-content:center}.config-section-hero__icon svg{width:100%;height:100%;stroke:currentColor;fill:none}.config-section-hero__text{display:grid;gap:2px;min-width:0}.config-section-hero__title{font-size:15px;font-weight:600}.config-section-hero__desc{font-size:12px;color:var(--muted)}.config-subnav{display:flex;gap:8px;padding:10px 20px 12px;border-bottom:1px solid var(--border);background:#00000008;overflow-x:auto}:root[data-theme=light] .config-subnav{background:#00000005}.config-subnav__item{border:1px solid transparent;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;color:var(--muted);background:#0000001f;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap}:root[data-theme=light] .config-subnav__item{background:#0000000f}.config-subnav__item:hover{color:var(--text);background:#ffffff14}:root[data-theme=light] .config-subnav__item:hover{background:#00000014}.config-subnav__item.active{color:var(--accent);border-color:#f59f4a66;background:#f59f4a1f}.config-content{flex:1;overflow-y:auto;padding:20px}.config-raw-field textarea{min-height:500px;font-family:var(--mono);font-size:13px;line-height:1.5}.config-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:80px 20px;color:var(--muted)}.config-loading__spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.config-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:80px 20px;text-align:center}.config-empty__icon{font-size:56px;opacity:.4}.config-empty__text{color:var(--muted);font-size:15px}.config-form--modern{display:grid;gap:24px}.config-section-card{border:1px solid var(--border);border-radius:12px;background:#ffffff05;overflow:hidden}:root[data-theme=light] .config-section-card{background:#ffffff80}.config-section-card__header{display:flex;align-items:flex-start;gap:14px;padding:18px 20px;background:#0000000f;border-bottom:1px solid var(--border)}:root[data-theme=light] .config-section-card__header{background:#00000005}.config-section-card__icon{width:32px;height:32px;color:var(--accent);flex-shrink:0}.config-section-card__icon svg{width:100%;height:100%}.config-section-card__titles{flex:1;min-width:0}.config-section-card__title{margin:0;font-size:17px;font-weight:600}.config-section-card__desc{margin:4px 0 0;font-size:13px;color:var(--muted);line-height:1.4}.config-section-card__content{padding:20px}.cfg-fields{display:grid;gap:20px}.cfg-field{display:grid;gap:6px}.cfg-field--error{padding:12px;border-radius:8px;background:#ff5c5c1a;border:1px solid rgba(255,92,92,.3)}.cfg-field__label{font-size:13px;font-weight:600;color:var(--text)}.cfg-field__help{font-size:12px;color:var(--muted);line-height:1.4}.cfg-field__error{font-size:12px;color:var(--danger)}.cfg-input-wrap{display:flex;gap:8px}.cfg-input{flex:1;padding:10px 12px;border:1px solid var(--border);border-radius:8px;background:#0000001f;font-size:14px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.cfg-input::placeholder{color:var(--muted);opacity:.7}.cfg-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus);background:#0000002e}:root[data-theme=light] .cfg-input{background:#fff}:root[data-theme=light] .cfg-input:focus{background:#fff}.cfg-input--sm{padding:8px 10px;font-size:13px}.cfg-input__reset{padding:8px 12px;border:1px solid var(--border);border-radius:8px;background:#ffffff0d;color:var(--muted);font-size:14px;cursor:pointer;transition:background .15s ease,color .15s ease}.cfg-input__reset:hover:not(:disabled){background:#ffffff1a;color:var(--text)}.cfg-input__reset:disabled{opacity:.5;cursor:not-allowed}.cfg-textarea{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:8px;background:#0000001f;font-family:var(--mono);font-size:13px;line-height:1.5;resize:vertical;outline:none;transition:border-color .15s ease,box-shadow .15s ease}.cfg-textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus)}:root[data-theme=light] .cfg-textarea{background:#fff}.cfg-textarea--sm{padding:8px 10px;font-size:12px}.cfg-number{display:inline-flex;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:#0000001f}:root[data-theme=light] .cfg-number{background:#fff}.cfg-number__btn{width:40px;border:none;background:#ffffff0d;color:var(--text);font-size:18px;font-weight:300;cursor:pointer;transition:background .15s ease}.cfg-number__btn:hover:not(:disabled){background:#ffffff1a}.cfg-number__btn:disabled{opacity:.4;cursor:not-allowed}:root[data-theme=light] .cfg-number__btn{background:#00000008}:root[data-theme=light] .cfg-number__btn:hover:not(:disabled){background:#0000000f}.cfg-number__input{width:80px;padding:10px;border:none;border-left:1px solid var(--border);border-right:1px solid var(--border);background:transparent;font-size:14px;text-align:center;outline:none;-moz-appearance:textfield}.cfg-number__input::-webkit-outer-spin-button,.cfg-number__input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.cfg-select{padding:10px 36px 10px 12px;border:1px solid var(--border);border-radius:8px;background-color:#0000001f;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;font-size:14px;cursor:pointer;outline:none;appearance:none;transition:border-color .15s ease,box-shadow .15s ease}.cfg-select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus)}:root[data-theme=light] .cfg-select{background-color:#fff}.cfg-segmented{display:inline-flex;padding:3px;border:1px solid var(--border);border-radius:8px;background:#0000001f}:root[data-theme=light] .cfg-segmented{background:#0000000a}.cfg-segmented__btn{padding:8px 16px;border:none;border-radius:6px;background:transparent;color:var(--muted);font-size:13px;font-weight:500;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}.cfg-segmented__btn:hover:not(:disabled):not(.active){color:var(--text)}.cfg-segmented__btn.active{background:#ffffff1f;color:var(--text);box-shadow:0 1px 3px #0003}:root[data-theme=light] .cfg-segmented__btn.active{background:#fff;box-shadow:0 1px 3px #0000001a}.cfg-segmented__btn:disabled{opacity:.5;cursor:not-allowed}.cfg-toggle-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 16px;border:1px solid var(--border);border-radius:10px;background:#0000000f;cursor:pointer;transition:background .15s ease,border-color .15s ease}.cfg-toggle-row:hover:not(.disabled){background:#0000001a;border-color:var(--border-strong)}.cfg-toggle-row.disabled{opacity:.6;cursor:not-allowed}:root[data-theme=light] .cfg-toggle-row{background:#ffffff80}:root[data-theme=light] .cfg-toggle-row:hover:not(.disabled){background:#fffc}.cfg-toggle-row__content{flex:1;min-width:0}.cfg-toggle-row__label{display:block;font-size:14px;font-weight:500;color:var(--text)}.cfg-toggle-row__help{display:block;margin-top:2px;font-size:12px;color:var(--muted);line-height:1.4}.cfg-toggle{position:relative;flex-shrink:0}.cfg-toggle input{position:absolute;opacity:0;width:0;height:0}.cfg-toggle__track{display:block;width:48px;height:28px;background:#ffffff1f;border:1px solid var(--border);border-radius:999px;position:relative;transition:background .2s ease,border-color .2s ease}:root[data-theme=light] .cfg-toggle__track{background:#0000001a}.cfg-toggle__track:after{content:"";position:absolute;top:3px;left:3px;width:20px;height:20px;background:var(--text);border-radius:50%;box-shadow:0 2px 4px #0000004d;transition:transform .2s ease,background .2s ease}.cfg-toggle input:checked+.cfg-toggle__track{background:#2bd97f40;border-color:#2bd97f80}.cfg-toggle input:checked+.cfg-toggle__track:after{transform:translate(20px);background:var(--ok)}.cfg-toggle input:focus+.cfg-toggle__track{box-shadow:0 0 0 3px var(--focus)}.cfg-object{border:1px solid var(--border);border-radius:10px;background:#0000000a;overflow:hidden}:root[data-theme=light] .cfg-object{background:#fff6}.cfg-object__header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;cursor:pointer;list-style:none;transition:background .15s ease}.cfg-object__header:hover{background:#ffffff08}:root[data-theme=light] .cfg-object__header:hover{background:#00000005}.cfg-object__header::-webkit-details-marker{display:none}.cfg-object__title{font-size:14px;font-weight:600;color:var(--text)}.cfg-object__chevron{width:18px;height:18px;color:var(--muted);transition:transform .2s ease}.cfg-object__chevron svg{width:100%;height:100%}.cfg-object[open] .cfg-object__chevron{transform:rotate(180deg)}.cfg-object__help{padding:0 16px 12px;font-size:12px;color:var(--muted);border-bottom:1px solid var(--border)}.cfg-object__content{padding:16px;display:grid;gap:16px}.cfg-array{border:1px solid var(--border);border-radius:10px;overflow:hidden}.cfg-array__header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#0000000f;border-bottom:1px solid var(--border)}:root[data-theme=light] .cfg-array__header{background:#00000005}.cfg-array__label{flex:1;font-size:14px;font-weight:600;color:var(--text)}.cfg-array__count{font-size:12px;color:var(--muted);padding:3px 8px;background:#ffffff0f;border-radius:999px}:root[data-theme=light] .cfg-array__count{background:#0000000f}.cfg-array__add{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:#ffffff0d;color:var(--text);font-size:12px;font-weight:500;cursor:pointer;transition:background .15s ease}.cfg-array__add:hover:not(:disabled){background:#ffffff1a}.cfg-array__add:disabled{opacity:.5;cursor:not-allowed}.cfg-array__add-icon{width:14px;height:14px}.cfg-array__add-icon svg{width:100%;height:100%}.cfg-array__help{padding:10px 16px;font-size:12px;color:var(--muted);border-bottom:1px solid var(--border)}.cfg-array__empty{padding:32px 16px;text-align:center;color:var(--muted);font-size:13px}.cfg-array__items{display:grid;gap:1px;background:var(--border)}.cfg-array__item{background:var(--panel)}.cfg-array__item-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:#0000000a;border-bottom:1px solid var(--border)}:root[data-theme=light] .cfg-array__item-header{background:#00000005}.cfg-array__item-index{font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}.cfg-array__item-remove{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:none;border-radius:6px;background:transparent;color:var(--muted);cursor:pointer;transition:background .15s ease,color .15s ease}.cfg-array__item-remove svg{width:16px;height:16px}.cfg-array__item-remove:hover:not(:disabled){background:#ff5c5c26;color:var(--danger)}.cfg-array__item-remove:disabled{opacity:.4;cursor:not-allowed}.cfg-array__item-content{padding:16px}.cfg-map{border:1px solid var(--border);border-radius:10px;overflow:hidden}.cfg-map__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;background:#0000000f;border-bottom:1px solid var(--border)}:root[data-theme=light] .cfg-map__header{background:#00000005}.cfg-map__label{font-size:13px;font-weight:600;color:var(--muted)}.cfg-map__add{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:#ffffff0d;color:var(--text);font-size:12px;font-weight:500;cursor:pointer;transition:background .15s ease}.cfg-map__add:hover:not(:disabled){background:#ffffff1a}.cfg-map__add-icon{width:14px;height:14px}.cfg-map__add-icon svg{width:100%;height:100%}.cfg-map__empty{padding:24px 16px;text-align:center;color:var(--muted);font-size:13px}.cfg-map__items{display:grid;gap:8px;padding:12px}.cfg-map__item{display:grid;grid-template-columns:140px 1fr auto;gap:8px;align-items:start}.cfg-map__item-key,.cfg-map__item-value{min-width:0}.cfg-map__item-remove{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border:none;border-radius:6px;background:transparent;color:var(--muted);cursor:pointer;transition:background .15s ease,color .15s ease}.cfg-map__item-remove svg{width:16px;height:16px}.cfg-map__item-remove:hover:not(:disabled){background:#ff5c5c26;color:var(--danger)}.pill--sm{padding:4px 10px;font-size:11px}.pill--ok{border-color:#2bd97f66;color:var(--ok)}.pill--danger{border-color:#ff5c5c66;color:var(--danger)}@media(max-width:768px){.config-layout{grid-template-columns:1fr}.config-sidebar{border-right:none;border-bottom:1px solid var(--border)}.config-sidebar__header{padding:12px 16px}.config-nav{display:flex;flex-wrap:nowrap;gap:4px;padding:8px 12px;overflow-x:auto;-webkit-overflow-scrolling:touch}.config-nav__item{flex:0 0 auto;padding:8px 12px;white-space:nowrap}.config-nav__label{display:inline}.config-sidebar__footer{display:none}.config-actions{flex-wrap:wrap;padding:12px 16px}.config-actions__left,.config-actions__right{width:100%;justify-content:center}.config-section-hero{padding:12px 16px}.config-subnav{padding:8px 16px 10px}.config-content{padding:16px}.config-section-card__header{padding:14px 16px}.config-section-card__content{padding:16px}.cfg-toggle-row{padding:12px 14px}.cfg-map__item{grid-template-columns:1fr;gap:8px}.cfg-map__item-remove{justify-self:end}}@media(max-width:480px){.config-nav__icon{width:24px;height:24px;font-size:16px}.config-nav__label{display:none}.config-section-card__icon{width:28px;height:28px}.config-section-card__title{font-size:15px}.cfg-segmented{flex-wrap:wrap}.cfg-segmented__btn{flex:1 0 auto;min-width:60px}}
diff --git a/dist/control-ui/assets/index-BvhR9FCb.css b/dist/control-ui/assets/index-BvhR9FCb.css
new file mode 100644
index 000000000..d9800ad6b
--- /dev/null
+++ b/dist/control-ui/assets/index-BvhR9FCb.css
@@ -0,0 +1 @@
+@import"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Unbounded:wght@400;500;600&family=Work+Sans:wght@400;500;600;700&display=swap";:root{--bg: #0a0f14;--bg-accent: #111826;--bg-grad-1: #162031;--bg-grad-2: #1f2a22;--bg-overlay: rgba(255, 255, 255, .05);--bg-glow: rgba(245, 159, 74, .12);--panel: rgba(14, 20, 30, .88);--panel-strong: rgba(18, 26, 38, .96);--chrome: rgba(9, 14, 20, .72);--chrome-strong: rgba(9, 14, 20, .86);--text: rgba(244, 246, 251, .96);--chat-text: rgba(231, 237, 244, .92);--muted: rgba(156, 169, 189, .72);--border: rgba(255, 255, 255, .09);--border-strong: rgba(255, 255, 255, .16);--accent: #f59f4a;--accent-2: #34c7b7;--ok: #2bd97f;--warn: #f2c94c;--danger: #ff6b6b;--focus: rgba(245, 159, 74, .35);--grid-line: rgba(255, 255, 255, .04);--theme-switch-x: 50%;--theme-switch-y: 50%;--mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--font-body: "Work Sans", system-ui, sans-serif;--font-display: "Unbounded", "Times New Roman", serif;color-scheme:dark}:root[data-theme=light]{--bg: #f5f1ea;--bg-accent: #ffffff;--bg-grad-1: #f1e6d6;--bg-grad-2: #e5eef4;--bg-overlay: rgba(28, 32, 46, .05);--bg-glow: rgba(52, 199, 183, .14);--panel: rgba(255, 255, 255, .9);--panel-strong: rgba(255, 255, 255, .97);--chrome: rgba(255, 255, 255, .75);--chrome-strong: rgba(255, 255, 255, .88);--text: rgba(27, 36, 50, .98);--chat-text: rgba(36, 48, 66, .9);--muted: rgba(80, 94, 114, .7);--border: rgba(18, 24, 40, .12);--border-strong: rgba(18, 24, 40, .2);--accent: #e28a3f;--accent-2: #1ba99d;--ok: #1aa86c;--warn: #b3771c;--danger: #d44848;--focus: rgba(226, 138, 63, .35);--grid-line: rgba(18, 24, 40, .06);color-scheme:light}*{box-sizing:border-box}html,body{height:100%}body{margin:0;font:15px/1.5 var(--font-body);background:radial-gradient(1200px 900px at 15% -10%,var(--bg-grad-1) 0%,transparent 55%) fixed,radial-gradient(900px 700px at 80% 10%,var(--bg-grad-2) 0%,transparent 60%) fixed,linear-gradient(160deg,var(--bg) 0%,var(--bg-accent) 100%) fixed;color:var(--text)}body:before{content:"";position:fixed;inset:0;background:linear-gradient(140deg,var(--bg-overlay) 0%,rgba(255,255,255,0) 40%),radial-gradient(620px 420px at 75% 75%,var(--bg-glow),transparent 60%);pointer-events:none;z-index:0}@keyframes theme-circle-transition{0%{clip-path:circle(0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%))}to{clip-path:circle(150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%))}}html.theme-transition{view-transition-name:theme}html.theme-transition::view-transition-old(theme){mix-blend-mode:normal;animation:none;z-index:1}html.theme-transition::view-transition-new(theme){mix-blend-mode:normal;z-index:2;animation:theme-circle-transition .45s ease-out forwards}@media(prefers-reduced-motion:reduce){html.theme-transition::view-transition-old(theme),html.theme-transition::view-transition-new(theme){animation:none!important}}clawdbot-app{display:block;position:relative;z-index:1;min-height:100vh}a{color:inherit}button,input,textarea,select{font:inherit;color:inherit}@keyframes rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes dashboard-enter{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.shell{--shell-pad: 16px;--shell-gap: 16px;--shell-nav-width: 220px;--shell-topbar-height: 56px;--shell-focus-duration: .22s;--shell-focus-ease: cubic-bezier(.2, .85, .25, 1);height:100vh;display:grid;grid-template-columns:var(--shell-nav-width) minmax(0,1fr);grid-template-rows:var(--shell-topbar-height) 1fr;grid-template-areas:"topbar topbar" "nav content";gap:0;animation:dashboard-enter .6s ease-out;transition:grid-template-columns var(--shell-focus-duration) var(--shell-focus-ease);overflow:hidden}@supports (height: 100dvh){.shell{height:100dvh}}.shell--chat{min-height:100vh;height:100vh;overflow:hidden}@supports (height: 100dvh){.shell--chat{height:100dvh}}.shell--nav-collapsed,.shell--chat-focus{grid-template-columns:0px minmax(0,1fr)}.shell--onboarding{grid-template-rows:0 1fr}.shell--onboarding .topbar{display:none}.shell--onboarding .content{padding-top:0}.shell--chat-focus .content{padding-top:0;gap:0}.topbar{grid-area:topbar;position:sticky;top:0;z-index:40;display:flex;justify-content:space-between;align-items:center;gap:16px;padding:0 20px;height:var(--shell-topbar-height);border-bottom:1px solid var(--border);background:var(--panel);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.topbar-left{display:flex;align-items:center;gap:12px}.topbar .nav-collapse-toggle{width:44px;height:44px;margin-bottom:0}.topbar .nav-collapse-toggle__icon{font-size:22px}.brand{display:flex;flex-direction:column;gap:2px}.brand-title{font-family:var(--font-display);font-size:16px;letter-spacing:1px;text-transform:uppercase;font-weight:600;line-height:1.1}.brand-sub{font-size:10px;color:var(--muted);letter-spacing:.8px;text-transform:uppercase;line-height:1}.topbar-status{display:flex;align-items:center;gap:8px}.topbar-status .pill{padding:4px 10px;gap:6px;font-size:11px}.topbar-status .statusDot{width:6px;height:6px}.topbar-status .theme-toggle{--theme-item: 22px;--theme-gap: 4px;--theme-pad: 4px}.topbar-status .theme-icon{width:12px;height:12px}.nav{grid-area:nav;overflow-y:auto;overflow-x:hidden;padding:16px;border-right:1px solid var(--border);background:var(--panel);-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);transition:width var(--shell-focus-duration) var(--shell-focus-ease),padding var(--shell-focus-duration) var(--shell-focus-ease);min-height:0}.shell--chat-focus .nav{width:0;padding:0;border-width:0;overflow:hidden;pointer-events:none}.nav--collapsed{width:0;min-width:0;padding:0;overflow:hidden;border:none;opacity:0;pointer-events:none}.nav-collapse-toggle{width:32px;height:32px;display:flex;align-items:center;justify-content:center;background:transparent;border:1px solid transparent;border-radius:6px;cursor:pointer;transition:background .15s ease,border-color .15s ease;margin-bottom:16px}.nav-collapse-toggle:hover{background:#ffffff14;border-color:var(--border)}:root[data-theme=light] .nav-collapse-toggle:hover{background:#0000000f}.nav-collapse-toggle__icon{font-size:16px;color:var(--muted)}.nav-group{margin-bottom:18px;display:grid;gap:6px;padding-bottom:12px;border-bottom:1px dashed rgba(255,255,255,.08)}.nav-group:last-child{margin-bottom:0;padding-bottom:0;border-bottom:none}.nav-group__items{display:grid;gap:4px}.nav-group--collapsed .nav-group__items{display:none}.nav-label{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:4px 0;font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:1.4px;color:var(--text);opacity:.7;margin-bottom:4px;background:transparent;border:none;cursor:pointer;text-align:left}.nav-label:hover{opacity:1}.nav-label--static{cursor:default}.nav-label--static:hover{opacity:.7}.nav-label__text{flex:1}.nav-label__chevron{font-size:12px;opacity:.6}.nav-item{position:relative;display:flex;align-items:center;justify-content:flex-start;gap:8px;padding:10px 12px 10px 14px;border-radius:12px;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;text-decoration:none;transition:border-color .16s ease,background .16s ease,color .16s ease}.nav-item__icon{font-size:16px;width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.nav-item__text{font-size:13px;white-space:nowrap}.nav-item:hover{color:var(--text);border-color:#ffffff1f;background:#ffffff0f}.nav-item:before{content:"";position:absolute;left:0;top:50%;width:4px;height:60%;border-radius:0 999px 999px 0;transform:translateY(-50%);background:transparent}.nav-item.active{color:var(--text);border-color:#f59f4a73;background:#f59f4a1f}.nav-item.active:before{background:var(--accent);box-shadow:0 0 12px #f59f4a66}.content{grid-area:content;padding:8px 6px 20px;display:flex;flex-direction:column;gap:20px;min-height:0;overflow-y:auto;overflow-x:hidden}.content--chat{overflow:hidden}.content-header{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;padding:0 6px;overflow:hidden;transform-origin:top center;transition:opacity var(--shell-focus-duration) var(--shell-focus-ease),transform var(--shell-focus-duration) var(--shell-focus-ease),max-height var(--shell-focus-duration) var(--shell-focus-ease),padding var(--shell-focus-duration) var(--shell-focus-ease);max-height:90px}.shell--chat-focus .content-header{opacity:0;transform:translateY(-10px);max-height:0px;padding:0;pointer-events:none}.page-title{font-family:var(--font-display);font-size:26px;letter-spacing:.6px}.page-sub{color:var(--muted);font-size:12px;letter-spacing:.4px}.page-meta{display:flex;gap:10px}.content--chat .content-header{flex-direction:row;align-items:center;justify-content:space-between;gap:16px}.content--chat .content-header>div:first-child{text-align:left}.content--chat .page-meta{justify-content:flex-start}.content--chat .chat-controls{flex-shrink:0}.grid{display:grid;gap:18px}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.stat-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}.note-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.row{display:flex;gap:12px;align-items:center}.stack{display:grid;gap:14px}.filters{display:flex;flex-wrap:wrap;gap:10px;align-items:center}@media(max-width:1100px){.shell{--shell-pad: 12px;--shell-gap: 12px;--shell-nav-col: 1fr;grid-template-columns:1fr;grid-template-rows:auto auto 1fr;grid-template-areas:"topbar" "nav" "content"}.nav{position:static;max-height:none;display:flex;gap:16px;overflow-x:auto;border-right:none;padding:12px}.nav-group{grid-auto-flow:column;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));border-bottom:none;padding-bottom:0}.grid-cols-2,.grid-cols-3{grid-template-columns:1fr}.topbar{position:static;flex-direction:column;align-items:flex-start;gap:12px}.topbar-status{width:100%;flex-wrap:wrap}.table-head,.table-row,.list-item{grid-template-columns:1fr}}@media(max-width:1100px){.nav{display:flex;flex-direction:row;flex-wrap:nowrap;gap:6px;padding:10px 12px;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}.nav::-webkit-scrollbar{display:none}.nav-group,.nav-group__items{display:contents}.nav-label{display:none}.nav-group--collapsed .nav-group__items{display:contents}.nav-item{padding:8px 14px;font-size:13px;border-radius:10px;white-space:nowrap;flex-shrink:0}.nav-item:before{display:none}}@media(max-width:600px){.shell{--shell-pad: 8px;--shell-gap: 8px}.topbar{padding:10px 12px;border-radius:12px;gap:8px;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center}.brand{flex:1;min-width:0}.brand-title{font-size:15px;letter-spacing:.3px}.brand-sub{display:none}.topbar-status{gap:6px;width:auto;flex-wrap:nowrap}.topbar-status .pill{padding:4px 8px;font-size:11px;gap:4px}.topbar-status .pill .mono{display:none}.topbar-status .pill span:nth-child(2){display:none}.nav{padding:8px;border-radius:12px;gap:8px;-webkit-overflow-scrolling:touch;scrollbar-width:none}.nav::-webkit-scrollbar{display:none}.nav-group{display:contents}.nav-label{display:none}.nav-item{padding:7px 10px;font-size:12px;border-radius:8px;white-space:nowrap;flex-shrink:0}.nav-item:before{display:none}.content-header{display:none}.content{padding:4px 4px 16px;gap:12px}.card{padding:12px;border-radius:12px}.card-title{font-size:14px}.stat-grid{gap:8px;grid-template-columns:repeat(2,1fr)}.stat{padding:10px;border-radius:10px}.stat-label{font-size:10px}.stat-value{font-size:16px}.note-grid,.form-grid{grid-template-columns:1fr;gap:10px}.field input,.field textarea,.field select{padding:8px 10px;border-radius:10px;font-size:14px}.btn{padding:8px 12px;font-size:13px}.pill{padding:4px 10px;font-size:12px}.chat-header{flex-direction:column;align-items:stretch;gap:8px}.chat-header__left{flex-direction:column;align-items:stretch}.chat-header__right{justify-content:space-between}.chat-session{min-width:unset;width:100%}.chat-thread{margin-top:8px;padding:10px 8px;border-radius:12px}.chat-msg{max-width:92%}.chat-bubble{padding:8px 10px;border-radius:12px}.chat-compose{gap:8px}.chat-compose__field textarea{min-height:60px;padding:8px 10px;border-radius:12px;font-size:14px}.log-stream{border-radius:10px;max-height:400px}.log-row{grid-template-columns:1fr;gap:4px;padding:8px}.log-time{font-size:10px}.log-level{font-size:9px}.log-subsystem{font-size:11px}.log-message{font-size:12px}.list-item{padding:10px;border-radius:10px}.list-title{font-size:14px}.list-sub{font-size:11px}.code-block{padding:8px;border-radius:10px;font-size:11px}.theme-toggle{--theme-item: 24px;--theme-gap: 4px;--theme-pad: 4px}.theme-icon{width:14px;height:14px}}.chat{position:relative;display:flex;flex-direction:column;flex:1 1 0;height:100%;min-height:0;overflow:hidden;background:transparent!important;border:none!important;box-shadow:none!important}.chat-header{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:nowrap;flex-shrink:0;padding-bottom:12px;margin-bottom:12px;background:transparent}.chat-header__left{display:flex;align-items:center;gap:12px;flex-wrap:wrap;min-width:0}.chat-header__right{display:flex;align-items:center;gap:8px}.chat-session{min-width:180px}.chat-thread{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding:12px;margin:0 -12px;min-height:0;border-radius:12px;background:transparent}.chat-focus-exit{position:absolute;top:12px;right:12px;z-index:100;width:32px;height:32px;border-radius:50%;border:1px solid var(--border);background:var(--panel);color:var(--muted);font-size:20px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease-out,color .15s ease-out,border-color .15s ease-out;box-shadow:0 4px 12px #0003}.chat-focus-exit:hover{background:var(--panel-strong);color:var(--text);border-color:var(--accent)}.chat-compose{position:sticky;bottom:0;flex-shrink:0;display:flex;align-items:flex-end;gap:12px;margin-top:auto;padding:16px 0 4px;background:linear-gradient(to bottom,transparent,var(--bg) 20%);z-index:10}.chat-compose__field{flex:1 1 auto;min-width:0}.chat-compose__field>span{display:none}.chat-compose .chat-compose__field textarea{width:100%;min-height:36px;max-height:150px;padding:8px 12px;border-radius:10px;resize:vertical;white-space:pre-wrap;font-family:var(--font-body);font-size:14px;line-height:1.45}.chat-compose__actions{flex-shrink:0;display:flex;align-items:stretch}.chat-compose .chat-compose__actions .btn{padding:8px 16px;font-size:13px;min-height:36px;white-space:nowrap}.chat-controls{display:flex;align-items:center;justify-content:flex-start;gap:12px;flex-wrap:wrap}.chat-controls__session{min-width:140px}.chat-controls__thinking{display:flex;align-items:center;gap:6px;font-size:13px}.btn--icon{padding:8px!important;min-width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--border);background:#ffffff0f}.chat-controls__separator{color:#fff6;font-size:18px;margin:0 8px;font-weight:300}:root[data-theme=light] .chat-controls__separator{color:#1018284d}.btn--icon:hover{background:#ffffff1f;border-color:#fff3}:root[data-theme=light] .btn--icon{background:#ffffffe6;border-color:#10182833;box-shadow:0 1px 2px #1018280d;color:#101828b3}:root[data-theme=light] .btn--icon:hover{background:#fff;border-color:#1018284d;color:#101828e6}.btn--icon svg{display:block}.chat-controls__session select{padding:6px 10px;font-size:13px}.chat-controls__thinking{display:flex;align-items:center;gap:4px;font-size:12px;padding:4px 10px;background:#ffffff0a;border-radius:6px;border:1px solid var(--border)}:root[data-theme=light] .chat-controls__thinking{background:#ffffffe6;border-color:#10182826}@media(max-width:640px){.chat-session{min-width:140px}.chat-compose{grid-template-columns:1fr}.chat-controls{flex-wrap:wrap;gap:8px}.chat-controls__session{min-width:120px}}.chat-thinking{margin-bottom:10px;padding:10px 12px;border-radius:10px;border:1px dashed rgba(255,255,255,.18);background:#ffffff0a;color:var(--muted);font-size:12px;line-height:1.4}:root[data-theme=light] .chat-thinking{border-color:#1018282e;background:#10182808}.chat-text{font-size:14px;line-height:1.5;word-wrap:break-word;overflow-wrap:break-word}.chat-text :where(p+p,p+ul,p+ol,p+pre,p+blockquote){margin-top:.75em}.chat-text :where(ul,ol){padding-left:1.5em}.chat-text :where(a){color:var(--accent);text-decoration:underline;text-underline-offset:2px}.chat-text :where(a:hover){opacity:.8}.chat-text :where(code){font-family:var(--mono);font-size:.9em}.chat-text :where(:not(pre)>code){background:#00000026;padding:.15em .4em;border-radius:4px}.chat-text :where(pre){background:#00000026;border-radius:6px;padding:10px 12px;overflow-x:auto}.chat-text :where(pre code){background:none;padding:0}.chat-text :where(blockquote){border-left:3px solid var(--border);padding-left:12px;color:var(--muted)}.chat-text :where(hr){border:none;border-top:1px solid var(--border);margin:1em 0}.chat-group{display:flex;gap:12px;align-items:flex-start;margin-bottom:16px;margin-left:16px;margin-right:16px}.chat-group.user{flex-direction:row-reverse;justify-content:flex-start}.chat-group-messages{display:flex;flex-direction:column;gap:2px;max-width:min(900px,calc(100% - 60px))}.chat-group.user .chat-group-messages{align-items:flex-end}.chat-group.user .chat-group-footer{justify-content:flex-end}.chat-group-footer{display:flex;gap:8px;align-items:baseline;margin-top:6px}.chat-sender-name{font-weight:500;font-size:12px;color:var(--muted)}.chat-group-timestamp{font-size:11px;color:var(--muted);opacity:.7}.chat-avatar{width:40px;height:40px;border-radius:8px;background:var(--panel-strong);display:grid;place-items:center;font-weight:600;font-size:14px;flex-shrink:0;align-self:flex-end;margin-bottom:4px}.chat-avatar.user{background:#f59f4a33;color:#f59f4a}.chat-avatar.assistant{background:#34c7b733;color:#34c7b7}.chat-avatar.other{background:#96969633;color:#969696}.chat-avatar.tool{background:#868e9633;color:#868e96}img.chat-avatar{display:block;object-fit:cover;object-position:center}.chat-bubble{position:relative;display:inline-block;border:1px solid var(--border);background:#0000001f;border-radius:12px;padding:10px 14px;box-shadow:none;transition:background .15s ease-out,border-color .15s ease-out;max-width:100%;word-wrap:break-word}.chat-bubble.has-copy{padding-right:36px}.chat-copy-btn{position:absolute;top:6px;right:8px;border:1px solid var(--border);background:#00000038;color:var(--muted);border-radius:8px;padding:4px 6px;font-size:14px;line-height:1;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .12s ease-out,background .12s ease-out}.chat-copy-btn__icon{display:inline-block;width:1em;text-align:center}.chat-bubble:hover .chat-copy-btn{opacity:1;pointer-events:auto}.chat-copy-btn:hover{background:#0000004d}.chat-copy-btn[data-copying="1"]{opacity:0;pointer-events:none}.chat-copy-btn[data-error="1"]{opacity:1;pointer-events:auto;border-color:#ff453acc;background:#ff453a2e;color:#ff453a}.chat-copy-btn[data-copied="1"]{opacity:1;pointer-events:auto;border-color:#34c7b7cc;background:#34c7b72e;color:#34c7b7}.chat-copy-btn:focus-visible{opacity:1;pointer-events:auto;outline:2px solid var(--accent);outline-offset:2px}@media(hover:none){.chat-copy-btn{opacity:1;pointer-events:auto}}.chat-bubble:hover{background:#0000002e}.chat-group.user .chat-bubble{background:#f59f4a26;border-color:#f59f4a4d}.chat-group.user .chat-bubble:hover{background:#f59f4a38}.chat-bubble.streaming{animation:pulsing-border 1.5s ease-out infinite}@keyframes pulsing-border{0%,to{border-color:var(--border)}50%{border-color:var(--accent)}}.chat-bubble.fade-in{animation:fade-in .2s ease-out}@keyframes fade-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.chat-tool-card{border:1px solid var(--border);border-radius:8px;padding:12px;margin-top:8px;transition:border-color .15s ease-out,background .15s ease-out;max-height:120px;overflow:hidden}.chat-tool-card:hover{border-color:var(--accent);background:#0000000f}.chat-tool-card:first-child{margin-top:0}.chat-tool-card--clickable{cursor:pointer}.chat-tool-card--clickable:focus{outline:2px solid var(--accent);outline-offset:2px}.chat-tool-card__header{display:flex;justify-content:space-between;align-items:center;gap:8px}.chat-tool-card__title{display:inline-flex;align-items:center;gap:6px;font-weight:600;font-size:13px;line-height:1.2}.chat-tool-card__icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;font-size:14px;line-height:1;font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif;vertical-align:middle;flex-shrink:0}.chat-tool-card__action{font-size:12px;color:var(--accent);opacity:.8;transition:opacity .15s ease-out}.chat-tool-card--clickable:hover .chat-tool-card__action{opacity:1}.chat-tool-card__status{font-size:14px;color:var(--ok)}.chat-tool-card__status-text{font-size:11px;margin-top:4px}.chat-tool-card__detail{font-size:12px;color:var(--muted);margin-top:4px}.chat-tool-card__preview{font-size:11px;color:var(--muted);margin-top:8px;padding:8px 10px;background:#00000014;border-radius:6px;white-space:pre-wrap;overflow:hidden;max-height:44px;line-height:1.4;border:1px solid rgba(255,255,255,.04)}.chat-tool-card--clickable:hover .chat-tool-card__preview{background:#0000001f;border-color:#ffffff14}.chat-tool-card__inline{font-size:11px;color:var(--text);margin-top:6px;padding:6px 8px;background:#0000000f;border-radius:4px;white-space:pre-wrap;word-break:break-word}.chat-reading-indicator{background:transparent;border:1px solid var(--border);padding:12px;display:inline-flex}.chat-reading-indicator__dots{display:flex;gap:6px;align-items:center}.chat-reading-indicator__dots span{width:6px;height:6px;border-radius:50%;background:var(--muted);animation:reading-pulse 1.4s ease-in-out infinite}.chat-reading-indicator__dots span:nth-child(1){animation-delay:0s}.chat-reading-indicator__dots span:nth-child(2){animation-delay:.2s}.chat-reading-indicator__dots span:nth-child(3){animation-delay:.4s}@keyframes reading-pulse{0%,60%,to{opacity:.3;transform:scale(.8)}30%{opacity:1;transform:scale(1)}}.chat-split-container{display:flex;gap:0;flex:1;min-height:0;height:100%}.chat-main{min-width:400px;display:flex;flex-direction:column;overflow:hidden;transition:flex .25s ease-out}.chat-sidebar{flex:1;min-width:300px;border-left:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;animation:slide-in .2s ease-out}@keyframes slide-in{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.sidebar-panel{display:flex;flex-direction:column;height:100%;background:var(--panel)}.sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--border);flex-shrink:0;position:sticky;top:0;z-index:10;background:var(--panel)}.sidebar-header .btn{padding:4px 8px;font-size:14px;min-width:auto;line-height:1}.sidebar-title{font-weight:600;font-size:14px}.sidebar-content{flex:1;overflow:auto;padding:16px}.sidebar-markdown{font-size:14px;line-height:1.5}.sidebar-markdown pre{background:#0000001f;border-radius:4px;padding:12px;overflow-x:auto}.sidebar-markdown code{font-family:var(--mono);font-size:13px}@media(max-width:768px){.chat-split-container--open{position:fixed;inset:0;z-index:1000}.chat-split-container--open .chat-main{display:none}.chat-split-container--open .chat-sidebar{width:100%;min-width:0;border-left:none}}.card{border:1px solid var(--border);background:linear-gradient(160deg,rgba(255,255,255,.04),transparent 65%),var(--panel);border-radius:16px;padding:16px;box-shadow:0 18px 36px #00000047;animation:rise .4s ease}.card-title{font-family:var(--font-display);font-size:16px;letter-spacing:.6px;text-transform:uppercase}.card-sub{color:var(--muted);font-size:12px}.stat{background:linear-gradient(140deg,rgba(255,255,255,.04),transparent 70%),var(--panel-strong);border-radius:14px;padding:12px;border:1px solid var(--border-strong)}.stat-label{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:1px}.stat-value{font-size:18px;margin-top:6px}.stat-value.ok{color:var(--ok)}.stat-value.warn{color:var(--warn)}.stat-card{display:grid;gap:6px}.note-title{font-weight:600;letter-spacing:.2px}.status-list{display:grid;gap:8px}.status-list div{display:flex;justify-content:space-between;gap:12px;padding:6px 0;border-bottom:1px dashed rgba(255,255,255,.06)}.status-list div:last-child{border-bottom:none}.account-count{margin-top:8px;font-size:12px;font-weight:600;letter-spacing:.4px;color:var(--muted)}.account-card-list{margin-top:16px;display:grid;gap:10px}.account-card{border:1px solid var(--border);border-radius:10px;padding:12px;background:linear-gradient(160deg,rgba(255,255,255,.06),transparent),#ffffff08}.account-card-header{display:flex;justify-content:space-between;align-items:baseline;gap:12px}.account-card-title{font-weight:600}.account-card-id{font-family:var(--mono);font-size:12px;color:var(--muted)}.account-card-status{margin-top:8px;font-size:13px}.account-card-status div{padding:4px 0}.account-card-error{margin-top:6px;color:var(--danger);font-size:12px}.label{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.9px}.pill{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--border-strong);padding:6px 12px;border-radius:999px;background:linear-gradient(160deg,rgba(255,255,255,.06),transparent),var(--panel)}.theme-toggle{--theme-item: 28px;--theme-gap: 6px;--theme-pad: 6px;position:relative}.theme-toggle__track{position:relative;display:grid;grid-template-columns:repeat(3,var(--theme-item));gap:var(--theme-gap);padding:var(--theme-pad);border-radius:999px;border:1px solid var(--border-strong);background:#ffffff0a}.theme-toggle__indicator{position:absolute;top:50%;left:var(--theme-pad);width:var(--theme-item);height:var(--theme-item);border-radius:999px;transform:translateY(-50%) translate(calc(var(--theme-index, 0) * (var(--theme-item) + var(--theme-gap))));background:linear-gradient(160deg,rgba(255,255,255,.12),transparent),var(--panel-strong);border:1px solid var(--border-strong);box-shadow:0 8px 16px #00000040;transition:transform .18s ease-out,background .18s ease-out,box-shadow .18s ease-out;z-index:0}.theme-toggle__button{height:var(--theme-item);width:var(--theme-item);display:grid;place-items:center;border:0;border-radius:999px;background:transparent;color:var(--muted);cursor:pointer;position:relative;z-index:1;transition:color .15s ease-out,background .15s ease-out}.theme-toggle__button:hover{color:var(--text);background:#ffffff14}.theme-toggle__button.active{color:var(--text)}.theme-icon{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75px;stroke-linecap:round;stroke-linejoin:round}.pill.danger{border-color:#ff5c5c80;color:var(--danger)}.statusDot{width:8px;height:8px;border-radius:999px;background:var(--danger);box-shadow:0 0 0 2px #00000040}.statusDot.ok{background:var(--ok);box-shadow:0 0 0 2px #00000040,0 0 10px #2bd97f66}.btn{border:1px solid var(--border-strong);background:#ffffff0a;padding:8px 14px;border-radius:999px;cursor:pointer;transition:transform .15s ease,border-color .15s ease,background .15s ease}.btn:hover{background:#ffffff1a;transform:translateY(-1px)}.btn.primary{border-color:#f59f4a73;background:#f59f4a33}.btn.active{border-color:#f59f4a8c;background:#f59f4a29}.btn.danger{border-color:#ff6b6b73;background:#ff6b6b2e}.btn--sm{padding:5px 10px;font-size:12px}.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.field{display:grid;gap:6px}.field.full{grid-column:1 / -1}.field span{color:var(--muted);font-size:11px;letter-spacing:.4px}.field input,.field textarea,.field select{border:1px solid var(--border-strong);background:#00000038;border-radius:12px;padding:9px 11px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus);background:#00000047}.field select{appearance:none;padding-right:38px;background-color:var(--panel-strong);background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),linear-gradient(135deg,var(--muted) 50%,transparent 50%),linear-gradient(to right,transparent,transparent);background-position:calc(100% - 18px) 50%,calc(100% - 12px) 50%,calc(100% - 38px) 50%;background-size:6px 6px,6px 6px,1px 60%;background-repeat:no-repeat;box-shadow:inset 0 1px #ffffff0a}.field textarea{font-family:var(--mono);min-height:180px;resize:vertical;white-space:pre}.field textarea:focus{background:#00000052}.field.checkbox{grid-template-columns:auto 1fr;align-items:center}.config-form .field.checkbox{grid-template-columns:18px minmax(0,1fr);column-gap:10px}.config-form .field.checkbox input[type=checkbox]{margin:0}.form-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}:root[data-theme=light] .field input,:root[data-theme=light] .field textarea,:root[data-theme=light] .field select{background:#fff;border-color:#10182840;box-shadow:0 1px 2px #1018280f}:root[data-theme=light] .field input:focus,:root[data-theme=light] .field textarea:focus,:root[data-theme=light] .field select:focus{background:#fff}:root[data-theme=light] .btn{background:#ffffffe6;border-color:#10182833;box-shadow:0 1px 2px #1018280d}:root[data-theme=light] .btn:hover{background:#fff;border-color:#1018284d}:root[data-theme=light] .btn.primary{background:#f59f4a26}:root[data-theme=light] .btn.active{background:#f59f4a1f}.muted{color:var(--muted)}.mono{font-family:var(--mono)}.callout{padding:10px 12px;border-radius:14px;background:linear-gradient(160deg,rgba(255,255,255,.06),transparent),#ffffff08;border:1px solid var(--border)}.callout.danger{border-color:#ff5c5c66;color:var(--danger)}.callout.info{border-color:#5c9cff66;color:var(--accent)}.callout.success{border-color:#5cff8066;color:var(--positive, #5cff80)}.compaction-indicator{font-size:13px;padding:8px 12px;margin-bottom:8px;animation:compaction-fade-in .2s ease-out}.compaction-indicator--active{animation:compaction-pulse 1.5s ease-in-out infinite}.compaction-indicator--complete{animation:compaction-fade-in .2s ease-out}@keyframes compaction-fade-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes compaction-pulse{0%,to{opacity:.7}50%{opacity:1}}.code-block{font-family:var(--mono);font-size:12px;background:#00000059;padding:10px;border-radius:12px;border:1px solid var(--border);max-height:360px;overflow:auto}:root[data-theme=light] .code-block,:root[data-theme=light] .list-item,:root[data-theme=light] .table-row,:root[data-theme=light] .chip{background:#ffffffd9}.list{display:grid;gap:12px;container-type:inline-size}.list-item{display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,260px);gap:14px;align-items:start;border:1px solid var(--border);border-radius:14px;padding:12px;background:#0003}.list-item-clickable{cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.list-item-clickable:hover{border-color:var(--border-strong)}.list-item-selected{border-color:var(--accent);box-shadow:0 0 0 1px var(--focus)}.list-main{display:grid;gap:6px;min-width:0}.list-title{font-weight:600}.list-sub{color:var(--muted);font-size:12px}.list-meta{text-align:right;color:var(--muted);font-size:11px;display:grid;gap:4px;min-width:220px}.list-meta .btn{padding:6px 10px}.list-meta .field input,.list-meta .field textarea,.list-meta .field select{width:100%}@container (max-width: 560px){.list-item{grid-template-columns:1fr}.list-meta{min-width:0;text-align:left}}.chip-row{display:flex;flex-wrap:wrap;gap:6px}.chip{font-size:11px;border:1px solid var(--border);border-radius:999px;padding:4px 8px;color:var(--muted);background:#0003}.chip input{margin-right:6px}.chip-ok{color:var(--ok);border-color:#1bd98a66}.chip-warn{color:var(--warn);border-color:#f2c94c66}.table{display:grid;gap:8px}.table-head,.table-row{display:grid;grid-template-columns:1.4fr 1fr .8fr .7fr .8fr .8fr .8fr .8fr .6fr;gap:12px;align-items:center}.table-head{font-size:11px;text-transform:uppercase;letter-spacing:.8px;color:var(--muted)}.table-row{border:1px solid var(--border);padding:10px;border-radius:12px;background:#0003}.session-link{text-decoration:none;color:var(--accent)}.session-link:hover{text-decoration:underline}.log-stream{border:1px solid var(--border);border-radius:14px;background:#0003;max-height:520px;overflow:auto;container-type:inline-size}.log-row{display:grid;grid-template-columns:90px 70px minmax(140px,200px) minmax(0,1fr);gap:12px;align-items:start;padding:6px 10px;border-bottom:1px solid var(--border);font-size:12px}.log-row:last-child{border-bottom:none}.log-time{color:var(--muted)}.log-level{text-transform:uppercase;font-size:10px;font-weight:600;border:1px solid var(--border);border-radius:999px;padding:2px 6px;width:fit-content}.log-level.trace,.log-level.debug{color:var(--muted)}.log-level.info{color:var(--info);border-color:#4c96f266}.log-level.warn{color:var(--warn);border-color:#f2c94c66}.log-level.error,.log-level.fatal{color:var(--danger);border-color:#ff5c5c66}.log-chip.trace,.log-chip.debug{color:var(--muted)}.log-chip.info{color:var(--info);border-color:#4c96f266}.log-chip.warn{color:var(--warn);border-color:#f2c94c66}.log-chip.error,.log-chip.fatal{color:var(--danger);border-color:#ff5c5c66}.log-subsystem{color:var(--muted)}.log-message{white-space:pre-wrap;word-break:break-word}@container (max-width: 620px){.log-row{grid-template-columns:70px 60px minmax(0,1fr)}.log-subsystem{display:none}}.chat{display:flex;flex-direction:column;min-height:0}.shell--chat .chat{flex:1}.chat-header{display:flex;justify-content:space-between;align-items:flex-end;gap:12px;flex-wrap:wrap}.chat-header__left{display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap;min-width:0}.chat-header__right{display:flex;align-items:center;gap:10px}.chat-session{min-width:240px}.chat-thread{margin-top:12px;display:flex;flex-direction:column;gap:12px;flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:14px 12px;min-width:0;border-radius:0;border:none;background:transparent}:root[data-theme=light] .chat-thread{background:transparent}.chat-queue{margin-top:12px;padding:10px 12px;border-radius:16px;border:1px solid var(--border);background:#0000002e;display:grid;gap:8px}:root[data-theme=light] .chat-queue{background:#1018280a}.chat-queue__title{font-family:var(--font-mono);font-size:12px;color:var(--muted)}.chat-queue__list{display:grid;gap:8px}.chat-queue__item{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:10px;padding:8px 10px;border-radius:12px;border:1px dashed var(--border);background:#0003}:root[data-theme=light] .chat-queue__item{background:#1018280d}.chat-queue__text{color:var(--chat-text);font-size:13px;line-height:1.4;white-space:pre-wrap;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}.chat-queue__remove{align-self:start;padding:4px 10px;font-size:12px;line-height:1}.chat-line{display:flex}.chat-line.user{justify-content:flex-end}.chat-line.assistant,.chat-line.other{justify-content:flex-start}.chat-msg{display:grid;gap:6px;max-width:min(720px,82%)}.chat-line.user .chat-msg{justify-items:end}.chat-bubble{border:1px solid var(--border);background:#0000003d;border-radius:16px;padding:10px 12px;min-width:0;box-shadow:0 12px 22px #0000003d}:root[data-theme=light] .chat-bubble{background:#ffffffd9;box-shadow:0 12px 26px #10182814}.chat-line.user .chat-bubble{border-color:#f59f4a73;background:linear-gradient(135deg,#f59f4a42,#f59f4a1f)}.chat-line.assistant .chat-bubble{border-color:#34c7b733;background:linear-gradient(135deg,#34c7b71f,#0000003d)}:root[data-theme=light] .chat-line.assistant .chat-bubble{background:linear-gradient(135deg,#1bb9b11f,#ffffffd9)}@keyframes chatStreamPulse{0%{box-shadow:0 12px 22px #0000003d,0 0 #34c7b700}60%{box-shadow:0 12px 22px #0000003d,0 0 0 6px #34c7b714}to{box-shadow:0 12px 22px #0000003d,0 0 #34c7b700}}.chat-bubble.streaming{border-color:#34c7b766;animation:chatStreamPulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.chat-bubble.streaming{animation:none}}.chat-bubble.chat-reading-indicator{width:fit-content;padding:10px 14px}.chat-reading-indicator__dots{display:inline-flex;align-items:center;gap:6px;height:10px}.chat-reading-indicator__dots>span{display:inline-block;width:6px;height:6px;border-radius:999px;background:var(--chat-text);opacity:.55;transform:translateY(0);animation:chatReadingDot 1.1s ease-in-out infinite;will-change:transform,opacity}.chat-reading-indicator__dots>span:nth-child(2){animation-delay:.12s}.chat-reading-indicator__dots>span:nth-child(3){animation-delay:.24s}@keyframes chatReadingDot{0%,80%,to{opacity:.38;transform:translateY(0) scale(.92)}40%{opacity:1;transform:translateY(-3px) scale(1.18)}}@media(prefers-reduced-motion:reduce){.chat-reading-indicator__dots>span{animation:none;opacity:.75}}.chat-text{overflow-wrap:anywhere;word-break:break-word;color:var(--chat-text);line-height:1.5}.chat-text :where(p,ul,ol,pre,blockquote,table){margin:0}.chat-text :where(p+p,p+ul,p+ol,p+pre,p+blockquote,p+table){margin-top:.75em}.chat-text :where(ul,ol){padding-left:1.1em}.chat-text :where(li+li){margin-top:.25em}.chat-text :where(a){color:var(--accent);text-decoration-thickness:2px;text-underline-offset:2px}.chat-text :where(a:hover){text-decoration-thickness:3px}.chat-text :where(blockquote){border-left:2px solid rgba(255,255,255,.14);padding-left:12px;color:var(--muted)}:root[data-theme=light] .chat-text :where(blockquote){border-left-color:#10182829}.chat-text :where(hr){border:0;border-top:1px solid var(--border);opacity:.6;margin:.9em 0}.chat-text :where(code){font-family:var(--font-mono);font-size:.92em}.chat-text :where(:not(pre)>code){padding:.15em .35em;border-radius:8px;border:1px solid var(--border);background:#0003}:root[data-theme=light] .chat-text :where(:not(pre)>code){background:#1018280d}.chat-text :where(pre){margin-top:.75em;padding:10px 12px;border-radius:14px;border:1px solid var(--border);background:#00000038;overflow:auto}:root[data-theme=light] .chat-text :where(pre){background:#1018280a}.chat-text :where(pre code){font-size:12px;white-space:pre}.chat-text :where(table){margin-top:.75em;border-collapse:collapse;width:100%;font-size:12px}.chat-text :where(th,td){border:1px solid var(--border);padding:6px 8px;vertical-align:top}.chat-text :where(th){font-family:var(--font-mono);font-weight:600;color:var(--muted)}.chat-tool-card{margin-top:8px;padding:8px 10px;border-radius:12px;border:1px solid var(--border);background:#00000038;display:grid;gap:4px}:root[data-theme=light] .chat-tool-card{background:#ffffffb3}.chat-tool-card__title{font-family:var(--font-mono);font-size:12px;color:var(--chat-text)}.chat-tool-card__detail{font-family:var(--font-mono);font-size:11px;color:var(--muted)}.chat-tool-card__details{margin-top:6px}.chat-tool-card__summary{font-family:var(--font-mono);font-size:11px;color:var(--muted);cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px}.chat-tool-card__summary::-webkit-details-marker{display:none}.chat-tool-card__summary-meta{color:var(--muted);opacity:.8}.chat-tool-card__details[open] .chat-tool-card__summary{color:var(--chat-text)}.chat-tool-card__output{margin-top:6px;font-family:var(--font-mono);font-size:11px;line-height:1.45;white-space:pre-wrap;color:var(--chat-text);padding:8px;border-radius:10px;border:1px solid var(--border);background:#0003}:root[data-theme=light] .chat-tool-card__output{background:#1018280d}.chat-stamp{font-size:11px;color:var(--muted)}.chat-line.user .chat-stamp{text-align:right}.chat-compose{margin-top:12px;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:10px}.shell--chat .chat-compose{position:sticky;bottom:0;z-index:5;margin-top:0;padding-top:12px;background:linear-gradient(180deg,rgba(0,0,0,0) 0%,var(--panel) 35%)}.shell--chat-focus .chat-compose{bottom:calc(var(--shell-pad) + 8px);padding-bottom:calc(14px + env(safe-area-inset-bottom,0px));border-bottom-left-radius:18px;border-bottom-right-radius:18px}.chat-compose__field{gap:4px}.chat-compose__field textarea{min-height:72px;padding:10px 12px;border-radius:16px;resize:vertical;white-space:pre-wrap;font-family:var(--font-body);line-height:1.45}.chat-compose__field textarea:disabled{opacity:.7;cursor:not-allowed}.chat-compose__actions{justify-content:flex-end;align-self:end}@media(max-width:900px){.chat-session{min-width:200px}.chat-compose{grid-template-columns:1fr}}.qr-wrap{margin-top:12px;border-radius:14px;background:#0003;border:1px dashed rgba(255,255,255,.18);padding:12px;display:inline-flex}.qr-wrap img{width:180px;height:180px;border-radius:10px;image-rendering:pixelated}.exec-approval-overlay{position:fixed;inset:0;background:#080c12b3;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;padding:24px;z-index:200}.exec-approval-card{width:min(560px,100%);background:var(--panel-strong);border:1px solid var(--border-strong);border-radius:18px;padding:20px;box-shadow:0 28px 60px #00000059;animation:rise .25s ease}.exec-approval-header{display:flex;align-items:center;justify-content:space-between;gap:12px}.exec-approval-title{font-family:var(--font-display);font-size:14px;letter-spacing:.8px;text-transform:uppercase}.exec-approval-sub{color:var(--muted);font-size:12px}.exec-approval-queue{font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--muted);border:1px solid var(--border);border-radius:999px;padding:4px 10px}.exec-approval-command{margin-top:12px;padding:10px 12px;background:#00000040;border:1px solid var(--border);border-radius:12px;word-break:break-word;white-space:pre-wrap}.exec-approval-meta{margin-top:12px;display:grid;gap:6px;font-size:12px;color:var(--muted)}.exec-approval-meta-row{display:flex;justify-content:space-between;gap:12px}.exec-approval-meta-row span:last-child{color:var(--text);font-family:var(--mono)}.exec-approval-error{margin-top:10px;font-size:12px;color:var(--danger)}.exec-approval-actions{margin-top:16px;display:flex;flex-wrap:wrap;gap:10px}.config-layout{display:grid;grid-template-columns:240px minmax(0,1fr);gap:0;min-height:calc(100vh - 140px);margin:-16px;border-radius:16px;overflow:hidden;border:1px solid var(--border);background:var(--panel)}.config-sidebar{display:flex;flex-direction:column;background:#0003;border-right:1px solid var(--border)}:root[data-theme=light] .config-sidebar{background:#00000008}.config-sidebar__header{display:flex;align-items:center;justify-content:space-between;padding:16px;border-bottom:1px solid var(--border)}.config-sidebar__title{font-weight:600;font-size:14px;letter-spacing:.3px}.config-sidebar__footer{margin-top:auto;padding:12px;border-top:1px solid var(--border)}.config-search{position:relative;padding:12px;border-bottom:1px solid var(--border)}.config-search__icon{position:absolute;left:24px;top:50%;transform:translateY(-50%);width:16px;height:16px;color:var(--muted);pointer-events:none}.config-search__input{width:100%;padding:10px 32px 10px 40px;border:1px solid var(--border);border-radius:8px;background:#00000026;font-size:13px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.config-search__input::placeholder{color:var(--muted)}.config-search__input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus);background:#0003}:root[data-theme=light] .config-search__input{background:#fffc}:root[data-theme=light] .config-search__input:focus{background:#fff}.config-search__clear{position:absolute;right:20px;top:50%;transform:translateY(-50%);width:20px;height:20px;border:none;border-radius:50%;background:#ffffff1a;color:var(--muted);font-size:16px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease,color .15s ease}.config-search__clear:hover{background:#fff3;color:var(--text)}.config-nav{flex:1;overflow-y:auto;padding:8px}.config-nav__item{display:flex;align-items:center;gap:12px;width:100%;padding:10px 12px;border:none;border-radius:8px;background:transparent;color:var(--muted);font-size:13px;font-weight:500;text-align:left;cursor:pointer;transition:background .15s ease,color .15s ease}.config-nav__item:hover{background:#ffffff0d;color:var(--text)}:root[data-theme=light] .config-nav__item:hover{background:#0000000d}.config-nav__item.active{background:#f59f4a1f;color:var(--accent)}.config-nav__icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:14px}.config-nav__icon svg{width:18px;height:18px;stroke:currentColor;fill:none}.config-nav__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.config-mode-toggle{display:flex;padding:3px;background:#0003;border-radius:8px;border:1px solid var(--border)}:root[data-theme=light] .config-mode-toggle{background:#0000000f}.config-mode-toggle__btn{flex:1;padding:8px 12px;border:none;border-radius:6px;background:transparent;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}.config-mode-toggle__btn:hover{color:var(--text)}.config-mode-toggle__btn.active{background:#ffffff1a;color:var(--text);box-shadow:0 1px 3px #0003}:root[data-theme=light] .config-mode-toggle__btn.active{background:#fff;box-shadow:0 1px 3px #0000001a}.config-main{display:flex;flex-direction:column;min-width:0;background:var(--panel)}.config-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 20px;background:#00000014;border-bottom:1px solid var(--border)}:root[data-theme=light] .config-actions{background:#00000005}.config-actions__left,.config-actions__right{display:flex;align-items:center;gap:8px}.config-changes-badge{padding:5px 12px;border-radius:999px;background:#f59f4a26;border:1px solid rgba(245,159,74,.3);color:var(--accent);font-size:12px;font-weight:600}.config-status{font-size:13px;color:var(--muted)}.config-diff{margin:16px 20px 0;border:1px solid rgba(245,159,74,.3);border-radius:10px;background:#f59f4a0d;overflow:hidden}.config-diff__summary{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;cursor:pointer;font-size:13px;font-weight:600;color:var(--accent);list-style:none}.config-diff__summary::-webkit-details-marker{display:none}.config-diff__chevron{width:16px;height:16px;transition:transform .2s ease}.config-diff__chevron svg{width:100%;height:100%}.config-diff[open] .config-diff__chevron{transform:rotate(180deg)}.config-diff__content{padding:0 16px 16px;display:grid;gap:8px}.config-diff__item{display:flex;align-items:baseline;gap:12px;padding:8px 12px;border-radius:6px;background:#0000001a;font-size:12px;font-family:var(--mono)}:root[data-theme=light] .config-diff__item{background:#fff9}.config-diff__path{font-weight:600;color:var(--text);flex-shrink:0}.config-diff__values{display:flex;align-items:baseline;gap:8px;min-width:0;flex-wrap:wrap}.config-diff__from{color:var(--danger);opacity:.8}.config-diff__arrow{color:var(--muted)}.config-diff__to{color:var(--ok)}.config-section-hero{display:flex;align-items:center;gap:14px;padding:14px 20px;border-bottom:1px solid var(--border);background:#0000000a}:root[data-theme=light] .config-section-hero{background:#00000004}.config-section-hero__icon{width:28px;height:28px;color:var(--accent);display:flex;align-items:center;justify-content:center}.config-section-hero__icon svg{width:100%;height:100%;stroke:currentColor;fill:none}.config-section-hero__text{display:grid;gap:2px;min-width:0}.config-section-hero__title{font-size:15px;font-weight:600}.config-section-hero__desc{font-size:12px;color:var(--muted)}.config-subnav{display:flex;gap:8px;padding:10px 20px 12px;border-bottom:1px solid var(--border);background:#00000008;overflow-x:auto}:root[data-theme=light] .config-subnav{background:#00000005}.config-subnav__item{border:1px solid transparent;border-radius:999px;padding:6px 12px;font-size:12px;font-weight:600;color:var(--muted);background:#0000001f;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease;white-space:nowrap}:root[data-theme=light] .config-subnav__item{background:#0000000f}.config-subnav__item:hover{color:var(--text);background:#ffffff14}:root[data-theme=light] .config-subnav__item:hover{background:#00000014}.config-subnav__item.active{color:var(--accent);border-color:#f59f4a66;background:#f59f4a1f}.config-content{flex:1;overflow-y:auto;padding:20px}.config-raw-field textarea{min-height:500px;font-family:var(--mono);font-size:13px;line-height:1.5}.config-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:80px 20px;color:var(--muted)}.config-loading__spinner{width:36px;height:36px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.config-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:80px 20px;text-align:center}.config-empty__icon{font-size:56px;opacity:.4}.config-empty__text{color:var(--muted);font-size:15px}.config-form--modern{display:grid;gap:24px}.config-section-card{border:1px solid var(--border);border-radius:12px;background:#ffffff05;overflow:hidden}:root[data-theme=light] .config-section-card{background:#ffffff80}.config-section-card__header{display:flex;align-items:flex-start;gap:14px;padding:18px 20px;background:#0000000f;border-bottom:1px solid var(--border)}:root[data-theme=light] .config-section-card__header{background:#00000005}.config-section-card__icon{width:32px;height:32px;color:var(--accent);flex-shrink:0}.config-section-card__icon svg{width:100%;height:100%}.config-section-card__titles{flex:1;min-width:0}.config-section-card__title{margin:0;font-size:17px;font-weight:600}.config-section-card__desc{margin:4px 0 0;font-size:13px;color:var(--muted);line-height:1.4}.config-section-card__content{padding:20px}.cfg-fields{display:grid;gap:20px}.cfg-field{display:grid;gap:6px}.cfg-field--error{padding:12px;border-radius:8px;background:#ff5c5c1a;border:1px solid rgba(255,92,92,.3)}.cfg-field__label{font-size:13px;font-weight:600;color:var(--text)}.cfg-field__help{font-size:12px;color:var(--muted);line-height:1.4}.cfg-field__error{font-size:12px;color:var(--danger)}.cfg-input-wrap{display:flex;gap:8px}.cfg-input{flex:1;padding:10px 12px;border:1px solid var(--border);border-radius:8px;background:#0000001f;font-size:14px;outline:none;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}.cfg-input::placeholder{color:var(--muted);opacity:.7}.cfg-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus);background:#0000002e}:root[data-theme=light] .cfg-input{background:#fff}:root[data-theme=light] .cfg-input:focus{background:#fff}.cfg-input--sm{padding:8px 10px;font-size:13px}.cfg-input__reset{padding:8px 12px;border:1px solid var(--border);border-radius:8px;background:#ffffff0d;color:var(--muted);font-size:14px;cursor:pointer;transition:background .15s ease,color .15s ease}.cfg-input__reset:hover:not(:disabled){background:#ffffff1a;color:var(--text)}.cfg-input__reset:disabled{opacity:.5;cursor:not-allowed}.cfg-textarea{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:8px;background:#0000001f;font-family:var(--mono);font-size:13px;line-height:1.5;resize:vertical;outline:none;transition:border-color .15s ease,box-shadow .15s ease}.cfg-textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus)}:root[data-theme=light] .cfg-textarea{background:#fff}.cfg-textarea--sm{padding:8px 10px;font-size:12px}.cfg-number{display:inline-flex;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:#0000001f}:root[data-theme=light] .cfg-number{background:#fff}.cfg-number__btn{width:40px;border:none;background:#ffffff0d;color:var(--text);font-size:18px;font-weight:300;cursor:pointer;transition:background .15s ease}.cfg-number__btn:hover:not(:disabled){background:#ffffff1a}.cfg-number__btn:disabled{opacity:.4;cursor:not-allowed}:root[data-theme=light] .cfg-number__btn{background:#00000008}:root[data-theme=light] .cfg-number__btn:hover:not(:disabled){background:#0000000f}.cfg-number__input{width:80px;padding:10px;border:none;border-left:1px solid var(--border);border-right:1px solid var(--border);background:transparent;font-size:14px;text-align:center;outline:none;-moz-appearance:textfield}.cfg-number__input::-webkit-outer-spin-button,.cfg-number__input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.cfg-select{padding:10px 36px 10px 12px;border:1px solid var(--border);border-radius:8px;background-color:#0000001f;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;font-size:14px;cursor:pointer;outline:none;appearance:none;transition:border-color .15s ease,box-shadow .15s ease}.cfg-select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--focus)}:root[data-theme=light] .cfg-select{background-color:#fff}.cfg-segmented{display:inline-flex;padding:3px;border:1px solid var(--border);border-radius:8px;background:#0000001f}:root[data-theme=light] .cfg-segmented{background:#0000000a}.cfg-segmented__btn{padding:8px 16px;border:none;border-radius:6px;background:transparent;color:var(--muted);font-size:13px;font-weight:500;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}.cfg-segmented__btn:hover:not(:disabled):not(.active){color:var(--text)}.cfg-segmented__btn.active{background:#ffffff1f;color:var(--text);box-shadow:0 1px 3px #0003}:root[data-theme=light] .cfg-segmented__btn.active{background:#fff;box-shadow:0 1px 3px #0000001a}.cfg-segmented__btn:disabled{opacity:.5;cursor:not-allowed}.cfg-toggle-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:14px 16px;border:1px solid var(--border);border-radius:10px;background:#0000000f;cursor:pointer;transition:background .15s ease,border-color .15s ease}.cfg-toggle-row:hover:not(.disabled){background:#0000001a;border-color:var(--border-strong)}.cfg-toggle-row.disabled{opacity:.6;cursor:not-allowed}:root[data-theme=light] .cfg-toggle-row{background:#ffffff80}:root[data-theme=light] .cfg-toggle-row:hover:not(.disabled){background:#fffc}.cfg-toggle-row__content{flex:1;min-width:0}.cfg-toggle-row__label{display:block;font-size:14px;font-weight:500;color:var(--text)}.cfg-toggle-row__help{display:block;margin-top:2px;font-size:12px;color:var(--muted);line-height:1.4}.cfg-toggle{position:relative;flex-shrink:0}.cfg-toggle input{position:absolute;opacity:0;width:0;height:0}.cfg-toggle__track{display:block;width:48px;height:28px;background:#ffffff1f;border:1px solid var(--border);border-radius:999px;position:relative;transition:background .2s ease,border-color .2s ease}:root[data-theme=light] .cfg-toggle__track{background:#0000001a}.cfg-toggle__track:after{content:"";position:absolute;top:3px;left:3px;width:20px;height:20px;background:var(--text);border-radius:50%;box-shadow:0 2px 4px #0000004d;transition:transform .2s ease,background .2s ease}.cfg-toggle input:checked+.cfg-toggle__track{background:#2bd97f40;border-color:#2bd97f80}.cfg-toggle input:checked+.cfg-toggle__track:after{transform:translate(20px);background:var(--ok)}.cfg-toggle input:focus+.cfg-toggle__track{box-shadow:0 0 0 3px var(--focus)}.cfg-object{border:1px solid var(--border);border-radius:10px;background:#0000000a;overflow:hidden}:root[data-theme=light] .cfg-object{background:#fff6}.cfg-object__header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;cursor:pointer;list-style:none;transition:background .15s ease}.cfg-object__header:hover{background:#ffffff08}:root[data-theme=light] .cfg-object__header:hover{background:#00000005}.cfg-object__header::-webkit-details-marker{display:none}.cfg-object__title{font-size:14px;font-weight:600;color:var(--text)}.cfg-object__chevron{width:18px;height:18px;color:var(--muted);transition:transform .2s ease}.cfg-object__chevron svg{width:100%;height:100%}.cfg-object[open] .cfg-object__chevron{transform:rotate(180deg)}.cfg-object__help{padding:0 16px 12px;font-size:12px;color:var(--muted);border-bottom:1px solid var(--border)}.cfg-object__content{padding:16px;display:grid;gap:16px}.cfg-array{border:1px solid var(--border);border-radius:10px;overflow:hidden}.cfg-array__header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#0000000f;border-bottom:1px solid var(--border)}:root[data-theme=light] .cfg-array__header{background:#00000005}.cfg-array__label{flex:1;font-size:14px;font-weight:600;color:var(--text)}.cfg-array__count{font-size:12px;color:var(--muted);padding:3px 8px;background:#ffffff0f;border-radius:999px}:root[data-theme=light] .cfg-array__count{background:#0000000f}.cfg-array__add{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:#ffffff0d;color:var(--text);font-size:12px;font-weight:500;cursor:pointer;transition:background .15s ease}.cfg-array__add:hover:not(:disabled){background:#ffffff1a}.cfg-array__add:disabled{opacity:.5;cursor:not-allowed}.cfg-array__add-icon{width:14px;height:14px}.cfg-array__add-icon svg{width:100%;height:100%}.cfg-array__help{padding:10px 16px;font-size:12px;color:var(--muted);border-bottom:1px solid var(--border)}.cfg-array__empty{padding:32px 16px;text-align:center;color:var(--muted);font-size:13px}.cfg-array__items{display:grid;gap:1px;background:var(--border)}.cfg-array__item{background:var(--panel)}.cfg-array__item-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:#0000000a;border-bottom:1px solid var(--border)}:root[data-theme=light] .cfg-array__item-header{background:#00000005}.cfg-array__item-index{font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}.cfg-array__item-remove{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:none;border-radius:6px;background:transparent;color:var(--muted);cursor:pointer;transition:background .15s ease,color .15s ease}.cfg-array__item-remove svg{width:16px;height:16px}.cfg-array__item-remove:hover:not(:disabled){background:#ff5c5c26;color:var(--danger)}.cfg-array__item-remove:disabled{opacity:.4;cursor:not-allowed}.cfg-array__item-content{padding:16px}.cfg-map{border:1px solid var(--border);border-radius:10px;overflow:hidden}.cfg-map__header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;background:#0000000f;border-bottom:1px solid var(--border)}:root[data-theme=light] .cfg-map__header{background:#00000005}.cfg-map__label{font-size:13px;font-weight:600;color:var(--muted)}.cfg-map__add{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:#ffffff0d;color:var(--text);font-size:12px;font-weight:500;cursor:pointer;transition:background .15s ease}.cfg-map__add:hover:not(:disabled){background:#ffffff1a}.cfg-map__add-icon{width:14px;height:14px}.cfg-map__add-icon svg{width:100%;height:100%}.cfg-map__empty{padding:24px 16px;text-align:center;color:var(--muted);font-size:13px}.cfg-map__items{display:grid;gap:8px;padding:12px}.cfg-map__item{display:grid;grid-template-columns:140px 1fr auto;gap:8px;align-items:start}.cfg-map__item-key,.cfg-map__item-value{min-width:0}.cfg-map__item-remove{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border:none;border-radius:6px;background:transparent;color:var(--muted);cursor:pointer;transition:background .15s ease,color .15s ease}.cfg-map__item-remove svg{width:16px;height:16px}.cfg-map__item-remove:hover:not(:disabled){background:#ff5c5c26;color:var(--danger)}.pill--sm{padding:4px 10px;font-size:11px}.pill--ok{border-color:#2bd97f66;color:var(--ok)}.pill--danger{border-color:#ff5c5c66;color:var(--danger)}@media(max-width:768px){.config-layout{grid-template-columns:1fr}.config-sidebar{border-right:none;border-bottom:1px solid var(--border)}.config-sidebar__header{padding:12px 16px}.config-nav{display:flex;flex-wrap:nowrap;gap:4px;padding:8px 12px;overflow-x:auto;-webkit-overflow-scrolling:touch}.config-nav__item{flex:0 0 auto;padding:8px 12px;white-space:nowrap}.config-nav__label{display:inline}.config-sidebar__footer{display:none}.config-actions{flex-wrap:wrap;padding:12px 16px}.config-actions__left,.config-actions__right{width:100%;justify-content:center}.config-section-hero{padding:12px 16px}.config-subnav{padding:8px 16px 10px}.config-content{padding:16px}.config-section-card__header{padding:14px 16px}.config-section-card__content{padding:16px}.cfg-toggle-row{padding:12px 14px}.cfg-map__item{grid-template-columns:1fr;gap:8px}.cfg-map__item-remove{justify-self:end}}@media(max-width:480px){.config-nav__icon{width:24px;height:24px;font-size:16px}.config-nav__label{display:none}.config-section-card__icon{width:28px;height:28px}.config-section-card__title{font-size:15px}.cfg-segmented{flex-wrap:wrap}.cfg-segmented__btn{flex:1 0 auto;min-width:60px}}
diff --git a/dist/control-ui/assets/index-DsXRcnEw.js b/dist/control-ui/assets/index-DsXRcnEw.js
new file mode 100644
index 000000000..1b0c3042c
--- /dev/null
+++ b/dist/control-ui/assets/index-DsXRcnEw.js
@@ -0,0 +1,3059 @@
+(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))s(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const a of o.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&s(a)}).observe(document,{childList:!0,subtree:!0});function n(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function s(i){if(i.ep)return;i.ep=!0;const o=n(i);fetch(i.href,o)}})();const jt=globalThis,Cs=jt.ShadowRoot&&(jt.ShadyCSS===void 0||jt.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Es=Symbol(),Ni=new WeakMap;let Go=class{constructor(t,n,s){if(this._$cssResult$=!0,s!==Es)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=n}get styleSheet(){let t=this.o;const n=this.t;if(Cs&&t===void 0){const s=n!==void 0&&n.length===1;s&&(t=Ni.get(n)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&Ni.set(n,t))}return t}toString(){return this.cssText}};const Br=e=>new Go(typeof e=="string"?e:e+"",void 0,Es),Fr=(e,...t)=>{const n=e.length===1?e[0]:t.reduce((s,i,o)=>s+(a=>{if(a._$cssResult$===!0)return a.cssText;if(typeof a=="number")return a;throw Error("Value passed to 'css' function must be a 'css' function result: "+a+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+e[o+1],e[0]);return new Go(n,e,Es)},Ur=(e,t)=>{if(Cs)e.adoptedStyleSheets=t.map(n=>n instanceof CSSStyleSheet?n:n.styleSheet);else for(const n of t){const s=document.createElement("style"),i=jt.litNonce;i!==void 0&&s.setAttribute("nonce",i),s.textContent=n.cssText,e.appendChild(s)}},Oi=Cs?e=>e:e=>e instanceof CSSStyleSheet?(t=>{let n="";for(const s of t.cssRules)n+=s.cssText;return Br(n)})(e):e;const{is:Kr,defineProperty:Hr,getOwnPropertyDescriptor:zr,getOwnPropertyNames:jr,getOwnPropertySymbols:qr,getPrototypeOf:Wr}=Object,tn=globalThis,Di=tn.trustedTypes,Vr=Di?Di.emptyScript:"",Gr=tn.reactiveElementPolyfillSupport,mt=(e,t)=>e,Vt={toAttribute(e,t){switch(t){case Boolean:e=e?Vr:null;break;case Object:case Array:e=e==null?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=e!==null;break;case Number:n=e===null?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch{n=null}}return n}},Is=(e,t)=>!Kr(e,t),Bi={attribute:!0,type:String,converter:Vt,reflect:!1,useDefault:!1,hasChanged:Is};Symbol.metadata??=Symbol("metadata"),tn.litPropertyMetadata??=new WeakMap;let Ge=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,n=Bi){if(n.state&&(n.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((n=Object.create(n)).wrapped=!0),this.elementProperties.set(t,n),!n.noAccessor){const s=Symbol(),i=this.getPropertyDescriptor(t,s,n);i!==void 0&&Hr(this.prototype,t,i)}}static getPropertyDescriptor(t,n,s){const{get:i,set:o}=zr(this.prototype,t)??{get(){return this[n]},set(a){this[n]=a}};return{get:i,set(a){const l=i?.call(this);o?.call(this,a),this.requestUpdate(t,l,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??Bi}static _$Ei(){if(this.hasOwnProperty(mt("elementProperties")))return;const t=Wr(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(mt("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(mt("properties"))){const n=this.properties,s=[...jr(n),...qr(n)];for(const i of s)this.createProperty(i,n[i])}const t=this[Symbol.metadata];if(t!==null){const n=litPropertyMetadata.get(t);if(n!==void 0)for(const[s,i]of n)this.elementProperties.set(s,i)}this._$Eh=new Map;for(const[n,s]of this.elementProperties){const i=this._$Eu(n,s);i!==void 0&&this._$Eh.set(i,n)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const n=[];if(Array.isArray(t)){const s=new Set(t.flat(1/0).reverse());for(const i of s)n.unshift(Oi(i))}else t!==void 0&&n.push(Oi(t));return n}static _$Eu(t,n){const s=n.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,n=this.constructor.elementProperties;for(const s of n.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return Ur(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,n,s){this._$AK(t,s)}_$ET(t,n){const s=this.constructor.elementProperties.get(t),i=this.constructor._$Eu(t,s);if(i!==void 0&&s.reflect===!0){const o=(s.converter?.toAttribute!==void 0?s.converter:Vt).toAttribute(n,s.type);this._$Em=t,o==null?this.removeAttribute(i):this.setAttribute(i,o),this._$Em=null}}_$AK(t,n){const s=this.constructor,i=s._$Eh.get(t);if(i!==void 0&&this._$Em!==i){const o=s.getPropertyOptions(i),a=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:Vt;this._$Em=i;const l=a.fromAttribute(n,o.type);this[i]=l??this._$Ej?.get(i)??l,this._$Em=null}}requestUpdate(t,n,s,i=!1,o){if(t!==void 0){const a=this.constructor;if(i===!1&&(o=this[t]),s??=a.getPropertyOptions(t),!((s.hasChanged??Is)(o,n)||s.useDefault&&s.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(a._$Eu(t,s))))return;this.C(t,n,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,n,{useDefault:s,reflect:i,wrapped:o},a){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,a??n??this[t]),o!==!0||a!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(n=void 0),this._$AL.set(t,n)),i===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(n){Promise.reject(n)}const t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[i,o]of this._$Ep)this[i]=o;this._$Ep=void 0}const s=this.constructor.elementProperties;if(s.size>0)for(const[i,o]of s){const{wrapped:a}=o,l=this[i];a!==!0||this._$AL.has(i)||l===void 0||this.C(i,void 0,o,l)}}let t=!1;const n=this._$AL;try{t=this.shouldUpdate(n),t?(this.willUpdate(n),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(n)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(n)}willUpdate(t){}_$AE(t){this._$EO?.forEach(n=>n.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(n=>this._$ET(n,this[n])),this._$EM()}updated(t){}firstUpdated(t){}};Ge.elementStyles=[],Ge.shadowRootOptions={mode:"open"},Ge[mt("elementProperties")]=new Map,Ge[mt("finalized")]=new Map,Gr?.({ReactiveElement:Ge}),(tn.reactiveElementVersions??=[]).push("2.1.2");const Ls=globalThis,Fi=e=>e,Gt=Ls.trustedTypes,Ui=Gt?Gt.createPolicy("lit-html",{createHTML:e=>e}):void 0,Yo="$lit$",we=`lit$${Math.random().toFixed(9).slice(2)}$`,Qo="?"+we,Yr=`<${Qo}>`,Ne=document,wt=()=>Ne.createComment(""),$t=e=>e===null||typeof e!="object"&&typeof e!="function",Rs=Array.isArray,Qr=e=>Rs(e)||typeof e?.[Symbol.iterator]=="function",Nn=`[
+\f\r]`,at=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Ki=/-->/g,Hi=/>/g,Ee=RegExp(`>|${Nn}(?:([^\\s"'>=/]+)(${Nn}*=${Nn}*(?:[^
+\f\r"'\`<>=]|("|')|))|$)`,"g"),zi=/'/g,ji=/"/g,Jo=/^(?:script|style|textarea|title)$/i,Jr=e=>(t,...n)=>({_$litType$:e,strings:t,values:n}),c=Jr(1),xe=Symbol.for("lit-noChange"),g=Symbol.for("lit-nothing"),qi=new WeakMap,Me=Ne.createTreeWalker(Ne,129);function Zo(e,t){if(!Rs(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return Ui!==void 0?Ui.createHTML(t):t}const Zr=(e,t)=>{const n=e.length-1,s=[];let i,o=t===2?"":t===3?"":"")),s]};let ns=class Xo{constructor({strings:t,_$litType$:n},s){let i;this.parts=[];let o=0,a=0;const l=t.length-1,r=this.parts,[p,d]=Zr(t,n);if(this.el=Xo.createElement(p,s),Me.currentNode=this.el.content,n===2||n===3){const u=this.el.content.firstChild;u.replaceWith(...u.childNodes)}for(;(i=Me.nextNode())!==null&&r.length0){i.textContent=Gt?Gt.emptyScript:"";for(let v=0;v2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=g}_$AI(t,n=this,s,i){const o=this.strings;let a=!1;if(o===void 0)t=Je(this,t,n,0),a=!$t(t)||t!==this._$AH&&t!==xe,a&&(this._$AH=t);else{const l=t;let r,p;for(t=o[0],r=0;r{const s=n?.renderBefore??t;let i=s._$litPart$;if(i===void 0){const o=n?.renderBefore??null;s._$litPart$=i=new nn(t.insertBefore(wt(),o),o,void 0,n??{})}return i._$AI(e),i};const Ms=globalThis;let Qe=class extends Ge{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const n=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=al(n,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return xe}};Qe._$litElement$=!0,Qe.finalized=!0,Ms.litElementHydrateSupport?.({LitElement:Qe});const rl=Ms.litElementPolyfillSupport;rl?.({LitElement:Qe});(Ms.litElementVersions??=[]).push("4.2.2");const ta=e=>(t,n)=>{n!==void 0?n.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)};const ll={attribute:!0,type:String,converter:Vt,reflect:!1,hasChanged:Is},cl=(e=ll,t,n)=>{const{kind:s,metadata:i}=n;let o=globalThis.litPropertyMetadata.get(i);if(o===void 0&&globalThis.litPropertyMetadata.set(i,o=new Map),s==="setter"&&((e=Object.create(e)).wrapped=!0),o.set(n.name,e),s==="accessor"){const{name:a}=n;return{set(l){const r=t.get.call(this);t.set.call(this,l),this.requestUpdate(a,r,e,!0,l)},init(l){return l!==void 0&&this.C(a,void 0,e,l),l}}}if(s==="setter"){const{name:a}=n;return function(l){const r=this[a];t.call(this,l),this.requestUpdate(a,r,e,!0,l)}}throw Error("Unsupported decorator location: "+s)};function on(e){return(t,n)=>typeof n=="object"?cl(e,t,n):((s,i,o)=>{const a=i.hasOwnProperty(o);return i.constructor.createProperty(o,s),a?Object.getOwnPropertyDescriptor(i,o):void 0})(e,t,n)}function y(e){return on({...e,state:!0,attribute:!1})}const dl=50,ul=200,pl="Assistant";function Wi(e,t){if(typeof e!="string")return;const n=e.trim();if(n)return n.length<=t?n:n.slice(0,t)}function ss(e){const t=Wi(e?.name,dl)??pl,n=Wi(e?.avatar??void 0,ul)??null;return{agentId:typeof e?.agentId=="string"&&e.agentId.trim()?e.agentId.trim():null,name:t,avatar:n}}function fl(){return ss(typeof window>"u"?{}:{name:window.__CLAWDBOT_ASSISTANT_NAME__,avatar:window.__CLAWDBOT_ASSISTANT_AVATAR__})}const na="clawdbot.control.settings.v1";function hl(){const t={gatewayUrl:`${location.protocol==="https:"?"wss":"ws"}://${location.host}`,token:"",sessionKey:"main",lastActiveSessionKey:"main",theme:"system",chatFocusMode:!1,chatShowThinking:!0,splitRatio:.6,navCollapsed:!1,navGroupsCollapsed:{}};try{const n=localStorage.getItem(na);if(!n)return t;const s=JSON.parse(n);return{gatewayUrl:typeof s.gatewayUrl=="string"&&s.gatewayUrl.trim()?s.gatewayUrl.trim():t.gatewayUrl,token:typeof s.token=="string"?s.token:t.token,sessionKey:typeof s.sessionKey=="string"&&s.sessionKey.trim()?s.sessionKey.trim():t.sessionKey,lastActiveSessionKey:typeof s.lastActiveSessionKey=="string"&&s.lastActiveSessionKey.trim()?s.lastActiveSessionKey.trim():typeof s.sessionKey=="string"&&s.sessionKey.trim()||t.lastActiveSessionKey,theme:s.theme==="light"||s.theme==="dark"||s.theme==="system"?s.theme:t.theme,chatFocusMode:typeof s.chatFocusMode=="boolean"?s.chatFocusMode:t.chatFocusMode,chatShowThinking:typeof s.chatShowThinking=="boolean"?s.chatShowThinking:t.chatShowThinking,splitRatio:typeof s.splitRatio=="number"&&s.splitRatio>=.4&&s.splitRatio<=.7?s.splitRatio:t.splitRatio,navCollapsed:typeof s.navCollapsed=="boolean"?s.navCollapsed:t.navCollapsed,navGroupsCollapsed:typeof s.navGroupsCollapsed=="object"&&s.navGroupsCollapsed!==null?s.navGroupsCollapsed:t.navGroupsCollapsed}}catch{return t}}function gl(e){localStorage.setItem(na,JSON.stringify(e))}function sa(e){const t=(e??"").trim();if(!t)return null;const n=t.split(":").filter(Boolean);if(n.length<3||n[0]!=="agent")return null;const s=n[1]?.trim(),i=n.slice(2).join(":");return!s||!i?null:{agentId:s,rest:i}}const vl=[{label:"Chat",tabs:["chat"]},{label:"Control",tabs:["overview","channels","instances","sessions","cron"]},{label:"Agent",tabs:["skills","nodes"]},{label:"Settings",tabs:["config","debug","logs"]}],ia={overview:"/overview",channels:"/channels",instances:"/instances",sessions:"/sessions",cron:"/cron",skills:"/skills",nodes:"/nodes",chat:"/chat",config:"/config",debug:"/debug",logs:"/logs"},oa=new Map(Object.entries(ia).map(([e,t])=>[t,e]));function an(e){if(!e)return"";let t=e.trim();return t.startsWith("/")||(t=`/${t}`),t==="/"?"":(t.endsWith("/")&&(t=t.slice(0,-1)),t)}function kt(e){if(!e)return"/";let t=e.trim();return t.startsWith("/")||(t=`/${t}`),t.length>1&&t.endsWith("/")&&(t=t.slice(0,-1)),t}function Ps(e,t=""){const n=an(t),s=ia[e];return n?`${n}${s}`:s}function aa(e,t=""){const n=an(t);let s=e||"/";n&&(s===n?s="/":s.startsWith(`${n}/`)&&(s=s.slice(n.length)));let i=kt(s).toLowerCase();return i.endsWith("/index.html")&&(i="/"),i==="/"?"chat":oa.get(i)??null}function ml(e){let t=kt(e);if(t.endsWith("/index.html")&&(t=kt(t.slice(0,-11))),t==="/")return"";const n=t.split("/").filter(Boolean);if(n.length===0)return"";for(let s=0;s!!(t&&t.trim())).join(", ")}function as(e,t=120){return e.length<=t?e:`${e.slice(0,Math.max(0,t-1))}…`}function la(e,t){return e.length<=t?{text:e,truncated:!1,total:e.length}:{text:e.slice(0,Math.max(0,t)),truncated:!0,total:e.length}}function Yt(e,t){const n=Number(e);return Number.isFinite(n)?n:t}const On=/<\s*\/?\s*think(?:ing)?\s*>/gi,Vi=/<\s*think(?:ing)?\s*>/i,Gi=/<\s*\/\s*think(?:ing)?\s*>/i;function Dn(e){if(!e)return e;const t=Vi.test(e),n=Gi.test(e);if(!t&&!n)return e;if(t!==n)return t?e.replace(Vi,"").trimStart():e.replace(Gi,"").trimStart();if(!On.test(e))return e;On.lastIndex=0;let s="",i=0,o=!1;for(const a of e.matchAll(On)){const l=a.index??0;o||(s+=e.slice(i,l)),o=!a[0].toLowerCase().includes("/"),i=l+a[0].length}return o||(s+=e.slice(i)),s.trimStart()}const wl=/^\[([^\]]+)\]\s*/,$l=["WebChat","WhatsApp","Telegram","Signal","Slack","Discord","iMessage","Teams","Matrix","Zalo","Zalo Personal","BlueBubbles"],Bn=new WeakMap,Fn=new WeakMap;function kl(e){return/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(e)||/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(e)?!0:$l.some(t=>e.startsWith(`${t} `))}function Un(e){const t=e.match(wl);if(!t)return e;const n=t[1]??"";return kl(n)?e.slice(t[0].length):e}function rs(e){const t=e,n=typeof t.role=="string"?t.role:"",s=t.content;if(typeof s=="string")return n==="assistant"?Dn(s):Un(s);if(Array.isArray(s)){const i=s.map(o=>{const a=o;return a.type==="text"&&typeof a.text=="string"?a.text:null}).filter(o=>typeof o=="string");if(i.length>0){const o=i.join(`
+`);return n==="assistant"?Dn(o):Un(o)}}return typeof t.text=="string"?n==="assistant"?Dn(t.text):Un(t.text):null}function ca(e){if(!e||typeof e!="object")return rs(e);const t=e;if(Bn.has(t))return Bn.get(t)??null;const n=rs(e);return Bn.set(t,n),n}function Yi(e){const n=e.content,s=[];if(Array.isArray(n))for(const l of n){const r=l;if(r.type==="thinking"&&typeof r.thinking=="string"){const p=r.thinking.trim();p&&s.push(p)}}if(s.length>0)return s.join(`
+`);const i=Al(e);if(!i)return null;const a=[...i.matchAll(/<\s*think(?:ing)?\s*>([\s\S]*?)<\s*\/\s*think(?:ing)?\s*>/gi)].map(l=>(l[1]??"").trim()).filter(Boolean);return a.length>0?a.join(`
+`):null}function xl(e){if(!e||typeof e!="object")return Yi(e);const t=e;if(Fn.has(t))return Fn.get(t)??null;const n=Yi(e);return Fn.set(t,n),n}function Al(e){const t=e,n=t.content;if(typeof n=="string")return n;if(Array.isArray(n)){const s=n.map(i=>{const o=i;return o.type==="text"&&typeof o.text=="string"?o.text:null}).filter(i=>typeof i=="string");if(s.length>0)return s.join(`
+`)}return typeof t.text=="string"?t.text:null}function Sl(e){const t=e.trim();if(!t)return"";const n=t.split(/\r?\n/).map(s=>s.trim()).filter(Boolean).map(s=>`_${s}_`);return n.length?["_Reasoning:_",...n].join(`
+`):""}function Qi(e){e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t="";for(let n=0;n>>8&255,e[2]^=t>>>16&255,e[3]^=t>>>24&255,e}function Ns(e=globalThis.crypto){if(e&&typeof e.randomUUID=="function")return e.randomUUID();if(e&&typeof e.getRandomValues=="function"){const t=new Uint8Array(16);return e.getRandomValues(t),Qi(t)}return Qi(_l())}async function Ze(e){if(!(!e.client||!e.connected)){e.chatLoading=!0,e.lastError=null;try{const t=await e.client.request("chat.history",{sessionKey:e.sessionKey,limit:200});e.chatMessages=Array.isArray(t.messages)?t.messages:[],e.chatThinkingLevel=t.thinkingLevel??null}catch(t){e.lastError=String(t)}finally{e.chatLoading=!1}}}async function Tl(e,t){if(!e.client||!e.connected)return!1;const n=t.trim();if(!n)return!1;const s=Date.now();e.chatMessages=[...e.chatMessages,{role:"user",content:[{type:"text",text:n}],timestamp:s}],e.chatSending=!0,e.lastError=null;const i=Ns();e.chatRunId=i,e.chatStream="",e.chatStreamStartedAt=s;try{return await e.client.request("chat.send",{sessionKey:e.sessionKey,message:n,deliver:!1,idempotencyKey:i}),!0}catch(o){const a=String(o);return e.chatRunId=null,e.chatStream=null,e.chatStreamStartedAt=null,e.lastError=a,e.chatMessages=[...e.chatMessages,{role:"assistant",content:[{type:"text",text:"Error: "+a}],timestamp:Date.now()}],!1}finally{e.chatSending=!1}}async function Cl(e){if(!e.client||!e.connected)return!1;const t=e.chatRunId;try{return await e.client.request("chat.abort",t?{sessionKey:e.sessionKey,runId:t}:{sessionKey:e.sessionKey}),!0}catch(n){return e.lastError=String(n),!1}}function El(e,t){if(!t||t.sessionKey!==e.sessionKey||t.runId&&e.chatRunId&&t.runId!==e.chatRunId)return null;if(t.state==="delta"){const n=rs(t.message);if(typeof n=="string"){const s=e.chatStream??"";(!s||n.length>=s.length)&&(e.chatStream=n)}}else t.state==="final"||t.state==="aborted"?(e.chatStream=null,e.chatRunId=null,e.chatStreamStartedAt=null):t.state==="error"&&(e.chatStream=null,e.chatRunId=null,e.chatStreamStartedAt=null,e.lastError=t.errorMessage??"chat error");return t.state}async function nt(e){if(!(!e.client||!e.connected)&&!e.sessionsLoading){e.sessionsLoading=!0,e.sessionsError=null;try{const t={includeGlobal:e.sessionsIncludeGlobal,includeUnknown:e.sessionsIncludeUnknown},n=Yt(e.sessionsFilterActive,0),s=Yt(e.sessionsFilterLimit,0);n>0&&(t.activeMinutes=n),s>0&&(t.limit=s);const i=await e.client.request("sessions.list",t);i&&(e.sessionsResult=i)}catch(t){e.sessionsError=String(t)}finally{e.sessionsLoading=!1}}}async function Il(e,t,n){if(!e.client||!e.connected)return;const s={key:t};"label"in n&&(s.label=n.label),"thinkingLevel"in n&&(s.thinkingLevel=n.thinkingLevel),"verboseLevel"in n&&(s.verboseLevel=n.verboseLevel),"reasoningLevel"in n&&(s.reasoningLevel=n.reasoningLevel);try{await e.client.request("sessions.patch",s),await nt(e)}catch(i){e.sessionsError=String(i)}}async function Ll(e,t){if(!(!e.client||!e.connected||e.sessionsLoading||!window.confirm(`Delete session "${t}"?
+
+Deletes the session entry and archives its transcript.`))){e.sessionsLoading=!0,e.sessionsError=null;try{await e.client.request("sessions.delete",{key:t,deleteTranscript:!0}),await nt(e)}catch(s){e.sessionsError=String(s)}finally{e.sessionsLoading=!1}}}const Ji=50,Rl=80,Ml=12e4;function Pl(e){if(!e||typeof e!="object")return null;const t=e;if(typeof t.text=="string")return t.text;const n=t.content;if(!Array.isArray(n))return null;const s=n.map(i=>{if(!i||typeof i!="object")return null;const o=i;return o.type==="text"&&typeof o.text=="string"?o.text:null}).filter(i=>!!i);return s.length===0?null:s.join(`
+`)}function Zi(e){if(e==null)return null;if(typeof e=="number"||typeof e=="boolean")return String(e);const t=Pl(e);let n;if(typeof e=="string")n=e;else if(t)n=t;else try{n=JSON.stringify(e,null,2)}catch{n=String(e)}const s=la(n,Ml);return s.truncated?`${s.text}
+
+… truncated (${s.total} chars, showing first ${s.text.length}).`:s.text}function Nl(e){const t=[];return t.push({type:"toolcall",name:e.name,arguments:e.args??{}}),e.output&&t.push({type:"toolresult",name:e.name,text:e.output}),{role:"assistant",toolCallId:e.toolCallId,runId:e.runId,content:t,timestamp:e.startedAt}}function Ol(e){if(e.toolStreamOrder.length<=Ji)return;const t=e.toolStreamOrder.length-Ji,n=e.toolStreamOrder.splice(0,t);for(const s of n)e.toolStreamById.delete(s)}function Dl(e){e.chatToolMessages=e.toolStreamOrder.map(t=>e.toolStreamById.get(t)?.message).filter(t=>!!t)}function ls(e){e.toolStreamSyncTimer!=null&&(clearTimeout(e.toolStreamSyncTimer),e.toolStreamSyncTimer=null),Dl(e)}function Bl(e,t=!1){if(t){ls(e);return}e.toolStreamSyncTimer==null&&(e.toolStreamSyncTimer=window.setTimeout(()=>ls(e),Rl))}function Os(e){e.toolStreamById.clear(),e.toolStreamOrder=[],e.chatToolMessages=[],ls(e)}const Fl=5e3;function Ul(e,t){const n=t.data??{},s=typeof n.phase=="string"?n.phase:"";e.compactionClearTimer!=null&&(window.clearTimeout(e.compactionClearTimer),e.compactionClearTimer=null),s==="start"?e.compactionStatus={active:!0,startedAt:Date.now(),completedAt:null}:s==="end"&&(e.compactionStatus={active:!1,startedAt:e.compactionStatus?.startedAt??null,completedAt:Date.now()},e.compactionClearTimer=window.setTimeout(()=>{e.compactionStatus=null,e.compactionClearTimer=null},Fl))}function Kl(e,t){if(!t)return;if(t.stream==="compaction"){Ul(e,t);return}if(t.stream!=="tool")return;const n=typeof t.sessionKey=="string"?t.sessionKey:void 0;if(n&&n!==e.sessionKey||!n&&e.chatRunId&&t.runId!==e.chatRunId||e.chatRunId&&t.runId!==e.chatRunId||!e.chatRunId)return;const s=t.data??{},i=typeof s.toolCallId=="string"?s.toolCallId:"";if(!i)return;const o=typeof s.name=="string"?s.name:"tool",a=typeof s.phase=="string"?s.phase:"",l=a==="start"?s.args:void 0,r=a==="update"?Zi(s.partialResult):a==="result"?Zi(s.result):void 0,p=Date.now();let d=e.toolStreamById.get(i);d?(d.name=o,l!==void 0&&(d.args=l),r!==void 0&&(d.output=r),d.updatedAt=p):(d={toolCallId:i,runId:t.runId,sessionKey:n,name:o,args:l,output:r,startedAt:typeof t.ts=="number"?t.ts:p,updatedAt:p,message:{}},e.toolStreamById.set(i,d),e.toolStreamOrder.push(i)),d.message=Nl(d),Ol(e),Bl(e,a==="result")}function rn(e,t=!1){e.chatScrollFrame&&cancelAnimationFrame(e.chatScrollFrame),e.chatScrollTimeout!=null&&(clearTimeout(e.chatScrollTimeout),e.chatScrollTimeout=null);const n=()=>{const s=e.querySelector(".chat-thread");if(s){const i=getComputedStyle(s).overflowY;if(i==="auto"||i==="scroll"||s.scrollHeight-s.clientHeight>1)return s}return document.scrollingElement??document.documentElement};e.updateComplete.then(()=>{e.chatScrollFrame=requestAnimationFrame(()=>{e.chatScrollFrame=null;const s=n();if(!s)return;const i=s.scrollHeight-s.scrollTop-s.clientHeight;if(!(t||e.chatUserNearBottom||i<200))return;t&&(e.chatHasAutoScrolled=!0),s.scrollTop=s.scrollHeight,e.chatUserNearBottom=!0;const a=t?150:120;e.chatScrollTimeout=window.setTimeout(()=>{e.chatScrollTimeout=null;const l=n();if(!l)return;const r=l.scrollHeight-l.scrollTop-l.clientHeight;(t||e.chatUserNearBottom||r<200)&&(l.scrollTop=l.scrollHeight,e.chatUserNearBottom=!0)},a)})})}function da(e,t=!1){e.logsScrollFrame&&cancelAnimationFrame(e.logsScrollFrame),e.updateComplete.then(()=>{e.logsScrollFrame=requestAnimationFrame(()=>{e.logsScrollFrame=null;const n=e.querySelector(".log-stream");if(!n)return;const s=n.scrollHeight-n.scrollTop-n.clientHeight;(t||s<80)&&(n.scrollTop=n.scrollHeight)})})}function Hl(e,t){const n=t.currentTarget;if(!n)return;const s=n.scrollHeight-n.scrollTop-n.clientHeight;e.chatUserNearBottom=s<200}function zl(e,t){const n=t.currentTarget;if(!n)return;const s=n.scrollHeight-n.scrollTop-n.clientHeight;e.logsAtBottom=s<80}function jl(e){e.chatHasAutoScrolled=!1,e.chatUserNearBottom=!0}function ql(e,t){if(e.length===0)return;const n=new Blob([`${e.join(`
+`)}
+`],{type:"text/plain"}),s=URL.createObjectURL(n),i=document.createElement("a"),o=new Date().toISOString().slice(0,19).replace(/[:T]/g,"-");i.href=s,i.download=`clawdbot-logs-${t}-${o}.log`,i.click(),URL.revokeObjectURL(s)}function Wl(e){if(typeof ResizeObserver>"u")return;const t=e.querySelector(".topbar");if(!t)return;const n=()=>{const{height:s}=t.getBoundingClientRect();e.style.setProperty("--topbar-height",`${s}px`)};n(),e.topbarObserver=new ResizeObserver(()=>n()),e.topbarObserver.observe(t)}function Oe(e){return typeof structuredClone=="function"?structuredClone(e):JSON.parse(JSON.stringify(e))}function Xe(e){return`${JSON.stringify(e,null,2).trimEnd()}
+`}function ua(e,t,n){if(t.length===0)return;let s=e;for(let o=0;o0&&(n.timeoutSeconds=s),n}async function Xl(e){if(!(!e.client||!e.connected||e.cronBusy)){e.cronBusy=!0,e.cronError=null;try{const t=Jl(e.cronForm),n=Zl(e.cronForm),s=e.cronForm.agentId.trim(),i={name:e.cronForm.name.trim(),description:e.cronForm.description.trim()||void 0,agentId:s||void 0,enabled:e.cronForm.enabled,schedule:t,sessionTarget:e.cronForm.sessionTarget,wakeMode:e.cronForm.wakeMode,payload:n,isolation:e.cronForm.postToMainPrefix.trim()&&e.cronForm.sessionTarget==="isolated"?{postToMainPrefix:e.cronForm.postToMainPrefix.trim()}:void 0};if(!i.name)throw new Error("Name required.");await e.client.request("cron.add",i),e.cronForm={...e.cronForm,name:"",description:"",payloadText:""},await ln(e),await _t(e)}catch(t){e.cronError=String(t)}finally{e.cronBusy=!1}}}async function ec(e,t,n){if(!(!e.client||!e.connected||e.cronBusy)){e.cronBusy=!0,e.cronError=null;try{await e.client.request("cron.update",{id:t.id,patch:{enabled:n}}),await ln(e),await _t(e)}catch(s){e.cronError=String(s)}finally{e.cronBusy=!1}}}async function tc(e,t){if(!(!e.client||!e.connected||e.cronBusy)){e.cronBusy=!0,e.cronError=null;try{await e.client.request("cron.run",{id:t.id,mode:"force"}),await ha(e,t.id)}catch(n){e.cronError=String(n)}finally{e.cronBusy=!1}}}async function nc(e,t){if(!(!e.client||!e.connected||e.cronBusy)){e.cronBusy=!0,e.cronError=null;try{await e.client.request("cron.remove",{id:t.id}),e.cronRunsJobId===t.id&&(e.cronRunsJobId=null,e.cronRuns=[]),await ln(e),await _t(e)}catch(n){e.cronError=String(n)}finally{e.cronBusy=!1}}}async function ha(e,t){if(!(!e.client||!e.connected))try{const n=await e.client.request("cron.runs",{id:t,limit:50});e.cronRunsJobId=t,e.cronRuns=Array.isArray(n.entries)?n.entries:[]}catch(n){e.cronError=String(n)}}async function oe(e,t){if(!(!e.client||!e.connected)&&!e.channelsLoading){e.channelsLoading=!0,e.channelsError=null;try{const n=await e.client.request("channels.status",{probe:t,timeoutMs:8e3});e.channelsSnapshot=n,e.channelsLastSuccess=Date.now()}catch(n){e.channelsError=String(n)}finally{e.channelsLoading=!1}}}async function sc(e,t){if(!(!e.client||!e.connected||e.whatsappBusy)){e.whatsappBusy=!0;try{const n=await e.client.request("web.login.start",{force:t,timeoutMs:3e4});e.whatsappLoginMessage=n.message??null,e.whatsappLoginQrDataUrl=n.qrDataUrl??null,e.whatsappLoginConnected=null}catch(n){e.whatsappLoginMessage=String(n),e.whatsappLoginQrDataUrl=null,e.whatsappLoginConnected=null}finally{e.whatsappBusy=!1}}}async function ic(e){if(!(!e.client||!e.connected||e.whatsappBusy)){e.whatsappBusy=!0;try{const t=await e.client.request("web.login.wait",{timeoutMs:12e4});e.whatsappLoginMessage=t.message??null,e.whatsappLoginConnected=t.connected??null,t.connected&&(e.whatsappLoginQrDataUrl=null)}catch(t){e.whatsappLoginMessage=String(t),e.whatsappLoginConnected=null}finally{e.whatsappBusy=!1}}}async function oc(e){if(!(!e.client||!e.connected||e.whatsappBusy)){e.whatsappBusy=!0;try{await e.client.request("channels.logout",{channel:"whatsapp"}),e.whatsappLoginMessage="Logged out.",e.whatsappLoginQrDataUrl=null,e.whatsappLoginConnected=null}catch(t){e.whatsappLoginMessage=String(t)}finally{e.whatsappBusy=!1}}}async function cn(e){if(!(!e.client||!e.connected)&&!e.debugLoading){e.debugLoading=!0;try{const[t,n,s,i]=await Promise.all([e.client.request("status",{}),e.client.request("health",{}),e.client.request("models.list",{}),e.client.request("last-heartbeat",{})]);e.debugStatus=t,e.debugHealth=n;const o=s;e.debugModels=Array.isArray(o?.models)?o?.models:[],e.debugHeartbeat=i}catch(t){e.debugCallError=String(t)}finally{e.debugLoading=!1}}}async function ac(e){if(!(!e.client||!e.connected)){e.debugCallError=null,e.debugCallResult=null;try{const t=e.debugCallParams.trim()?JSON.parse(e.debugCallParams):{},n=await e.client.request(e.debugCallMethod.trim(),t);e.debugCallResult=JSON.stringify(n,null,2)}catch(t){e.debugCallError=String(t)}}}const rc=2e3,lc=new Set(["trace","debug","info","warn","error","fatal"]);function cc(e){if(typeof e!="string")return null;const t=e.trim();if(!t.startsWith("{")||!t.endsWith("}"))return null;try{const n=JSON.parse(t);return!n||typeof n!="object"?null:n}catch{return null}}function dc(e){if(typeof e!="string")return null;const t=e.toLowerCase();return lc.has(t)?t:null}function uc(e){if(!e.trim())return{raw:e,message:e};try{const t=JSON.parse(e),n=t&&typeof t._meta=="object"&&t._meta!==null?t._meta:null,s=typeof t.time=="string"?t.time:typeof n?.date=="string"?n?.date:null,i=dc(n?.logLevelName??n?.level),o=typeof t[0]=="string"?t[0]:typeof n?.name=="string"?n?.name:null,a=cc(o);let l=null;a&&(typeof a.subsystem=="string"?l=a.subsystem:typeof a.module=="string"&&(l=a.module)),!l&&o&&o.length<120&&(l=o);let r=null;return typeof t[1]=="string"?r=t[1]:!a&&typeof t[0]=="string"?r=t[0]:typeof t.message=="string"&&(r=t.message),{raw:e,time:s,level:i,subsystem:l,message:r??e,meta:n??void 0}}catch{return{raw:e,message:e}}}async function Ds(e,t){if(!(!e.client||!e.connected)&&!(e.logsLoading&&!t?.quiet)){t?.quiet||(e.logsLoading=!0),e.logsError=null;try{const s=await e.client.request("logs.tail",{cursor:t?.reset?void 0:e.logsCursor??void 0,limit:e.logsLimit,maxBytes:e.logsMaxBytes}),o=(Array.isArray(s.lines)?s.lines.filter(l=>typeof l=="string"):[]).map(uc),a=!!(t?.reset||s.reset||e.logsCursor==null);e.logsEntries=a?o:[...e.logsEntries,...o].slice(-rc),typeof s.cursor=="number"&&(e.logsCursor=s.cursor),typeof s.file=="string"&&(e.logsFile=s.file),e.logsTruncated=!!s.truncated,e.logsLastFetchAt=Date.now()}catch(n){e.logsError=String(n)}finally{t?.quiet||(e.logsLoading=!1)}}}const ga={p:0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,n:0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,h:8n,a:0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,d:0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,Gx:0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,Gy:0x6666666666666666666666666666666666666666666666666666666666666658n},{p:W,n:qt,Gx:eo,Gy:to,a:Kn,d:Hn,h:pc}=ga,De=32,Bs=64,fc=(...e)=>{"captureStackTrace"in Error&&typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(...e)},H=(e="")=>{const t=new Error(e);throw fc(t,H),t},hc=e=>typeof e=="bigint",gc=e=>typeof e=="string",vc=e=>e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name==="Uint8Array",Ae=(e,t,n="")=>{const s=vc(e),i=e?.length,o=t!==void 0;if(!s||o&&i!==t){const a=n&&`"${n}" `,l=o?` of length ${t}`:"",r=s?`length=${i}`:`type=${typeof e}`;H(a+"expected Uint8Array"+l+", got "+r)}return e},dn=e=>new Uint8Array(e),va=e=>Uint8Array.from(e),ma=(e,t)=>e.toString(16).padStart(t,"0"),ba=e=>Array.from(Ae(e)).map(t=>ma(t,2)).join(""),ge={_0:48,_9:57,A:65,F:70,a:97,f:102},no=e=>{if(e>=ge._0&&e<=ge._9)return e-ge._0;if(e>=ge.A&&e<=ge.F)return e-(ge.A-10);if(e>=ge.a&&e<=ge.f)return e-(ge.a-10)},ya=e=>{const t="hex invalid";if(!gc(e))return H(t);const n=e.length,s=n/2;if(n%2)return H(t);const i=dn(s);for(let o=0,a=0;oglobalThis?.crypto,mc=()=>wa()?.subtle??H("crypto.subtle must be defined, consider polyfill"),At=(...e)=>{const t=dn(e.reduce((s,i)=>s+Ae(i).length,0));let n=0;return e.forEach(s=>{t.set(s,n),n+=s.length}),t},bc=(e=De)=>wa().getRandomValues(dn(e)),Qt=BigInt,Re=(e,t,n,s="bad number: out of range")=>hc(e)&&t<=e&&e{const n=e%t;return n>=0n?n:t+n},$a=e=>S(e,qt),yc=(e,t)=>{(e===0n||t<=0n)&&H("no inverse n="+e+" mod="+t);let n=S(e,t),s=t,i=0n,o=1n;for(;n!==0n;){const a=s/n,l=s%n,r=i-o*a;s=n,n=l,i=o,o=r}return s===1n?S(i,t):H("no inverse")},wc=e=>{const t=Sa[e];return typeof t!="function"&&H("hashes."+e+" not set"),t},zn=e=>e instanceof X?e:H("Point expected"),ds=2n**256n;class X{static BASE;static ZERO;X;Y;Z;T;constructor(t,n,s,i){const o=ds;this.X=Re(t,0n,o),this.Y=Re(n,0n,o),this.Z=Re(s,1n,o),this.T=Re(i,0n,o),Object.freeze(this)}static CURVE(){return ga}static fromAffine(t){return new X(t.x,t.y,1n,S(t.x*t.y))}static fromBytes(t,n=!1){const s=Hn,i=va(Ae(t,De)),o=t[31];i[31]=o&-129;const a=xa(i);Re(a,0n,n?ds:W);const r=S(a*a),p=S(r-1n),d=S(s*r+1n);let{isValid:u,value:h}=kc(p,d);u||H("bad point: y not sqrt");const v=(h&1n)===1n,w=(o&128)!==0;return!n&&h===0n&&w&&H("bad point: x==0, isLastByteOdd"),w!==v&&(h=S(-h)),new X(h,a,1n,S(h*a))}static fromHex(t,n){return X.fromBytes(ya(t),n)}get x(){return this.toAffine().x}get y(){return this.toAffine().y}assertValidity(){const t=Kn,n=Hn,s=this;if(s.is0())return H("bad point: ZERO");const{X:i,Y:o,Z:a,T:l}=s,r=S(i*i),p=S(o*o),d=S(a*a),u=S(d*d),h=S(r*t),v=S(d*S(h+p)),w=S(u+S(n*S(r*p)));if(v!==w)return H("bad point: equation left != right (1)");const $=S(i*o),x=S(a*l);return $!==x?H("bad point: equation left != right (2)"):this}equals(t){const{X:n,Y:s,Z:i}=this,{X:o,Y:a,Z:l}=zn(t),r=S(n*l),p=S(o*i),d=S(s*l),u=S(a*i);return r===p&&d===u}is0(){return this.equals(Ye)}negate(){return new X(S(-this.X),this.Y,this.Z,S(-this.T))}double(){const{X:t,Y:n,Z:s}=this,i=Kn,o=S(t*t),a=S(n*n),l=S(2n*S(s*s)),r=S(i*o),p=t+n,d=S(S(p*p)-o-a),u=r+a,h=u-l,v=r-a,w=S(d*h),$=S(u*v),x=S(d*v),C=S(h*u);return new X(w,$,C,x)}add(t){const{X:n,Y:s,Z:i,T:o}=this,{X:a,Y:l,Z:r,T:p}=zn(t),d=Kn,u=Hn,h=S(n*a),v=S(s*l),w=S(o*u*p),$=S(i*r),x=S((n+s)*(a+l)-h-v),C=S($-w),I=S($+w),R=S(v-d*h),E=S(x*C),A=S(I*R),B=S(x*R),ue=S(C*I);return new X(E,A,ue,B)}subtract(t){return this.add(zn(t).negate())}multiply(t,n=!0){if(!n&&(t===0n||this.is0()))return Ye;if(Re(t,1n,qt),t===1n)return this;if(this.equals(Be))return Mc(t).p;let s=Ye,i=Be;for(let o=this;t>0n;o=o.double(),t>>=1n)t&1n?s=s.add(o):n&&(i=i.add(o));return s}multiplyUnsafe(t){return this.multiply(t,!1)}toAffine(){const{X:t,Y:n,Z:s}=this;if(this.equals(Ye))return{x:0n,y:1n};const i=yc(s,W);S(s*i)!==1n&&H("invalid inverse");const o=S(t*i),a=S(n*i);return{x:o,y:a}}toBytes(){const{x:t,y:n}=this.assertValidity().toAffine(),s=ka(n);return s[31]|=t&1n?128:0,s}toHex(){return ba(this.toBytes())}clearCofactor(){return this.multiply(Qt(pc),!1)}isSmallOrder(){return this.clearCofactor().is0()}isTorsionFree(){let t=this.multiply(qt/2n,!1).double();return qt%2n&&(t=t.add(this)),t.is0()}}const Be=new X(eo,to,1n,S(eo*to)),Ye=new X(0n,1n,1n,0n);X.BASE=Be;X.ZERO=Ye;const ka=e=>ya(ma(Re(e,0n,ds),Bs)).reverse(),xa=e=>Qt("0x"+ba(va(Ae(e)).reverse())),le=(e,t)=>{let n=e;for(;t-- >0n;)n*=n,n%=W;return n},$c=e=>{const n=e*e%W*e%W,s=le(n,2n)*n%W,i=le(s,1n)*e%W,o=le(i,5n)*i%W,a=le(o,10n)*o%W,l=le(a,20n)*a%W,r=le(l,40n)*l%W,p=le(r,80n)*r%W,d=le(p,80n)*r%W,u=le(d,10n)*o%W;return{pow_p_5_8:le(u,2n)*e%W,b2:n}},so=0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n,kc=(e,t)=>{const n=S(t*t*t),s=S(n*n*t),i=$c(e*s).pow_p_5_8;let o=S(e*n*i);const a=S(t*o*o),l=o,r=S(o*so),p=a===e,d=a===S(-e),u=a===S(-e*so);return p&&(o=l),(d||u)&&(o=r),(S(o)&1n)===1n&&(o=S(-o)),{isValid:p||d,value:o}},us=e=>$a(xa(e)),Fs=(...e)=>Sa.sha512Async(At(...e)),xc=(...e)=>wc("sha512")(At(...e)),Aa=e=>{const t=e.slice(0,De);t[0]&=248,t[31]&=127,t[31]|=64;const n=e.slice(De,Bs),s=us(t),i=Be.multiply(s),o=i.toBytes();return{head:t,prefix:n,scalar:s,point:i,pointBytes:o}},Us=e=>Fs(Ae(e,De)).then(Aa),Ac=e=>Aa(xc(Ae(e,De))),Sc=e=>Us(e).then(t=>t.pointBytes),_c=e=>Fs(e.hashable).then(e.finish),Tc=(e,t,n)=>{const{pointBytes:s,scalar:i}=e,o=us(t),a=Be.multiply(o).toBytes();return{hashable:At(a,s,n),finish:p=>{const d=$a(o+us(p)*i);return Ae(At(a,ka(d)),Bs)}}},Cc=async(e,t)=>{const n=Ae(e),s=await Us(t),i=await Fs(s.prefix,n);return _c(Tc(s,i,n))},Sa={sha512Async:async e=>{const t=mc(),n=At(e);return dn(await t.digest("SHA-512",n.buffer))},sha512:void 0},Ec=(e=bc(De))=>e,Ic={getExtendedPublicKeyAsync:Us,getExtendedPublicKey:Ac,randomSecretKey:Ec},Jt=8,Lc=256,_a=Math.ceil(Lc/Jt)+1,ps=2**(Jt-1),Rc=()=>{const e=[];let t=Be,n=t;for(let s=0;s<_a;s++){n=t,e.push(n);for(let i=1;i{const n=t.negate();return e?n:t},Mc=e=>{const t=io||(io=Rc());let n=Ye,s=Be;const i=2**Jt,o=i,a=Qt(i-1),l=Qt(Jt);for(let r=0;r<_a;r++){let p=Number(e&a);e>>=l,p>ps&&(p-=o,e+=1n);const d=r*ps,u=d,h=d+Math.abs(p)-1,v=r%2!==0,w=p<0;p===0?s=s.add(oo(v,t[u])):n=n.add(oo(w,t[h]))}return e!==0n&&H("invalid wnaf"),{p:n,f:s}},jn="clawdbot-device-identity-v1";function fs(e){let t="";for(const n of e)t+=String.fromCharCode(n);return btoa(t).replaceAll("+","-").replaceAll("/","_").replace(/=+$/g,"")}function Ta(e){const t=e.replaceAll("-","+").replaceAll("_","/"),n=t+"=".repeat((4-t.length%4)%4),s=atob(n),i=new Uint8Array(s.length);for(let o=0;ot.toString(16).padStart(2,"0")).join("")}async function Ca(e){const t=await crypto.subtle.digest("SHA-256",e);return Pc(new Uint8Array(t))}async function Nc(){const e=Ic.randomSecretKey(),t=await Sc(e);return{deviceId:await Ca(t),publicKey:fs(t),privateKey:fs(e)}}async function Ks(){try{const n=localStorage.getItem(jn);if(n){const s=JSON.parse(n);if(s?.version===1&&typeof s.deviceId=="string"&&typeof s.publicKey=="string"&&typeof s.privateKey=="string"){const i=await Ca(Ta(s.publicKey));if(i!==s.deviceId){const o={...s,deviceId:i};return localStorage.setItem(jn,JSON.stringify(o)),{deviceId:i,publicKey:s.publicKey,privateKey:s.privateKey}}return{deviceId:s.deviceId,publicKey:s.publicKey,privateKey:s.privateKey}}}}catch{}const e=await Nc(),t={version:1,deviceId:e.deviceId,publicKey:e.publicKey,privateKey:e.privateKey,createdAtMs:Date.now()};return localStorage.setItem(jn,JSON.stringify(t)),e}async function Oc(e,t){const n=Ta(e),s=new TextEncoder().encode(t),i=await Cc(s,n);return fs(i)}const Ea="clawdbot.device.auth.v1";function Hs(e){return e.trim()}function Dc(e){if(!Array.isArray(e))return[];const t=new Set;for(const n of e){const s=n.trim();s&&t.add(s)}return[...t].sort()}function zs(){try{const e=window.localStorage.getItem(Ea);if(!e)return null;const t=JSON.parse(e);return!t||t.version!==1||!t.deviceId||typeof t.deviceId!="string"||!t.tokens||typeof t.tokens!="object"?null:t}catch{return null}}function Ia(e){try{window.localStorage.setItem(Ea,JSON.stringify(e))}catch{}}function Bc(e){const t=zs();if(!t||t.deviceId!==e.deviceId)return null;const n=Hs(e.role),s=t.tokens[n];return!s||typeof s.token!="string"?null:s}function La(e){const t=Hs(e.role),n={version:1,deviceId:e.deviceId,tokens:{}},s=zs();s&&s.deviceId===e.deviceId&&(n.tokens={...s.tokens});const i={token:e.token,role:t,scopes:Dc(e.scopes),updatedAtMs:Date.now()};return n.tokens[t]=i,Ia(n),i}function Ra(e){const t=zs();if(!t||t.deviceId!==e.deviceId)return;const n=Hs(e.role);if(!t.tokens[n])return;const s={...t,tokens:{...t.tokens}};delete s.tokens[n],Ia(s)}async function Se(e,t){if(!(!e.client||!e.connected)&&!e.devicesLoading){e.devicesLoading=!0,t?.quiet||(e.devicesError=null);try{const n=await e.client.request("device.pair.list",{});e.devicesList={pending:Array.isArray(n?.pending)?n.pending:[],paired:Array.isArray(n?.paired)?n.paired:[]}}catch(n){t?.quiet||(e.devicesError=String(n))}finally{e.devicesLoading=!1}}}async function Fc(e,t){if(!(!e.client||!e.connected))try{await e.client.request("device.pair.approve",{requestId:t}),await Se(e)}catch(n){e.devicesError=String(n)}}async function Uc(e,t){if(!(!e.client||!e.connected||!window.confirm("Reject this device pairing request?")))try{await e.client.request("device.pair.reject",{requestId:t}),await Se(e)}catch(s){e.devicesError=String(s)}}async function Kc(e,t){if(!(!e.client||!e.connected))try{const n=await e.client.request("device.token.rotate",t);if(n?.token){const s=await Ks(),i=n.role??t.role;(n.deviceId===s.deviceId||t.deviceId===s.deviceId)&&La({deviceId:s.deviceId,role:i,token:n.token,scopes:n.scopes??t.scopes??[]}),window.prompt("New device token (copy and store securely):",n.token)}await Se(e)}catch(n){e.devicesError=String(n)}}async function Hc(e,t){if(!(!e.client||!e.connected||!window.confirm(`Revoke token for ${t.deviceId} (${t.role})?`)))try{await e.client.request("device.token.revoke",t);const s=await Ks();t.deviceId===s.deviceId&&Ra({deviceId:s.deviceId,role:t.role}),await Se(e)}catch(s){e.devicesError=String(s)}}async function un(e,t){if(!(!e.client||!e.connected)&&!e.nodesLoading){e.nodesLoading=!0,t?.quiet||(e.lastError=null);try{const n=await e.client.request("node.list",{});e.nodes=Array.isArray(n.nodes)?n.nodes:[]}catch(n){t?.quiet||(e.lastError=String(n))}finally{e.nodesLoading=!1}}}function zc(e){if(!e||e.kind==="gateway")return{method:"exec.approvals.get",params:{}};const t=e.nodeId.trim();return t?{method:"exec.approvals.node.get",params:{nodeId:t}}:null}function jc(e,t){if(!e||e.kind==="gateway")return{method:"exec.approvals.set",params:t};const n=e.nodeId.trim();return n?{method:"exec.approvals.node.set",params:{...t,nodeId:n}}:null}async function js(e,t){if(!(!e.client||!e.connected)&&!e.execApprovalsLoading){e.execApprovalsLoading=!0,e.lastError=null;try{const n=zc(t);if(!n){e.lastError="Select a node before loading exec approvals.";return}const s=await e.client.request(n.method,n.params);qc(e,s)}catch(n){e.lastError=String(n)}finally{e.execApprovalsLoading=!1}}}function qc(e,t){e.execApprovalsSnapshot=t,e.execApprovalsDirty||(e.execApprovalsForm=Oe(t.file??{}))}async function Wc(e,t){if(!(!e.client||!e.connected)){e.execApprovalsSaving=!0,e.lastError=null;try{const n=e.execApprovalsSnapshot?.hash;if(!n){e.lastError="Exec approvals hash missing; reload and retry.";return}const s=e.execApprovalsForm??e.execApprovalsSnapshot?.file??{},i=jc(t,{file:s,baseHash:n});if(!i){e.lastError="Select a node before saving exec approvals.";return}await e.client.request(i.method,i.params),e.execApprovalsDirty=!1,await js(e,t)}catch(n){e.lastError=String(n)}finally{e.execApprovalsSaving=!1}}}function Vc(e,t,n){const s=Oe(e.execApprovalsForm??e.execApprovalsSnapshot?.file??{});ua(s,t,n),e.execApprovalsForm=s,e.execApprovalsDirty=!0}function Gc(e,t){const n=Oe(e.execApprovalsForm??e.execApprovalsSnapshot?.file??{});pa(n,t),e.execApprovalsForm=n,e.execApprovalsDirty=!0}async function qs(e){if(!(!e.client||!e.connected)&&!e.presenceLoading){e.presenceLoading=!0,e.presenceError=null,e.presenceStatus=null;try{const t=await e.client.request("system-presence",{});Array.isArray(t)?(e.presenceEntries=t,e.presenceStatus=t.length===0?"No instances yet.":null):(e.presenceEntries=[],e.presenceStatus="No presence payload.")}catch(t){e.presenceError=String(t)}finally{e.presenceLoading=!1}}}function et(e,t,n){if(!t.trim())return;const s={...e.skillMessages};n?s[t]=n:delete s[t],e.skillMessages=s}function pn(e){return e instanceof Error?e.message:String(e)}async function Tt(e,t){if(t?.clearMessages&&Object.keys(e.skillMessages).length>0&&(e.skillMessages={}),!(!e.client||!e.connected)&&!e.skillsLoading){e.skillsLoading=!0,e.skillsError=null;try{const n=await e.client.request("skills.status",{});n&&(e.skillsReport=n)}catch(n){e.skillsError=pn(n)}finally{e.skillsLoading=!1}}}function Yc(e,t,n){e.skillEdits={...e.skillEdits,[t]:n}}async function Qc(e,t,n){if(!(!e.client||!e.connected)){e.skillsBusyKey=t,e.skillsError=null;try{await e.client.request("skills.update",{skillKey:t,enabled:n}),await Tt(e),et(e,t,{kind:"success",message:n?"Skill enabled":"Skill disabled"})}catch(s){const i=pn(s);e.skillsError=i,et(e,t,{kind:"error",message:i})}finally{e.skillsBusyKey=null}}}async function Jc(e,t){if(!(!e.client||!e.connected)){e.skillsBusyKey=t,e.skillsError=null;try{const n=e.skillEdits[t]??"";await e.client.request("skills.update",{skillKey:t,apiKey:n}),await Tt(e),et(e,t,{kind:"success",message:"API key saved"})}catch(n){const s=pn(n);e.skillsError=s,et(e,t,{kind:"error",message:s})}finally{e.skillsBusyKey=null}}}async function Zc(e,t,n,s){if(!(!e.client||!e.connected)){e.skillsBusyKey=t,e.skillsError=null;try{const i=await e.client.request("skills.install",{name:n,installId:s,timeoutMs:12e4});await Tt(e),et(e,t,{kind:"success",message:i?.message??"Installed"})}catch(i){const o=pn(i);e.skillsError=o,et(e,t,{kind:"error",message:o})}finally{e.skillsBusyKey=null}}}function Xc(){return typeof window>"u"||typeof window.matchMedia!="function"||window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function Ws(e){return e==="system"?Xc():e}const Dt=e=>Number.isNaN(e)?.5:e<=0?0:e>=1?1:e,ed=()=>typeof window>"u"||typeof window.matchMedia!="function"?!1:window.matchMedia("(prefers-reduced-motion: reduce)").matches??!1,Bt=e=>{e.classList.remove("theme-transition"),e.style.removeProperty("--theme-switch-x"),e.style.removeProperty("--theme-switch-y")},td=({nextTheme:e,applyTheme:t,context:n,currentTheme:s})=>{if(s===e)return;const i=globalThis.document??null;if(!i){t();return}const o=i.documentElement,a=i,l=ed();if(!!a.startViewTransition&&!l){let p=.5,d=.5;if(n?.pointerClientX!==void 0&&n?.pointerClientY!==void 0&&typeof window<"u")p=Dt(n.pointerClientX/window.innerWidth),d=Dt(n.pointerClientY/window.innerHeight);else if(n?.element){const u=n.element.getBoundingClientRect();u.width>0&&u.height>0&&typeof window<"u"&&(p=Dt((u.left+u.width/2)/window.innerWidth),d=Dt((u.top+u.height/2)/window.innerHeight))}o.style.setProperty("--theme-switch-x",`${p*100}%`),o.style.setProperty("--theme-switch-y",`${d*100}%`),o.classList.add("theme-transition");try{const u=a.startViewTransition?.(()=>{t()});u?.finished?u.finished.finally(()=>Bt(o)):Bt(o)}catch{Bt(o),t()}return}t(),Bt(o)};function nd(e){e.nodesPollInterval==null&&(e.nodesPollInterval=window.setInterval(()=>{un(e,{quiet:!0})},5e3))}function sd(e){e.nodesPollInterval!=null&&(clearInterval(e.nodesPollInterval),e.nodesPollInterval=null)}function Vs(e){e.logsPollInterval==null&&(e.logsPollInterval=window.setInterval(()=>{e.tab==="logs"&&Ds(e,{quiet:!0})},2e3))}function Gs(e){e.logsPollInterval!=null&&(clearInterval(e.logsPollInterval),e.logsPollInterval=null)}function Ys(e){e.debugPollInterval==null&&(e.debugPollInterval=window.setInterval(()=>{e.tab==="debug"&&cn(e)},3e3))}function Qs(e){e.debugPollInterval!=null&&(clearInterval(e.debugPollInterval),e.debugPollInterval=null)}function $e(e,t){const n={...t,lastActiveSessionKey:t.lastActiveSessionKey?.trim()||t.sessionKey.trim()||"main"};e.settings=n,gl(n),t.theme!==e.theme&&(e.theme=t.theme,fn(e,Ws(t.theme))),e.applySessionKey=e.settings.lastActiveSessionKey}function Ma(e,t){const n=t.trim();n&&e.settings.lastActiveSessionKey!==n&&$e(e,{...e.settings,lastActiveSessionKey:n})}function id(e){if(!window.location.search)return;const t=new URLSearchParams(window.location.search),n=t.get("token"),s=t.get("password"),i=t.get("session"),o=t.get("gatewayUrl");let a=!1;if(n!=null){const r=n.trim();r&&r!==e.settings.token&&$e(e,{...e.settings,token:r}),t.delete("token"),a=!0}if(s!=null){const r=s.trim();r&&(e.password=r),t.delete("password"),a=!0}if(i!=null){const r=i.trim();r&&(e.sessionKey=r,$e(e,{...e.settings,sessionKey:r,lastActiveSessionKey:r}))}if(o!=null){const r=o.trim();r&&r!==e.settings.gatewayUrl&&$e(e,{...e.settings,gatewayUrl:r}),t.delete("gatewayUrl"),a=!0}if(!a)return;const l=new URL(window.location.href);l.search=t.toString(),window.history.replaceState({},"",l.toString())}function od(e,t){e.tab!==t&&(e.tab=t),t==="chat"&&(e.chatHasAutoScrolled=!1),t==="logs"?Vs(e):Gs(e),t==="debug"?Ys(e):Qs(e),Js(e),Na(e,t,!1)}function ad(e,t,n){td({nextTheme:t,applyTheme:()=>{e.theme=t,$e(e,{...e.settings,theme:t}),fn(e,Ws(t))},context:n,currentTheme:e.theme})}async function Js(e){e.tab==="overview"&&await Oa(e),e.tab==="channels"&&await hd(e),e.tab==="instances"&&await qs(e),e.tab==="sessions"&&await nt(e),e.tab==="cron"&&await Zs(e),e.tab==="skills"&&await Tt(e),e.tab==="nodes"&&(await un(e),await Se(e),await me(e),await js(e)),e.tab==="chat"&&(await yd(e),rn(e,!e.chatHasAutoScrolled)),e.tab==="config"&&(await fa(e),await me(e)),e.tab==="debug"&&(await cn(e),e.eventLog=e.eventLogBuffer),e.tab==="logs"&&(e.logsAtBottom=!0,await Ds(e,{reset:!0}),da(e,!0))}function rd(){if(typeof window>"u")return"";const e=window.__CLAWDBOT_CONTROL_UI_BASE_PATH__;return typeof e=="string"&&e.trim()?an(e):ml(window.location.pathname)}function ld(e){e.theme=e.settings.theme??"system",fn(e,Ws(e.theme))}function fn(e,t){if(e.themeResolved=t,typeof document>"u")return;const n=document.documentElement;n.dataset.theme=t,n.style.colorScheme=t}function cd(e){if(typeof window>"u"||typeof window.matchMedia!="function")return;if(e.themeMedia=window.matchMedia("(prefers-color-scheme: dark)"),e.themeMediaHandler=n=>{e.theme==="system"&&fn(e,n.matches?"dark":"light")},typeof e.themeMedia.addEventListener=="function"){e.themeMedia.addEventListener("change",e.themeMediaHandler);return}e.themeMedia.addListener(e.themeMediaHandler)}function dd(e){if(!e.themeMedia||!e.themeMediaHandler)return;if(typeof e.themeMedia.removeEventListener=="function"){e.themeMedia.removeEventListener("change",e.themeMediaHandler);return}e.themeMedia.removeListener(e.themeMediaHandler),e.themeMedia=null,e.themeMediaHandler=null}function ud(e,t){if(typeof window>"u")return;const n=aa(window.location.pathname,e.basePath)??"chat";Pa(e,n),Na(e,n,t)}function pd(e){if(typeof window>"u")return;const t=aa(window.location.pathname,e.basePath);if(!t)return;const s=new URL(window.location.href).searchParams.get("session")?.trim();s&&(e.sessionKey=s,$e(e,{...e.settings,sessionKey:s,lastActiveSessionKey:s})),Pa(e,t)}function Pa(e,t){e.tab!==t&&(e.tab=t),t==="chat"&&(e.chatHasAutoScrolled=!1),t==="logs"?Vs(e):Gs(e),t==="debug"?Ys(e):Qs(e),e.connected&&Js(e)}function Na(e,t,n){if(typeof window>"u")return;const s=kt(Ps(t,e.basePath)),i=kt(window.location.pathname),o=new URL(window.location.href);t==="chat"&&e.sessionKey?o.searchParams.set("session",e.sessionKey):o.searchParams.delete("session"),i!==s&&(o.pathname=s),n?window.history.replaceState({},"",o.toString()):window.history.pushState({},"",o.toString())}function fd(e,t,n){if(typeof window>"u")return;const s=new URL(window.location.href);s.searchParams.set("session",t),window.history.replaceState({},"",s.toString())}async function Oa(e){await Promise.all([oe(e,!1),qs(e),nt(e),_t(e),cn(e)])}async function hd(e){await Promise.all([oe(e,!0),fa(e),me(e)])}async function Zs(e){await Promise.all([oe(e,!1),_t(e),ln(e)])}function Da(e){return e.chatSending||!!e.chatRunId}function gd(e){const t=e.trim();if(!t)return!1;const n=t.toLowerCase();return n==="/stop"?!0:n==="stop"||n==="esc"||n==="abort"||n==="wait"||n==="exit"}async function Ba(e){e.connected&&(e.chatMessage="",await Cl(e))}function vd(e,t){const n=t.trim();n&&(e.chatQueue=[...e.chatQueue,{id:Ns(),text:n,createdAt:Date.now()}])}async function Fa(e,t,n){Os(e);const s=await Tl(e,t);return!s&&n?.previousDraft!=null&&(e.chatMessage=n.previousDraft),s&&Ma(e,e.sessionKey),s&&n?.restoreDraft&&n.previousDraft?.trim()&&(e.chatMessage=n.previousDraft),rn(e),s&&!e.chatRunId&&Ua(e),s}async function Ua(e){if(!e.connected||Da(e))return;const[t,...n]=e.chatQueue;if(!t)return;e.chatQueue=n,await Fa(e,t.text)||(e.chatQueue=[t,...e.chatQueue])}function md(e,t){e.chatQueue=e.chatQueue.filter(n=>n.id!==t)}async function bd(e,t,n){if(!e.connected)return;const s=e.chatMessage,i=(t??e.chatMessage).trim();if(i){if(gd(i)){await Ba(e);return}if(t==null&&(e.chatMessage=""),Da(e)){vd(e,i);return}await Fa(e,i,{previousDraft:t==null?s:void 0,restoreDraft:!!(t&&n?.restoreDraft)})}}async function yd(e){await Promise.all([Ze(e),nt(e),hs(e)]),rn(e,!0)}const wd=Ua;function $d(e){const t=sa(e.sessionKey);return t?.agentId?t.agentId:e.hello?.snapshot?.sessionDefaults?.defaultAgentId?.trim()||"main"}function kd(e,t){const n=an(e),s=encodeURIComponent(t);return n?`${n}/avatar/${s}?meta=1`:`/avatar/${s}?meta=1`}async function hs(e){if(!e.connected){e.chatAvatarUrl=null;return}const t=$d(e);if(!t){e.chatAvatarUrl=null;return}e.chatAvatarUrl=null;const n=kd(e.basePath,t);try{const s=await fetch(n,{method:"GET"});if(!s.ok){e.chatAvatarUrl=null;return}const i=await s.json(),o=typeof i.avatarUrl=="string"?i.avatarUrl.trim():"";e.chatAvatarUrl=o||null}catch{e.chatAvatarUrl=null}}const Ka={CHILD:2},Ha=e=>(...t)=>({_$litDirective$:e,values:t});let za=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,n,s){this._$Ct=t,this._$AM=n,this._$Ci=s}_$AS(t,n){return this.update(t,n)}update(t,n){return this.render(...n)}};const{I:xd}=il,ao=e=>e,ro=()=>document.createComment(""),rt=(e,t,n)=>{const s=e._$AA.parentNode,i=t===void 0?e._$AB:t._$AA;if(n===void 0){const o=s.insertBefore(ro(),i),a=s.insertBefore(ro(),i);n=new xd(o,a,e,e.options)}else{const o=n._$AB.nextSibling,a=n._$AM,l=a!==e;if(l){let r;n._$AQ?.(e),n._$AM=e,n._$AP!==void 0&&(r=e._$AU)!==a._$AU&&n._$AP(r)}if(o!==i||l){let r=n._$AA;for(;r!==o;){const p=ao(r).nextSibling;ao(s).insertBefore(r,i),r=p}}}return n},Ie=(e,t,n=e)=>(e._$AI(t,n),e),Ad={},Sd=(e,t=Ad)=>e._$AH=t,_d=e=>e._$AH,qn=e=>{e._$AR(),e._$AA.remove()};const lo=(e,t,n)=>{const s=new Map;for(let i=t;i<=n;i++)s.set(e[i],i);return s},ja=Ha(class extends za{constructor(e){if(super(e),e.type!==Ka.CHILD)throw Error("repeat() can only be used in text expressions")}dt(e,t,n){let s;n===void 0?n=t:t!==void 0&&(s=t);const i=[],o=[];let a=0;for(const l of e)i[a]=s?s(l,a):a,o[a]=n(l,a),a++;return{values:o,keys:i}}render(e,t,n){return this.dt(e,t,n).values}update(e,[t,n,s]){const i=_d(e),{values:o,keys:a}=this.dt(t,n,s);if(!Array.isArray(i))return this.ut=a,o;const l=this.ut??=[],r=[];let p,d,u=0,h=i.length-1,v=0,w=o.length-1;for(;u<=h&&v<=w;)if(i[u]===null)u++;else if(i[h]===null)h--;else if(l[u]===a[v])r[v]=Ie(i[u],o[v]),u++,v++;else if(l[h]===a[w])r[w]=Ie(i[h],o[w]),h--,w--;else if(l[u]===a[w])r[w]=Ie(i[u],o[w]),rt(e,r[w+1],i[u]),u++,w--;else if(l[h]===a[v])r[v]=Ie(i[h],o[v]),rt(e,i[u],i[h]),h--,v++;else if(p===void 0&&(p=lo(a,v,w),d=lo(l,u,h)),p.has(l[u]))if(p.has(l[h])){const $=d.get(a[v]),x=$!==void 0?i[$]:null;if(x===null){const C=rt(e,i[u]);Ie(C,o[v]),r[v]=C}else r[v]=Ie(x,o[v]),rt(e,i[u],x),i[$]=null;v++}else qn(i[h]),h--;else qn(i[u]),u++;for(;v<=w;){const $=rt(e,r[w+1]);Ie($,o[v]),r[v++]=$}for(;u<=h;){const $=i[u++];$!==null&&qn($)}return this.ut=a,Sd(e,r),xe}});function qa(e){const t=e;let n=typeof t.role=="string"?t.role:"unknown";const s=typeof t.toolCallId=="string"||typeof t.tool_call_id=="string",i=t.content,o=Array.isArray(i)?i:null,a=Array.isArray(o)&&o.some(u=>{const v=String(u.type??"").toLowerCase();return v==="toolresult"||v==="tool_result"}),l=typeof t.toolName=="string"||typeof t.tool_name=="string";(s||a||l)&&(n="toolResult");let r=[];typeof t.content=="string"?r=[{type:"text",text:t.content}]:Array.isArray(t.content)?r=t.content.map(u=>({type:u.type||"text",text:u.text,name:u.name,args:u.args||u.arguments})):typeof t.text=="string"&&(r=[{type:"text",text:t.text}]);const p=typeof t.timestamp=="number"?t.timestamp:Date.now(),d=typeof t.id=="string"?t.id:void 0;return{role:n,content:r,timestamp:p,id:d}}function Xs(e){const t=e.toLowerCase();return e==="user"||e==="User"?e:e==="assistant"?"assistant":e==="system"?"system":t==="toolresult"||t==="tool_result"||t==="tool"||t==="function"?"tool":e}function Wa(e){const t=e,n=typeof t.role=="string"?t.role.toLowerCase():"";return n==="toolresult"||n==="tool_result"}class gs extends za{constructor(t){if(super(t),this.it=g,t.type!==Ka.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(t){if(t===g||t==null)return this._t=void 0,this.it=t;if(t===xe)return t;if(typeof t!="string")throw Error(this.constructor.directiveName+"() called with a non-string value");if(t===this.it)return this._t;this.it=t;const n=[t];return n.raw=n,this._t={_$litType$:this.constructor.resultType,strings:n,values:[]}}}gs.directiveName="unsafeHTML",gs.resultType=1;const vs=Ha(gs);const{entries:Va,setPrototypeOf:co,isFrozen:Td,getPrototypeOf:Cd,getOwnPropertyDescriptor:Ed}=Object;let{freeze:Q,seal:te,create:ms}=Object,{apply:bs,construct:ys}=typeof Reflect<"u"&&Reflect;Q||(Q=function(t){return t});te||(te=function(t){return t});bs||(bs=function(t,n){for(var s=arguments.length,i=new Array(s>2?s-2:0),o=2;o1?n-1:0),i=1;i1?n-1:0),i=1;i2&&arguments[2]!==void 0?arguments[2]:Wt;co&&co(e,null);let s=t.length;for(;s--;){let i=t[s];if(typeof i=="string"){const o=n(i);o!==i&&(Td(t)||(t[s]=o),i=o)}e[i]=!0}return e}function Nd(e){for(let t=0;t/gm),Ud=te(/\$\{[\w\W]*/gm),Kd=te(/^data-[\-\w.\u00B7-\uFFFF]+$/),Hd=te(/^aria-[\-\w]+$/),Ga=te(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),zd=te(/^(?:\w+script|data):/i),jd=te(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Ya=te(/^html$/i),qd=te(/^[a-z][.\w]*(-[.\w]+)+$/i);var vo=Object.freeze({__proto__:null,ARIA_ATTR:Hd,ATTR_WHITESPACE:jd,CUSTOM_ELEMENT:qd,DATA_ATTR:Kd,DOCTYPE_NAME:Ya,ERB_EXPR:Fd,IS_ALLOWED_URI:Ga,IS_SCRIPT_OR_DATA:zd,MUSTACHE_EXPR:Bd,TMPLIT_EXPR:Ud});const pt={element:1,text:3,progressingInstruction:7,comment:8,document:9},Wd=function(){return typeof window>"u"?null:window},Vd=function(t,n){if(typeof t!="object"||typeof t.createPolicy!="function")return null;let s=null;const i="data-tt-policy-suffix";n&&n.hasAttribute(i)&&(s=n.getAttribute(i));const o="dompurify"+(s?"#"+s:"");try{return t.createPolicy(o,{createHTML(a){return a},createScriptURL(a){return a}})}catch{return console.warn("TrustedTypes policy "+o+" could not be created."),null}},mo=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}};function Qa(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:Wd();const t=T=>Qa(T);if(t.version="3.3.1",t.removed=[],!e||!e.document||e.document.nodeType!==pt.document||!e.Element)return t.isSupported=!1,t;let{document:n}=e;const s=n,i=s.currentScript,{DocumentFragment:o,HTMLTemplateElement:a,Node:l,Element:r,NodeFilter:p,NamedNodeMap:d=e.NamedNodeMap||e.MozNamedAttrMap,HTMLFormElement:u,DOMParser:h,trustedTypes:v}=e,w=r.prototype,$=ut(w,"cloneNode"),x=ut(w,"remove"),C=ut(w,"nextSibling"),I=ut(w,"childNodes"),R=ut(w,"parentNode");if(typeof a=="function"){const T=n.createElement("template");T.content&&T.content.ownerDocument&&(n=T.content.ownerDocument)}let E,A="";const{implementation:B,createNodeIterator:ue,createDocumentFragment:bn,getElementsByTagName:yn}=n,{importNode:Sr}=s;let V=mo();t.isSupported=typeof Va=="function"&&typeof R=="function"&&B&&B.createHTMLDocument!==void 0;const{MUSTACHE_EXPR:wn,ERB_EXPR:$n,TMPLIT_EXPR:kn,DATA_ATTR:_r,ARIA_ATTR:Tr,IS_SCRIPT_OR_DATA:Cr,ATTR_WHITESPACE:ui,CUSTOM_ELEMENT:Er}=vo;let{IS_ALLOWED_URI:pi}=vo,K=null;const fi=L({},[...po,...Gn,...Yn,...Qn,...fo]);let z=null;const hi=L({},[...ho,...Jn,...go,...Ut]);let D=Object.seal(ms(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),st=null,xn=null;const Ke=Object.seal(ms(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let gi=!0,An=!0,vi=!1,mi=!0,He=!1,Et=!0,Te=!1,Sn=!1,_n=!1,ze=!1,It=!1,Lt=!1,bi=!0,yi=!1;const Ir="user-content-";let Tn=!0,it=!1,je={},ae=null;const Cn=L({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let wi=null;const $i=L({},["audio","video","img","source","image","track"]);let En=null;const ki=L({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Rt="http://www.w3.org/1998/Math/MathML",Mt="http://www.w3.org/2000/svg",pe="http://www.w3.org/1999/xhtml";let qe=pe,In=!1,Ln=null;const Lr=L({},[Rt,Mt,pe],Wn);let Pt=L({},["mi","mo","mn","ms","mtext"]),Nt=L({},["annotation-xml"]);const Rr=L({},["title","style","font","a","script"]);let ot=null;const Mr=["application/xhtml+xml","text/html"],Pr="text/html";let U=null,We=null;const Nr=n.createElement("form"),xi=function(f){return f instanceof RegExp||f instanceof Function},Rn=function(){let f=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!(We&&We===f)){if((!f||typeof f!="object")&&(f={}),f=ce(f),ot=Mr.indexOf(f.PARSER_MEDIA_TYPE)===-1?Pr:f.PARSER_MEDIA_TYPE,U=ot==="application/xhtml+xml"?Wn:Wt,K=ne(f,"ALLOWED_TAGS")?L({},f.ALLOWED_TAGS,U):fi,z=ne(f,"ALLOWED_ATTR")?L({},f.ALLOWED_ATTR,U):hi,Ln=ne(f,"ALLOWED_NAMESPACES")?L({},f.ALLOWED_NAMESPACES,Wn):Lr,En=ne(f,"ADD_URI_SAFE_ATTR")?L(ce(ki),f.ADD_URI_SAFE_ATTR,U):ki,wi=ne(f,"ADD_DATA_URI_TAGS")?L(ce($i),f.ADD_DATA_URI_TAGS,U):$i,ae=ne(f,"FORBID_CONTENTS")?L({},f.FORBID_CONTENTS,U):Cn,st=ne(f,"FORBID_TAGS")?L({},f.FORBID_TAGS,U):ce({}),xn=ne(f,"FORBID_ATTR")?L({},f.FORBID_ATTR,U):ce({}),je=ne(f,"USE_PROFILES")?f.USE_PROFILES:!1,gi=f.ALLOW_ARIA_ATTR!==!1,An=f.ALLOW_DATA_ATTR!==!1,vi=f.ALLOW_UNKNOWN_PROTOCOLS||!1,mi=f.ALLOW_SELF_CLOSE_IN_ATTR!==!1,He=f.SAFE_FOR_TEMPLATES||!1,Et=f.SAFE_FOR_XML!==!1,Te=f.WHOLE_DOCUMENT||!1,ze=f.RETURN_DOM||!1,It=f.RETURN_DOM_FRAGMENT||!1,Lt=f.RETURN_TRUSTED_TYPE||!1,_n=f.FORCE_BODY||!1,bi=f.SANITIZE_DOM!==!1,yi=f.SANITIZE_NAMED_PROPS||!1,Tn=f.KEEP_CONTENT!==!1,it=f.IN_PLACE||!1,pi=f.ALLOWED_URI_REGEXP||Ga,qe=f.NAMESPACE||pe,Pt=f.MATHML_TEXT_INTEGRATION_POINTS||Pt,Nt=f.HTML_INTEGRATION_POINTS||Nt,D=f.CUSTOM_ELEMENT_HANDLING||{},f.CUSTOM_ELEMENT_HANDLING&&xi(f.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(D.tagNameCheck=f.CUSTOM_ELEMENT_HANDLING.tagNameCheck),f.CUSTOM_ELEMENT_HANDLING&&xi(f.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(D.attributeNameCheck=f.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),f.CUSTOM_ELEMENT_HANDLING&&typeof f.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(D.allowCustomizedBuiltInElements=f.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),He&&(An=!1),It&&(ze=!0),je&&(K=L({},fo),z=[],je.html===!0&&(L(K,po),L(z,ho)),je.svg===!0&&(L(K,Gn),L(z,Jn),L(z,Ut)),je.svgFilters===!0&&(L(K,Yn),L(z,Jn),L(z,Ut)),je.mathMl===!0&&(L(K,Qn),L(z,go),L(z,Ut))),f.ADD_TAGS&&(typeof f.ADD_TAGS=="function"?Ke.tagCheck=f.ADD_TAGS:(K===fi&&(K=ce(K)),L(K,f.ADD_TAGS,U))),f.ADD_ATTR&&(typeof f.ADD_ATTR=="function"?Ke.attributeCheck=f.ADD_ATTR:(z===hi&&(z=ce(z)),L(z,f.ADD_ATTR,U))),f.ADD_URI_SAFE_ATTR&&L(En,f.ADD_URI_SAFE_ATTR,U),f.FORBID_CONTENTS&&(ae===Cn&&(ae=ce(ae)),L(ae,f.FORBID_CONTENTS,U)),f.ADD_FORBID_CONTENTS&&(ae===Cn&&(ae=ce(ae)),L(ae,f.ADD_FORBID_CONTENTS,U)),Tn&&(K["#text"]=!0),Te&&L(K,["html","head","body"]),K.table&&(L(K,["tbody"]),delete st.tbody),f.TRUSTED_TYPES_POLICY){if(typeof f.TRUSTED_TYPES_POLICY.createHTML!="function")throw dt('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof f.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw dt('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');E=f.TRUSTED_TYPES_POLICY,A=E.createHTML("")}else E===void 0&&(E=Vd(v,i)),E!==null&&typeof A=="string"&&(A=E.createHTML(""));Q&&Q(f),We=f}},Ai=L({},[...Gn,...Yn,...Od]),Si=L({},[...Qn,...Dd]),Or=function(f){let k=R(f);(!k||!k.tagName)&&(k={namespaceURI:qe,tagName:"template"});const _=Wt(f.tagName),N=Wt(k.tagName);return Ln[f.namespaceURI]?f.namespaceURI===Mt?k.namespaceURI===pe?_==="svg":k.namespaceURI===Rt?_==="svg"&&(N==="annotation-xml"||Pt[N]):!!Ai[_]:f.namespaceURI===Rt?k.namespaceURI===pe?_==="math":k.namespaceURI===Mt?_==="math"&&Nt[N]:!!Si[_]:f.namespaceURI===pe?k.namespaceURI===Mt&&!Nt[N]||k.namespaceURI===Rt&&!Pt[N]?!1:!Si[_]&&(Rr[_]||!Ai[_]):!!(ot==="application/xhtml+xml"&&Ln[f.namespaceURI]):!1},re=function(f){lt(t.removed,{element:f});try{R(f).removeChild(f)}catch{x(f)}},Ce=function(f,k){try{lt(t.removed,{attribute:k.getAttributeNode(f),from:k})}catch{lt(t.removed,{attribute:null,from:k})}if(k.removeAttribute(f),f==="is")if(ze||It)try{re(k)}catch{}else try{k.setAttribute(f,"")}catch{}},_i=function(f){let k=null,_=null;if(_n)f=" "+f;else{const F=Vn(f,/^[\r\n\t ]+/);_=F&&F[0]}ot==="application/xhtml+xml"&&qe===pe&&(f=''+f+"");const N=E?E.createHTML(f):f;if(qe===pe)try{k=new h().parseFromString(N,ot)}catch{}if(!k||!k.documentElement){k=B.createDocument(qe,"template",null);try{k.documentElement.innerHTML=In?A:N}catch{}}const q=k.body||k.documentElement;return f&&_&&q.insertBefore(n.createTextNode(_),q.childNodes[0]||null),qe===pe?yn.call(k,Te?"html":"body")[0]:Te?k.documentElement:q},Ti=function(f){return ue.call(f.ownerDocument||f,f,p.SHOW_ELEMENT|p.SHOW_COMMENT|p.SHOW_TEXT|p.SHOW_PROCESSING_INSTRUCTION|p.SHOW_CDATA_SECTION,null)},Mn=function(f){return f instanceof u&&(typeof f.nodeName!="string"||typeof f.textContent!="string"||typeof f.removeChild!="function"||!(f.attributes instanceof d)||typeof f.removeAttribute!="function"||typeof f.setAttribute!="function"||typeof f.namespaceURI!="string"||typeof f.insertBefore!="function"||typeof f.hasChildNodes!="function")},Ci=function(f){return typeof l=="function"&&f instanceof l};function fe(T,f,k){Ft(T,_=>{_.call(t,f,k,We)})}const Ei=function(f){let k=null;if(fe(V.beforeSanitizeElements,f,null),Mn(f))return re(f),!0;const _=U(f.nodeName);if(fe(V.uponSanitizeElement,f,{tagName:_,allowedTags:K}),Et&&f.hasChildNodes()&&!Ci(f.firstElementChild)&&G(/<[/\w!]/g,f.innerHTML)&&G(/<[/\w!]/g,f.textContent)||f.nodeType===pt.progressingInstruction||Et&&f.nodeType===pt.comment&&G(/<[/\w]/g,f.data))return re(f),!0;if(!(Ke.tagCheck instanceof Function&&Ke.tagCheck(_))&&(!K[_]||st[_])){if(!st[_]&&Li(_)&&(D.tagNameCheck instanceof RegExp&&G(D.tagNameCheck,_)||D.tagNameCheck instanceof Function&&D.tagNameCheck(_)))return!1;if(Tn&&!ae[_]){const N=R(f)||f.parentNode,q=I(f)||f.childNodes;if(q&&N){const F=q.length;for(let Z=F-1;Z>=0;--Z){const he=$(q[Z],!0);he.__removalCount=(f.__removalCount||0)+1,N.insertBefore(he,C(f))}}}return re(f),!0}return f instanceof r&&!Or(f)||(_==="noscript"||_==="noembed"||_==="noframes")&&G(/<\/no(script|embed|frames)/i,f.innerHTML)?(re(f),!0):(He&&f.nodeType===pt.text&&(k=f.textContent,Ft([wn,$n,kn],N=>{k=ct(k,N," ")}),f.textContent!==k&&(lt(t.removed,{element:f.cloneNode()}),f.textContent=k)),fe(V.afterSanitizeElements,f,null),!1)},Ii=function(f,k,_){if(bi&&(k==="id"||k==="name")&&(_ in n||_ in Nr))return!1;if(!(An&&!xn[k]&&G(_r,k))){if(!(gi&&G(Tr,k))){if(!(Ke.attributeCheck instanceof Function&&Ke.attributeCheck(k,f))){if(!z[k]||xn[k]){if(!(Li(f)&&(D.tagNameCheck instanceof RegExp&&G(D.tagNameCheck,f)||D.tagNameCheck instanceof Function&&D.tagNameCheck(f))&&(D.attributeNameCheck instanceof RegExp&&G(D.attributeNameCheck,k)||D.attributeNameCheck instanceof Function&&D.attributeNameCheck(k,f))||k==="is"&&D.allowCustomizedBuiltInElements&&(D.tagNameCheck instanceof RegExp&&G(D.tagNameCheck,_)||D.tagNameCheck instanceof Function&&D.tagNameCheck(_))))return!1}else if(!En[k]){if(!G(pi,ct(_,ui,""))){if(!((k==="src"||k==="xlink:href"||k==="href")&&f!=="script"&&Rd(_,"data:")===0&&wi[f])){if(!(vi&&!G(Cr,ct(_,ui,"")))){if(_)return!1}}}}}}}return!0},Li=function(f){return f!=="annotation-xml"&&Vn(f,Er)},Ri=function(f){fe(V.beforeSanitizeAttributes,f,null);const{attributes:k}=f;if(!k||Mn(f))return;const _={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:z,forceKeepAttr:void 0};let N=k.length;for(;N--;){const q=k[N],{name:F,namespaceURI:Z,value:he}=q,Ve=U(F),Pn=he;let j=F==="value"?Pn:Md(Pn);if(_.attrName=Ve,_.attrValue=j,_.keepAttr=!0,_.forceKeepAttr=void 0,fe(V.uponSanitizeAttribute,f,_),j=_.attrValue,yi&&(Ve==="id"||Ve==="name")&&(Ce(F,f),j=Ir+j),Et&&G(/((--!?|])>)|<\/(style|title|textarea)/i,j)){Ce(F,f);continue}if(Ve==="attributename"&&Vn(j,"href")){Ce(F,f);continue}if(_.forceKeepAttr)continue;if(!_.keepAttr){Ce(F,f);continue}if(!mi&&G(/\/>/i,j)){Ce(F,f);continue}He&&Ft([wn,$n,kn],Pi=>{j=ct(j,Pi," ")});const Mi=U(f.nodeName);if(!Ii(Mi,Ve,j)){Ce(F,f);continue}if(E&&typeof v=="object"&&typeof v.getAttributeType=="function"&&!Z)switch(v.getAttributeType(Mi,Ve)){case"TrustedHTML":{j=E.createHTML(j);break}case"TrustedScriptURL":{j=E.createScriptURL(j);break}}if(j!==Pn)try{Z?f.setAttributeNS(Z,F,j):f.setAttribute(F,j),Mn(f)?re(f):uo(t.removed)}catch{Ce(F,f)}}fe(V.afterSanitizeAttributes,f,null)},Dr=function T(f){let k=null;const _=Ti(f);for(fe(V.beforeSanitizeShadowDOM,f,null);k=_.nextNode();)fe(V.uponSanitizeShadowNode,k,null),Ei(k),Ri(k),k.content instanceof o&&T(k.content);fe(V.afterSanitizeShadowDOM,f,null)};return t.sanitize=function(T){let f=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},k=null,_=null,N=null,q=null;if(In=!T,In&&(T=""),typeof T!="string"&&!Ci(T))if(typeof T.toString=="function"){if(T=T.toString(),typeof T!="string")throw dt("dirty is not a string, aborting")}else throw dt("toString is not a function");if(!t.isSupported)return T;if(Sn||Rn(f),t.removed=[],typeof T=="string"&&(it=!1),it){if(T.nodeName){const he=U(T.nodeName);if(!K[he]||st[he])throw dt("root node is forbidden and cannot be sanitized in-place")}}else if(T instanceof l)k=_i(""),_=k.ownerDocument.importNode(T,!0),_.nodeType===pt.element&&_.nodeName==="BODY"||_.nodeName==="HTML"?k=_:k.appendChild(_);else{if(!ze&&!He&&!Te&&T.indexOf("<")===-1)return E&&Lt?E.createHTML(T):T;if(k=_i(T),!k)return ze?null:Lt?A:""}k&&_n&&re(k.firstChild);const F=Ti(it?T:k);for(;N=F.nextNode();)Ei(N),Ri(N),N.content instanceof o&&Dr(N.content);if(it)return T;if(ze){if(It)for(q=bn.call(k.ownerDocument);k.firstChild;)q.appendChild(k.firstChild);else q=k;return(z.shadowroot||z.shadowrootmode)&&(q=Sr.call(s,q,!0)),q}let Z=Te?k.outerHTML:k.innerHTML;return Te&&K["!doctype"]&&k.ownerDocument&&k.ownerDocument.doctype&&k.ownerDocument.doctype.name&&G(Ya,k.ownerDocument.doctype.name)&&(Z="
+`+Z),He&&Ft([wn,$n,kn],he=>{Z=ct(Z,he," ")}),E&&Lt?E.createHTML(Z):Z},t.setConfig=function(){let T=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};Rn(T),Sn=!0},t.clearConfig=function(){We=null,Sn=!1},t.isValidAttribute=function(T,f,k){We||Rn({});const _=U(T),N=U(f);return Ii(_,N,k)},t.addHook=function(T,f){typeof f=="function"&<(V[T],f)},t.removeHook=function(T,f){if(f!==void 0){const k=Id(V[T],f);return k===-1?void 0:Ld(V[T],k,1)[0]}return uo(V[T])},t.removeHooks=function(T){V[T]=[]},t.removeAllHooks=function(){V=mo()},t}var ws=Qa();function ei(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var Ue=ei();function Ja(e){Ue=e}var bt={exec:()=>null};function M(e,t=""){let n=typeof e=="string"?e:e.source,s={replace:(i,o)=>{let a=typeof o=="string"?o:o.source;return a=a.replace(Y.caret,"$1"),n=n.replace(i,a),s},getRegex:()=>new RegExp(n,t)};return s}var Gd=(()=>{try{return!!new RegExp("(?<=1)(?/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] +\S/,listReplaceTask:/^\[[ xX]\] +/,listTaskCheckbox:/\[[ xX]\]/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},Yd=/^(?:[ \t]*(?:\n|$))+/,Qd=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Jd=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,Ct=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,Zd=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,ti=/(?:[*+-]|\d{1,9}[.)])/,Za=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,Xa=M(Za).replace(/bull/g,ti).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Xd=M(Za).replace(/bull/g,ti).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),ni=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,eu=/^[^\n]+/,si=/(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/,tu=M(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",si).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),nu=M(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,ti).getRegex(),hn="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",ii=/|$))/,su=M("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",ii).replace("tag",hn).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),er=M(ni).replace("hr",Ct).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",hn).getRegex(),iu=M(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",er).getRegex(),oi={blockquote:iu,code:Qd,def:tu,fences:Jd,heading:Zd,hr:Ct,html:su,lheading:Xa,list:nu,newline:Yd,paragraph:er,table:bt,text:eu},bo=M("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",Ct).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",hn).getRegex(),ou={...oi,lheading:Xd,table:bo,paragraph:M(ni).replace("hr",Ct).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",bo).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",hn).getRegex()},au={...oi,html:M(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)| \\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",ii).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:bt,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:M(ni).replace("hr",Ct).replace("heading",` *#{1,6} *[^
+]`).replace("lheading",Xa).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},ru=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,lu=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,tr=/^( {2,}|\\)\n(?!\s*$)/,cu=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`+)[^`]+\k(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-",Gd?"(?`+)[^`]+\k(?!`)/).replace("html",/<(?! )[^<>]*?>/).getRegex(),ir=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,hu=M(ir,"u").replace(/punct/g,gn).getRegex(),gu=M(ir,"u").replace(/punct/g,sr).getRegex(),or="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",vu=M(or,"gu").replace(/notPunctSpace/g,nr).replace(/punctSpace/g,ai).replace(/punct/g,gn).getRegex(),mu=M(or,"gu").replace(/notPunctSpace/g,pu).replace(/punctSpace/g,uu).replace(/punct/g,sr).getRegex(),bu=M("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,nr).replace(/punctSpace/g,ai).replace(/punct/g,gn).getRegex(),yu=M(/\\(punct)/,"gu").replace(/punct/g,gn).getRegex(),wu=M(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),$u=M(ii).replace("(?:-->|$)","-->").getRegex(),ku=M("^comment|^[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",$u).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),Zt=/(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+[^`]*?`+(?!`)|[^\[\]\\`])*?/,xu=M(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",Zt).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),ar=M(/^!?\[(label)\]\[(ref)\]/).replace("label",Zt).replace("ref",si).getRegex(),rr=M(/^!?\[(ref)\](?:\[\])?/).replace("ref",si).getRegex(),Au=M("reflink|nolink(?!\\()","g").replace("reflink",ar).replace("nolink",rr).getRegex(),yo=/[hH][tT][tT][pP][sS]?|[fF][tT][pP]/,ri={_backpedal:bt,anyPunctuation:yu,autolink:wu,blockSkip:fu,br:tr,code:lu,del:bt,emStrongLDelim:hu,emStrongRDelimAst:vu,emStrongRDelimUnd:bu,escape:ru,link:xu,nolink:rr,punctuation:du,reflink:ar,reflinkSearch:Au,tag:ku,text:cu,url:bt},Su={...ri,link:M(/^!?\[(label)\]\((.*?)\)/).replace("label",Zt).getRegex(),reflink:M(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Zt).getRegex()},$s={...ri,emStrongRDelimAst:mu,emStrongLDelim:gu,url:M(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol",yo).replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,text:M(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},wo=e=>Tu[e];function ve(e,t){if(t){if(Y.escapeTest.test(e))return e.replace(Y.escapeReplace,wo)}else if(Y.escapeTestNoEncode.test(e))return e.replace(Y.escapeReplaceNoEncode,wo);return e}function $o(e){try{e=encodeURI(e).replace(Y.percentDecode,"%")}catch{return null}return e}function ko(e,t){let n=e.replace(Y.findPipe,(o,a,l)=>{let r=!1,p=a;for(;--p>=0&&l[p]==="\\";)r=!r;return r?"|":" |"}),s=n.split(Y.splitPipe),i=0;if(s[0].trim()||s.shift(),s.length>0&&!s.at(-1)?.trim()&&s.pop(),t)if(s.length>t)s.splice(t);else for(;s.length0?-2:-1}function xo(e,t,n,s,i){let o=t.href,a=t.title||null,l=e[1].replace(i.other.outputLinkReplace,"$1");s.state.inLink=!0;let r={type:e[0].charAt(0)==="!"?"image":"link",raw:n,href:o,title:a,text:l,tokens:s.inlineTokens(l)};return s.state.inLink=!1,r}function Eu(e,t,n){let s=e.match(n.other.indentCodeCompensation);if(s===null)return t;let i=s[1];return t.split(`
+`).map(o=>{let a=o.match(n.other.beginningSpace);if(a===null)return o;let[l]=a;return l.length>=i.length?o.slice(i.length):o}).join(`
+`)}var Xt=class{options;rules;lexer;constructor(e){this.options=e||Ue}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:ht(n,`
+`)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],s=Eu(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:s}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let s=ht(n,"#");(this.options.pedantic||!s||this.rules.other.endingSpaceChar.test(s))&&(n=s.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:ht(t[0],`
+`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=ht(t[0],`
+`).split(`
+`),s="",i="",o=[];for(;n.length>0;){let a=!1,l=[],r;for(r=0;r1,i={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");let o=this.rules.other.listItemRegex(n),a=!1;for(;e;){let r=!1,p="",d="";if(!(t=o.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let u=t[2].split(`
+`,1)[0].replace(this.rules.other.listReplaceTabs,$=>" ".repeat(3*$.length)),h=e.split(`
+`,1)[0],v=!u.trim(),w=0;if(this.options.pedantic?(w=2,d=u.trimStart()):v?w=t[1].length+1:(w=t[2].search(this.rules.other.nonSpaceChar),w=w>4?1:w,d=u.slice(w),w+=t[1].length),v&&this.rules.other.blankLine.test(h)&&(p+=h+`
+`,e=e.substring(h.length+1),r=!0),!r){let $=this.rules.other.nextBulletRegex(w),x=this.rules.other.hrRegex(w),C=this.rules.other.fencesBeginRegex(w),I=this.rules.other.headingBeginRegex(w),R=this.rules.other.htmlBeginRegex(w);for(;e;){let E=e.split(`
+`,1)[0],A;if(h=E,this.options.pedantic?(h=h.replace(this.rules.other.listReplaceNesting," "),A=h):A=h.replace(this.rules.other.tabCharGlobal," "),C.test(h)||I.test(h)||R.test(h)||$.test(h)||x.test(h))break;if(A.search(this.rules.other.nonSpaceChar)>=w||!h.trim())d+=`
+`+A.slice(w);else{if(v||u.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||C.test(u)||I.test(u)||x.test(u))break;d+=`
+`+h}!v&&!h.trim()&&(v=!0),p+=E+`
+`,e=e.substring(E.length+1),u=A.slice(w)}}i.loose||(a?i.loose=!0:this.rules.other.doubleBlankLine.test(p)&&(a=!0)),i.items.push({type:"list_item",raw:p,task:!!this.options.gfm&&this.rules.other.listIsTask.test(d),loose:!1,text:d,tokens:[]}),i.raw+=p}let l=i.items.at(-1);if(l)l.raw=l.raw.trimEnd(),l.text=l.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let r of i.items){if(this.lexer.state.top=!1,r.tokens=this.lexer.blockTokens(r.text,[]),r.task){if(r.text=r.text.replace(this.rules.other.listReplaceTask,""),r.tokens[0]?.type==="text"||r.tokens[0]?.type==="paragraph"){r.tokens[0].raw=r.tokens[0].raw.replace(this.rules.other.listReplaceTask,""),r.tokens[0].text=r.tokens[0].text.replace(this.rules.other.listReplaceTask,"");for(let d=this.lexer.inlineQueue.length-1;d>=0;d--)if(this.rules.other.listIsTask.test(this.lexer.inlineQueue[d].src)){this.lexer.inlineQueue[d].src=this.lexer.inlineQueue[d].src.replace(this.rules.other.listReplaceTask,"");break}}let p=this.rules.other.listTaskCheckbox.exec(r.raw);if(p){let d={type:"checkbox",raw:p[0]+" ",checked:p[0]!=="[ ]"};r.checked=d.checked,i.loose?r.tokens[0]&&["paragraph","text"].includes(r.tokens[0].type)&&"tokens"in r.tokens[0]&&r.tokens[0].tokens?(r.tokens[0].raw=d.raw+r.tokens[0].raw,r.tokens[0].text=d.raw+r.tokens[0].text,r.tokens[0].tokens.unshift(d)):r.tokens.unshift({type:"paragraph",raw:d.raw,text:d.raw,tokens:[d]}):r.tokens.unshift(d)}}if(!i.loose){let p=r.tokens.filter(u=>u.type==="space"),d=p.length>0&&p.some(u=>this.rules.other.anyLine.test(u.raw));i.loose=d}}if(i.loose)for(let r of i.items){r.loose=!0;for(let p of r.tokens)p.type==="text"&&(p.type="paragraph")}return i}}html(e){let t=this.rules.block.html.exec(e);if(t)return{type:"html",block:!0,raw:t[0],pre:t[1]==="pre"||t[1]==="script"||t[1]==="style",text:t[0]}}def(e){let t=this.rules.block.def.exec(e);if(t){let n=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),s=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",i=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:n,raw:t[0],href:s,title:i}}}table(e){let t=this.rules.block.table.exec(e);if(!t||!this.rules.other.tableDelimiter.test(t[2]))return;let n=ko(t[1]),s=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),i=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split(`
+`):[],o={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(let a of s)this.rules.other.tableAlignRight.test(a)?o.align.push("right"):this.rules.other.tableAlignCenter.test(a)?o.align.push("center"):this.rules.other.tableAlignLeft.test(a)?o.align.push("left"):o.align.push(null);for(let a=0;a({text:l,tokens:this.lexer.inline(l),header:!1,align:o.align[r]})));return o}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===`
+`?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let o=ht(n.slice(0,-1),"\\");if((n.length-o.length)%2===0)return}else{let o=Cu(t[2],"()");if(o===-2)return;if(o>-1){let a=(t[0].indexOf("!")===0?5:4)+t[1].length+o;t[2]=t[2].substring(0,o),t[0]=t[0].substring(0,a).trim(),t[3]=""}}let s=t[2],i="";if(this.options.pedantic){let o=this.rules.other.pedanticHrefTitle.exec(s);o&&(s=o[1],i=o[3])}else i=t[3]?t[3].slice(1,-1):"";return s=s.trim(),this.rules.other.startAngleBracket.test(s)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?s=s.slice(1):s=s.slice(1,-1)),xo(t,{href:s&&s.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let s=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[s.toLowerCase()];if(!i){let o=n[0].charAt(0);return{type:"text",raw:o,text:o}}return xo(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!(!s||s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))&&(!(s[1]||s[2])||!n||this.rules.inline.punctuation.exec(n))){let i=[...s[0]].length-1,o,a,l=i,r=0,p=s[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(p.lastIndex=0,t=t.slice(-1*e.length+i);(s=p.exec(t))!=null;){if(o=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!o)continue;if(a=[...o].length,s[3]||s[4]){l+=a;continue}else if((s[5]||s[6])&&i%3&&!((i+a)%3)){r+=a;continue}if(l-=a,l>0)continue;a=Math.min(a,a+l+r);let d=[...s[0]][0].length,u=e.slice(0,i+s.index+d+a);if(Math.min(i,a)%2){let v=u.slice(1,-1);return{type:"em",raw:u,text:v,tokens:this.lexer.inlineTokens(v)}}let h=u.slice(2,-2);return{type:"strong",raw:u,text:h,tokens:this.lexer.inlineTokens(h)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),s=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return s&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){let t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,s;return t[2]==="@"?(n=t[1],s="mailto:"+n):(n=t[1],s=n),{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,s;if(t[2]==="@")n=t[0],s="mailto:"+n;else{let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?s="http://"+t[0]:s=t[0]}return{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}},se=class ks{tokens;options;state;inlineQueue;tokenizer;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||Ue,this.options.tokenizer=this.options.tokenizer||new Xt,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let n={other:Y,block:Kt.normal,inline:ft.normal};this.options.pedantic?(n.block=Kt.pedantic,n.inline=ft.pedantic):this.options.gfm&&(n.block=Kt.gfm,this.options.breaks?n.inline=ft.breaks:n.inline=ft.gfm),this.tokenizer.rules=n}static get rules(){return{block:Kt,inline:ft}}static lex(t,n){return new ks(n).lex(t)}static lexInline(t,n){return new ks(n).inlineTokens(t)}lex(t){t=t.replace(Y.carriageReturn,`
+`),this.blockTokens(t,this.tokens);for(let n=0;n(i=a.call({lexer:this},t,n))?(t=t.substring(i.raw.length),n.push(i),!0):!1))continue;if(i=this.tokenizer.space(t)){t=t.substring(i.raw.length);let a=n.at(-1);i.raw.length===1&&a!==void 0?a.raw+=`
+`:n.push(i);continue}if(i=this.tokenizer.code(t)){t=t.substring(i.raw.length);let a=n.at(-1);a?.type==="paragraph"||a?.type==="text"?(a.raw+=(a.raw.endsWith(`
+`)?"":`
+`)+i.raw,a.text+=`
+`+i.text,this.inlineQueue.at(-1).src=a.text):n.push(i);continue}if(i=this.tokenizer.fences(t)){t=t.substring(i.raw.length),n.push(i);continue}if(i=this.tokenizer.heading(t)){t=t.substring(i.raw.length),n.push(i);continue}if(i=this.tokenizer.hr(t)){t=t.substring(i.raw.length),n.push(i);continue}if(i=this.tokenizer.blockquote(t)){t=t.substring(i.raw.length),n.push(i);continue}if(i=this.tokenizer.list(t)){t=t.substring(i.raw.length),n.push(i);continue}if(i=this.tokenizer.html(t)){t=t.substring(i.raw.length),n.push(i);continue}if(i=this.tokenizer.def(t)){t=t.substring(i.raw.length);let a=n.at(-1);a?.type==="paragraph"||a?.type==="text"?(a.raw+=(a.raw.endsWith(`
+`)?"":`
+`)+i.raw,a.text+=`
+`+i.raw,this.inlineQueue.at(-1).src=a.text):this.tokens.links[i.tag]||(this.tokens.links[i.tag]={href:i.href,title:i.title},n.push(i));continue}if(i=this.tokenizer.table(t)){t=t.substring(i.raw.length),n.push(i);continue}if(i=this.tokenizer.lheading(t)){t=t.substring(i.raw.length),n.push(i);continue}let o=t;if(this.options.extensions?.startBlock){let a=1/0,l=t.slice(1),r;this.options.extensions.startBlock.forEach(p=>{r=p.call({lexer:this},l),typeof r=="number"&&r>=0&&(a=Math.min(a,r))}),a<1/0&&a>=0&&(o=t.substring(0,a+1))}if(this.state.top&&(i=this.tokenizer.paragraph(o))){let a=n.at(-1);s&&a?.type==="paragraph"?(a.raw+=(a.raw.endsWith(`
+`)?"":`
+`)+i.raw,a.text+=`
+`+i.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=a.text):n.push(i),s=o.length!==t.length,t=t.substring(i.raw.length);continue}if(i=this.tokenizer.text(t)){t=t.substring(i.raw.length);let a=n.at(-1);a?.type==="text"?(a.raw+=(a.raw.endsWith(`
+`)?"":`
+`)+i.raw,a.text+=`
+`+i.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=a.text):n.push(i);continue}if(t){let a="Infinite loop on byte: "+t.charCodeAt(0);if(this.options.silent){console.error(a);break}else throw new Error(a)}}return this.state.top=!0,n}inline(t,n=[]){return this.inlineQueue.push({src:t,tokens:n}),n}inlineTokens(t,n=[]){let s=t,i=null;if(this.tokens.links){let r=Object.keys(this.tokens.links);if(r.length>0)for(;(i=this.tokenizer.rules.inline.reflinkSearch.exec(s))!=null;)r.includes(i[0].slice(i[0].lastIndexOf("[")+1,-1))&&(s=s.slice(0,i.index)+"["+"a".repeat(i[0].length-2)+"]"+s.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(i=this.tokenizer.rules.inline.anyPunctuation.exec(s))!=null;)s=s.slice(0,i.index)+"++"+s.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let o;for(;(i=this.tokenizer.rules.inline.blockSkip.exec(s))!=null;)o=i[2]?i[2].length:0,s=s.slice(0,i.index+o)+"["+"a".repeat(i[0].length-o-2)+"]"+s.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);s=this.options.hooks?.emStrongMask?.call({lexer:this},s)??s;let a=!1,l="";for(;t;){a||(l=""),a=!1;let r;if(this.options.extensions?.inline?.some(d=>(r=d.call({lexer:this},t,n))?(t=t.substring(r.raw.length),n.push(r),!0):!1))continue;if(r=this.tokenizer.escape(t)){t=t.substring(r.raw.length),n.push(r);continue}if(r=this.tokenizer.tag(t)){t=t.substring(r.raw.length),n.push(r);continue}if(r=this.tokenizer.link(t)){t=t.substring(r.raw.length),n.push(r);continue}if(r=this.tokenizer.reflink(t,this.tokens.links)){t=t.substring(r.raw.length);let d=n.at(-1);r.type==="text"&&d?.type==="text"?(d.raw+=r.raw,d.text+=r.text):n.push(r);continue}if(r=this.tokenizer.emStrong(t,s,l)){t=t.substring(r.raw.length),n.push(r);continue}if(r=this.tokenizer.codespan(t)){t=t.substring(r.raw.length),n.push(r);continue}if(r=this.tokenizer.br(t)){t=t.substring(r.raw.length),n.push(r);continue}if(r=this.tokenizer.del(t)){t=t.substring(r.raw.length),n.push(r);continue}if(r=this.tokenizer.autolink(t)){t=t.substring(r.raw.length),n.push(r);continue}if(!this.state.inLink&&(r=this.tokenizer.url(t))){t=t.substring(r.raw.length),n.push(r);continue}let p=t;if(this.options.extensions?.startInline){let d=1/0,u=t.slice(1),h;this.options.extensions.startInline.forEach(v=>{h=v.call({lexer:this},u),typeof h=="number"&&h>=0&&(d=Math.min(d,h))}),d<1/0&&d>=0&&(p=t.substring(0,d+1))}if(r=this.tokenizer.inlineText(p)){t=t.substring(r.raw.length),r.raw.slice(-1)!=="_"&&(l=r.raw.slice(-1)),a=!0;let d=n.at(-1);d?.type==="text"?(d.raw+=r.raw,d.text+=r.text):n.push(r);continue}if(t){let d="Infinite loop on byte: "+t.charCodeAt(0);if(this.options.silent){console.error(d);break}else throw new Error(d)}}return n}},en=class{options;parser;constructor(e){this.options=e||Ue}space(e){return""}code({text:e,lang:t,escaped:n}){let s=(t||"").match(Y.notSpaceStart)?.[0],i=e.replace(Y.endingNewline,"")+`
+`;return s?''+(n?i:ve(i,!0))+`
+`:""+(n?i:ve(i,!0))+`
+`}blockquote({tokens:e}){return`
+${this.parser.parse(e)}
+`}html({text:e}){return e}def(e){return""}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)}
+`}hr(e){return`
+`}list(e){let t=e.ordered,n=e.start,s="";for(let a=0;a
+`+s+""+i+`>
+`}listitem(e){return`${this.parser.parse(e.tokens)}
+`}checkbox({checked:e}){return" '}paragraph({tokens:e}){return`${this.parser.parseInline(e)}
+`}table(e){let t="",n="";for(let i=0;i${s}`),`
+
+`+t+`
+`+s+`
+`}tablerow({text:e}){return`
+${e}
+`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`${n}>
+`}strong({tokens:e}){return`${this.parser.parseInline(e)}`}em({tokens:e}){return`${this.parser.parseInline(e)}`}codespan({text:e}){return`${ve(e,!0)}`}br(e){return"
"}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){let s=this.parser.parseInline(n),i=$o(e);if(i===null)return s;e=i;let o='"+s+"",o}image({href:e,title:t,text:n,tokens:s}){s&&(n=this.parser.parseInline(s,this.parser.textRenderer));let i=$o(e);if(i===null)return ve(n);e=i;let o=`
",o}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:ve(e.text)}},li=class{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}checkbox({raw:e}){return e}},ie=class xs{options;renderer;textRenderer;constructor(t){this.options=t||Ue,this.options.renderer=this.options.renderer||new en,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new li}static parse(t,n){return new xs(n).parse(t)}static parseInline(t,n){return new xs(n).parseInline(t)}parse(t){let n="";for(let s=0;s{let a=i[o].flat(1/0);n=n.concat(this.walkTokens(a,t))}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let s={...n};if(s.async=this.defaults.async||s.async||!1,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let o=t.renderers[i.name];o?t.renderers[i.name]=function(...a){let l=i.renderer.apply(this,a);return l===!1&&(l=o.apply(this,a)),l}:t.renderers[i.name]=i.renderer}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let o=t[i.level];o?o.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]))}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens)}),s.extensions=t),n.renderer){let i=this.defaults.renderer||new en(this.defaults);for(let o in n.renderer){if(!(o in i))throw new Error(`renderer '${o}' does not exist`);if(["options","parser"].includes(o))continue;let a=o,l=n.renderer[a],r=i[a];i[a]=(...p)=>{let d=l.apply(i,p);return d===!1&&(d=r.apply(i,p)),d||""}}s.renderer=i}if(n.tokenizer){let i=this.defaults.tokenizer||new Xt(this.defaults);for(let o in n.tokenizer){if(!(o in i))throw new Error(`tokenizer '${o}' does not exist`);if(["options","rules","lexer"].includes(o))continue;let a=o,l=n.tokenizer[a],r=i[a];i[a]=(...p)=>{let d=l.apply(i,p);return d===!1&&(d=r.apply(i,p)),d}}s.tokenizer=i}if(n.hooks){let i=this.defaults.hooks||new gt;for(let o in n.hooks){if(!(o in i))throw new Error(`hook '${o}' does not exist`);if(["options","block"].includes(o))continue;let a=o,l=n.hooks[a],r=i[a];gt.passThroughHooks.has(o)?i[a]=p=>{if(this.defaults.async&>.passThroughHooksRespectAsync.has(o))return(async()=>{let u=await l.call(i,p);return r.call(i,u)})();let d=l.call(i,p);return r.call(i,d)}:i[a]=(...p)=>{if(this.defaults.async)return(async()=>{let u=await l.apply(i,p);return u===!1&&(u=await r.apply(i,p)),u})();let d=l.apply(i,p);return d===!1&&(d=r.apply(i,p)),d}}s.hooks=i}if(n.walkTokens){let i=this.defaults.walkTokens,o=n.walkTokens;s.walkTokens=function(a){let l=[];return l.push(o.call(this,a)),i&&(l=l.concat(i.call(this,a))),l}}this.defaults={...this.defaults,...s}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return se.lex(e,t??this.defaults)}parser(e,t){return ie.parse(e,t??this.defaults)}parseMarkdown(e){return(t,n)=>{let s={...n},i={...this.defaults,...s},o=this.onError(!!i.silent,!!i.async);if(this.defaults.async===!0&&s.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof t>"u"||t===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof t!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(t)+", string expected"));if(i.hooks&&(i.hooks.options=i,i.hooks.block=e),i.async)return(async()=>{let a=i.hooks?await i.hooks.preprocess(t):t,l=await(i.hooks?await i.hooks.provideLexer():e?se.lex:se.lexInline)(a,i),r=i.hooks?await i.hooks.processAllTokens(l):l;i.walkTokens&&await Promise.all(this.walkTokens(r,i.walkTokens));let p=await(i.hooks?await i.hooks.provideParser():e?ie.parse:ie.parseInline)(r,i);return i.hooks?await i.hooks.postprocess(p):p})().catch(o);try{i.hooks&&(t=i.hooks.preprocess(t));let a=(i.hooks?i.hooks.provideLexer():e?se.lex:se.lexInline)(t,i);i.hooks&&(a=i.hooks.processAllTokens(a)),i.walkTokens&&this.walkTokens(a,i.walkTokens);let l=(i.hooks?i.hooks.provideParser():e?ie.parse:ie.parseInline)(a,i);return i.hooks&&(l=i.hooks.postprocess(l)),l}catch(a){return o(a)}}}onError(e,t){return n=>{if(n.message+=`
+Please report this to https://github.com/markedjs/marked.`,e){let s="An error occurred:
"+ve(n.message+"",!0)+"
";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}},Fe=new Iu;function P(e,t){return Fe.parse(e,t)}P.options=P.setOptions=function(e){return Fe.setOptions(e),P.defaults=Fe.defaults,Ja(P.defaults),P};P.getDefaults=ei;P.defaults=Ue;P.use=function(...e){return Fe.use(...e),P.defaults=Fe.defaults,Ja(P.defaults),P};P.walkTokens=function(e,t){return Fe.walkTokens(e,t)};P.parseInline=Fe.parseInline;P.Parser=ie;P.parser=ie.parse;P.Renderer=en;P.TextRenderer=li;P.Lexer=se;P.lexer=se.lex;P.Tokenizer=Xt;P.Hooks=gt;P.parse=P;P.options;P.setOptions;P.use;P.walkTokens;P.parseInline;ie.parse;se.lex;P.setOptions({gfm:!0,breaks:!0,mangle:!1});const Ao=["a","b","blockquote","br","code","del","em","h1","h2","h3","h4","hr","i","li","ol","p","pre","strong","table","tbody","td","th","thead","tr","ul"],So=["class","href","rel","target","title","start"];let _o=!1;const Lu=14e4,Ru=4e4,Mu=200,Zn=5e4,Pe=new Map;function Pu(e){const t=Pe.get(e);return t===void 0?null:(Pe.delete(e),Pe.set(e,t),t)}function To(e,t){if(Pe.set(e,t),Pe.size<=Mu)return;const n=Pe.keys().next().value;n&&Pe.delete(n)}function Nu(){_o||(_o=!0,ws.addHook("afterSanitizeAttributes",e=>{!(e instanceof HTMLAnchorElement)||!e.getAttribute("href")||(e.setAttribute("rel","noreferrer noopener"),e.setAttribute("target","_blank"))}))}function As(e){const t=e.trim();if(!t)return"";if(Nu(),t.length<=Zn){const a=Pu(t);if(a!==null)return a}const n=la(t,Lu),s=n.truncated?`
+
+… truncated (${n.total} chars, showing first ${n.text.length}).`:"";if(n.text.length>Ru){const l=`${Ou(`${n.text}${s}`)}`,r=ws.sanitize(l,{ALLOWED_TAGS:Ao,ALLOWED_ATTR:So});return t.length<=Zn&&To(t,r),r}const i=P.parse(`${n.text}${s}`),o=ws.sanitize(i,{ALLOWED_TAGS:Ao,ALLOWED_ATTR:So});return t.length<=Zn&&To(t,o),o}function Ou(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function Du(e,t){return c``}function Ht(e,t){e&&(e.textContent=t)}const Bu=1500,Fu=2e3,lr="Copy as markdown",Uu="Copied",Ku="Copy failed",Xn="📋",Hu="✓",zu="!";async function ju(e){if(!e)return!1;try{return await navigator.clipboard.writeText(e),!0}catch{return!1}}function zt(e,t){e.title=t,e.setAttribute("aria-label",t)}function qu(e){const t=e.label??lr;return c`
+
+ `}function Wu(e){return qu({text:()=>e,label:lr})}const Vu={emoji:"🧩",detailKeys:["command","path","url","targetUrl","targetId","ref","element","node","nodeId","id","requestId","to","channelId","guildId","userId","name","query","pattern","messageId"]},Gu={bash:{emoji:"🛠️",title:"Bash",detailKeys:["command"]},process:{emoji:"🧰",title:"Process",detailKeys:["sessionId"]},read:{emoji:"📖",title:"Read",detailKeys:["path"]},write:{emoji:"✍️",title:"Write",detailKeys:["path"]},edit:{emoji:"📝",title:"Edit",detailKeys:["path"]},attach:{emoji:"📎",title:"Attach",detailKeys:["path","url","fileName"]},browser:{emoji:"🌐",title:"Browser",actions:{status:{label:"status"},start:{label:"start"},stop:{label:"stop"},tabs:{label:"tabs"},open:{label:"open",detailKeys:["targetUrl"]},focus:{label:"focus",detailKeys:["targetId"]},close:{label:"close",detailKeys:["targetId"]},snapshot:{label:"snapshot",detailKeys:["targetUrl","targetId","ref","element","format"]},screenshot:{label:"screenshot",detailKeys:["targetUrl","targetId","ref","element"]},navigate:{label:"navigate",detailKeys:["targetUrl","targetId"]},console:{label:"console",detailKeys:["level","targetId"]},pdf:{label:"pdf",detailKeys:["targetId"]},upload:{label:"upload",detailKeys:["paths","ref","inputRef","element","targetId"]},dialog:{label:"dialog",detailKeys:["accept","promptText","targetId"]},act:{label:"act",detailKeys:["request.kind","request.ref","request.selector","request.text","request.value"]}}},canvas:{emoji:"🖼️",title:"Canvas",actions:{present:{label:"present",detailKeys:["target","node","nodeId"]},hide:{label:"hide",detailKeys:["node","nodeId"]},navigate:{label:"navigate",detailKeys:["url","node","nodeId"]},eval:{label:"eval",detailKeys:["javaScript","node","nodeId"]},snapshot:{label:"snapshot",detailKeys:["format","node","nodeId"]},a2ui_push:{label:"A2UI push",detailKeys:["jsonlPath","node","nodeId"]},a2ui_reset:{label:"A2UI reset",detailKeys:["node","nodeId"]}}},nodes:{emoji:"📱",title:"Nodes",actions:{status:{label:"status"},describe:{label:"describe",detailKeys:["node","nodeId"]},pending:{label:"pending"},approve:{label:"approve",detailKeys:["requestId"]},reject:{label:"reject",detailKeys:["requestId"]},notify:{label:"notify",detailKeys:["node","nodeId","title","body"]},camera_snap:{label:"camera snap",detailKeys:["node","nodeId","facing","deviceId"]},camera_list:{label:"camera list",detailKeys:["node","nodeId"]},camera_clip:{label:"camera clip",detailKeys:["node","nodeId","facing","duration","durationMs"]},screen_record:{label:"screen record",detailKeys:["node","nodeId","duration","durationMs","fps","screenIndex"]}}},cron:{emoji:"⏰",title:"Cron",actions:{status:{label:"status"},list:{label:"list"},add:{label:"add",detailKeys:["job.name","job.id","job.schedule","job.cron"]},update:{label:"update",detailKeys:["id"]},remove:{label:"remove",detailKeys:["id"]},run:{label:"run",detailKeys:["id"]},runs:{label:"runs",detailKeys:["id"]},wake:{label:"wake",detailKeys:["text","mode"]}}},gateway:{emoji:"🔌",title:"Gateway",actions:{restart:{label:"restart",detailKeys:["reason","delayMs"]},"config.get":{label:"config get"},"config.schema":{label:"config schema"},"config.apply":{label:"config apply",detailKeys:["restartDelayMs"]},"update.run":{label:"update run",detailKeys:["restartDelayMs"]}}},whatsapp_login:{emoji:"🟢",title:"WhatsApp Login",actions:{start:{label:"start"},wait:{label:"wait"}}},discord:{emoji:"💬",title:"Discord",actions:{react:{label:"react",detailKeys:["channelId","messageId","emoji"]},reactions:{label:"reactions",detailKeys:["channelId","messageId"]},sticker:{label:"sticker",detailKeys:["to","stickerIds"]},poll:{label:"poll",detailKeys:["question","to"]},permissions:{label:"permissions",detailKeys:["channelId"]},readMessages:{label:"read messages",detailKeys:["channelId","limit"]},sendMessage:{label:"send",detailKeys:["to","content"]},editMessage:{label:"edit",detailKeys:["channelId","messageId"]},deleteMessage:{label:"delete",detailKeys:["channelId","messageId"]},threadCreate:{label:"thread create",detailKeys:["channelId","name"]},threadList:{label:"thread list",detailKeys:["guildId","channelId"]},threadReply:{label:"thread reply",detailKeys:["channelId","content"]},pinMessage:{label:"pin",detailKeys:["channelId","messageId"]},unpinMessage:{label:"unpin",detailKeys:["channelId","messageId"]},listPins:{label:"list pins",detailKeys:["channelId"]},searchMessages:{label:"search",detailKeys:["guildId","content"]},memberInfo:{label:"member",detailKeys:["guildId","userId"]},roleInfo:{label:"roles",detailKeys:["guildId"]},emojiList:{label:"emoji list",detailKeys:["guildId"]},roleAdd:{label:"role add",detailKeys:["guildId","userId","roleId"]},roleRemove:{label:"role remove",detailKeys:["guildId","userId","roleId"]},channelInfo:{label:"channel",detailKeys:["channelId"]},channelList:{label:"channels",detailKeys:["guildId"]},voiceStatus:{label:"voice",detailKeys:["guildId","userId"]},eventList:{label:"events",detailKeys:["guildId"]},eventCreate:{label:"event create",detailKeys:["guildId","name"]},timeout:{label:"timeout",detailKeys:["guildId","userId"]},kick:{label:"kick",detailKeys:["guildId","userId"]},ban:{label:"ban",detailKeys:["guildId","userId"]}}},slack:{emoji:"💬",title:"Slack",actions:{react:{label:"react",detailKeys:["channelId","messageId","emoji"]},reactions:{label:"reactions",detailKeys:["channelId","messageId"]},sendMessage:{label:"send",detailKeys:["to","content"]},editMessage:{label:"edit",detailKeys:["channelId","messageId"]},deleteMessage:{label:"delete",detailKeys:["channelId","messageId"]},readMessages:{label:"read messages",detailKeys:["channelId","limit"]},pinMessage:{label:"pin",detailKeys:["channelId","messageId"]},unpinMessage:{label:"unpin",detailKeys:["channelId","messageId"]},listPins:{label:"list pins",detailKeys:["channelId"]},memberInfo:{label:"member",detailKeys:["userId"]},emojiList:{label:"emoji list"}}}},Yu={fallback:Vu,tools:Gu},cr=Yu,Co=cr.fallback??{emoji:"🧩"},Qu=cr.tools??{};function Ju(e){return(e??"tool").trim()}function Zu(e){const t=e.replace(/_/g," ").trim();return t?t.split(/\s+/).map(n=>n.length<=2&&n.toUpperCase()===n?n:`${n.at(0)?.toUpperCase()??""}${n.slice(1)}`).join(" "):"Tool"}function Xu(e){const t=e?.trim();if(t)return t.replace(/_/g," ")}function dr(e){if(e!=null){if(typeof e=="string"){const t=e.trim();if(!t)return;const n=t.split(/\r?\n/)[0]?.trim()??"";return n?n.length>160?`${n.slice(0,157)}…`:n:void 0}if(typeof e=="number"||typeof e=="boolean")return String(e);if(Array.isArray(e)){const t=e.map(s=>dr(s)).filter(s=>!!s);if(t.length===0)return;const n=t.slice(0,3).join(", ");return t.length>3?`${n}…`:n}}}function ep(e,t){if(!e||typeof e!="object")return;let n=e;for(const s of t.split(".")){if(!s||!n||typeof n!="object")return;n=n[s]}return n}function tp(e,t){for(const n of t){const s=ep(e,n),i=dr(s);if(i)return i}}function np(e){if(!e||typeof e!="object")return;const t=e,n=typeof t.path=="string"?t.path:void 0;if(!n)return;const s=typeof t.offset=="number"?t.offset:void 0,i=typeof t.limit=="number"?t.limit:void 0;return s!==void 0&&i!==void 0?`${n}:${s}-${s+i}`:n}function sp(e){if(!e||typeof e!="object")return;const t=e;return typeof t.path=="string"?t.path:void 0}function ip(e,t){if(!(!e||!t))return e.actions?.[t]??void 0}function op(e){const t=Ju(e.name),n=t.toLowerCase(),s=Qu[n],i=s?.emoji??Co.emoji??"🧩",o=s?.title??Zu(t),a=s?.label??t,l=e.args&&typeof e.args=="object"?e.args.action:void 0,r=typeof l=="string"?l.trim():void 0,p=ip(s,r),d=Xu(p?.label??r);let u;n==="read"&&(u=np(e.args)),!u&&(n==="write"||n==="edit"||n==="attach")&&(u=sp(e.args));const h=p?.detailKeys??s?.detailKeys??Co.detailKeys??[];return!u&&h.length>0&&(u=tp(e.args,h)),!u&&e.meta&&(u=e.meta),u&&(u=rp(u)),{name:t,emoji:i,title:o,label:a,verb:d,detail:u}}function ap(e){const t=[];if(e.verb&&t.push(e.verb),e.detail&&t.push(e.detail),t.length!==0)return t.join(" · ")}function rp(e){return e&&e.replace(/\/Users\/[^/]+/g,"~").replace(/\/home\/[^/]+/g,"~")}const lp=80,cp=2,Eo=100;function dp(e){const t=e.trim();if(t.startsWith("{")||t.startsWith("["))try{const n=JSON.parse(t);return"```json\n"+JSON.stringify(n,null,2)+"\n```"}catch{}return e}function up(e){const t=e.split(`
+`),n=t.slice(0,cp),s=n.join(`
+`);return s.length>Eo?s.slice(0,Eo)+"…":n.lengthi.kind==="result")){const i=typeof t.toolName=="string"&&t.toolName||typeof t.tool_name=="string"&&t.tool_name||"tool",o=ca(e)??void 0;s.push({kind:"result",name:i,text:o})}return s}function Io(e,t){const n=op({name:e.name,args:e.args}),s=ap(n),i=!!e.text?.trim(),o=!!t,a=o?()=>{if(i){t(dp(e.text));return}const u=`## ${n.label}
+
+${s?`**Command:** \`${s}\`
+
+`:""}*No output — tool completed successfully.*`;t(u)}:void 0,l=i&&(e.text?.length??0)<=lp,r=i&&!l,p=i&&l,d=!i;return c`
+ {u.key!=="Enter"&&u.key!==" "||(u.preventDefault(),a?.())}:g}
+ >
+
+
+ ${n.emoji}
+ ${n.label}
+
+ ${o?c`${i?"View ›":"›"}`:g}
+ ${d&&!o?c`✓`:g}
+
+ ${s?c`${s}`:g}
+ ${d?c`Completed`:g}
+ ${r?c`${up(e.text)}`:g}
+ ${p?c`${e.text}`:g}
+
+ `}function fp(e){return Array.isArray(e)?e.filter(Boolean):[]}function hp(e){if(typeof e!="string")return e;const t=e.trim();if(!t||!t.startsWith("{")&&!t.startsWith("["))return e;try{return JSON.parse(t)}catch{return e}}function gp(e){if(typeof e.text=="string")return e.text;if(typeof e.content=="string")return e.content}function vp(e){return c`
+
+ ${ci("assistant",e)}
+
+
+ `}function mp(e,t,n,s){const i=new Date(t).toLocaleTimeString([],{hour:"numeric",minute:"2-digit"}),o=s?.name??"Assistant";return c`
+
+ ${ci("assistant",s)}
+
+
+ `}function bp(e,t){const n=Xs(e.role),s=t.assistantName??"Assistant",i=n==="user"?"You":n==="assistant"?s:n,o=n==="user"?"user":n==="assistant"?"assistant":"other",a=new Date(e.timestamp).toLocaleTimeString([],{hour:"numeric",minute:"2-digit"});return c`
+
+ ${ci(e.role,{name:s,avatar:t.assistantAvatar??null})}
+
+
+ `}function ci(e,t){const n=Xs(e),s=t?.name?.trim()||"Assistant",i=t?.avatar?.trim()||"",o=n==="user"?"U":n==="assistant"?s.charAt(0).toUpperCase()||"A":n==="tool"?"⚙":"?",a=n==="user"?"user":n==="assistant"?"assistant":n==="tool"?"tool":"other";return i&&n==="assistant"?yp(i)?c`
`:c`${i}`:c`${o}`}function yp(e){return/^https?:\/\//i.test(e)||/^data:image\//i.test(e)||/^\//.test(e)}function ur(e,t,n){const s=e,i=typeof s.role=="string"?s.role:"unknown",o=Wa(e)||i.toLowerCase()==="toolresult"||i.toLowerCase()==="tool_result"||typeof s.toolCallId=="string"||typeof s.tool_call_id=="string",a=pp(e),l=a.length>0,r=ca(e),p=t.showReasoning&&i==="assistant"?xl(e):null,d=r?.trim()?r:null,u=p?Sl(p):null,h=d,v=i==="assistant"&&!!h?.trim(),w=["chat-bubble",v?"has-copy":"",t.isStreaming?"streaming":"","fade-in"].filter(Boolean).join(" ");return!h&&l&&o?c`${a.map($=>Io($,n))}`:!h&&!l?g:c`
+
+ ${v?Wu(h):g}
+ ${u?c`${vs(As(u))}`:g}
+ ${h?c`${vs(As(h))}`:g}
+ ${a.map($=>Io($,n))}
+
+ `}function wp(e){return c`
+
+ `}var $p=Object.defineProperty,kp=Object.getOwnPropertyDescriptor,vn=(e,t,n,s)=>{for(var i=s>1?void 0:s?kp(t,n):t,o=e.length-1,a;o>=0;o--)(a=e[o])&&(i=(s?a(t,n,i):a(i))||i);return s&&i&&$p(t,n,i),i};let tt=class extends Qe{constructor(){super(...arguments),this.splitRatio=.6,this.minRatio=.4,this.maxRatio=.7,this.isDragging=!1,this.startX=0,this.startRatio=0,this.handleMouseDown=e=>{this.isDragging=!0,this.startX=e.clientX,this.startRatio=this.splitRatio,this.classList.add("dragging"),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("mouseup",this.handleMouseUp),e.preventDefault()},this.handleMouseMove=e=>{if(!this.isDragging)return;const t=this.parentElement;if(!t)return;const n=t.getBoundingClientRect().width,i=(e.clientX-this.startX)/n;let o=this.startRatio+i;o=Math.max(this.minRatio,Math.min(this.maxRatio,o)),this.dispatchEvent(new CustomEvent("resize",{detail:{splitRatio:o},bubbles:!0,composed:!0}))},this.handleMouseUp=()=>{this.isDragging=!1,this.classList.remove("dragging"),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp)}}render(){return c``}connectedCallback(){super.connectedCallback(),this.addEventListener("mousedown",this.handleMouseDown)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener("mousedown",this.handleMouseDown),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp)}};tt.styles=Fr`
+ :host {
+ width: 4px;
+ cursor: col-resize;
+ background: var(--border, #333);
+ transition: background 150ms ease-out;
+ flex-shrink: 0;
+ position: relative;
+ }
+
+ :host::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: -4px;
+ right: -4px;
+ bottom: 0;
+ }
+
+ :host(:hover) {
+ background: var(--accent, #007bff);
+ }
+
+ :host(.dragging) {
+ background: var(--accent, #007bff);
+ }
+ `;vn([on({type:Number})],tt.prototype,"splitRatio",2);vn([on({type:Number})],tt.prototype,"minRatio",2);vn([on({type:Number})],tt.prototype,"maxRatio",2);tt=vn([ta("resizable-divider")],tt);const xp=5e3;function Ap(e){return e?e.active?c`
+
+ 🧹 Compacting context...
+
+ `:e.completedAt&&Date.now()-e.completedAt
+ 🧹 Context compacted
+
+ `:g:g}function Sp(e){const t=e.connected,n=e.sending||e.stream!==null,i=e.sessions?.sessions?.find(u=>u.key===e.sessionKey)?.reasoningLevel??"off",o=e.showThinking&&i!=="off",a={name:e.assistantName,avatar:e.assistantAvatar??e.assistantAvatarUrl??null},l=e.connected?"Message (↩ to send, Shift+↩ for line breaks)":"Connect to the gateway to start chatting…",r=e.splitRatio??.6,p=!!(e.sidebarOpen&&e.onCloseSidebar),d=c`
+
+ ${e.loading?c`Loading chat…`:g}
+ ${ja(Tp(e),u=>u.key,u=>u.kind==="reading-indicator"?vp(a):u.kind==="stream"?mp(u.text,u.startedAt,e.onOpenSidebar,a):u.kind==="group"?bp(u,{onOpenSidebar:e.onOpenSidebar,showReasoning:o,assistantName:e.assistantName,assistantAvatar:a.avatar}):g)}
+
+ `;return c`
+
+ ${e.disabledReason?c`${e.disabledReason}`:g}
+
+ ${e.error?c`${e.error}`:g}
+
+ ${Ap(e.compactionStatus)}
+
+ ${e.focusMode?c`
+
+ `:g}
+
+
+
+ ${d}
+
+
+ ${p?c`
+ e.onSplitRatioChange?.(u.detail.splitRatio)}
+ >
+
+ `:g}
+
+
+ ${e.queue.length?c`
+
+ Queued (${e.queue.length})
+
+ ${e.queue.map(u=>c`
+
+ ${u.text}
+
+
+ `)}
+
+
+ `:g}
+
+
+
+
+
+
+
+
+
+ `}const Lo=200;function _p(e){const t=[];let n=null;for(const s of e){if(s.kind!=="message"){n&&(t.push(n),n=null),t.push(s);continue}const i=qa(s.message),o=Xs(i.role),a=i.timestamp||Date.now();!n||n.role!==o?(n&&t.push(n),n={kind:"group",key:`group:${o}:${s.key}`,role:o,messages:[{message:s.message,key:s.key}],timestamp:a,isStreaming:!1}):n.messages.push({message:s.message,key:s.key})}return n&&t.push(n),t}function Tp(e){const t=[],n=Array.isArray(e.messages)?e.messages:[],s=Array.isArray(e.toolMessages)?e.toolMessages:[],i=Math.max(0,n.length-Lo);i>0&&t.push({kind:"message",key:"chat:history:notice",message:{role:"system",content:`Showing last ${Lo} messages (${i} hidden).`,timestamp:Date.now()}});for(let o=i;o0?t.push({kind:"stream",key:o,text:e.stream,startedAt:e.streamStartedAt??Date.now()}):t.push({kind:"reading-indicator",key:o})}return _p(t)}function Ro(e,t){const n=e,s=typeof n.toolCallId=="string"?n.toolCallId:"";if(s)return`tool:${s}`;const i=typeof n.id=="string"?n.id:"";if(i)return`msg:${i}`;const o=typeof n.messageId=="string"?n.messageId:"";if(o)return`msg:${o}`;const a=typeof n.timestamp=="number"?n.timestamp:null,l=typeof n.role=="string"?n.role:"unknown";return a!=null?`msg:${l}:${a}:${t}`:`msg:${l}:${t}`}function de(e){if(e)return Array.isArray(e.type)?e.type.filter(n=>n!=="null")[0]??e.type[0]:e.type}function pr(e){if(!e)return"";if(e.default!==void 0)return e.default;switch(de(e)){case"object":return{};case"array":return[];case"boolean":return!1;case"number":case"integer":return 0;case"string":return"";default:return""}}function mn(e){return e.filter(t=>typeof t=="string").join(".")}function ee(e,t){const n=mn(e),s=t[n];if(s)return s;const i=n.split(".");for(const[o,a]of Object.entries(t)){if(!o.includes("*"))continue;const l=o.split(".");if(l.length!==i.length)continue;let r=!0;for(let p=0;pt.toUpperCase())}function Cp(e){const t=mn(e).toLowerCase();return t.includes("token")||t.includes("password")||t.includes("secret")||t.includes("apikey")||t.endsWith("key")}const Ep=new Set(["title","description","default","nullable"]);function Ip(e){return Object.keys(e??{}).filter(n=>!Ep.has(n)).length===0}function Lp(e){if(e===void 0)return"";try{return JSON.stringify(e,null,2)??""}catch{return""}}const St={chevronDown:c``,plus:c``,minus:c``,trash:c``,edit:c``};function be(e){const{schema:t,value:n,path:s,hints:i,unsupported:o,disabled:a,onPatch:l}=e,r=e.showLabel??!0,p=de(t),d=ee(s,i),u=d?.label??t.title??ye(String(s.at(-1))),h=d?.help??t.description,v=mn(s);if(o.has(v))return c`
+ ${u}
+ Unsupported schema node. Use Raw mode.
+ `;if(t.anyOf||t.oneOf){const $=(t.anyOf??t.oneOf??[]).filter(A=>!(A.type==="null"||Array.isArray(A.type)&&A.type.includes("null")));if($.length===1)return be({...e,schema:$[0]});const x=A=>{if(A.const!==void 0)return A.const;if(A.enum&&A.enum.length===1)return A.enum[0]},C=$.map(x),I=C.every(A=>A!==void 0);if(I&&C.length>0&&C.length<=5){const A=n??t.default;return c`
+
+ ${r?c``:g}
+ ${h?c`${h}`:g}
+
+ ${C.map((B,ue)=>c`
+
+ `)}
+
+
+ `}if(I&&C.length>5)return Po({...e,options:C,value:n??t.default});const R=new Set($.map(A=>de(A)).filter(Boolean)),E=new Set([...R].map(A=>A==="integer"?"number":A));if([...E].every(A=>["string","number","boolean"].includes(A))){const A=E.has("string"),B=E.has("number");if(E.has("boolean")&&E.size===1)return be({...e,schema:{...t,type:"boolean",anyOf:void 0,oneOf:void 0}});if(A||B)return Mo({...e,inputType:B&&!A?"number":"text"})}}if(t.enum){const w=t.enum;if(w.length<=5){const $=n??t.default;return c`
+
+ ${r?c``:g}
+ ${h?c`${h}`:g}
+
+ ${w.map(x=>c`
+
+ `)}
+
+
+ `}return Po({...e,options:w,value:n??t.default})}if(p==="object")return Mp(e);if(p==="array")return Pp(e);if(p==="boolean"){const w=typeof n=="boolean"?n:typeof t.default=="boolean"?t.default:!1;return c`
+
+ `}return p==="number"||p==="integer"?Rp(e):p==="string"?Mo({...e,inputType:"text"}):c`
+
+ ${u}
+ Unsupported type: ${p}. Use Raw mode.
+
+ `}function Mo(e){const{schema:t,value:n,path:s,hints:i,disabled:o,onPatch:a,inputType:l}=e,r=e.showLabel??!0,p=ee(s,i),d=p?.label??t.title??ye(String(s.at(-1))),u=p?.help??t.description,h=p?.sensitive??Cp(s),v=p?.placeholder??(h?"••••":t.default!==void 0?`Default: ${t.default}`:""),w=n??"";return c`
+
+ ${r?c``:g}
+ ${u?c`${u}`:g}
+
+ {const x=$.target.value;if(l==="number"){if(x.trim()===""){a(s,void 0);return}const C=Number(x);a(s,Number.isNaN(C)?x:C);return}a(s,x)}}
+ />
+ ${t.default!==void 0?c`
+
+ `:g}
+
+
+ `}function Rp(e){const{schema:t,value:n,path:s,hints:i,disabled:o,onPatch:a}=e,l=e.showLabel??!0,r=ee(s,i),p=r?.label??t.title??ye(String(s.at(-1))),d=r?.help??t.description,u=n??t.default??"",h=typeof u=="number"?u:0;return c`
+
+ ${l?c``:g}
+ ${d?c`${d}`:g}
+
+
+ {const w=v.target.value,$=w===""?void 0:Number(w);a(s,$)}}
+ />
+
+
+
+ `}function Po(e){const{schema:t,value:n,path:s,hints:i,disabled:o,options:a,onPatch:l}=e,r=e.showLabel??!0,p=ee(s,i),d=p?.label??t.title??ye(String(s.at(-1))),u=p?.help??t.description,h=n??t.default,v=a.findIndex($=>$===h||String($)===String(h)),w="__unset__";return c`
+
+ ${r?c``:g}
+ ${u?c`${u}`:g}
+
+
+ `}function Mp(e){const{schema:t,value:n,path:s,hints:i,unsupported:o,disabled:a,onPatch:l}=e;e.showLabel;const r=ee(s,i),p=r?.label??t.title??ye(String(s.at(-1))),d=r?.help??t.description,u=n??t.default,h=u&&typeof u=="object"&&!Array.isArray(u)?u:{},v=t.properties??{},$=Object.entries(v).sort((R,E)=>{const A=ee([...s,R[0]],i)?.order??0,B=ee([...s,E[0]],i)?.order??0;return A!==B?A-B:R[0].localeCompare(E[0])}),x=new Set(Object.keys(v)),C=t.additionalProperties,I=!!C&&typeof C=="object";return s.length===1?c`
+
+ ${$.map(([R,E])=>be({schema:E,value:h[R],path:[...s,R],hints:i,unsupported:o,disabled:a,onPatch:l}))}
+ ${I?No({schema:C,value:h,path:s,hints:i,unsupported:o,disabled:a,reservedKeys:x,onPatch:l}):g}
+
+ `:c`
+
+
+ ${p}
+ ${St.chevronDown}
+
+ ${d?c`${d}`:g}
+
+ ${$.map(([R,E])=>be({schema:E,value:h[R],path:[...s,R],hints:i,unsupported:o,disabled:a,onPatch:l}))}
+ ${I?No({schema:C,value:h,path:s,hints:i,unsupported:o,disabled:a,reservedKeys:x,onPatch:l}):g}
+
+
+ `}function Pp(e){const{schema:t,value:n,path:s,hints:i,unsupported:o,disabled:a,onPatch:l}=e,r=e.showLabel??!0,p=ee(s,i),d=p?.label??t.title??ye(String(s.at(-1))),u=p?.help??t.description,h=Array.isArray(t.items)?t.items[0]:t.items;if(!h)return c`
+
+ ${d}
+ Unsupported array schema. Use Raw mode.
+
+ `;const v=Array.isArray(n)?n:Array.isArray(t.default)?t.default:[];return c`
+
+
+ ${r?c`${d}`:g}
+ ${v.length} item${v.length!==1?"s":""}
+
+
+ ${u?c`${u}`:g}
+
+ ${v.length===0?c`
+
+ No items yet. Click "Add" to create one.
+
+ `:c`
+
+ ${v.map((w,$)=>c`
+
+
+ #${$+1}
+
+
+
+ ${be({schema:h,value:w,path:[...s,$],hints:i,unsupported:o,disabled:a,showLabel:!1,onPatch:l})}
+
+
+ `)}
+
+ `}
+
+ `}function No(e){const{schema:t,value:n,path:s,hints:i,unsupported:o,disabled:a,reservedKeys:l,onPatch:r}=e,p=Ip(t),d=Object.entries(n??{}).filter(([u])=>!l.has(u));return c`
+
+
+ Custom entries
+
+
+
+ ${d.length===0?c`
+ No custom entries.
+ `:c`
+
+ ${d.map(([u,h])=>{const v=[...s,u],w=Lp(h);return c`
+
+
+ {const x=$.target.value.trim();if(!x||x===u)return;const C={...n??{}};x in C||(C[x]=C[u],delete C[u],r(s,C))}}
+ />
+
+
+ ${p?c`
+
+ `:be({schema:t,value:h,path:v,hints:i,unsupported:o,disabled:a,showLabel:!1,onPatch:r})}
+
+
+
+ `})}
+
+ `}
+
+ `}const Oo={env:c``,update:c``,agents:c``,auth:c``,channels:c``,messages:c``,commands:c``,hooks:c``,skills:c``,tools:c``,gateway:c``,wizard:c``,meta:c``,logging:c``,browser:c``,ui:c``,models:c``,bindings:c``,broadcast:c``,audio:c``,session:c``,cron:c``,web:c``,discovery:c``,canvasHost:c``,talk:c``,plugins:c``,default:c``},di={env:{label:"Environment Variables",description:"Environment variables passed to the gateway process"},update:{label:"Updates",description:"Auto-update settings and release channel"},agents:{label:"Agents",description:"Agent configurations, models, and identities"},auth:{label:"Authentication",description:"API keys and authentication profiles"},channels:{label:"Channels",description:"Messaging channels (Telegram, Discord, Slack, etc.)"},messages:{label:"Messages",description:"Message handling and routing settings"},commands:{label:"Commands",description:"Custom slash commands"},hooks:{label:"Hooks",description:"Webhooks and event hooks"},skills:{label:"Skills",description:"Skill packs and capabilities"},tools:{label:"Tools",description:"Tool configurations (browser, search, etc.)"},gateway:{label:"Gateway",description:"Gateway server settings (port, auth, binding)"},wizard:{label:"Setup Wizard",description:"Setup wizard state and history"},meta:{label:"Metadata",description:"Gateway metadata and version information"},logging:{label:"Logging",description:"Log levels and output configuration"},browser:{label:"Browser",description:"Browser automation settings"},ui:{label:"UI",description:"User interface preferences"},models:{label:"Models",description:"AI model configurations and providers"},bindings:{label:"Bindings",description:"Key bindings and shortcuts"},broadcast:{label:"Broadcast",description:"Broadcast and notification settings"},audio:{label:"Audio",description:"Audio input/output settings"},session:{label:"Session",description:"Session management and persistence"},cron:{label:"Cron",description:"Scheduled tasks and automation"},web:{label:"Web",description:"Web server and API settings"},discovery:{label:"Discovery",description:"Service discovery and networking"},canvasHost:{label:"Canvas Host",description:"Canvas rendering and display"},talk:{label:"Talk",description:"Voice and speech settings"},plugins:{label:"Plugins",description:"Plugin management and extensions"}};function Do(e){return Oo[e]??Oo.default}function Np(e,t,n){if(!n)return!0;const s=n.toLowerCase(),i=di[e];return e.toLowerCase().includes(s)||i&&(i.label.toLowerCase().includes(s)||i.description.toLowerCase().includes(s))?!0:vt(t,s)}function vt(e,t){if(e.title?.toLowerCase().includes(t)||e.description?.toLowerCase().includes(t)||e.enum?.some(s=>String(s).toLowerCase().includes(t)))return!0;if(e.properties){for(const[s,i]of Object.entries(e.properties))if(s.toLowerCase().includes(t)||vt(i,t))return!0}if(e.items){const s=Array.isArray(e.items)?e.items:[e.items];for(const i of s)if(i&&vt(i,t))return!0}if(e.additionalProperties&&typeof e.additionalProperties=="object"&&vt(e.additionalProperties,t))return!0;const n=e.anyOf??e.oneOf??e.allOf;if(n){for(const s of n)if(s&&vt(s,t))return!0}return!1}function Op(e){if(!e.schema)return c`Schema unavailable.`;const t=e.schema,n=e.value??{};if(de(t)!=="object"||!t.properties)return c`Unsupported schema. Use Raw.`;const s=new Set(e.unsupportedPaths??[]),i=t.properties,o=e.searchQuery??"",a=e.activeSection,l=e.activeSubsection??null,p=Object.entries(i).sort((u,h)=>{const v=ee([u[0]],e.uiHints)?.order??50,w=ee([h[0]],e.uiHints)?.order??50;return v!==w?v-w:u[0].localeCompare(h[0])}).filter(([u,h])=>!(a&&u!==a||o&&!Np(u,h,o)));let d=null;if(a&&l&&p.length===1){const u=p[0]?.[1];u&&de(u)==="object"&&u.properties&&u.properties[l]&&(d={sectionKey:a,subsectionKey:l,schema:u.properties[l]})}return p.length===0?c`
+
+ 🔍
+
+ ${o?`No settings match "${o}"`:"No settings in this section"}
+
+
+ `:c`
+
+ ${d?(()=>{const{sectionKey:u,subsectionKey:h,schema:v}=d,w=ee([u,h],e.uiHints),$=w?.label??v.title??ye(h),x=w?.help??v.description??"",C=n[u],I=C&&typeof C=="object"?C[h]:void 0,R=`config-section-${u}-${h}`;return c`
+
+
+ ${Do(u)}
+
+ ${$}
+ ${x?c`${x}
`:g}
+
+
+
+ ${be({schema:v,value:I,path:[u,h],hints:e.uiHints,unsupported:s,disabled:e.disabled??!1,showLabel:!1,onPatch:e.onPatch})}
+
+
+ `})():p.map(([u,h])=>{const v=di[u]??{label:u.charAt(0).toUpperCase()+u.slice(1),description:h.description??""};return c`
+
+
+ ${Do(u)}
+
+ ${v.label}
+ ${v.description?c`${v.description}
`:g}
+
+
+
+ ${be({schema:h,value:n[u],path:[u],hints:e.uiHints,unsupported:s,disabled:e.disabled??!1,showLabel:!1,onPatch:e.onPatch})}
+
+
+ `})}
+
+ `}const Dp=new Set(["title","description","default","nullable"]);function Bp(e){return Object.keys(e??{}).filter(n=>!Dp.has(n)).length===0}function fr(e){const t=e.filter(i=>i!=null),n=t.length!==e.length,s=[];for(const i of t)s.some(o=>Object.is(o,i))||s.push(i);return{enumValues:s,nullable:n}}function hr(e){return!e||typeof e!="object"?{schema:null,unsupportedPaths:[""]}:yt(e,[])}function yt(e,t){const n=new Set,s={...e},i=mn(t)||"";if(e.anyOf||e.oneOf||e.allOf){const l=Fp(e,t);return l||{schema:e,unsupportedPaths:[i]}}const o=Array.isArray(e.type)&&e.type.includes("null"),a=de(e)??(e.properties||e.additionalProperties?"object":void 0);if(s.type=a??e.type,s.nullable=o||e.nullable,s.enum){const{enumValues:l,nullable:r}=fr(s.enum);s.enum=l,r&&(s.nullable=!0),l.length===0&&n.add(i)}if(a==="object"){const l=e.properties??{},r={};for(const[p,d]of Object.entries(l)){const u=yt(d,[...t,p]);u.schema&&(r[p]=u.schema);for(const h of u.unsupportedPaths)n.add(h)}if(s.properties=r,e.additionalProperties===!0)n.add(i);else if(e.additionalProperties===!1)s.additionalProperties=!1;else if(e.additionalProperties&&typeof e.additionalProperties=="object"&&!Bp(e.additionalProperties)){const p=yt(e.additionalProperties,[...t,"*"]);s.additionalProperties=p.schema??e.additionalProperties,p.unsupportedPaths.length>0&&n.add(i)}}else if(a==="array"){const l=Array.isArray(e.items)?e.items[0]:e.items;if(!l)n.add(i);else{const r=yt(l,[...t,"*"]);s.items=r.schema??l,r.unsupportedPaths.length>0&&n.add(i)}}else a!=="string"&&a!=="number"&&a!=="integer"&&a!=="boolean"&&!s.enum&&n.add(i);return{schema:s,unsupportedPaths:Array.from(n)}}function Fp(e,t){if(e.allOf)return null;const n=e.anyOf??e.oneOf;if(!n)return null;const s=[],i=[];let o=!1;for(const l of n){if(!l||typeof l!="object")return null;if(Array.isArray(l.enum)){const{enumValues:r,nullable:p}=fr(l.enum);s.push(...r),p&&(o=!0);continue}if("const"in l){if(l.const==null){o=!0;continue}s.push(l.const);continue}if(de(l)==="null"){o=!0;continue}i.push(l)}if(s.length>0&&i.length===0){const l=[];for(const r of s)l.some(p=>Object.is(p,r))||l.push(r);return{schema:{...e,enum:l,nullable:o,anyOf:void 0,oneOf:void 0,allOf:void 0},unsupportedPaths:[]}}if(i.length===1){const l=yt(i[0],t);return l.schema&&(l.schema.nullable=o||l.schema.nullable),l}const a=["string","number","integer","boolean"];return i.length>0&&s.length===0&&i.every(l=>l.type&&a.includes(String(l.type)))?{schema:{...e,nullable:o},unsupportedPaths:[]}:null}const Ss={all:c``,env:c``,update:c``,agents:c``,auth:c``,channels:c``,messages:c``,commands:c``,hooks:c``,skills:c``,tools:c``,gateway:c``,wizard:c``,meta:c``,logging:c``,browser:c``,ui:c``,models:c``,bindings:c``,broadcast:c``,audio:c``,session:c``,cron:c``,web:c``,discovery:c``,canvasHost:c``,talk:c``,plugins:c``,default:c``},Bo=[{key:"env",label:"Environment"},{key:"update",label:"Updates"},{key:"agents",label:"Agents"},{key:"auth",label:"Authentication"},{key:"channels",label:"Channels"},{key:"messages",label:"Messages"},{key:"commands",label:"Commands"},{key:"hooks",label:"Hooks"},{key:"skills",label:"Skills"},{key:"tools",label:"Tools"},{key:"gateway",label:"Gateway"},{key:"wizard",label:"Setup Wizard"}],Fo="__all__";function Uo(e){return Ss[e]??Ss.default}function Up(e,t){const n=di[e];return n||{label:t?.title??ye(e),description:t?.description??""}}function Kp(e){const{key:t,schema:n,uiHints:s}=e;if(!n||de(n)!=="object"||!n.properties)return[];const i=Object.entries(n.properties).map(([o,a])=>{const l=ee([t,o],s),r=l?.label??a.title??ye(o),p=l?.help??a.description??"",d=l?.order??50;return{key:o,label:r,description:p,order:d}});return i.sort((o,a)=>o.order!==a.order?o.order-a.order:o.key.localeCompare(a.key)),i}function Hp(e,t){if(!e||!t)return[];const n=[];function s(i,o,a){if(i===o)return;if(typeof i!=typeof o){n.push({path:a,from:i,to:o});return}if(typeof i!="object"||i===null||o===null){i!==o&&n.push({path:a,from:i,to:o});return}if(Array.isArray(i)&&Array.isArray(o)){JSON.stringify(i)!==JSON.stringify(o)&&n.push({path:a,from:i,to:o});return}const l=i,r=o,p=new Set([...Object.keys(l),...Object.keys(r)]);for(const d of p)s(l[d],r[d],a?`${a}.${d}`:d)}return s(e,t,""),n}function Ko(e,t=40){let n;try{n=JSON.stringify(e)??String(e)}catch{n=String(e)}return n.length<=t?n:n.slice(0,t-3)+"..."}function zp(e){const t=e.valid==null?"unknown":e.valid?"valid":"invalid",n=hr(e.schema),s=n.schema?n.unsupportedPaths.length>0:!1,i=!!e.formValue&&!e.loading&&!s,o=e.connected&&!e.saving&&(e.formMode==="raw"?!0:i),a=e.connected&&!e.applying&&!e.updating&&(e.formMode==="raw"?!0:i),l=e.connected&&!e.applying&&!e.updating,r=n.schema?.properties??{},p=Bo.filter(A=>A.key in r),d=new Set(Bo.map(A=>A.key)),u=Object.keys(r).filter(A=>!d.has(A)).map(A=>({key:A,label:A.charAt(0).toUpperCase()+A.slice(1)})),h=[...p,...u],v=e.activeSection&&n.schema&&de(n.schema)==="object"?n.schema.properties?.[e.activeSection]:void 0,w=e.activeSection?Up(e.activeSection,v):null,$=e.activeSection?Kp({key:e.activeSection,schema:v,uiHints:e.uiHints}):[],x=e.formMode==="form"&&!!e.activeSection&&$.length>0,C=e.activeSubsection===Fo,I=e.searchQuery||C?null:e.activeSubsection??$[0]?.key??null,R=e.formMode==="form"?Hp(e.originalValue,e.formValue):[],E=R.length>0;return c`
+
+
+
+
+
+
+
+
+
+ ${E?c`
+ ${R.length} unsaved change${R.length!==1?"s":""}
+ `:c`
+ No changes
+ `}
+
+
+
+
+
+
+
+
+
+
+ ${E?c`
+
+
+ View ${R.length} pending change${R.length!==1?"s":""}
+
+
+
+ ${R.map(A=>c`
+
+ ${A.path}
+
+ ${Ko(A.from)}
+ →
+ ${Ko(A.to)}
+
+
+ `)}
+
+
+ `:g}
+
+ ${w&&e.formMode==="form"?c`
+
+ ${Uo(e.activeSection??"")}
+
+ ${w.label}
+ ${w.description?c`${w.description}`:g}
+
+
+ `:g}
+
+ ${x?c`
+
+ `:g}
+
+
+
+ ${e.formMode==="form"?c`
+ ${e.schemaLoading?c`
+
+ Loading schema…
+ `:Op({schema:n.schema,uiHints:e.uiHints,value:e.formValue,disabled:e.loading||!e.formValue,unsupportedPaths:n.unsupportedPaths,onPatch:e.onFormPatch,searchQuery:e.searchQuery,activeSection:e.activeSection,activeSubsection:I})}
+ ${s?c`
+ Form view can't safely edit some fields.
+ Use Raw to avoid losing config entries.
+ `:g}
+ `:c`
+
+ `}
+
+
+ ${e.issues.length>0?c`
+ ${JSON.stringify(e.issues,null,2)}
+ `:g}
+
+
+ `}function jp(e){if(!e&&e!==0)return"n/a";const t=Math.round(e/1e3);if(t<60)return`${t}s`;const n=Math.round(t/60);return n<60?`${n}m`:`${Math.round(n/60)}h`}function qp(e,t){const n=t.snapshot,s=n?.channels;if(!n||!s)return!1;const i=s[e],o=typeof i?.configured=="boolean"&&i.configured,a=typeof i?.running=="boolean"&&i.running,l=typeof i?.connected=="boolean"&&i.connected,p=(n.channelAccounts?.[e]??[]).some(d=>d.configured||d.running||d.connected);return o||a||l||p}function Wp(e,t){return t?.[e]?.length??0}function gr(e,t){const n=Wp(e,t);return n<2?g:c`Accounts (${n})`}function Vp(e,t){let n=e;for(const s of t){if(!n)return null;const i=de(n);if(i==="object"){const o=n.properties??{};if(typeof s=="string"&&o[s]){n=o[s];continue}const a=n.additionalProperties;if(typeof s=="string"&&a&&typeof a=="object"){n=a;continue}return null}if(i==="array"){if(typeof s!="number")return null;n=(Array.isArray(n.items)?n.items[0]:n.items)??null;continue}return null}return n}function Gp(e,t){const s=(e.channels??{})[t],i=e[t];return(s&&typeof s=="object"?s:null)??(i&&typeof i=="object"?i:null)??{}}function Yp(e){const t=hr(e.schema),n=t.schema;if(!n)return c`Schema unavailable. Use Raw.`;const s=Vp(n,["channels",e.channelId]);if(!s)return c`Channel config schema unavailable.`;const i=e.configValue??{},o=Gp(i,e.channelId);return c`
+
+ ${be({schema:s,value:o,path:["channels",e.channelId],hints:e.uiHints,unsupported:new Set(t.unsupportedPaths),disabled:e.disabled,showLabel:!1,onPatch:e.onPatch})}
+
+ `}function _e(e){const{channelId:t,props:n}=e,s=n.configSaving||n.configSchemaLoading;return c`
+
+ ${n.configSchemaLoading?c`Loading config schema…`:Yp({channelId:t,configValue:n.configForm,schema:n.configSchema,uiHints:n.configUiHints,disabled:s,onPatch:n.onConfigPatch})}
+
+
+
+
+
+ `}function Qp(e){const{props:t,discord:n,accountCountLabel:s}=e;return c`
+
+ Discord
+ Bot status and channel configuration.
+ ${s}
+
+
+
+ Configured
+ ${n?.configured?"Yes":"No"}
+
+
+ Running
+ ${n?.running?"Yes":"No"}
+
+
+ Last start
+ ${n?.lastStartAt?O(n.lastStartAt):"n/a"}
+
+
+ Last probe
+ ${n?.lastProbeAt?O(n.lastProbeAt):"n/a"}
+
+
+
+ ${n?.lastError?c`
+ ${n.lastError}
+ `:g}
+
+ ${n?.probe?c`
+ Probe ${n.probe.ok?"ok":"failed"} ·
+ ${n.probe.status??""} ${n.probe.error??""}
+ `:g}
+
+ ${_e({channelId:"discord",props:t})}
+
+
+
+
+
+ `}function Jp(e){const{props:t,imessage:n,accountCountLabel:s}=e;return c`
+
+ iMessage
+ macOS bridge status and channel configuration.
+ ${s}
+
+
+
+ Configured
+ ${n?.configured?"Yes":"No"}
+
+
+ Running
+ ${n?.running?"Yes":"No"}
+
+
+ Last start
+ ${n?.lastStartAt?O(n.lastStartAt):"n/a"}
+
+
+ Last probe
+ ${n?.lastProbeAt?O(n.lastProbeAt):"n/a"}
+
+
+
+ ${n?.lastError?c`
+ ${n.lastError}
+ `:g}
+
+ ${n?.probe?c`
+ Probe ${n.probe.ok?"ok":"failed"} ·
+ ${n.probe.error??""}
+ `:g}
+
+ ${_e({channelId:"imessage",props:t})}
+
+
+
+
+
+ `}function Zp(e){const{values:t,original:n}=e;return t.name!==n.name||t.displayName!==n.displayName||t.about!==n.about||t.picture!==n.picture||t.banner!==n.banner||t.website!==n.website||t.nip05!==n.nip05||t.lud16!==n.lud16}function Xp(e){const{state:t,callbacks:n,accountId:s}=e,i=Zp(t),o=(l,r,p={})=>{const{type:d="text",placeholder:u,maxLength:h,help:v}=p,w=t.values[l]??"",$=t.fieldErrors[l],x=`nostr-profile-${l}`;return d==="textarea"?c`
+
+
+
+ ${v?c`${v}`:g}
+ ${$?c`${$}`:g}
+
+ `:c`
+
+
+ {const I=C.target;n.onFieldChange(l,I.value)}}
+ ?disabled=${t.saving}
+ />
+ ${v?c`${v}`:g}
+ ${$?c`${$}`:g}
+
+ `},a=()=>{const l=t.values.picture;return l?c`
+
+
{const p=r.target;p.style.display="none"}}
+ @load=${r=>{const p=r.target;p.style.display="block"}}
+ />
+
+ `:g};return c`
+
+
+ Edit Profile
+ Account: ${s}
+
+
+ ${t.error?c`${t.error}`:g}
+
+ ${t.success?c`${t.success}`:g}
+
+ ${a()}
+
+ ${o("name","Username",{placeholder:"satoshi",maxLength:256,help:"Short username (e.g., satoshi)"})}
+
+ ${o("displayName","Display Name",{placeholder:"Satoshi Nakamoto",maxLength:256,help:"Your full display name"})}
+
+ ${o("about","Bio",{type:"textarea",placeholder:"Tell people about yourself...",maxLength:2e3,help:"A brief bio or description"})}
+
+ ${o("picture","Avatar URL",{type:"url",placeholder:"https://example.com/avatar.jpg",help:"HTTPS URL to your profile picture"})}
+
+ ${t.showAdvanced?c`
+
+ Advanced
+
+ ${o("banner","Banner URL",{type:"url",placeholder:"https://example.com/banner.jpg",help:"HTTPS URL to a banner image"})}
+
+ ${o("website","Website",{type:"url",placeholder:"https://example.com",help:"Your personal website"})}
+
+ ${o("nip05","NIP-05 Identifier",{placeholder:"you@example.com",help:"Verifiable identifier (e.g., you@domain.com)"})}
+
+ ${o("lud16","Lightning Address",{placeholder:"you@getalby.com",help:"Lightning address for tips (LUD-16)"})}
+
+ `:g}
+
+
+
+
+
+
+
+
+
+
+
+ ${i?c`
+ You have unsaved changes
+ `:g}
+
+ `}function ef(e){const t={name:e?.name??"",displayName:e?.displayName??"",about:e?.about??"",picture:e?.picture??"",banner:e?.banner??"",website:e?.website??"",nip05:e?.nip05??"",lud16:e?.lud16??""};return{values:t,original:{...t},saving:!1,importing:!1,error:null,success:null,fieldErrors:{},showAdvanced:!!(e?.banner||e?.website||e?.nip05||e?.lud16)}}function Ho(e){return e?e.length<=20?e:`${e.slice(0,8)}...${e.slice(-8)}`:"n/a"}function tf(e){const{props:t,nostr:n,nostrAccounts:s,accountCountLabel:i,profileFormState:o,profileFormCallbacks:a,onEditProfile:l}=e,r=s[0],p=n?.configured??r?.configured??!1,d=n?.running??r?.running??!1,u=n?.publicKey??r?.publicKey,h=n?.lastStartAt??r?.lastStartAt??null,v=n?.lastError??r?.lastError??null,w=s.length>1,$=o!=null,x=I=>{const R=I.publicKey,E=I.profile,A=E?.displayName??E?.name??I.name??I.accountId;return c`
+
+
+ ${A}
+ ${I.accountId}
+
+
+
+ Running
+ ${I.running?"Yes":"No"}
+
+
+ Configured
+ ${I.configured?"Yes":"No"}
+
+
+ Public Key
+ ${Ho(R)}
+
+
+ Last inbound
+ ${I.lastInboundAt?O(I.lastInboundAt):"n/a"}
+
+ ${I.lastError?c`
+ ${I.lastError}
+ `:g}
+
+
+ `},C=()=>{if($&&a)return Xp({state:o,callbacks:a,accountId:s[0]?.accountId??"default"});const I=r?.profile??n?.profile,{name:R,displayName:E,about:A,picture:B,nip05:ue}=I??{},bn=R||E||A||B||ue;return c`
+
+
+ Profile
+ ${p?c`
+
+ `:g}
+
+ ${bn?c`
+
+ ${B?c`
+
+
{yn.target.style.display="none"}}
+ />
+
+ `:g}
+ ${R?c`Name${R}`:g}
+ ${E?c`Display Name${E}`:g}
+ ${A?c`About`:g}
+ ${ue?c`NIP-05${ue}`:g}
+
+ `:c`
+
+ No profile set. Click "Edit Profile" to add your name, bio, and avatar.
+
+ `}
+
+ `};return c`
+
+ Nostr
+ Decentralized DMs via Nostr relays (NIP-04).
+ ${i}
+
+ ${w?c`
+
+ ${s.map(I=>x(I))}
+
+ `:c`
+
+
+ Configured
+ ${p?"Yes":"No"}
+
+
+ Running
+ ${d?"Yes":"No"}
+
+
+ Public Key
+ ${Ho(u)}
+
+
+ Last start
+ ${h?O(h):"n/a"}
+
+
+ `}
+
+ ${v?c`${v}`:g}
+
+ ${C()}
+
+ ${_e({channelId:"nostr",props:t})}
+
+
+
+
+
+ `}function nf(e){const{props:t,signal:n,accountCountLabel:s}=e;return c`
+
+ Signal
+ signal-cli status and channel configuration.
+ ${s}
+
+
+
+ Configured
+ ${n?.configured?"Yes":"No"}
+
+
+ Running
+ ${n?.running?"Yes":"No"}
+
+
+ Base URL
+ ${n?.baseUrl??"n/a"}
+
+
+ Last start
+ ${n?.lastStartAt?O(n.lastStartAt):"n/a"}
+
+
+ Last probe
+ ${n?.lastProbeAt?O(n.lastProbeAt):"n/a"}
+
+
+
+ ${n?.lastError?c`
+ ${n.lastError}
+ `:g}
+
+ ${n?.probe?c`
+ Probe ${n.probe.ok?"ok":"failed"} ·
+ ${n.probe.status??""} ${n.probe.error??""}
+ `:g}
+
+ ${_e({channelId:"signal",props:t})}
+
+
+
+
+
+ `}function sf(e){const{props:t,slack:n,accountCountLabel:s}=e;return c`
+
+ Slack
+ Socket mode status and channel configuration.
+ ${s}
+
+
+
+ Configured
+ ${n?.configured?"Yes":"No"}
+
+
+ Running
+ ${n?.running?"Yes":"No"}
+
+
+ Last start
+ ${n?.lastStartAt?O(n.lastStartAt):"n/a"}
+
+
+ Last probe
+ ${n?.lastProbeAt?O(n.lastProbeAt):"n/a"}
+
+
+
+ ${n?.lastError?c`
+ ${n.lastError}
+ `:g}
+
+ ${n?.probe?c`
+ Probe ${n.probe.ok?"ok":"failed"} ·
+ ${n.probe.status??""} ${n.probe.error??""}
+ `:g}
+
+ ${_e({channelId:"slack",props:t})}
+
+
+
+
+
+ `}function of(e){const{props:t,telegram:n,telegramAccounts:s,accountCountLabel:i}=e,o=s.length>1,a=l=>{const p=l.probe?.bot?.username,d=l.name||l.accountId;return c`
+
+
+
+ ${p?`@${p}`:d}
+
+ ${l.accountId}
+
+
+
+ Running
+ ${l.running?"Yes":"No"}
+
+
+ Configured
+ ${l.configured?"Yes":"No"}
+
+
+ Last inbound
+ ${l.lastInboundAt?O(l.lastInboundAt):"n/a"}
+
+ ${l.lastError?c`
+
+ ${l.lastError}
+
+ `:g}
+
+
+ `};return c`
+
+ Telegram
+ Bot status and channel configuration.
+ ${i}
+
+ ${o?c`
+
+ ${s.map(l=>a(l))}
+
+ `:c`
+
+
+ Configured
+ ${n?.configured?"Yes":"No"}
+
+
+ Running
+ ${n?.running?"Yes":"No"}
+
+
+ Mode
+ ${n?.mode??"n/a"}
+
+
+ Last start
+ ${n?.lastStartAt?O(n.lastStartAt):"n/a"}
+
+
+ Last probe
+ ${n?.lastProbeAt?O(n.lastProbeAt):"n/a"}
+
+
+ `}
+
+ ${n?.lastError?c`
+ ${n.lastError}
+ `:g}
+
+ ${n?.probe?c`
+ Probe ${n.probe.ok?"ok":"failed"} ·
+ ${n.probe.status??""} ${n.probe.error??""}
+ `:g}
+
+ ${_e({channelId:"telegram",props:t})}
+
+
+
+
+
+ `}function af(e){const{props:t,whatsapp:n,accountCountLabel:s}=e;return c`
+
+ WhatsApp
+ Link WhatsApp Web and monitor connection health.
+ ${s}
+
+
+
+ Configured
+ ${n?.configured?"Yes":"No"}
+
+
+ Linked
+ ${n?.linked?"Yes":"No"}
+
+
+ Running
+ ${n?.running?"Yes":"No"}
+
+
+ Connected
+ ${n?.connected?"Yes":"No"}
+
+
+ Last connect
+
+ ${n?.lastConnectedAt?O(n.lastConnectedAt):"n/a"}
+
+
+
+ Last message
+
+ ${n?.lastMessageAt?O(n.lastMessageAt):"n/a"}
+
+
+
+ Auth age
+
+ ${n?.authAgeMs!=null?jp(n.authAgeMs):"n/a"}
+
+
+
+
+ ${n?.lastError?c`
+ ${n.lastError}
+ `:g}
+
+ ${t.whatsappMessage?c`
+ ${t.whatsappMessage}
+ `:g}
+
+ ${t.whatsappQrDataUrl?c`
+
+ `:g}
+
+
+
+
+
+
+
+
+
+ ${_e({channelId:"whatsapp",props:t})}
+
+ `}function rf(e){const t=e.snapshot?.channels,n=t?.whatsapp??void 0,s=t?.telegram??void 0,i=t?.discord??null,o=t?.slack??null,a=t?.signal??null,l=t?.imessage??null,r=t?.nostr??null,d=lf(e.snapshot).map((u,h)=>({key:u,enabled:qp(u,e),order:h})).sort((u,h)=>u.enabled!==h.enabled?u.enabled?-1:1:u.order-h.order);return c`
+
+ ${d.map(u=>cf(u.key,e,{whatsapp:n,telegram:s,discord:i,slack:o,signal:a,imessage:l,nostr:r,channelAccounts:e.snapshot?.channelAccounts??null}))}
+
+
+
+
+
+ Channel health
+ Channel status snapshots from the gateway.
+
+ ${e.lastSuccessAt?O(e.lastSuccessAt):"n/a"}
+
+ ${e.lastError?c`
+ ${e.lastError}
+ `:g}
+
+${e.snapshot?JSON.stringify(e.snapshot,null,2):"No snapshot yet."}
+
+
+ `}function lf(e){return e?.channelMeta?.length?e.channelMeta.map(t=>t.id):e?.channelOrder?.length?e.channelOrder:["whatsapp","telegram","discord","slack","signal","imessage","nostr"]}function cf(e,t,n){const s=gr(e,n.channelAccounts);switch(e){case"whatsapp":return af({props:t,whatsapp:n.whatsapp,accountCountLabel:s});case"telegram":return of({props:t,telegram:n.telegram,telegramAccounts:n.channelAccounts?.telegram??[],accountCountLabel:s});case"discord":return Qp({props:t,discord:n.discord,accountCountLabel:s});case"slack":return sf({props:t,slack:n.slack,accountCountLabel:s});case"signal":return nf({props:t,signal:n.signal,accountCountLabel:s});case"imessage":return Jp({props:t,imessage:n.imessage,accountCountLabel:s});case"nostr":{const i=n.channelAccounts?.nostr??[],o=i[0],a=o?.accountId??"default",l=o?.profile??null,r=t.nostrProfileAccountId===a?t.nostrProfileFormState:null,p=r?{onFieldChange:t.onNostrProfileFieldChange,onSave:t.onNostrProfileSave,onImport:t.onNostrProfileImport,onCancel:t.onNostrProfileCancel,onToggleAdvanced:t.onNostrProfileToggleAdvanced}:null;return tf({props:t,nostr:n.nostr,nostrAccounts:i,accountCountLabel:s,profileFormState:r,profileFormCallbacks:p,onEditProfile:()=>t.onNostrProfileEdit(a,l)})}default:return df(e,t,n.channelAccounts??{})}}function df(e,t,n){const s=pf(t.snapshot,e),i=t.snapshot?.channels?.[e],o=typeof i?.configured=="boolean"?i.configured:void 0,a=typeof i?.running=="boolean"?i.running:void 0,l=typeof i?.connected=="boolean"?i.connected:void 0,r=typeof i?.lastError=="string"?i.lastError:void 0,p=n[e]??[],d=gr(e,n);return c`
+
+ ${s}
+ Channel status and configuration.
+ ${d}
+
+ ${p.length>0?c`
+
+ ${p.map(u=>vf(u))}
+
+ `:c`
+
+
+ Configured
+ ${o==null?"n/a":o?"Yes":"No"}
+
+
+ Running
+ ${a==null?"n/a":a?"Yes":"No"}
+
+
+ Connected
+ ${l==null?"n/a":l?"Yes":"No"}
+
+
+ `}
+
+ ${r?c`
+ ${r}
+ `:g}
+
+ ${_e({channelId:e,props:t})}
+
+ `}function uf(e){return e?.channelMeta?.length?Object.fromEntries(e.channelMeta.map(t=>[t.id,t])):{}}function pf(e,t){return uf(e)[t]?.label??e?.channelLabels?.[t]??t}const ff=600*1e3;function vr(e){return e.lastInboundAt?Date.now()-e.lastInboundAt
+
+ ${e.name||e.accountId}
+ ${e.accountId}
+
+
+
+ Running
+ ${t}
+
+
+ Configured
+ ${e.configured?"Yes":"No"}
+
+
+ Connected
+ ${n}
+
+
+ Last inbound
+ ${e.lastInboundAt?O(e.lastInboundAt):"n/a"}
+
+ ${e.lastError?c`
+
+ ${e.lastError}
+
+ `:g}
+
+
+ `}function mf(e){const t=e.host??"unknown",n=e.ip?`(${e.ip})`:"",s=e.mode??"",i=e.version??"";return`${t} ${n} ${s} ${i}`.trim()}function bf(e){const t=e.ts??null;return t?O(t):"n/a"}function mr(e){return e?`${xt(e)} (${O(e)})`:"n/a"}function yf(e){if(e.totalTokens==null)return"n/a";const t=e.totalTokens??0,n=e.contextTokens??0;return n?`${t} / ${n}`:String(t)}function wf(e){if(e==null)return"";try{return JSON.stringify(e,null,2)}catch{return String(e)}}function $f(e){const t=e.state??{},n=t.nextRunAtMs?xt(t.nextRunAtMs):"n/a",s=t.lastRunAtMs?xt(t.lastRunAtMs):"n/a";return`${t.lastStatus??"n/a"} · next ${n} · last ${s}`}function kf(e){const t=e.schedule;return t.kind==="at"?`At ${xt(t.atMs)}`:t.kind==="every"?`Every ${ra(t.everyMs)}`:`Cron ${t.expr}${t.tz?` (${t.tz})`:""}`}function xf(e){const t=e.payload;return t.kind==="systemEvent"?`System: ${t.text}`:`Agent: ${t.message}`}function Af(e){const t=["last",...e.channels.filter(Boolean)],n=e.form.channel?.trim();n&&!t.includes(n)&&t.push(n);const s=new Set;return t.filter(i=>s.has(i)?!1:(s.add(i),!0))}function Sf(e,t){if(t==="last")return"last";const n=e.channelMeta?.find(s=>s.id===t);return n?.label?n.label:e.channelLabels?.[t]??t}function _f(e){const t=Af(e);return c`
+
+
+ Scheduler
+ Gateway-owned cron scheduler status.
+
+
+ Enabled
+
+ ${e.status?e.status.enabled?"Yes":"No":"n/a"}
+
+
+
+ Jobs
+ ${e.status?.jobs??"n/a"}
+
+
+ Next wake
+ ${mr(e.status?.nextWakeAtMs??null)}
+
+
+
+
+ ${e.error?c`${e.error}`:g}
+
+
+
+
+ New Job
+ Create a scheduled wakeup or agent run.
+
+
+
+
+
+
+
+ ${Tf(e)}
+
+
+
+
+
+
+ ${e.form.payloadKind==="agentTurn"?c`
+
+
+
+
+
+ ${e.form.sessionTarget==="isolated"?c`
+
+ `:g}
+
+ `:g}
+
+
+
+
+
+
+
+ Jobs
+ All scheduled jobs stored in the gateway.
+ ${e.jobs.length===0?c`No jobs yet.`:c`
+
+ ${e.jobs.map(n=>Cf(n,e))}
+
+ `}
+
+
+
+ Run history
+ Latest runs for ${e.runsJobId??"(select a job)"}.
+ ${e.runsJobId==null?c`
+
+ Select a job to inspect run history.
+
+ `:e.runs.length===0?c`No runs yet.`:c`
+
+ ${e.runs.map(n=>Ef(n))}
+
+ `}
+
+ `}function Tf(e){const t=e.form;return t.scheduleKind==="at"?c`
+
+ `:t.scheduleKind==="every"?c`
+
+
+
+
+ `:c`
+
+
+
+
+ `}function Cf(e,t){const s=`list-item list-item-clickable${t.runsJobId===e.id?" list-item-selected":""}`;return c`
+ t.onLoadRuns(e.id)}>
+
+ ${e.name}
+ ${kf(e)}
+ ${xf(e)}
+ ${e.agentId?c`Agent: ${e.agentId}`:g}
+
+ ${e.enabled?"enabled":"disabled"}
+ ${e.sessionTarget}
+ ${e.wakeMode}
+
+
+
+
+ `}function Ef(e){return c`
+
+
+ ${e.status}
+ ${e.summary??""}
+
+
+
+ `}function If(e){return c`
+
+
+
+
+ Snapshots
+ Status, health, and heartbeat data.
+
+
+
+
+
+ Status
+ ${JSON.stringify(e.status??{},null,2)}
+
+
+ Health
+ ${JSON.stringify(e.health??{},null,2)}
+
+
+ Last heartbeat
+ ${JSON.stringify(e.heartbeat??{},null,2)}
+
+
+
+
+
+ Manual RPC
+ Send a raw gateway method with JSON params.
+
+
+
+
+
+
+
+ ${e.callError?c`
+ ${e.callError}
+ `:g}
+ ${e.callResult?c`${e.callResult}`:g}
+
+
+
+
+ Models
+ Catalog from models.list.
+ ${JSON.stringify(e.models??[],null,2)}
+
+
+
+ Event Log
+ Latest gateway events.
+ ${e.eventLog.length===0?c`No events yet.`:c`
+
+ ${e.eventLog.map(t=>c`
+
+
+ ${t.event}
+ ${new Date(t.ts).toLocaleTimeString()}
+
+
+
+ `)}
+
+ `}
+
+ `}function Lf(e){return c`
+
+
+
+ Connected Instances
+ Presence beacons from the gateway and clients.
+
+
+
+ ${e.lastError?c`
+ ${e.lastError}
+ `:g}
+ ${e.statusMessage?c`
+ ${e.statusMessage}
+ `:g}
+
+ ${e.entries.length===0?c`No instances reported yet.`:e.entries.map(t=>Rf(t))}
+
+
+ `}function Rf(e){const t=e.lastInputSeconds!=null?`${e.lastInputSeconds}s ago`:"n/a",n=e.mode??"unknown",s=Array.isArray(e.roles)?e.roles.filter(Boolean):[],i=Array.isArray(e.scopes)?e.scopes.filter(Boolean):[],o=i.length>0?i.length>3?`${i.length} scopes`:`scopes: ${i.join(", ")}`:null;return c`
+
+
+ ${e.host??"unknown host"}
+ ${mf(e)}
+
+ ${n}
+ ${s.map(a=>c`${a}`)}
+ ${o?c`${o}`:g}
+ ${e.platform?c`${e.platform}`:g}
+ ${e.deviceFamily?c`${e.deviceFamily}`:g}
+ ${e.modelIdentifier?c`${e.modelIdentifier}`:g}
+ ${e.version?c`${e.version}`:g}
+
+
+
+
+ `}const zo=["trace","debug","info","warn","error","fatal"];function Mf(e){if(!e)return"";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleTimeString()}function Pf(e,t){return t?[e.message,e.subsystem,e.raw].filter(Boolean).join(" ").toLowerCase().includes(t):!0}function Nf(e){const t=e.filterText.trim().toLowerCase(),n=zo.some(o=>!e.levelFilters[o]),s=e.entries.filter(o=>o.level&&!e.levelFilters[o.level]?!1:Pf(o,t)),i=t||n?"filtered":"visible";return c`
+
+
+
+ Logs
+ Gateway file logs (JSONL).
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${zo.map(o=>c`
+
+ `)}
+
+
+ ${e.file?c`File: ${e.file}`:g}
+ ${e.truncated?c`
+ Log output truncated; showing latest chunk.
+ `:g}
+ ${e.error?c`${e.error}`:g}
+
+
+ ${s.length===0?c`No log entries.`:s.map(o=>c`
+
+ ${Mf(o.time)}
+ ${o.level??""}
+ ${o.subsystem??""}
+
+
+ `)}
+
+
+ `}function Of(e){const t=Hf(e),n=Gf(e);return c`
+ ${Qf(n)}
+ ${Yf(t)}
+ ${Df(e)}
+
+
+
+ Nodes
+ Paired devices and live links.
+
+
+
+
+ ${e.nodes.length===0?c`No nodes found.`:e.nodes.map(s=>ah(s))}
+
+
+ `}function Df(e){const t=e.devicesList??{pending:[],paired:[]},n=Array.isArray(t.pending)?t.pending:[],s=Array.isArray(t.paired)?t.paired:[];return c`
+
+
+
+ Devices
+ Pairing requests + role tokens.
+
+
+
+ ${e.devicesError?c`${e.devicesError}`:g}
+
+ ${n.length>0?c`
+ Pending
+ ${n.map(i=>Bf(i,e))}
+ `:g}
+ ${s.length>0?c`
+ Paired
+ ${s.map(i=>Ff(i,e))}
+ `:g}
+ ${n.length===0&&s.length===0?c`No paired devices.`:g}
+
+
+ `}function Bf(e,t){const n=e.displayName?.trim()||e.deviceId,s=typeof e.ts=="number"?O(e.ts):"n/a",i=e.role?.trim()?`role: ${e.role}`:"role: -",o=e.isRepair?" · repair":"",a=e.remoteIp?` · ${e.remoteIp}`:"";return c`
+
+
+ ${n}
+ ${e.deviceId}${a}
+
+ ${i} · requested ${s}${o}
+
+
+
+
+ `}function Ff(e,t){const n=e.displayName?.trim()||e.deviceId,s=e.remoteIp?` · ${e.remoteIp}`:"",i=`roles: ${os(e.roles)}`,o=`scopes: ${os(e.scopes)}`,a=Array.isArray(e.tokens)?e.tokens:[];return c`
+
+
+ ${n}
+ ${e.deviceId}${s}
+ ${i} · ${o}
+ ${a.length===0?c`Tokens: none`:c`
+ Tokens
+
+ ${a.map(l=>Uf(e.deviceId,l,t))}
+
+ `}
+
+
+ `}function Uf(e,t,n){const s=t.revokedAtMs?"revoked":"active",i=`scopes: ${os(t.scopes)}`,o=O(t.rotatedAtMs??t.createdAtMs??t.lastUsedAtMs??null);return c`
+
+ ${t.role} · ${s} · ${i} · ${o}
+
+
+ ${t.revokedAtMs?g:c`
+
+ `}
+
+
+ `}const ke="__defaults__",jo=[{value:"deny",label:"Deny"},{value:"allowlist",label:"Allowlist"},{value:"full",label:"Full"}],Kf=[{value:"off",label:"Off"},{value:"on-miss",label:"On miss"},{value:"always",label:"Always"}];function Hf(e){const t=e.configForm,n=sh(e.nodes),{defaultBinding:s,agents:i}=oh(t),o=!!t,a=e.configSaving||e.configFormMode==="raw";return{ready:o,disabled:a,configDirty:e.configDirty,configLoading:e.configLoading,configSaving:e.configSaving,defaultBinding:s,agents:i,nodes:n,onBindDefault:e.onBindDefault,onBindAgent:e.onBindAgent,onSave:e.onSaveBindings,onLoadConfig:e.onLoadConfig,formMode:e.configFormMode}}function qo(e){return e==="allowlist"||e==="full"||e==="deny"?e:"deny"}function zf(e){return e==="always"||e==="off"||e==="on-miss"?e:"on-miss"}function jf(e){const t=e?.defaults??{};return{security:qo(t.security),ask:zf(t.ask),askFallback:qo(t.askFallback??"deny"),autoAllowSkills:!!(t.autoAllowSkills??!1)}}function qf(e){const t=e?.agents??{},n=Array.isArray(t.list)?t.list:[],s=[];return n.forEach(i=>{if(!i||typeof i!="object")return;const o=i,a=typeof o.id=="string"?o.id.trim():"";if(!a)return;const l=typeof o.name=="string"?o.name.trim():void 0,r=o.default===!0;s.push({id:a,name:l||void 0,isDefault:r})}),s}function Wf(e,t){const n=qf(e),s=Object.keys(t?.agents??{}),i=new Map;n.forEach(a=>i.set(a.id,a)),s.forEach(a=>{i.has(a)||i.set(a,{id:a})});const o=Array.from(i.values());return o.length===0&&o.push({id:"main",isDefault:!0}),o.sort((a,l)=>{if(a.isDefault&&!l.isDefault)return-1;if(!a.isDefault&&l.isDefault)return 1;const r=a.name?.trim()?a.name:a.id,p=l.name?.trim()?l.name:l.id;return r.localeCompare(p)}),o}function Vf(e,t){return e===ke?ke:e&&t.some(n=>n.id===e)?e:ke}function Gf(e){const t=e.execApprovalsForm??e.execApprovalsSnapshot?.file??null,n=!!t,s=jf(t),i=Wf(e.configForm,t),o=ih(e.nodes),a=e.execApprovalsTarget;let l=a==="node"&&e.execApprovalsTargetNodeId?e.execApprovalsTargetNodeId:null;a==="node"&&l&&!o.some(u=>u.id===l)&&(l=null);const r=Vf(e.execApprovalsSelectedAgent,i),p=r!==ke?(t?.agents??{})[r]??null:null,d=Array.isArray(p?.allowlist)?p.allowlist??[]:[];return{ready:n,disabled:e.execApprovalsSaving||e.execApprovalsLoading,dirty:e.execApprovalsDirty,loading:e.execApprovalsLoading,saving:e.execApprovalsSaving,form:t,defaults:s,selectedScope:r,selectedAgent:p,agents:i,allowlist:d,target:a,targetNodeId:l,targetNodes:o,onSelectScope:e.onExecApprovalsSelectAgent,onSelectTarget:e.onExecApprovalsTargetChange,onPatch:e.onExecApprovalsPatch,onRemove:e.onExecApprovalsRemove,onLoad:e.onLoadExecApprovals,onSave:e.onSaveExecApprovals}}function Yf(e){const t=e.nodes.length>0,n=e.defaultBinding??"";return c`
+
+
+
+ Exec node binding
+
+ Pin agents to a specific node when using exec host=node.
+
+
+
+
+
+ ${e.formMode==="raw"?c`
+ Switch the Config tab to Form mode to edit bindings here.
+ `:g}
+
+ ${e.ready?c`
+
+
+
+ Default binding
+ Used when agents do not override a node binding.
+
+
+
+
+ ${e.agents.length===0?c`No agents found.`:e.agents.map(s=>nh(s,e))}
+
+ `:c`
+ Load config to edit bindings.
+
+ `}
+
+ `}function Qf(e){const t=e.ready,n=e.target!=="node"||!!e.targetNodeId;return c`
+
+
+
+ Exec approvals
+
+ Allowlist and approval policy for exec host=gateway/node.
+
+
+
+
+
+ ${Jf(e)}
+
+ ${t?c`
+ ${Zf(e)}
+ ${Xf(e)}
+ ${e.selectedScope===ke?g:eh(e)}
+ `:c`
+ Load exec approvals to edit allowlists.
+
+ `}
+
+ `}function Jf(e){const t=e.targetNodes.length>0,n=e.targetNodeId??"";return c`
+
+
+
+ Target
+
+ Gateway edits local approvals; node edits the selected node.
+
+
+
+
+ ${e.target==="node"&&!t?c`No nodes advertise exec approvals yet.`:g}
+
+ `}function Zf(e){return c`
+
+ Scope
+
+
+ ${e.agents.map(t=>{const n=t.name?.trim()?`${t.name} (${t.id})`:t.id;return c`
+
+ `})}
+
+
+ `}function Xf(e){const t=e.selectedScope===ke,n=e.defaults,s=e.selectedAgent??{},i=t?["defaults"]:["agents",e.selectedScope],o=typeof s.security=="string"?s.security:void 0,a=typeof s.ask=="string"?s.ask:void 0,l=typeof s.askFallback=="string"?s.askFallback:void 0,r=t?n.security:o??"__default__",p=t?n.ask:a??"__default__",d=t?n.askFallback:l??"__default__",u=typeof s.autoAllowSkills=="boolean"?s.autoAllowSkills:void 0,h=u??n.autoAllowSkills,v=u==null;return c`
+
+
+
+ Security
+
+ ${t?"Default security mode.":`Default: ${n.security}.`}
+
+
+
+
+
+
+
+ Ask
+
+ ${t?"Default prompt policy.":`Default: ${n.ask}.`}
+
+
+
+
+
+
+
+ Ask fallback
+
+ ${t?"Applied when the UI prompt is unavailable.":`Default: ${n.askFallback}.`}
+
+
+
+
+
+
+
+ Auto-allow skill CLIs
+
+ ${t?"Allow skill executables listed by the Gateway.":v?`Using default (${n.autoAllowSkills?"on":"off"}).`:`Override (${h?"on":"off"}).`}
+
+
+
+
+
+ `}function eh(e){const t=["agents",e.selectedScope,"allowlist"],n=e.allowlist;return c`
+
+
+ Allowlist
+ Case-insensitive glob patterns.
+
+
+
+
+ ${n.length===0?c`No allowlist entries yet.`:n.map((s,i)=>th(e,s,i))}
+
+ `}function th(e,t,n){const s=t.lastUsedAt?O(t.lastUsedAt):"never",i=t.lastUsedCommand?as(t.lastUsedCommand,120):null,o=t.lastResolvedPath?as(t.lastResolvedPath,120):null;return c`
+
+
+ ${t.pattern?.trim()?t.pattern:"New pattern"}
+ Last used: ${s}
+ ${i?c`${i}`:g}
+ ${o?c`${o}`:g}
+
+
+
+ `}function nh(e,t){const n=e.binding??"__default__",s=e.name?.trim()?`${e.name} (${e.id})`:e.id,i=t.nodes.length>0;return c`
+
+
+ ${s}
+
+ ${e.isDefault?"default agent":"agent"} ·
+ ${n==="__default__"?`uses default (${t.defaultBinding??"any"})`:`override: ${e.binding}`}
+
+
+
+
+ `}function sh(e){const t=[];for(const n of e){if(!(Array.isArray(n.commands)?n.commands:[]).some(l=>String(l)==="system.run"))continue;const o=typeof n.nodeId=="string"?n.nodeId.trim():"";if(!o)continue;const a=typeof n.displayName=="string"&&n.displayName.trim()?n.displayName.trim():o;t.push({id:o,label:a===o?o:`${a} · ${o}`})}return t.sort((n,s)=>n.label.localeCompare(s.label)),t}function ih(e){const t=[];for(const n of e){if(!(Array.isArray(n.commands)?n.commands:[]).some(l=>String(l)==="system.execApprovals.get"||String(l)==="system.execApprovals.set"))continue;const o=typeof n.nodeId=="string"?n.nodeId.trim():"";if(!o)continue;const a=typeof n.displayName=="string"&&n.displayName.trim()?n.displayName.trim():o;t.push({id:o,label:a===o?o:`${a} · ${o}`})}return t.sort((n,s)=>n.label.localeCompare(s.label)),t}function oh(e){const t={id:"main",name:void 0,index:0,isDefault:!0,binding:null};if(!e||typeof e!="object")return{defaultBinding:null,agents:[t]};const s=(e.tools??{}).exec??{},i=typeof s.node=="string"&&s.node.trim()?s.node.trim():null,o=e.agents??{},a=Array.isArray(o.list)?o.list:[];if(a.length===0)return{defaultBinding:i,agents:[t]};const l=[];return a.forEach((r,p)=>{if(!r||typeof r!="object")return;const d=r,u=typeof d.id=="string"?d.id.trim():"";if(!u)return;const h=typeof d.name=="string"?d.name.trim():void 0,v=d.default===!0,$=(d.tools??{}).exec??{},x=typeof $.node=="string"&&$.node.trim()?$.node.trim():null;l.push({id:u,name:h||void 0,index:p,isDefault:v,binding:x})}),l.length===0&&l.push(t),{defaultBinding:i,agents:l}}function ah(e){const t=!!e.connected,n=!!e.paired,s=typeof e.displayName=="string"&&e.displayName.trim()||(typeof e.nodeId=="string"?e.nodeId:"unknown"),i=Array.isArray(e.caps)?e.caps:[],o=Array.isArray(e.commands)?e.commands:[];return c`
+
+
+ ${s}
+
+ ${typeof e.nodeId=="string"?e.nodeId:""}
+ ${typeof e.remoteIp=="string"?` · ${e.remoteIp}`:""}
+ ${typeof e.version=="string"?` · ${e.version}`:""}
+
+
+ ${n?"paired":"unpaired"}
+
+ ${t?"connected":"offline"}
+
+ ${i.slice(0,12).map(a=>c`${String(a)}`)}
+ ${o.slice(0,8).map(a=>c`${String(a)}`)}
+
+
+
+ `}function rh(e){const t=e.hello?.snapshot,n=t?.uptimeMs?ra(t.uptimeMs):"n/a",s=t?.policy?.tickIntervalMs?`${t.policy.tickIntervalMs}ms`:"n/a",i=(()=>{if(e.connected||!e.lastError)return null;const a=e.lastError.toLowerCase();if(!(a.includes("unauthorized")||a.includes("connect failed")))return null;const r=!!e.settings.token.trim(),p=!!e.password.trim();return!r&&!p?c`
+
+ This gateway requires auth. Add a token or password, then click Connect.
+
+ clawdbot dashboard --no-open → tokenized URL
+ clawdbot doctor --generate-gateway-token → set token
+
+
+
+ `:c`
+
+ Auth failed. Re-copy a tokenized URL with
+ clawdbot dashboard --no-open, or update the token,
+ then click Connect.
+
+
+ `})(),o=(()=>{if(e.connected||!e.lastError||(typeof window<"u"?window.isSecureContext:!0)!==!1)return null;const l=e.lastError.toLowerCase();return!l.includes("secure context")&&!l.includes("device identity required")?null:c`
+
+ This page is HTTP, so the browser blocks device identity. Use HTTPS (Tailscale Serve) or
+ open http://127.0.0.1:18789 on the gateway host.
+
+ If you must stay on HTTP, set
+ gateway.controlUi.allowInsecureAuth: true (token-only).
+
+
+
+ `})();return c`
+
+
+ Gateway Access
+ Where the dashboard connects and how it authenticates.
+
+
+
+
+
+
+
+
+
+ Click Connect to apply connection changes.
+
+
+
+
+ Snapshot
+ Latest gateway handshake information.
+
+
+ Status
+
+ ${e.connected?"Connected":"Disconnected"}
+
+
+
+ Uptime
+ ${n}
+
+
+ Tick Interval
+ ${s}
+
+
+ Last Channels Refresh
+
+ ${e.lastChannelsRefresh?O(e.lastChannelsRefresh):"n/a"}
+
+
+
+ ${e.lastError?c`
+ ${e.lastError}
+ ${i??""}
+ ${o??""}
+ `:c`
+ Use Channels to link WhatsApp, Telegram, Discord, Signal, or iMessage.
+ `}
+
+
+
+
+
+ Instances
+ ${e.presenceCount}
+ Presence beacons in the last 5 minutes.
+
+
+ Sessions
+ ${e.sessionsCount??"n/a"}
+ Recent session keys tracked by the gateway.
+
+
+ Cron
+
+ ${e.cronEnabled==null?"n/a":e.cronEnabled?"Enabled":"Disabled"}
+
+ Next wake ${mr(e.cronNext)}
+
+
+
+
+ Notes
+ Quick reminders for remote control setups.
+
+
+ Tailscale serve
+
+ Prefer serve mode to keep the gateway on loopback with tailnet auth.
+
+
+
+ Session hygiene
+ Use /new or sessions.patch to reset context.
+
+
+ Cron reminders
+ Use isolated sessions for recurring runs.
+
+
+
+ `}const lh=["","off","minimal","low","medium","high"],ch=["","off","on"],dh=[{value:"",label:"inherit"},{value:"off",label:"off (explicit)"},{value:"on",label:"on"}],uh=["","off","on","stream"];function ph(e){if(!e)return"";const t=e.trim().toLowerCase();return t==="z.ai"||t==="z-ai"?"zai":t}function br(e){return ph(e)==="zai"}function fh(e){return br(e)?ch:lh}function hh(e,t){return!t||!e||e==="off"?e:"on"}function gh(e,t){return e?t&&e==="on"?"low":e:null}function vh(e){const t=e.result?.sessions??[];return c`
+
+
+
+ Sessions
+ Active session keys and per-session overrides.
+
+
+
+
+
+
+
+
+
+
+
+ ${e.error?c`${e.error}`:g}
+
+
+ ${e.result?`Store: ${e.result.path}`:""}
+
+
+
+
+ Key
+ Label
+ Kind
+ Updated
+ Tokens
+ Thinking
+ Verbose
+ Reasoning
+ Actions
+
+ ${t.length===0?c`No sessions found.`:t.map(n=>mh(n,e.basePath,e.onPatch,e.onDelete,e.loading))}
+
+
+ `}function mh(e,t,n,s,i){const o=e.updatedAt?O(e.updatedAt):"n/a",a=e.thinkingLevel??"",l=br(e.modelProvider),r=hh(a,l),p=fh(e.modelProvider),d=e.verboseLevel??"",u=e.reasoningLevel??"",h=e.displayName??e.key,v=e.kind!=="global",w=v?`${Ps("chat",t)}?session=${encodeURIComponent(e.key)}`:null;return c`
+
+ ${v?c`${h}`:h}
+
+ {const x=$.target.value.trim();n(e.key,{label:x||null})}}
+ />
+
+ ${e.kind}
+ ${o}
+ ${yf(e)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `}function bh(e){const t=Math.max(0,e),n=Math.floor(t/1e3);if(n<60)return`${n}s`;const s=Math.floor(n/60);return s<60?`${s}m`:`${Math.floor(s/60)}h`}function Le(e,t){return t?c``:g}function yh(e){const t=e.execApprovalQueue[0];if(!t)return g;const n=t.request,s=t.expiresAtMs-Date.now(),i=s>0?`expires in ${bh(s)}`:"expired",o=e.execApprovalQueue.length;return c`
+
+ `}function wh(e){const t=e.report?.skills??[],n=e.filter.trim().toLowerCase(),s=n?t.filter(i=>[i.name,i.description,i.source].join(" ").toLowerCase().includes(n)):t;return c`
+
+
+
+ Skills
+ Bundled, managed, and workspace skills.
+
+
+
+
+
+
+ ${s.length} shown
+
+
+ ${e.error?c`${e.error}`:g}
+
+ ${s.length===0?c`No skills found.`:c`
+
+ ${s.map(i=>$h(i,e))}
+
+ `}
+
+ `}function $h(e,t){const n=t.busyKey===e.skillKey,s=t.edits[e.skillKey]??"",i=t.messages[e.skillKey]??null,o=e.install.length>0&&e.missing.bins.length>0,a=[...e.missing.bins.map(r=>`bin:${r}`),...e.missing.env.map(r=>`env:${r}`),...e.missing.config.map(r=>`config:${r}`),...e.missing.os.map(r=>`os:${r}`)],l=[];return e.disabled&&l.push("disabled"),e.blockedByAllowlist&&l.push("blocked by allowlist"),c`
+
+
+
+ ${e.emoji?`${e.emoji} `:""}${e.name}
+
+ ${as(e.description,140)}
+
+ ${e.source}
+
+ ${e.eligible?"eligible":"blocked"}
+
+ ${e.disabled?c`disabled`:g}
+
+ ${a.length>0?c`
+
+ Missing: ${a.join(", ")}
+
+ `:g}
+ ${l.length>0?c`
+
+ Reason: ${l.join(", ")}
+
+ `:g}
+
+
+
+ `}function kh(e,t){const n=Ps(t,e.basePath);return c`
+ {s.defaultPrevented||s.button!==0||s.metaKey||s.ctrlKey||s.shiftKey||s.altKey||(s.preventDefault(),e.setTab(t))}}
+ title=${is(t)}
+ >
+
+
+
+ `}function xh(e){const t=Ah(e.sessionKey,e.sessionsResult),n=e.onboarding,s=e.onboarding,i=e.onboarding?!1:e.settings.chatShowThinking,o=e.onboarding?!0:e.settings.chatFocusMode,a=c``,l=c``;return c`
+
+
+
+ |
+
+
+
+ `}function Ah(e,t){const n=new Set,s=[],i=t?.sessions?.find(o=>o.key===e);if(n.add(e),s.push({key:e,displayName:i?.displayName}),t?.sessions)for(const o of t.sessions)n.has(o.key)||(n.add(o.key),s.push({key:o.key,displayName:o.displayName}));return s}const Sh=["system","light","dark"];function _h(e){const t=Math.max(0,Sh.indexOf(e.theme)),n=s=>i=>{const a={element:i.currentTarget};(i.clientX||i.clientY)&&(a.pointerClientX=i.clientX,a.pointerClientY=i.clientY),e.setTheme(s,a)};return c`
+
+
+
+
+
+
+
+
+ `}function Th(){return c`
+
+ `}function Ch(){return c`
+
+ `}function Eh(){return c`
+
+ `}const Ih=/^data:/i,Lh=/^https?:\/\//i;function Rh(e){const t=e.agentsList?.agents??[],s=sa(e.sessionKey)?.agentId??e.agentsList?.defaultId??"main",o=t.find(l=>l.id===s)?.identity,a=o?.avatarUrl??o?.avatar;if(a)return Ih.test(a)||Lh.test(a)?a:o?.avatarUrl}function Mh(e){const t=e.presenceEntries.length,n=e.sessionsResult?.count??null,s=e.cronStatus?.nextWakeAtMs??null,i=e.connected?null:"Disconnected from gateway.",o=e.tab==="chat",a=o&&(e.settings.chatFocusMode||e.onboarding),l=e.onboarding?!1:e.settings.chatShowThinking,r=Rh(e),p=e.chatAvatarUrl??r??null;return c`
+
+
+
+
+
+
+
+
+
+ ${is(e.tab)}
+ ${yl(e.tab)}
+
+
+
+
+ ${e.tab==="overview"?rh({connected:e.connected,hello:e.hello,settings:e.settings,password:e.password,lastError:e.lastError,presenceCount:t,sessionsCount:n,cronEnabled:e.cronStatus?.enabled??null,cronNext:s,lastChannelsRefresh:e.channelsLastSuccess,onSettingsChange:d=>e.applySettings(d),onPasswordChange:d=>e.password=d,onSessionKeyChange:d=>{e.sessionKey=d,e.chatMessage="",e.resetToolStream(),e.applySettings({...e.settings,sessionKey:d,lastActiveSessionKey:d}),e.loadAssistantIdentity()},onConnect:()=>e.connect(),onRefresh:()=>e.loadOverview()}):g}
+
+ ${e.tab==="channels"?rf({connected:e.connected,loading:e.channelsLoading,snapshot:e.channelsSnapshot,lastError:e.channelsError,lastSuccessAt:e.channelsLastSuccess,whatsappMessage:e.whatsappLoginMessage,whatsappQrDataUrl:e.whatsappLoginQrDataUrl,whatsappConnected:e.whatsappLoginConnected,whatsappBusy:e.whatsappBusy,configSchema:e.configSchema,configSchemaLoading:e.configSchemaLoading,configForm:e.configForm,configUiHints:e.configUiHints,configSaving:e.configSaving,configFormDirty:e.configFormDirty,nostrProfileFormState:e.nostrProfileFormState,nostrProfileAccountId:e.nostrProfileAccountId,onRefresh:d=>oe(e,d),onWhatsAppStart:d=>e.handleWhatsAppStart(d),onWhatsAppWait:()=>e.handleWhatsAppWait(),onWhatsAppLogout:()=>e.handleWhatsAppLogout(),onConfigPatch:(d,u)=>Ot(e,d,u),onConfigSave:()=>e.handleChannelConfigSave(),onConfigReload:()=>e.handleChannelConfigReload(),onNostrProfileEdit:(d,u)=>e.handleNostrProfileEdit(d,u),onNostrProfileCancel:()=>e.handleNostrProfileCancel(),onNostrProfileFieldChange:(d,u)=>e.handleNostrProfileFieldChange(d,u),onNostrProfileSave:()=>e.handleNostrProfileSave(),onNostrProfileImport:()=>e.handleNostrProfileImport(),onNostrProfileToggleAdvanced:()=>e.handleNostrProfileToggleAdvanced()}):g}
+
+ ${e.tab==="instances"?Lf({loading:e.presenceLoading,entries:e.presenceEntries,lastError:e.presenceError,statusMessage:e.presenceStatus,onRefresh:()=>qs(e)}):g}
+
+ ${e.tab==="sessions"?vh({loading:e.sessionsLoading,result:e.sessionsResult,error:e.sessionsError,activeMinutes:e.sessionsFilterActive,limit:e.sessionsFilterLimit,includeGlobal:e.sessionsIncludeGlobal,includeUnknown:e.sessionsIncludeUnknown,basePath:e.basePath,onFiltersChange:d=>{e.sessionsFilterActive=d.activeMinutes,e.sessionsFilterLimit=d.limit,e.sessionsIncludeGlobal=d.includeGlobal,e.sessionsIncludeUnknown=d.includeUnknown},onRefresh:()=>nt(e),onPatch:(d,u)=>Il(e,d,u),onDelete:d=>Ll(e,d)}):g}
+
+ ${e.tab==="cron"?_f({loading:e.cronLoading,status:e.cronStatus,jobs:e.cronJobs,error:e.cronError,busy:e.cronBusy,form:e.cronForm,channels:e.channelsSnapshot?.channelMeta?.length?e.channelsSnapshot.channelMeta.map(d=>d.id):e.channelsSnapshot?.channelOrder??[],channelLabels:e.channelsSnapshot?.channelLabels??{},channelMeta:e.channelsSnapshot?.channelMeta??[],runsJobId:e.cronRunsJobId,runs:e.cronRuns,onFormChange:d=>e.cronForm={...e.cronForm,...d},onRefresh:()=>e.loadCron(),onAdd:()=>Xl(e),onToggle:(d,u)=>ec(e,d,u),onRun:d=>tc(e,d),onRemove:d=>nc(e,d),onLoadRuns:d=>ha(e,d)}):g}
+
+ ${e.tab==="skills"?wh({loading:e.skillsLoading,report:e.skillsReport,error:e.skillsError,filter:e.skillsFilter,edits:e.skillEdits,messages:e.skillMessages,busyKey:e.skillsBusyKey,onFilterChange:d=>e.skillsFilter=d,onRefresh:()=>Tt(e,{clearMessages:!0}),onToggle:(d,u)=>Qc(e,d,u),onEdit:(d,u)=>Yc(e,d,u),onSaveKey:d=>Jc(e,d),onInstall:(d,u,h)=>Zc(e,d,u,h)}):g}
+
+ ${e.tab==="nodes"?Of({loading:e.nodesLoading,nodes:e.nodes,devicesLoading:e.devicesLoading,devicesError:e.devicesError,devicesList:e.devicesList,configForm:e.configForm??e.configSnapshot?.config,configLoading:e.configLoading,configSaving:e.configSaving,configDirty:e.configFormDirty,configFormMode:e.configFormMode,execApprovalsLoading:e.execApprovalsLoading,execApprovalsSaving:e.execApprovalsSaving,execApprovalsDirty:e.execApprovalsDirty,execApprovalsSnapshot:e.execApprovalsSnapshot,execApprovalsForm:e.execApprovalsForm,execApprovalsSelectedAgent:e.execApprovalsSelectedAgent,execApprovalsTarget:e.execApprovalsTarget,execApprovalsTargetNodeId:e.execApprovalsTargetNodeId,onRefresh:()=>un(e),onDevicesRefresh:()=>Se(e),onDeviceApprove:d=>Fc(e,d),onDeviceReject:d=>Uc(e,d),onDeviceRotate:(d,u,h)=>Kc(e,{deviceId:d,role:u,scopes:h}),onDeviceRevoke:(d,u)=>Hc(e,{deviceId:d,role:u}),onLoadConfig:()=>me(e),onLoadExecApprovals:()=>{const d=e.execApprovalsTarget==="node"&&e.execApprovalsTargetNodeId?{kind:"node",nodeId:e.execApprovalsTargetNodeId}:{kind:"gateway"};return js(e,d)},onBindDefault:d=>{d?Ot(e,["tools","exec","node"],d):Xi(e,["tools","exec","node"])},onBindAgent:(d,u)=>{const h=["agents","list",d,"tools","exec","node"];u?Ot(e,h,u):Xi(e,h)},onSaveBindings:()=>cs(e),onExecApprovalsTargetChange:(d,u)=>{e.execApprovalsTarget=d,e.execApprovalsTargetNodeId=u,e.execApprovalsSnapshot=null,e.execApprovalsForm=null,e.execApprovalsDirty=!1,e.execApprovalsSelectedAgent=null},onExecApprovalsSelectAgent:d=>{e.execApprovalsSelectedAgent=d},onExecApprovalsPatch:(d,u)=>Vc(e,d,u),onExecApprovalsRemove:d=>Gc(e,d),onSaveExecApprovals:()=>{const d=e.execApprovalsTarget==="node"&&e.execApprovalsTargetNodeId?{kind:"node",nodeId:e.execApprovalsTargetNodeId}:{kind:"gateway"};return Wc(e,d)}}):g}
+
+ ${e.tab==="chat"?Sp({sessionKey:e.sessionKey,onSessionKeyChange:d=>{e.sessionKey=d,e.chatMessage="",e.chatStream=null,e.chatStreamStartedAt=null,e.chatRunId=null,e.chatQueue=[],e.resetToolStream(),e.resetChatScroll(),e.applySettings({...e.settings,sessionKey:d,lastActiveSessionKey:d}),e.loadAssistantIdentity(),Ze(e),hs(e)},thinkingLevel:e.chatThinkingLevel,showThinking:l,loading:e.chatLoading,sending:e.chatSending,compactionStatus:e.compactionStatus,assistantAvatarUrl:p,messages:e.chatMessages,toolMessages:e.chatToolMessages,stream:e.chatStream,streamStartedAt:e.chatStreamStartedAt,draft:e.chatMessage,queue:e.chatQueue,connected:e.connected,canSend:e.connected,disabledReason:i,error:e.lastError,sessions:e.sessionsResult,focusMode:a,onRefresh:()=>(e.resetToolStream(),Promise.all([Ze(e),hs(e)])),onToggleFocusMode:()=>{e.onboarding||e.applySettings({...e.settings,chatFocusMode:!e.settings.chatFocusMode})},onChatScroll:d=>e.handleChatScroll(d),onDraftChange:d=>e.chatMessage=d,onSend:()=>e.handleSendChat(),canAbort:!!e.chatRunId,onAbort:()=>{e.handleAbortChat()},onQueueRemove:d=>e.removeQueuedMessage(d),onNewSession:()=>e.handleSendChat("/new",{restoreDraft:!0}),sidebarOpen:e.sidebarOpen,sidebarContent:e.sidebarContent,sidebarError:e.sidebarError,splitRatio:e.splitRatio,onOpenSidebar:d=>e.handleOpenSidebar(d),onCloseSidebar:()=>e.handleCloseSidebar(),onSplitRatioChange:d=>e.handleSplitRatioChange(d),assistantName:e.assistantName,assistantAvatar:e.assistantAvatar}):g}
+
+ ${e.tab==="config"?zp({raw:e.configRaw,valid:e.configValid,issues:e.configIssues,loading:e.configLoading,saving:e.configSaving,applying:e.configApplying,updating:e.updateRunning,connected:e.connected,schema:e.configSchema,schemaLoading:e.configSchemaLoading,uiHints:e.configUiHints,formMode:e.configFormMode,formValue:e.configForm,originalValue:e.configFormOriginal,searchQuery:e.configSearchQuery,activeSection:e.configActiveSection,activeSubsection:e.configActiveSubsection,onRawChange:d=>e.configRaw=d,onFormModeChange:d=>e.configFormMode=d,onFormPatch:(d,u)=>Ot(e,d,u),onSearchChange:d=>e.configSearchQuery=d,onSectionChange:d=>{e.configActiveSection=d,e.configActiveSubsection=null},onSubsectionChange:d=>e.configActiveSubsection=d,onReload:()=>me(e),onSave:()=>cs(e),onApply:()=>Yl(e),onUpdate:()=>Ql(e)}):g}
+
+ ${e.tab==="debug"?If({loading:e.debugLoading,status:e.debugStatus,health:e.debugHealth,models:e.debugModels,heartbeat:e.debugHeartbeat,eventLog:e.eventLog,callMethod:e.debugCallMethod,callParams:e.debugCallParams,callResult:e.debugCallResult,callError:e.debugCallError,onCallMethodChange:d=>e.debugCallMethod=d,onCallParamsChange:d=>e.debugCallParams=d,onRefresh:()=>cn(e),onCall:()=>ac(e)}):g}
+
+ ${e.tab==="logs"?Nf({loading:e.logsLoading,error:e.logsError,file:e.logsFile,entries:e.logsEntries,filterText:e.logsFilterText,levelFilters:e.logsLevelFilters,autoFollow:e.logsAutoFollow,truncated:e.logsTruncated,onFilterTextChange:d=>e.logsFilterText=d,onLevelToggle:(d,u)=>{e.logsLevelFilters={...e.logsLevelFilters,[d]:u}},onToggleAutoFollow:d=>e.logsAutoFollow=d,onRefresh:()=>Ds(e,{reset:!0}),onExport:(d,u)=>e.exportLogs(d,u),onScroll:d=>e.handleLogsScroll(d)}):g}
+
+ ${yh(e)}
+
+ `}const Ph={trace:!0,debug:!0,info:!0,warn:!0,error:!0,fatal:!0},Nh={name:"",description:"",agentId:"",enabled:!0,scheduleKind:"every",scheduleAt:"",everyAmount:"30",everyUnit:"minutes",cronExpr:"0 7 * * *",cronTz:"",sessionTarget:"main",wakeMode:"next-heartbeat",payloadKind:"systemEvent",payloadText:"",deliver:!1,channel:"last",to:"",timeoutSeconds:"",postToMainPrefix:""};async function Oh(e){if(!(!e.client||!e.connected)&&!e.agentsLoading){e.agentsLoading=!0,e.agentsError=null;try{const t=await e.client.request("agents.list",{});t&&(e.agentsList=t)}catch(t){e.agentsError=String(t)}finally{e.agentsLoading=!1}}}const yr={WEBCHAT_UI:"webchat-ui",CONTROL_UI:"clawdbot-control-ui",WEBCHAT:"webchat",CLI:"cli",GATEWAY_CLIENT:"gateway-client",MACOS_APP:"clawdbot-macos",IOS_APP:"clawdbot-ios",ANDROID_APP:"clawdbot-android",NODE_HOST:"node-host",TEST:"test",FINGERPRINT:"fingerprint",PROBE:"clawdbot-probe"},Wo=yr,_s={WEBCHAT:"webchat",CLI:"cli",UI:"ui",BACKEND:"backend",NODE:"node",PROBE:"probe",TEST:"test"};new Set(Object.values(yr));new Set(Object.values(_s));function Dh(e){const t=e.version??(e.nonce?"v2":"v1"),n=e.scopes.join(","),s=e.token??"",i=[t,e.deviceId,e.clientId,e.clientMode,e.role,n,String(e.signedAtMs),s];return t==="v2"&&i.push(e.nonce??""),i.join("|")}const Bh=4008;class Fh{constructor(t){this.opts=t,this.ws=null,this.pending=new Map,this.closed=!1,this.lastSeq=null,this.connectNonce=null,this.connectSent=!1,this.connectTimer=null,this.backoffMs=800}start(){this.closed=!1,this.connect()}stop(){this.closed=!0,this.ws?.close(),this.ws=null,this.flushPending(new Error("gateway client stopped"))}get connected(){return this.ws?.readyState===WebSocket.OPEN}connect(){this.closed||(this.ws=new WebSocket(this.opts.url),this.ws.onopen=()=>this.queueConnect(),this.ws.onmessage=t=>this.handleMessage(String(t.data??"")),this.ws.onclose=t=>{const n=String(t.reason??"");this.ws=null,this.flushPending(new Error(`gateway closed (${t.code}): ${n}`)),this.opts.onClose?.({code:t.code,reason:n}),this.scheduleReconnect()},this.ws.onerror=()=>{})}scheduleReconnect(){if(this.closed)return;const t=this.backoffMs;this.backoffMs=Math.min(this.backoffMs*1.7,15e3),window.setTimeout(()=>this.connect(),t)}flushPending(t){for(const[,n]of this.pending)n.reject(t);this.pending.clear()}async sendConnect(){if(this.connectSent)return;this.connectSent=!0,this.connectTimer!==null&&(window.clearTimeout(this.connectTimer),this.connectTimer=null);const t=typeof crypto<"u"&&!!crypto.subtle,n=["operator.admin","operator.approvals","operator.pairing"],s="operator";let i=null,o=!1,a=this.opts.token;if(t){i=await Ks();const d=Bc({deviceId:i.deviceId,role:s})?.token;a=d??this.opts.token,o=!!(d&&this.opts.token)}const l=a||this.opts.password?{token:a,password:this.opts.password}:void 0;let r;if(t&&i){const d=Date.now(),u=this.connectNonce??void 0,h=Dh({deviceId:i.deviceId,clientId:this.opts.clientName??Wo.CONTROL_UI,clientMode:this.opts.mode??_s.WEBCHAT,role:s,scopes:n,signedAtMs:d,token:a??null,nonce:u}),v=await Oc(i.privateKey,h);r={id:i.deviceId,publicKey:i.publicKey,signature:v,signedAt:d,nonce:u}}const p={minProtocol:3,maxProtocol:3,client:{id:this.opts.clientName??Wo.CONTROL_UI,version:this.opts.clientVersion??"dev",platform:this.opts.platform??navigator.platform??"web",mode:this.opts.mode??_s.WEBCHAT,instanceId:this.opts.instanceId},role:s,scopes:n,device:r,caps:[],auth:l,userAgent:navigator.userAgent,locale:navigator.language};this.request("connect",p).then(d=>{d?.auth?.deviceToken&&i&&La({deviceId:i.deviceId,role:d.auth.role??s,token:d.auth.deviceToken,scopes:d.auth.scopes??[]}),this.backoffMs=800,this.opts.onHello?.(d)}).catch(()=>{o&&i&&Ra({deviceId:i.deviceId,role:s}),this.ws?.close(Bh,"connect failed")})}handleMessage(t){let n;try{n=JSON.parse(t)}catch{return}const s=n;if(s.type==="event"){const i=n;if(i.event==="connect.challenge"){const a=i.payload,l=a&&typeof a.nonce=="string"?a.nonce:null;l&&(this.connectNonce=l,this.sendConnect());return}const o=typeof i.seq=="number"?i.seq:null;o!==null&&(this.lastSeq!==null&&o>this.lastSeq+1&&this.opts.onGap?.({expected:this.lastSeq+1,received:o}),this.lastSeq=o);try{this.opts.onEvent?.(i)}catch(a){console.error("[gateway] event handler error:",a)}return}if(s.type==="res"){const i=n,o=this.pending.get(i.id);if(!o)return;this.pending.delete(i.id),i.ok?o.resolve(i.payload):o.reject(new Error(i.error?.message??"request failed"));return}}request(t,n){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return Promise.reject(new Error("gateway not connected"));const s=Ns(),i={type:"req",id:s,method:t,params:n},o=new Promise((a,l)=>{this.pending.set(s,{resolve:r=>a(r),reject:l})});return this.ws.send(JSON.stringify(i)),o}queueConnect(){this.connectNonce=null,this.connectSent=!1,this.connectTimer!==null&&window.clearTimeout(this.connectTimer),this.connectTimer=window.setTimeout(()=>{this.sendConnect()},750)}}function Ts(e){return typeof e=="object"&&e!==null}function Uh(e){if(!Ts(e))return null;const t=typeof e.id=="string"?e.id.trim():"",n=e.request;if(!t||!Ts(n))return null;const s=typeof n.command=="string"?n.command.trim():"";if(!s)return null;const i=typeof e.createdAtMs=="number"?e.createdAtMs:0,o=typeof e.expiresAtMs=="number"?e.expiresAtMs:0;return!i||!o?null:{id:t,request:{command:s,cwd:typeof n.cwd=="string"?n.cwd:null,host:typeof n.host=="string"?n.host:null,security:typeof n.security=="string"?n.security:null,ask:typeof n.ask=="string"?n.ask:null,agentId:typeof n.agentId=="string"?n.agentId:null,resolvedPath:typeof n.resolvedPath=="string"?n.resolvedPath:null,sessionKey:typeof n.sessionKey=="string"?n.sessionKey:null},createdAtMs:i,expiresAtMs:o}}function Kh(e){if(!Ts(e))return null;const t=typeof e.id=="string"?e.id.trim():"";return t?{id:t,decision:typeof e.decision=="string"?e.decision:null,resolvedBy:typeof e.resolvedBy=="string"?e.resolvedBy:null,ts:typeof e.ts=="number"?e.ts:null}:null}function wr(e){const t=Date.now();return e.filter(n=>n.expiresAtMs>t)}function Hh(e,t){const n=wr(e).filter(s=>s.id!==t.id);return n.push(t),n}function Vo(e,t){return wr(e).filter(n=>n.id!==t)}async function $r(e,t){if(!e.client||!e.connected)return;const n=e.sessionKey.trim(),s=n?{sessionKey:n}:{};try{const i=await e.client.request("agent.identity.get",s);if(!i)return;const o=ss(i);e.assistantName=o.name,e.assistantAvatar=o.avatar,e.assistantAgentId=o.agentId??null}catch{}}function es(e,t){const n=(e??"").trim(),s=t.mainSessionKey?.trim();if(!s)return n;if(!n)return s;const i=t.mainKey?.trim()||"main",o=t.defaultAgentId?.trim();return n==="main"||n===i||o&&(n===`agent:${o}:main`||n===`agent:${o}:${i}`)?s:n}function zh(e,t){if(!t?.mainSessionKey)return;const n=es(e.sessionKey,t),s=es(e.settings.sessionKey,t),i=es(e.settings.lastActiveSessionKey,t),o=n||s||e.sessionKey,a={...e.settings,sessionKey:s||o,lastActiveSessionKey:i||o},l=a.sessionKey!==e.settings.sessionKey||a.lastActiveSessionKey!==e.settings.lastActiveSessionKey;o!==e.sessionKey&&(e.sessionKey=o),l&&$e(e,a)}function kr(e){e.lastError=null,e.hello=null,e.connected=!1,e.execApprovalQueue=[],e.execApprovalError=null,e.client?.stop(),e.client=new Fh({url:e.settings.gatewayUrl,token:e.settings.token.trim()?e.settings.token:void 0,password:e.password.trim()?e.password:void 0,clientName:"clawdbot-control-ui",mode:"webchat",onHello:t=>{e.connected=!0,e.hello=t,Wh(e,t),$r(e),Oh(e),un(e,{quiet:!0}),Se(e,{quiet:!0}),Js(e)},onClose:({code:t,reason:n})=>{e.connected=!1,e.lastError=`disconnected (${t}): ${n||"no reason"}`},onEvent:t=>jh(e,t),onGap:({expected:t,received:n})=>{e.lastError=`event gap detected (expected seq ${t}, got ${n}); refresh recommended`}}),e.client.start()}function jh(e,t){try{qh(e,t)}catch(n){console.error("[gateway] handleGatewayEvent error:",t.event,n)}}function qh(e,t){if(e.eventLogBuffer=[{ts:Date.now(),event:t.event,payload:t.payload},...e.eventLogBuffer].slice(0,250),e.tab==="debug"&&(e.eventLog=e.eventLogBuffer),t.event==="agent"){if(e.onboarding)return;Kl(e,t.payload);return}if(t.event==="chat"){const n=t.payload;n?.sessionKey&&Ma(e,n.sessionKey);const s=El(e,n);(s==="final"||s==="error"||s==="aborted")&&(Os(e),wd(e)),s==="final"&&Ze(e);return}if(t.event==="presence"){const n=t.payload;n?.presence&&Array.isArray(n.presence)&&(e.presenceEntries=n.presence,e.presenceError=null,e.presenceStatus=null);return}if(t.event==="cron"&&e.tab==="cron"&&Zs(e),(t.event==="device.pair.requested"||t.event==="device.pair.resolved")&&Se(e,{quiet:!0}),t.event==="exec.approval.requested"){const n=Uh(t.payload);if(n){e.execApprovalQueue=Hh(e.execApprovalQueue,n),e.execApprovalError=null;const s=Math.max(0,n.expiresAtMs-Date.now()+500);window.setTimeout(()=>{e.execApprovalQueue=Vo(e.execApprovalQueue,n.id)},s)}return}if(t.event==="exec.approval.resolved"){const n=Kh(t.payload);n&&(e.execApprovalQueue=Vo(e.execApprovalQueue,n.id))}}function Wh(e,t){const n=t.snapshot;n?.presence&&Array.isArray(n.presence)&&(e.presenceEntries=n.presence),n?.health&&(e.debugHealth=n.health),n?.sessionDefaults&&zh(e,n.sessionDefaults)}function Vh(e){e.basePath=rd(),ud(e,!0),ld(e),cd(e),window.addEventListener("popstate",e.popStateHandler),id(e),kr(e),nd(e),e.tab==="logs"&&Vs(e),e.tab==="debug"&&Ys(e)}function Gh(e){Wl(e)}function Yh(e){window.removeEventListener("popstate",e.popStateHandler),sd(e),Gs(e),Qs(e),dd(e),e.topbarObserver?.disconnect(),e.topbarObserver=null}function Qh(e,t){if(e.tab==="chat"&&(t.has("chatMessages")||t.has("chatToolMessages")||t.has("chatStream")||t.has("chatLoading")||t.has("tab"))){const n=t.has("tab"),s=t.has("chatLoading")&&t.get("chatLoading")===!0&&e.chatLoading===!1;rn(e,n||s||!e.chatHasAutoScrolled)}e.tab==="logs"&&(t.has("logsEntries")||t.has("logsAutoFollow")||t.has("tab"))&&e.logsAutoFollow&&e.logsAtBottom&&da(e,t.has("tab")||t.has("logsAutoFollow"))}async function Jh(e,t){await sc(e,t),await oe(e,!0)}async function Zh(e){await ic(e),await oe(e,!0)}async function Xh(e){await oc(e),await oe(e,!0)}async function eg(e){await cs(e),await me(e),await oe(e,!0)}async function tg(e){await me(e),await oe(e,!0)}function ng(e){if(!Array.isArray(e))return{};const t={};for(const n of e){if(typeof n!="string")continue;const[s,...i]=n.split(":");if(!s||i.length===0)continue;const o=s.trim(),a=i.join(":").trim();o&&a&&(t[o]=a)}return t}function xr(e){return(e.channelsSnapshot?.channelAccounts?.nostr??[])[0]?.accountId??e.nostrProfileAccountId??"default"}function Ar(e,t=""){return`/api/channels/nostr/${encodeURIComponent(e)}/profile${t}`}function sg(e,t,n){e.nostrProfileAccountId=t,e.nostrProfileFormState=ef(n??void 0)}function ig(e){e.nostrProfileFormState=null,e.nostrProfileAccountId=null}function og(e,t,n){const s=e.nostrProfileFormState;s&&(e.nostrProfileFormState={...s,values:{...s.values,[t]:n},fieldErrors:{...s.fieldErrors,[t]:""}})}function ag(e){const t=e.nostrProfileFormState;t&&(e.nostrProfileFormState={...t,showAdvanced:!t.showAdvanced})}async function rg(e){const t=e.nostrProfileFormState;if(!t||t.saving)return;const n=xr(e);e.nostrProfileFormState={...t,saving:!0,error:null,success:null,fieldErrors:{}};try{const s=await fetch(Ar(n),{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t.values)}),i=await s.json().catch(()=>null);if(!s.ok||i?.ok===!1||!i){const o=i?.error??`Profile update failed (${s.status})`;e.nostrProfileFormState={...t,saving:!1,error:o,success:null,fieldErrors:ng(i?.details)};return}if(!i.persisted){e.nostrProfileFormState={...t,saving:!1,error:"Profile publish failed on all relays.",success:null};return}e.nostrProfileFormState={...t,saving:!1,error:null,success:"Profile published to relays.",fieldErrors:{},original:{...t.values}},await oe(e,!0)}catch(s){e.nostrProfileFormState={...t,saving:!1,error:`Profile update failed: ${String(s)}`,success:null}}}async function lg(e){const t=e.nostrProfileFormState;if(!t||t.importing)return;const n=xr(e);e.nostrProfileFormState={...t,importing:!0,error:null,success:null};try{const s=await fetch(Ar(n,"/import"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({autoMerge:!0})}),i=await s.json().catch(()=>null);if(!s.ok||i?.ok===!1||!i){const r=i?.error??`Profile import failed (${s.status})`;e.nostrProfileFormState={...t,importing:!1,error:r,success:null};return}const o=i.merged??i.imported??null,a=o?{...t.values,...o}:t.values,l=!!(a.banner||a.website||a.nip05||a.lud16);e.nostrProfileFormState={...t,importing:!1,values:a,error:null,success:i.saved?"Profile imported from relays. Review and publish.":"Profile imported. Review and publish.",showAdvanced:l},i.saved&&await oe(e,!0)}catch(s){e.nostrProfileFormState={...t,importing:!1,error:`Profile import failed: ${String(s)}`,success:null}}}var cg=Object.defineProperty,dg=Object.getOwnPropertyDescriptor,b=(e,t,n,s)=>{for(var i=s>1?void 0:s?dg(t,n):t,o=e.length-1,a;o>=0;o--)(a=e[o])&&(i=(s?a(t,n,i):a(i))||i);return s&&i&&cg(t,n,i),i};const ts=fl();function ug(){if(!window.location.search)return!1;const t=new URLSearchParams(window.location.search).get("onboarding");if(!t)return!1;const n=t.trim().toLowerCase();return n==="1"||n==="true"||n==="yes"||n==="on"}let m=class extends Qe{constructor(){super(...arguments),this.settings=hl(),this.password="",this.tab="chat",this.onboarding=ug(),this.connected=!1,this.theme=this.settings.theme??"system",this.themeResolved="dark",this.hello=null,this.lastError=null,this.eventLog=[],this.eventLogBuffer=[],this.toolStreamSyncTimer=null,this.sidebarCloseTimer=null,this.assistantName=ts.name,this.assistantAvatar=ts.avatar,this.assistantAgentId=ts.agentId??null,this.sessionKey=this.settings.sessionKey,this.chatLoading=!1,this.chatSending=!1,this.chatMessage="",this.chatMessages=[],this.chatToolMessages=[],this.chatStream=null,this.chatStreamStartedAt=null,this.chatRunId=null,this.compactionStatus=null,this.chatAvatarUrl=null,this.chatThinkingLevel=null,this.chatQueue=[],this.sidebarOpen=!1,this.sidebarContent=null,this.sidebarError=null,this.splitRatio=this.settings.splitRatio,this.nodesLoading=!1,this.nodes=[],this.devicesLoading=!1,this.devicesError=null,this.devicesList=null,this.execApprovalsLoading=!1,this.execApprovalsSaving=!1,this.execApprovalsDirty=!1,this.execApprovalsSnapshot=null,this.execApprovalsForm=null,this.execApprovalsSelectedAgent=null,this.execApprovalsTarget="gateway",this.execApprovalsTargetNodeId=null,this.execApprovalQueue=[],this.execApprovalBusy=!1,this.execApprovalError=null,this.configLoading=!1,this.configRaw=`{
+}
+`,this.configValid=null,this.configIssues=[],this.configSaving=!1,this.configApplying=!1,this.updateRunning=!1,this.applySessionKey=this.settings.lastActiveSessionKey,this.configSnapshot=null,this.configSchema=null,this.configSchemaVersion=null,this.configSchemaLoading=!1,this.configUiHints={},this.configForm=null,this.configFormOriginal=null,this.configFormDirty=!1,this.configFormMode="form",this.configSearchQuery="",this.configActiveSection=null,this.configActiveSubsection=null,this.channelsLoading=!1,this.channelsSnapshot=null,this.channelsError=null,this.channelsLastSuccess=null,this.whatsappLoginMessage=null,this.whatsappLoginQrDataUrl=null,this.whatsappLoginConnected=null,this.whatsappBusy=!1,this.nostrProfileFormState=null,this.nostrProfileAccountId=null,this.presenceLoading=!1,this.presenceEntries=[],this.presenceError=null,this.presenceStatus=null,this.agentsLoading=!1,this.agentsList=null,this.agentsError=null,this.sessionsLoading=!1,this.sessionsResult=null,this.sessionsError=null,this.sessionsFilterActive="",this.sessionsFilterLimit="120",this.sessionsIncludeGlobal=!0,this.sessionsIncludeUnknown=!1,this.cronLoading=!1,this.cronJobs=[],this.cronStatus=null,this.cronError=null,this.cronForm={...Nh},this.cronRunsJobId=null,this.cronRuns=[],this.cronBusy=!1,this.skillsLoading=!1,this.skillsReport=null,this.skillsError=null,this.skillsFilter="",this.skillEdits={},this.skillsBusyKey=null,this.skillMessages={},this.debugLoading=!1,this.debugStatus=null,this.debugHealth=null,this.debugModels=[],this.debugHeartbeat=null,this.debugCallMethod="",this.debugCallParams="{}",this.debugCallResult=null,this.debugCallError=null,this.logsLoading=!1,this.logsError=null,this.logsFile=null,this.logsEntries=[],this.logsFilterText="",this.logsLevelFilters={...Ph},this.logsAutoFollow=!0,this.logsTruncated=!1,this.logsCursor=null,this.logsLastFetchAt=null,this.logsLimit=500,this.logsMaxBytes=25e4,this.logsAtBottom=!0,this.client=null,this.chatScrollFrame=null,this.chatScrollTimeout=null,this.chatHasAutoScrolled=!1,this.chatUserNearBottom=!0,this.nodesPollInterval=null,this.logsPollInterval=null,this.debugPollInterval=null,this.logsScrollFrame=null,this.toolStreamById=new Map,this.toolStreamOrder=[],this.basePath="",this.popStateHandler=()=>pd(this),this.themeMedia=null,this.themeMediaHandler=null,this.topbarObserver=null}createRenderRoot(){return this}connectedCallback(){super.connectedCallback(),Vh(this)}firstUpdated(){Gh(this)}disconnectedCallback(){Yh(this),super.disconnectedCallback()}updated(e){Qh(this,e)}connect(){kr(this)}handleChatScroll(e){Hl(this,e)}handleLogsScroll(e){zl(this,e)}exportLogs(e,t){ql(e,t)}resetToolStream(){Os(this)}resetChatScroll(){jl(this)}async loadAssistantIdentity(){await $r(this)}applySettings(e){$e(this,e)}setTab(e){od(this,e)}setTheme(e,t){ad(this,e,t)}async loadOverview(){await Oa(this)}async loadCron(){await Zs(this)}async handleAbortChat(){await Ba(this)}removeQueuedMessage(e){md(this,e)}async handleSendChat(e,t){await bd(this,e,t)}async handleWhatsAppStart(e){await Jh(this,e)}async handleWhatsAppWait(){await Zh(this)}async handleWhatsAppLogout(){await Xh(this)}async handleChannelConfigSave(){await eg(this)}async handleChannelConfigReload(){await tg(this)}handleNostrProfileEdit(e,t){sg(this,e,t)}handleNostrProfileCancel(){ig(this)}handleNostrProfileFieldChange(e,t){og(this,e,t)}async handleNostrProfileSave(){await rg(this)}async handleNostrProfileImport(){await lg(this)}handleNostrProfileToggleAdvanced(){ag(this)}async handleExecApprovalDecision(e){const t=this.execApprovalQueue[0];if(!(!t||!this.client||this.execApprovalBusy)){this.execApprovalBusy=!0,this.execApprovalError=null;try{await this.client.request("exec.approval.resolve",{id:t.id,decision:e}),this.execApprovalQueue=this.execApprovalQueue.filter(n=>n.id!==t.id)}catch(n){this.execApprovalError=`Exec approval failed: ${String(n)}`}finally{this.execApprovalBusy=!1}}}handleOpenSidebar(e){this.sidebarCloseTimer!=null&&(window.clearTimeout(this.sidebarCloseTimer),this.sidebarCloseTimer=null),this.sidebarContent=e,this.sidebarError=null,this.sidebarOpen=!0}handleCloseSidebar(){this.sidebarOpen=!1,this.sidebarCloseTimer!=null&&window.clearTimeout(this.sidebarCloseTimer),this.sidebarCloseTimer=window.setTimeout(()=>{this.sidebarOpen||(this.sidebarContent=null,this.sidebarError=null,this.sidebarCloseTimer=null)},200)}handleSplitRatioChange(e){const t=Math.max(.4,Math.min(.7,e));this.splitRatio=t,this.applySettings({...this.settings,splitRatio:t})}render(){return Mh(this)}};b([y()],m.prototype,"settings",2);b([y()],m.prototype,"password",2);b([y()],m.prototype,"tab",2);b([y()],m.prototype,"onboarding",2);b([y()],m.prototype,"connected",2);b([y()],m.prototype,"theme",2);b([y()],m.prototype,"themeResolved",2);b([y()],m.prototype,"hello",2);b([y()],m.prototype,"lastError",2);b([y()],m.prototype,"eventLog",2);b([y()],m.prototype,"assistantName",2);b([y()],m.prototype,"assistantAvatar",2);b([y()],m.prototype,"assistantAgentId",2);b([y()],m.prototype,"sessionKey",2);b([y()],m.prototype,"chatLoading",2);b([y()],m.prototype,"chatSending",2);b([y()],m.prototype,"chatMessage",2);b([y()],m.prototype,"chatMessages",2);b([y()],m.prototype,"chatToolMessages",2);b([y()],m.prototype,"chatStream",2);b([y()],m.prototype,"chatStreamStartedAt",2);b([y()],m.prototype,"chatRunId",2);b([y()],m.prototype,"compactionStatus",2);b([y()],m.prototype,"chatAvatarUrl",2);b([y()],m.prototype,"chatThinkingLevel",2);b([y()],m.prototype,"chatQueue",2);b([y()],m.prototype,"sidebarOpen",2);b([y()],m.prototype,"sidebarContent",2);b([y()],m.prototype,"sidebarError",2);b([y()],m.prototype,"splitRatio",2);b([y()],m.prototype,"nodesLoading",2);b([y()],m.prototype,"nodes",2);b([y()],m.prototype,"devicesLoading",2);b([y()],m.prototype,"devicesError",2);b([y()],m.prototype,"devicesList",2);b([y()],m.prototype,"execApprovalsLoading",2);b([y()],m.prototype,"execApprovalsSaving",2);b([y()],m.prototype,"execApprovalsDirty",2);b([y()],m.prototype,"execApprovalsSnapshot",2);b([y()],m.prototype,"execApprovalsForm",2);b([y()],m.prototype,"execApprovalsSelectedAgent",2);b([y()],m.prototype,"execApprovalsTarget",2);b([y()],m.prototype,"execApprovalsTargetNodeId",2);b([y()],m.prototype,"execApprovalQueue",2);b([y()],m.prototype,"execApprovalBusy",2);b([y()],m.prototype,"execApprovalError",2);b([y()],m.prototype,"configLoading",2);b([y()],m.prototype,"configRaw",2);b([y()],m.prototype,"configValid",2);b([y()],m.prototype,"configIssues",2);b([y()],m.prototype,"configSaving",2);b([y()],m.prototype,"configApplying",2);b([y()],m.prototype,"updateRunning",2);b([y()],m.prototype,"applySessionKey",2);b([y()],m.prototype,"configSnapshot",2);b([y()],m.prototype,"configSchema",2);b([y()],m.prototype,"configSchemaVersion",2);b([y()],m.prototype,"configSchemaLoading",2);b([y()],m.prototype,"configUiHints",2);b([y()],m.prototype,"configForm",2);b([y()],m.prototype,"configFormOriginal",2);b([y()],m.prototype,"configFormDirty",2);b([y()],m.prototype,"configFormMode",2);b([y()],m.prototype,"configSearchQuery",2);b([y()],m.prototype,"configActiveSection",2);b([y()],m.prototype,"configActiveSubsection",2);b([y()],m.prototype,"channelsLoading",2);b([y()],m.prototype,"channelsSnapshot",2);b([y()],m.prototype,"channelsError",2);b([y()],m.prototype,"channelsLastSuccess",2);b([y()],m.prototype,"whatsappLoginMessage",2);b([y()],m.prototype,"whatsappLoginQrDataUrl",2);b([y()],m.prototype,"whatsappLoginConnected",2);b([y()],m.prototype,"whatsappBusy",2);b([y()],m.prototype,"nostrProfileFormState",2);b([y()],m.prototype,"nostrProfileAccountId",2);b([y()],m.prototype,"presenceLoading",2);b([y()],m.prototype,"presenceEntries",2);b([y()],m.prototype,"presenceError",2);b([y()],m.prototype,"presenceStatus",2);b([y()],m.prototype,"agentsLoading",2);b([y()],m.prototype,"agentsList",2);b([y()],m.prototype,"agentsError",2);b([y()],m.prototype,"sessionsLoading",2);b([y()],m.prototype,"sessionsResult",2);b([y()],m.prototype,"sessionsError",2);b([y()],m.prototype,"sessionsFilterActive",2);b([y()],m.prototype,"sessionsFilterLimit",2);b([y()],m.prototype,"sessionsIncludeGlobal",2);b([y()],m.prototype,"sessionsIncludeUnknown",2);b([y()],m.prototype,"cronLoading",2);b([y()],m.prototype,"cronJobs",2);b([y()],m.prototype,"cronStatus",2);b([y()],m.prototype,"cronError",2);b([y()],m.prototype,"cronForm",2);b([y()],m.prototype,"cronRunsJobId",2);b([y()],m.prototype,"cronRuns",2);b([y()],m.prototype,"cronBusy",2);b([y()],m.prototype,"skillsLoading",2);b([y()],m.prototype,"skillsReport",2);b([y()],m.prototype,"skillsError",2);b([y()],m.prototype,"skillsFilter",2);b([y()],m.prototype,"skillEdits",2);b([y()],m.prototype,"skillsBusyKey",2);b([y()],m.prototype,"skillMessages",2);b([y()],m.prototype,"debugLoading",2);b([y()],m.prototype,"debugStatus",2);b([y()],m.prototype,"debugHealth",2);b([y()],m.prototype,"debugModels",2);b([y()],m.prototype,"debugHeartbeat",2);b([y()],m.prototype,"debugCallMethod",2);b([y()],m.prototype,"debugCallParams",2);b([y()],m.prototype,"debugCallResult",2);b([y()],m.prototype,"debugCallError",2);b([y()],m.prototype,"logsLoading",2);b([y()],m.prototype,"logsError",2);b([y()],m.prototype,"logsFile",2);b([y()],m.prototype,"logsEntries",2);b([y()],m.prototype,"logsFilterText",2);b([y()],m.prototype,"logsLevelFilters",2);b([y()],m.prototype,"logsAutoFollow",2);b([y()],m.prototype,"logsTruncated",2);b([y()],m.prototype,"logsCursor",2);b([y()],m.prototype,"logsLastFetchAt",2);b([y()],m.prototype,"logsLimit",2);b([y()],m.prototype,"logsMaxBytes",2);b([y()],m.prototype,"logsAtBottom",2);m=b([ta("clawdbot-app")],m);
+//# sourceMappingURL=index-DsXRcnEw.js.map
diff --git a/dist/control-ui/assets/index-DsXRcnEw.js.map b/dist/control-ui/assets/index-DsXRcnEw.js.map
new file mode 100644
index 000000000..a46b0b5de
--- /dev/null
+++ b/dist/control-ui/assets/index-DsXRcnEw.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"index-DsXRcnEw.js","sources":["../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/css-tag.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/reactive-element.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/lit-html.js","../../../node_modules/.pnpm/lit-element@4.2.2/node_modules/lit-element/lit-element.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/custom-element.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/property.js","../../../node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/decorators/state.js","../../../ui/src/ui/assistant-identity.ts","../../../ui/src/ui/storage.ts","../../../src/sessions/session-key-utils.ts","../../../ui/src/ui/navigation.ts","../../../ui/src/ui/format.ts","../../../ui/src/ui/chat/message-extract.ts","../../../ui/src/ui/uuid.ts","../../../ui/src/ui/controllers/chat.ts","../../../ui/src/ui/controllers/sessions.ts","../../../ui/src/ui/app-tool-stream.ts","../../../ui/src/ui/app-scroll.ts","../../../ui/src/ui/controllers/config/form-utils.ts","../../../ui/src/ui/controllers/config.ts","../../../ui/src/ui/controllers/cron.ts","../../../ui/src/ui/controllers/channels.ts","../../../ui/src/ui/controllers/debug.ts","../../../ui/src/ui/controllers/logs.ts","../../../node_modules/.pnpm/@noble+ed25519@3.0.0/node_modules/@noble/ed25519/index.js","../../../ui/src/ui/device-identity.ts","../../../ui/src/ui/device-auth.ts","../../../ui/src/ui/controllers/devices.ts","../../../ui/src/ui/controllers/nodes.ts","../../../ui/src/ui/controllers/exec-approvals.ts","../../../ui/src/ui/controllers/presence.ts","../../../ui/src/ui/controllers/skills.ts","../../../ui/src/ui/theme.ts","../../../ui/src/ui/theme-transition.ts","../../../ui/src/ui/app-polling.ts","../../../ui/src/ui/app-settings.ts","../../../ui/src/ui/app-chat.ts","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directive-helpers.js","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/repeat.js","../../../ui/src/ui/chat/message-normalizer.ts","../../../node_modules/.pnpm/lit-html@3.3.2/node_modules/lit-html/directives/unsafe-html.js","../../../node_modules/.pnpm/dompurify@3.3.1/node_modules/dompurify/dist/purify.es.mjs","../../../node_modules/.pnpm/marked@17.0.1/node_modules/marked/lib/marked.esm.js","../../../ui/src/ui/markdown.ts","../../../ui/src/ui/icons.ts","../../../ui/src/ui/chat/copy-as-markdown.ts","../../../ui/src/ui/tool-display.ts","../../../ui/src/ui/chat/constants.ts","../../../ui/src/ui/chat/tool-helpers.ts","../../../ui/src/ui/chat/tool-cards.ts","../../../ui/src/ui/chat/grouped-render.ts","../../../ui/src/ui/views/markdown-sidebar.ts","../../../ui/src/ui/components/resizable-divider.ts","../../../ui/src/ui/views/chat.ts","../../../ui/src/ui/views/config-form.shared.ts","../../../ui/src/ui/views/config-form.node.ts","../../../ui/src/ui/views/config-form.render.ts","../../../ui/src/ui/views/config-form.analyze.ts","../../../ui/src/ui/views/config.ts","../../../ui/src/ui/views/channels.shared.ts","../../../ui/src/ui/views/channels.config.ts","../../../ui/src/ui/views/channels.discord.ts","../../../ui/src/ui/views/channels.imessage.ts","../../../ui/src/ui/views/channels.nostr-profile-form.ts","../../../ui/src/ui/views/channels.nostr.ts","../../../ui/src/ui/views/channels.signal.ts","../../../ui/src/ui/views/channels.slack.ts","../../../ui/src/ui/views/channels.telegram.ts","../../../ui/src/ui/views/channels.whatsapp.ts","../../../ui/src/ui/views/channels.ts","../../../ui/src/ui/presenter.ts","../../../ui/src/ui/views/cron.ts","../../../ui/src/ui/views/debug.ts","../../../ui/src/ui/views/instances.ts","../../../ui/src/ui/views/logs.ts","../../../ui/src/ui/views/nodes.ts","../../../ui/src/ui/views/overview.ts","../../../ui/src/ui/views/sessions.ts","../../../ui/src/ui/views/exec-approval.ts","../../../ui/src/ui/views/skills.ts","../../../ui/src/ui/app-render.helpers.ts","../../../ui/src/ui/app-render.ts","../../../ui/src/ui/app-defaults.ts","../../../ui/src/ui/controllers/agents.ts","../../../src/gateway/protocol/client-info.ts","../../../src/gateway/device-auth.ts","../../../ui/src/ui/gateway.ts","../../../ui/src/ui/controllers/exec-approval.ts","../../../ui/src/ui/controllers/assistant-identity.ts","../../../ui/src/ui/app-gateway.ts","../../../ui/src/ui/app-lifecycle.ts","../../../ui/src/ui/app-channels.ts","../../../ui/src/ui/app.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2019 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&\"adoptedStyleSheets\"in Document.prototype&&\"replace\"in CSSStyleSheet.prototype,s=Symbol(),o=new WeakMap;class n{constructor(t,e,o){if(this._$cssResult$=!0,o!==s)throw Error(\"CSSResult is not constructable. Use `unsafeCSS` or `css` instead.\");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const s=this.t;if(e&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o.set(s,t))}return t}toString(){return this.cssText}}const r=t=>new n(\"string\"==typeof t?t:t+\"\",void 0,s),i=(t,...e)=>{const o=1===t.length?t[0]:e.reduce((e,s,o)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if(\"number\"==typeof t)return t;throw Error(\"Value passed to 'css' function must be a 'css' function result: \"+t+\". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.\")})(s)+t[o+1],t[0]);return new n(o,t,s)},S=(s,o)=>{if(e)s.adoptedStyleSheets=o.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of o){const o=document.createElement(\"style\"),n=t.litNonce;void 0!==n&&o.setAttribute(\"nonce\",n),o.textContent=e.cssText,s.appendChild(o)}},c=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e=\"\";for(const s of t.cssRules)e+=s.cssText;return r(e)})(t):t;export{n as CSSResult,S as adoptStyles,i as css,c as getCompatibleStyle,e as supportsAdoptingStyleSheets,r as unsafeCSS};\n//# sourceMappingURL=css-tag.js.map\n","import{getCompatibleStyle as t,adoptStyles as s}from\"./css-tag.js\";export{CSSResult,css,supportsAdoptingStyleSheets,unsafeCSS}from\"./css-tag.js\";\n/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */const{is:i,defineProperty:e,getOwnPropertyDescriptor:h,getOwnPropertyNames:r,getOwnPropertySymbols:o,getPrototypeOf:n}=Object,a=globalThis,c=a.trustedTypes,l=c?c.emptyScript:\"\",p=a.reactiveElementPolyfillSupport,d=(t,s)=>t,u={toAttribute(t,s){switch(s){case Boolean:t=t?l:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},f=(t,s)=>!i(t,s),b={attribute:!0,type:String,converter:u,reflect:!1,useDefault:!1,hasChanged:f};Symbol.metadata??=Symbol(\"metadata\"),a.litPropertyMetadata??=new WeakMap;class y extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=b){if(s.state&&(s.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=!0),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),h=this.getPropertyDescriptor(t,i,s);void 0!==h&&e(this.prototype,t,h)}}static getPropertyDescriptor(t,s,i){const{get:e,set:r}=h(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t}};return{get:e,set(s){const h=e?.call(this);r?.call(this,s),this.requestUpdate(t,h,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(d(\"elementProperties\")))return;const t=n(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(d(\"finalized\")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(d(\"properties\"))){const t=this.properties,s=[...r(t),...o(t)];for(const i of s)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(t(s))}else void 0!==s&&i.push(t(s));return i}static _$Eu(t,s){const i=s.attribute;return!1===i?void 0:\"string\"==typeof i?i:\"string\"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return s(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,s,i){this._$AK(t,i)}_$ET(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&!0===i.reflect){const h=(void 0!==i.converter?.toAttribute?i.converter:u).toAttribute(s,i.type);this._$Em=t,null==h?this.removeAttribute(e):this.setAttribute(e,h),this._$Em=null}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),h=\"function\"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u;this._$Em=e;const r=h.fromAttribute(s,t.type);this[e]=r??this._$Ej?.get(e)??r,this._$Em=null}}requestUpdate(t,s,i,e=!1,h){if(void 0!==t){const r=this.constructor;if(!1===e&&(h=this[t]),i??=r.getPropertyOptions(t),!((i.hasChanged??f)(h,s)||i.useDefault&&i.reflect&&h===this._$Ej?.get(t)&&!this.hasAttribute(r._$Eu(t,i))))return;this.C(t,s,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,s,{useDefault:i,reflect:e,wrapped:h},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??s??this[t]),!0!==h||void 0!==r)||(this._$AL.has(t)||(this.hasUpdated||i||(s=void 0),this._$AL.set(t,s)),!0===e&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t){const{wrapped:t}=i,e=this[s];!0!==t||this._$AL.has(s)||void 0===e||this.C(s,void 0,i,e)}}let t=!1;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(s)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(s)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}}y.elementStyles=[],y.shadowRootOptions={mode:\"open\"},y[d(\"elementProperties\")]=new Map,y[d(\"finalized\")]=new Map,p?.({ReactiveElement:y}),(a.reactiveElementVersions??=[]).push(\"2.1.2\");export{y as ReactiveElement,s as adoptStyles,u as defaultConverter,t as getCompatibleStyle,f as notEqual};\n//# sourceMappingURL=reactive-element.js.map\n","/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst t=globalThis,i=t=>t,s=t.trustedTypes,e=s?s.createPolicy(\"lit-html\",{createHTML:t=>t}):void 0,h=\"$lit$\",o=`lit$${Math.random().toFixed(9).slice(2)}$`,n=\"?\"+o,r=`<${n}>`,l=document,c=()=>l.createComment(\"\"),a=t=>null===t||\"object\"!=typeof t&&\"function\"!=typeof t,u=Array.isArray,d=t=>u(t)||\"function\"==typeof t?.[Symbol.iterator],f=\"[ \\t\\n\\f\\r]\",v=/<(?:(!--|\\/[^a-zA-Z])|(\\/?[a-zA-Z][^>\\s]*)|(\\/?$))/g,_=/-->/g,m=/>/g,p=RegExp(`>|${f}(?:([^\\\\s\"'>=/]+)(${f}*=${f}*(?:[^ \\t\\n\\f\\r\"'\\`<>=]|(\"|')|))|$)`,\"g\"),g=/'/g,$=/\"/g,y=/^(?:script|style|textarea|title)$/i,x=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),b=x(1),w=x(2),T=x(3),E=Symbol.for(\"lit-noChange\"),A=Symbol.for(\"lit-nothing\"),C=new WeakMap,P=l.createTreeWalker(l,129);function V(t,i){if(!u(t)||!t.hasOwnProperty(\"raw\"))throw Error(\"invalid template strings array\");return void 0!==e?e.createHTML(i):i}const N=(t,i)=>{const s=t.length-1,e=[];let n,l=2===i?\"\":3===i?\"\":\"\")),e]};class S{constructor({strings:t,_$litType$:i},e){let r;this.parts=[];let l=0,a=0;const u=t.length-1,d=this.parts,[f,v]=N(t,i);if(this.el=S.createElement(f,e),P.currentNode=this.el.content,2===i||3===i){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(r=P.nextNode())&&d.length0){r.textContent=s?s.emptyScript:\"\";for(let s=0;s2||\"\"!==s[0]||\"\"!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=A}_$AI(t,i=this,s,e){const h=this.strings;let o=!1;if(void 0===h)t=M(this,t,i,0),o=!a(t)||t!==this._$AH&&t!==E,o&&(this._$AH=t);else{const e=t;let n,r;for(t=h[0],n=0;n{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new k(i.insertBefore(c(),t),t,void 0,s??{})}return h._$AI(t),h};export{j as _$LH,b as html,T as mathml,E as noChange,A as nothing,D as render,w as svg};\n//# sourceMappingURL=lit-html.js.map\n","import{ReactiveElement as t}from\"@lit/reactive-element\";export*from\"@lit/reactive-element\";import{render as e,noChange as r}from\"lit-html\";export*from\"lit-html\";\n/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */const s=globalThis;class i extends t{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const r=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=e(r,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return r}}i._$litElement$=!0,i[\"finalized\"]=!0,s.litElementHydrateSupport?.({LitElement:i});const o=s.litElementPolyfillSupport;o?.({LitElement:i});const n={_$AK:(t,e,r)=>{t._$AK(e,r)},_$AL:t=>t._$AL};(s.litElementVersions??=[]).push(\"4.2.2\");export{i as LitElement,n as _$LE};\n//# sourceMappingURL=lit-element.js.map\n","/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\nconst t=t=>(e,o)=>{void 0!==o?o.addInitializer(()=>{customElements.define(t,e)}):customElements.define(t,e)};export{t as customElement};\n//# sourceMappingURL=custom-element.js.map\n","import{notEqual as t,defaultConverter as e}from\"../reactive-element.js\";\n/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */const o={attribute:!0,type:String,converter:e,reflect:!1,hasChanged:t},r=(t=o,e,r)=>{const{kind:n,metadata:i}=r;let s=globalThis.litPropertyMetadata.get(i);if(void 0===s&&globalThis.litPropertyMetadata.set(i,s=new Map),\"setter\"===n&&((t=Object.create(t)).wrapped=!0),s.set(r.name,t),\"accessor\"===n){const{name:o}=r;return{set(r){const n=e.get.call(this);e.set.call(this,r),this.requestUpdate(o,n,t,!0,r)},init(e){return void 0!==e&&this.C(o,void 0,t,e),e}}}if(\"setter\"===n){const{name:o}=r;return function(r){const n=this[o];e.call(this,r),this.requestUpdate(o,n,t,!0,r)}}throw Error(\"Unsupported decorator location: \"+n)};function n(t){return(e,o)=>\"object\"==typeof o?r(t,e,o):((t,e,o)=>{const r=e.hasOwnProperty(o);return e.constructor.createProperty(o,t),r?Object.getOwnPropertyDescriptor(e,o):void 0})(t,e,o)}export{n as property,r as standardProperty};\n//# sourceMappingURL=property.js.map\n","import{property as t}from\"./property.js\";\n/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */function r(r){return t({...r,state:!0,attribute:!1})}export{r as state};\n//# sourceMappingURL=state.js.map\n","const MAX_ASSISTANT_NAME = 50;\nconst MAX_ASSISTANT_AVATAR = 200;\n\nexport const DEFAULT_ASSISTANT_NAME = \"Assistant\";\nexport const DEFAULT_ASSISTANT_AVATAR = \"A\";\n\nexport type AssistantIdentity = {\n agentId?: string | null;\n name: string;\n avatar: string | null;\n};\n\ndeclare global {\n interface Window {\n __CLAWDBOT_ASSISTANT_NAME__?: string;\n __CLAWDBOT_ASSISTANT_AVATAR__?: string;\n }\n}\n\nfunction coerceIdentityValue(value: string | undefined, maxLength: number): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n if (trimmed.length <= maxLength) return trimmed;\n return trimmed.slice(0, maxLength);\n}\n\nexport function normalizeAssistantIdentity(\n input?: Partial | null,\n): AssistantIdentity {\n const name =\n coerceIdentityValue(input?.name, MAX_ASSISTANT_NAME) ?? DEFAULT_ASSISTANT_NAME;\n const avatar = coerceIdentityValue(input?.avatar ?? undefined, MAX_ASSISTANT_AVATAR) ?? null;\n const agentId =\n typeof input?.agentId === \"string\" && input.agentId.trim()\n ? input.agentId.trim()\n : null;\n return { agentId, name, avatar };\n}\n\nexport function resolveInjectedAssistantIdentity(): AssistantIdentity {\n if (typeof window === \"undefined\") {\n return normalizeAssistantIdentity({});\n }\n return normalizeAssistantIdentity({\n name: window.__CLAWDBOT_ASSISTANT_NAME__,\n avatar: window.__CLAWDBOT_ASSISTANT_AVATAR__,\n });\n}\n","const KEY = \"clawdbot.control.settings.v1\";\n\nimport type { ThemeMode } from \"./theme\";\n\nexport type UiSettings = {\n gatewayUrl: string;\n token: string;\n sessionKey: string;\n lastActiveSessionKey: string;\n theme: ThemeMode;\n chatFocusMode: boolean;\n chatShowThinking: boolean;\n splitRatio: number; // Sidebar split ratio (0.4 to 0.7, default 0.6)\n navCollapsed: boolean; // Collapsible sidebar state\n navGroupsCollapsed: Record; // Which nav groups are collapsed\n};\n\nexport function loadSettings(): UiSettings {\n const defaultUrl = (() => {\n const proto = location.protocol === \"https:\" ? \"wss\" : \"ws\";\n return `${proto}://${location.host}`;\n })();\n\n const defaults: UiSettings = {\n gatewayUrl: defaultUrl,\n token: \"\",\n sessionKey: \"main\",\n lastActiveSessionKey: \"main\",\n theme: \"system\",\n chatFocusMode: false,\n chatShowThinking: true,\n splitRatio: 0.6,\n navCollapsed: false,\n navGroupsCollapsed: {},\n };\n\n try {\n const raw = localStorage.getItem(KEY);\n if (!raw) return defaults;\n const parsed = JSON.parse(raw) as Partial;\n return {\n gatewayUrl:\n typeof parsed.gatewayUrl === \"string\" && parsed.gatewayUrl.trim()\n ? parsed.gatewayUrl.trim()\n : defaults.gatewayUrl,\n token: typeof parsed.token === \"string\" ? parsed.token : defaults.token,\n sessionKey:\n typeof parsed.sessionKey === \"string\" && parsed.sessionKey.trim()\n ? parsed.sessionKey.trim()\n : defaults.sessionKey,\n lastActiveSessionKey:\n typeof parsed.lastActiveSessionKey === \"string\" &&\n parsed.lastActiveSessionKey.trim()\n ? parsed.lastActiveSessionKey.trim()\n : (typeof parsed.sessionKey === \"string\" &&\n parsed.sessionKey.trim()) ||\n defaults.lastActiveSessionKey,\n theme:\n parsed.theme === \"light\" ||\n parsed.theme === \"dark\" ||\n parsed.theme === \"system\"\n ? parsed.theme\n : defaults.theme,\n chatFocusMode:\n typeof parsed.chatFocusMode === \"boolean\"\n ? parsed.chatFocusMode\n : defaults.chatFocusMode,\n chatShowThinking:\n typeof parsed.chatShowThinking === \"boolean\"\n ? parsed.chatShowThinking\n : defaults.chatShowThinking,\n splitRatio:\n typeof parsed.splitRatio === \"number\" &&\n parsed.splitRatio >= 0.4 &&\n parsed.splitRatio <= 0.7\n ? parsed.splitRatio\n : defaults.splitRatio,\n navCollapsed:\n typeof parsed.navCollapsed === \"boolean\"\n ? parsed.navCollapsed\n : defaults.navCollapsed,\n navGroupsCollapsed:\n typeof parsed.navGroupsCollapsed === \"object\" &&\n parsed.navGroupsCollapsed !== null\n ? parsed.navGroupsCollapsed\n : defaults.navGroupsCollapsed,\n };\n } catch {\n return defaults;\n }\n}\n\nexport function saveSettings(next: UiSettings) {\n localStorage.setItem(KEY, JSON.stringify(next));\n}\n","export type ParsedAgentSessionKey = {\n agentId: string;\n rest: string;\n};\n\nexport function parseAgentSessionKey(\n sessionKey: string | undefined | null,\n): ParsedAgentSessionKey | null {\n const raw = (sessionKey ?? \"\").trim();\n if (!raw) return null;\n const parts = raw.split(\":\").filter(Boolean);\n if (parts.length < 3) return null;\n if (parts[0] !== \"agent\") return null;\n const agentId = parts[1]?.trim();\n const rest = parts.slice(2).join(\":\");\n if (!agentId || !rest) return null;\n return { agentId, rest };\n}\n\nexport function isSubagentSessionKey(sessionKey: string | undefined | null): boolean {\n const raw = (sessionKey ?? \"\").trim();\n if (!raw) return false;\n if (raw.toLowerCase().startsWith(\"subagent:\")) return true;\n const parsed = parseAgentSessionKey(raw);\n return Boolean((parsed?.rest ?? \"\").toLowerCase().startsWith(\"subagent:\"));\n}\n\nexport function isAcpSessionKey(sessionKey: string | undefined | null): boolean {\n const raw = (sessionKey ?? \"\").trim();\n if (!raw) return false;\n const normalized = raw.toLowerCase();\n if (normalized.startsWith(\"acp:\")) return true;\n const parsed = parseAgentSessionKey(raw);\n return Boolean((parsed?.rest ?? \"\").toLowerCase().startsWith(\"acp:\"));\n}\n\nconst THREAD_SESSION_MARKERS = [\":thread:\", \":topic:\"];\n\nexport function resolveThreadParentSessionKey(\n sessionKey: string | undefined | null,\n): string | null {\n const raw = (sessionKey ?? \"\").trim();\n if (!raw) return null;\n const normalized = raw.toLowerCase();\n let idx = -1;\n for (const marker of THREAD_SESSION_MARKERS) {\n const candidate = normalized.lastIndexOf(marker);\n if (candidate > idx) idx = candidate;\n }\n if (idx <= 0) return null;\n const parent = raw.slice(0, idx).trim();\n return parent ? parent : null;\n}\n","export const TAB_GROUPS = [\n { label: \"Chat\", tabs: [\"chat\"] },\n {\n label: \"Control\",\n tabs: [\"overview\", \"channels\", \"instances\", \"sessions\", \"cron\"],\n },\n { label: \"Agent\", tabs: [\"skills\", \"nodes\"] },\n { label: \"Settings\", tabs: [\"config\", \"debug\", \"logs\"] },\n] as const;\n\nexport type Tab =\n | \"overview\"\n | \"channels\"\n | \"instances\"\n | \"sessions\"\n | \"cron\"\n | \"skills\"\n | \"nodes\"\n | \"chat\"\n | \"config\"\n | \"debug\"\n | \"logs\";\n\nconst TAB_PATHS: Record = {\n overview: \"/overview\",\n channels: \"/channels\",\n instances: \"/instances\",\n sessions: \"/sessions\",\n cron: \"/cron\",\n skills: \"/skills\",\n nodes: \"/nodes\",\n chat: \"/chat\",\n config: \"/config\",\n debug: \"/debug\",\n logs: \"/logs\",\n};\n\nconst PATH_TO_TAB = new Map(\n Object.entries(TAB_PATHS).map(([tab, path]) => [path, tab as Tab]),\n);\n\nexport function normalizeBasePath(basePath: string): string {\n if (!basePath) return \"\";\n let base = basePath.trim();\n if (!base.startsWith(\"/\")) base = `/${base}`;\n if (base === \"/\") return \"\";\n if (base.endsWith(\"/\")) base = base.slice(0, -1);\n return base;\n}\n\nexport function normalizePath(path: string): string {\n if (!path) return \"/\";\n let normalized = path.trim();\n if (!normalized.startsWith(\"/\")) normalized = `/${normalized}`;\n if (normalized.length > 1 && normalized.endsWith(\"/\")) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n}\n\nexport function pathForTab(tab: Tab, basePath = \"\"): string {\n const base = normalizeBasePath(basePath);\n const path = TAB_PATHS[tab];\n return base ? `${base}${path}` : path;\n}\n\nexport function tabFromPath(pathname: string, basePath = \"\"): Tab | null {\n const base = normalizeBasePath(basePath);\n let path = pathname || \"/\";\n if (base) {\n if (path === base) {\n path = \"/\";\n } else if (path.startsWith(`${base}/`)) {\n path = path.slice(base.length);\n }\n }\n let normalized = normalizePath(path).toLowerCase();\n if (normalized.endsWith(\"/index.html\")) normalized = \"/\";\n if (normalized === \"/\") return \"chat\";\n return PATH_TO_TAB.get(normalized) ?? null;\n}\n\nexport function inferBasePathFromPathname(pathname: string): string {\n let normalized = normalizePath(pathname);\n if (normalized.endsWith(\"/index.html\")) {\n normalized = normalizePath(normalized.slice(0, -\"/index.html\".length));\n }\n if (normalized === \"/\") return \"\";\n const segments = normalized.split(\"/\").filter(Boolean);\n if (segments.length === 0) return \"\";\n for (let i = 0; i < segments.length; i++) {\n const candidate = `/${segments.slice(i).join(\"/\")}`.toLowerCase();\n if (PATH_TO_TAB.has(candidate)) {\n const prefix = segments.slice(0, i);\n return prefix.length ? `/${prefix.join(\"/\")}` : \"\";\n }\n }\n return `/${segments.join(\"/\")}`;\n}\n\nexport function iconForTab(tab: Tab): string {\n switch (tab) {\n case \"chat\":\n return \"💬\";\n case \"overview\":\n return \"📊\";\n case \"channels\":\n return \"🔗\";\n case \"instances\":\n return \"📡\";\n case \"sessions\":\n return \"📄\";\n case \"cron\":\n return \"⏰\";\n case \"skills\":\n return \"⚡️\";\n case \"nodes\":\n return \"🖥️\";\n case \"config\":\n return \"⚙️\";\n case \"debug\":\n return \"🐞\";\n case \"logs\":\n return \"🧾\";\n default:\n return \"📁\";\n }\n}\n\nexport function titleForTab(tab: Tab) {\n switch (tab) {\n case \"overview\":\n return \"Overview\";\n case \"channels\":\n return \"Channels\";\n case \"instances\":\n return \"Instances\";\n case \"sessions\":\n return \"Sessions\";\n case \"cron\":\n return \"Cron Jobs\";\n case \"skills\":\n return \"Skills\";\n case \"nodes\":\n return \"Nodes\";\n case \"chat\":\n return \"Chat\";\n case \"config\":\n return \"Config\";\n case \"debug\":\n return \"Debug\";\n case \"logs\":\n return \"Logs\";\n default:\n return \"Control\";\n }\n}\n\nexport function subtitleForTab(tab: Tab) {\n switch (tab) {\n case \"overview\":\n return \"Gateway status, entry points, and a fast health read.\";\n case \"channels\":\n return \"Manage channels and settings.\";\n case \"instances\":\n return \"Presence beacons from connected clients and nodes.\";\n case \"sessions\":\n return \"Inspect active sessions and adjust per-session defaults.\";\n case \"cron\":\n return \"Schedule wakeups and recurring agent runs.\";\n case \"skills\":\n return \"Manage skill availability and API key injection.\";\n case \"nodes\":\n return \"Paired devices, capabilities, and command exposure.\";\n case \"chat\":\n return \"Direct gateway chat session for quick interventions.\";\n case \"config\":\n return \"Edit ~/.clawdbot/clawdbot.json safely.\";\n case \"debug\":\n return \"Gateway snapshots, events, and manual RPC calls.\";\n case \"logs\":\n return \"Live tail of the gateway file logs.\";\n default:\n return \"\";\n }\n}\n","export function formatMs(ms?: number | null): string {\n if (!ms && ms !== 0) return \"n/a\";\n return new Date(ms).toLocaleString();\n}\n\nexport function formatAgo(ms?: number | null): string {\n if (!ms && ms !== 0) return \"n/a\";\n const diff = Date.now() - ms;\n if (diff < 0) return \"just now\";\n const sec = Math.round(diff / 1000);\n if (sec < 60) return `${sec}s ago`;\n const min = Math.round(sec / 60);\n if (min < 60) return `${min}m ago`;\n const hr = Math.round(min / 60);\n if (hr < 48) return `${hr}h ago`;\n const day = Math.round(hr / 24);\n return `${day}d ago`;\n}\n\nexport function formatDurationMs(ms?: number | null): string {\n if (!ms && ms !== 0) return \"n/a\";\n if (ms < 1000) return `${ms}ms`;\n const sec = Math.round(ms / 1000);\n if (sec < 60) return `${sec}s`;\n const min = Math.round(sec / 60);\n if (min < 60) return `${min}m`;\n const hr = Math.round(min / 60);\n if (hr < 48) return `${hr}h`;\n const day = Math.round(hr / 24);\n return `${day}d`;\n}\n\nexport function formatList(values?: Array): string {\n if (!values || values.length === 0) return \"none\";\n return values.filter((v): v is string => Boolean(v && v.trim())).join(\", \");\n}\n\nexport function clampText(value: string, max = 120): string {\n if (value.length <= max) return value;\n return `${value.slice(0, Math.max(0, max - 1))}…`;\n}\n\nexport function truncateText(value: string, max: number): {\n text: string;\n truncated: boolean;\n total: number;\n} {\n if (value.length <= max) {\n return { text: value, truncated: false, total: value.length };\n }\n return {\n text: value.slice(0, Math.max(0, max)),\n truncated: true,\n total: value.length,\n };\n}\n\nexport function toNumber(value: string, fallback: number): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : fallback;\n}\n\nexport function parseList(input: string): string[] {\n return input\n .split(/[,\\n]/)\n .map((v) => v.trim())\n .filter((v) => v.length > 0);\n}\n\nconst THINKING_TAG_RE = /<\\s*\\/?\\s*think(?:ing)?\\s*>/gi;\nconst THINKING_OPEN_RE = /<\\s*think(?:ing)?\\s*>/i;\nconst THINKING_CLOSE_RE = /<\\s*\\/\\s*think(?:ing)?\\s*>/i;\n\nexport function stripThinkingTags(value: string): string {\n if (!value) return value;\n const hasOpen = THINKING_OPEN_RE.test(value);\n const hasClose = THINKING_CLOSE_RE.test(value);\n if (!hasOpen && !hasClose) return value;\n // If we don't have a balanced pair, avoid dropping trailing content.\n if (hasOpen !== hasClose) {\n if (!hasOpen) return value.replace(THINKING_CLOSE_RE, \"\").trimStart();\n return value.replace(THINKING_OPEN_RE, \"\").trimStart();\n }\n\n if (!THINKING_TAG_RE.test(value)) return value;\n THINKING_TAG_RE.lastIndex = 0;\n\n let result = \"\";\n let lastIndex = 0;\n let inThinking = false;\n for (const match of value.matchAll(THINKING_TAG_RE)) {\n const idx = match.index ?? 0;\n if (!inThinking) {\n result += value.slice(lastIndex, idx);\n }\n const tag = match[0].toLowerCase();\n inThinking = !tag.includes(\"/\");\n lastIndex = idx + match[0].length;\n }\n if (!inThinking) {\n result += value.slice(lastIndex);\n }\n return result.trimStart();\n}\n","import { stripThinkingTags } from \"../format\";\n\nconst ENVELOPE_PREFIX = /^\\[([^\\]]+)\\]\\s*/;\nconst ENVELOPE_CHANNELS = [\n \"WebChat\",\n \"WhatsApp\",\n \"Telegram\",\n \"Signal\",\n \"Slack\",\n \"Discord\",\n \"iMessage\",\n \"Teams\",\n \"Matrix\",\n \"Zalo\",\n \"Zalo Personal\",\n \"BlueBubbles\",\n];\n\nconst textCache = new WeakMap