refactor: rename clawdbot to moltbot with legacy compat

This commit is contained in:
Peter Steinberger
2026-01-27 12:19:58 +00:00
parent 83460df96f
commit 6d16a658e5
1839 changed files with 11250 additions and 11199 deletions

View File

@@ -1,5 +1,5 @@
# Repository Guidelines # Repository Guidelines
- Repo: https://github.com/clawdbot/clawdbot - Repo: https://github.com/moltbot/moltbot
- GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n". - GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n".
## Project Structure & Module Organization ## Project Structure & Module Organization
@@ -7,7 +7,7 @@
- Tests: colocated `*.test.ts`. - Tests: colocated `*.test.ts`.
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`. - 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/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). - Plugins: install runs `npm install --omit=dev` in plugin dir; runtime deps must live in `dependencies`. Avoid `workspace:*` in `dependencies` (npm install breaks); put `moltbot` in `devDependencies` or `peerDependencies` instead (runtime resolves `clawdbot/plugin-sdk` via jiti alias).
- Installers served from `https://molt.bot/*`: live in the sibling repo `../molt.bot` (`public/install.sh`, `public/install-cli.sh`, `public/install.ps1`). - Installers served from `https://molt.bot/*`: live in the sibling repo `../molt.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). - 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/` - Core channel docs: `docs/channels/`
@@ -28,12 +28,12 @@
## exe.dev VM ops (general) ## exe.dev VM ops (general)
- Access: stable path is `ssh exe.dev` then `ssh vm-name` (assume SSH key already set). - 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. - 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`). - Update: `sudo npm i -g moltbot@latest` (global install needs root on `/usr/lib/node_modules`).
- Config: use `clawdbot config set ...`; ensure `gateway.mode=local` is set. - Config: use `moltbot config set ...`; ensure `gateway.mode=local` is set.
- Discord: store raw token only (no `DISCORD_BOT_TOKEN=` prefix). - Discord: store raw token only (no `DISCORD_BOT_TOKEN=` prefix).
- Restart: stop old gateway and run: - 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 &` `pkill -9 -f moltbot-gateway || true; nohup moltbot gateway run --bind loopback --port 18789 --force > /tmp/moltbot-gateway.log 2>&1 &`
- Verify: `clawdbot channels status --probe`, `ss -ltnp | rg 18789`, `tail -n 120 /tmp/clawdbot-gateway.log`. - Verify: `moltbot channels status --probe`, `ss -ltnp | rg 18789`, `tail -n 120 /tmp/moltbot-gateway.log`.
## Build, Test, and Development Commands ## Build, Test, and Development Commands
- Runtime baseline: Node **22+** (keep Node + Bun paths working). - Runtime baseline: Node **22+** (keep Node + Bun paths working).
@@ -41,7 +41,7 @@
- Pre-commit hooks: `prek install` (runs same checks as CI) - Pre-commit hooks: `prek install` (runs same checks as CI)
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches). - Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`. - Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
- Run CLI in dev: `pnpm clawdbot ...` (bun) or `pnpm dev`. - Run CLI in dev: `pnpm moltbot ...` (bun) or `pnpm dev`.
- Node remains supported for running built output (`dist/*`) and production installs. - Node remains supported for running built output (`dist/*`) and production installs.
- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. Release checklist: `docs/platforms/mac/release.md`. - Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. Release checklist: `docs/platforms/mac/release.md`.
- Type-check/build: `pnpm build` (tsc) - Type-check/build: `pnpm build` (tsc)
@@ -54,7 +54,7 @@
- Add brief code comments for tricky or non-obvious logic. - Add brief code comments for tricky or non-obvious logic.
- Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`. - Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`.
- Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability. - Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability.
- Naming: use **Clawdbot** for product/app/docs headings; use `clawdbot` for CLI command, package/binary, paths, and config keys. - Naming: use **Moltbot** for product/app/docs headings; use `moltbot` for CLI command, package/binary, paths, and config keys.
## Release Channels (Naming) ## Release Channels (Naming)
- stable: tagged releases only (e.g. `vYYYY.M.D`), npm dist-tag `latest`. - stable: tagged releases only (e.g. `vYYYY.M.D`), npm dist-tag `latest`.
@@ -66,7 +66,7 @@
- Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`. - 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. - Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
- Do not set test workers above 16; tried already. - 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`. - Live tests (real keys): `CLAWDBOT_LIVE_TEST=1 pnpm test:live` (Moltbot-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 + whats covered: `docs/testing.md`. - Full kit + whats 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. - Pure test additions/fixes generally do **not** need a changelog entry unless they alter user-facing behavior or the user asks for one.
- Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available. - Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available.
@@ -97,19 +97,19 @@
- **Landing mode:** create an integration branch from `main`, bring in PR commits (**prefer rebase** for linear history; **merge allowed** when complexity/conflicts make it safer), apply fixes, add changelog (+ thanks + PR #), run full gate **locally before committing** (`pnpm lint && pnpm build && pnpm test`), commit, merge back to `main`, then `git switch main` (never stay on a topic branch after landing). Important: contributor needs to be in git graph after this! - **Landing mode:** create an integration branch from `main`, bring in PR commits (**prefer rebase** for linear history; **merge allowed** when complexity/conflicts make it safer), apply fixes, add changelog (+ thanks + PR #), run full gate **locally before committing** (`pnpm lint && pnpm build && pnpm test`), commit, merge back to `main`, then `git switch main` (never stay on a topic branch after landing). Important: contributor needs to be in git graph after this!
## Security & Configuration Tips ## Security & Configuration Tips
- Web provider stores creds at `~/.clawdbot/credentials/`; rerun `clawdbot login` if logged out. - Web provider stores creds at `~/.clawdbot/credentials/`; rerun `moltbot login` if logged out.
- Pi sessions live under `~/.clawdbot/sessions/` by default; the base directory is not configurable. - Pi sessions live under `~/.clawdbot/sessions/` by default; the base directory is not configurable.
- Environment variables: see `~/.profile`. - Environment variables: see `~/.profile`.
- Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples. - Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples.
- Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them. - Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them.
## Troubleshooting ## Troubleshooting
- Rebrand/migration issues or legacy config/service warnings: run `clawdbot doctor` (see `docs/gateway/doctor.md`). - Rebrand/migration issues or legacy config/service warnings: run `moltbot doctor` (see `docs/gateway/doctor.md`).
## Agent-Specific Notes ## Agent-Specific Notes
- Vocabulary: "makeup" = "mac app". - 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`. - 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`. - Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/moltbot && 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 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. - When answering questions, respond with high-confidence answers only: verify in code; do not guess.
- Never update the Carbon dependency. - Never update the Carbon dependency.
@@ -117,12 +117,12 @@
- Patching dependencies (pnpm patches, overrides, or vendored changes) requires explicit approval; do not do this by default. - Patching dependencies (pnpm patches, overrides, or vendored changes) requires explicit approval; do not do this by default.
- CLI progress: use `src/cli/progress.ts` (`osc-progress` + `@clack/prompts` spinner); dont hand-roll spinners/bars. - CLI progress: use `src/cli/progress.ts` (`osc-progress` + `@clack/prompts` spinner); dont hand-roll spinners/bars.
- Status output: keep tables + ANSI-safe wrapping (`src/terminal/table.ts`); `status --all` = read-only/pasteable, `status --deep` = probes. - Status output: keep tables + ANSI-safe wrapping (`src/terminal/table.ts`); `status --all` = read-only/pasteable, `status --deep` = probes.
- Gateway currently runs only as the menubar app; there is no separate LaunchAgent/helper label installed. Restart via the Clawdbot Mac app or `scripts/restart-mac.sh`; to verify/kill use `launchctl print gui/$UID | grep clawdbot` rather than assuming a fixed label. **When debugging on macOS, start/stop the gateway via the app, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.** - Gateway currently runs only as the menubar app; there is no separate LaunchAgent/helper label installed. Restart via the Moltbot Mac app or `scripts/restart-mac.sh`; to verify/kill use `launchctl print gui/$UID | grep moltbot` rather than assuming a fixed label. **When debugging on macOS, start/stop the gateway via the app, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.**
- macOS logs: use `./scripts/clawlog.sh` to query unified logs for the Clawdbot subsystem; it supports follow/tail/category filters and expects passwordless sudo for `/usr/bin/log`. - macOS logs: use `./scripts/clawlog.sh` to query unified logs for the Moltbot subsystem; it supports follow/tail/category filters and expects passwordless sudo for `/usr/bin/log`.
- If shared guardrails are available locally, review them; otherwise follow this repo's guidance. - If shared guardrails are available locally, review them; otherwise follow this repo's guidance.
- SwiftUI state management (iOS/macOS): prefer the `Observation` framework (`@Observable`, `@Bindable`) over `ObservableObject`/`@StateObject`; dont introduce new `ObservableObject` unless required for compatibility, and migrate existing usages when touching related code. - SwiftUI state management (iOS/macOS): prefer the `Observation` framework (`@Observable`, `@Bindable`) over `ObservableObject`/`@StateObject`; dont introduce new `ObservableObject` unless required for compatibility, and migrate existing usages when touching related code.
- Connection providers: when adding a new connection, update every UI surface and docs (macOS app, web UI, mobile if applicable, onboarding/overview docs) and add matching status + configuration forms so provider lists and settings stay in sync. - Connection providers: when adding a new connection, update every UI surface and docs (macOS app, web UI, mobile if applicable, onboarding/overview docs) and add matching status + configuration forms so provider lists and settings stay in sync.
- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/Sources/Info.plist` + `apps/ios/Tests/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `apps/macos/Sources/Clawdbot/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `docs/install/updating.md` (pinned npm version), `docs/platforms/mac/release.md` (APP_VERSION/APP_BUILD examples), Peekaboo Xcode projects/Info.plists (MARKETING_VERSION/CURRENT_PROJECT_VERSION). - Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/Sources/Info.plist` + `apps/ios/Tests/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `apps/macos/Sources/Moltbot/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `docs/install/updating.md` (pinned npm version), `docs/platforms/mac/release.md` (APP_VERSION/APP_BUILD examples), Peekaboo Xcode projects/Info.plists (MARKETING_VERSION/CURRENT_PROJECT_VERSION).
- **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch. - **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch.
- **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators. - **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators.
- iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`. - iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`.
@@ -149,9 +149,9 @@
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac. - Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
- Never send streaming/partial replies to external messaging surfaces (WhatsApp, Telegram); only final replies should be delivered there. Streaming/tool events may still go to internal UIs/control channel. - Never send streaming/partial replies to external messaging surfaces (WhatsApp, Telegram); only final replies should be delivered there. Streaming/tool events may still go to internal UIs/control channel.
- Voice wake forwarding tips: - Voice wake forwarding tips:
- Command template should stay `clawdbot-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Dont add extra quotes. - Command template should stay `moltbot-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Dont add extra quotes.
- launchd PATH is minimal; ensure the apps launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`clawdbot` binaries resolve when invoked via `clawdbot-mac`. - launchd PATH is minimal; ensure the apps launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`moltbot` binaries resolve when invoked via `moltbot-mac`.
- For manual `clawdbot message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tools escaping. - For manual `moltbot message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tools escaping.
- Release guardrails: do not change version numbers without operators explicit consent; always ask permission before running any npm publish/release step. - Release guardrails: do not change version numbers without operators explicit consent; always ask permission before running any npm publish/release step.
## NPM + 1Password (publish/verify) ## NPM + 1Password (publish/verify)

View File

@@ -6,7 +6,7 @@ Docs: https://docs.molt.bot
Status: unreleased. Status: unreleased.
### Changes ### Changes
- Rebrand: rename the npm package/CLI to `moltbot`, add a `clawdbot` compatibility shim, and move extensions to the `@moltbot/*` scope. - Rebrand: rename the npm package/CLI to `moltbot`, add a `moltbot` compatibility shim, and move extensions to the `@moltbot/*` scope.
- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev. - Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev.
- macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk). - macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk).
- Tools: add per-sender group tool policies and fix precedence. (#1757) Thanks @adam91holt. - Tools: add per-sender group tool policies and fix precedence. (#1757) Thanks @adam91holt.
@@ -195,9 +195,9 @@ Status: unreleased.
### Changes ### Changes
- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.molt.bot/multi-agent-sandbox-tools - Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.molt.bot/multi-agent-sandbox-tools
- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.molt.bot/bedrock - Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.molt.bot/bedrock
- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.molt.bot/cli/system - CLI: add `moltbot system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.molt.bot/cli/system
- CLI: add live auth probes to `clawdbot models status` for per-profile verification. (commit 40181afde) https://docs.molt.bot/cli/models - CLI: add live auth probes to `moltbot models status` for per-profile verification. (commit 40181afde) https://docs.molt.bot/cli/models
- CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it. (commit 2c85b1b40) - CLI: restart the gateway by default after `moltbot 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) - 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.molt.bot/tools/llm-task - Plugins: add optional `llm-task` JSON-only tool for workflows. (#1498) Thanks @vignesh07. https://docs.molt.bot/tools/llm-task
- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0. - Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
@@ -237,7 +237,7 @@ Status: unreleased.
- UI: keep the Control UI sidebar visible while scrolling long pages. (#1515) Thanks @pookNast. - 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) - 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) - 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) - CLI: auth probe output polish (table output, inline errors, reduced noise, and wrap fixes in `moltbot 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: 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. - 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. - Skills: gate bird Homebrew install to macOS. (#1569) Thanks @bradleypriest.
@@ -296,12 +296,12 @@ Status: unreleased.
- Exec approvals: support wildcard agent allowlists (`*`) across all agents. - Exec approvals: support wildcard agent allowlists (`*`) across all agents.
- Exec approvals: allowlist matches resolved binary paths only, add safe stdin-only bins, and tighten allowlist shell parsing. - Exec approvals: allowlist matches resolved binary paths only, add safe stdin-only bins, and tighten allowlist shell parsing.
- Nodes: expose node PATH in status/describe and bootstrap PATH for node-host execution. - Nodes: expose node PATH in status/describe and bootstrap PATH for node-host execution.
- CLI: flatten node service commands under `clawdbot node` and remove `service node` docs. - CLI: flatten node service commands under `moltbot node` and remove `service node` docs.
- CLI: move gateway service commands under `clawdbot gateway` and add `gateway probe` for reachability. - CLI: move gateway service commands under `moltbot gateway` and add `gateway probe` for reachability.
- Sessions: add per-channel reset overrides via `session.resetByChannel`. (#1353) Thanks @cash-echo-bot. - Sessions: add per-channel reset overrides via `session.resetByChannel`. (#1353) Thanks @cash-echo-bot.
- Agents: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer. - Agents: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer.
- UI: show per-session assistant identity in the Control UI. (#1420) Thanks @robbyczgw-cla. - UI: show per-session assistant identity in the Control UI. (#1420) Thanks @robbyczgw-cla.
- CLI: add `clawdbot update wizard` for interactive channel selection and restart prompts. https://docs.molt.bot/cli/update - CLI: add `moltbot update wizard` for interactive channel selection and restart prompts. https://docs.molt.bot/cli/update
- Signal: add typing indicators and DM read receipts via signal-cli. - Signal: add typing indicators and DM read receipts via signal-cli.
- MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero. - MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero.
- Onboarding: remove the run setup-token auth option (paste setup-token or reuse CLI creds instead). - Onboarding: remove the run setup-token auth option (paste setup-token or reuse CLI creds instead).
@@ -342,8 +342,8 @@ Status: unreleased.
- TUI: session picker shows derived titles, fuzzy search, relative times, and last message preview. (#1271) https://docs.molt.bot/tui - TUI: session picker shows derived titles, fuzzy search, relative times, and last message preview. (#1271) https://docs.molt.bot/tui
- TUI: add a searchable model picker for quicker model selection. (#1198) https://docs.molt.bot/tui - TUI: add a searchable model picker for quicker model selection. (#1198) https://docs.molt.bot/tui
- TUI: add input history (up/down) for submitted messages. (#1348) https://docs.molt.bot/tui - TUI: add input history (up/down) for submitted messages. (#1348) https://docs.molt.bot/tui
- ACP: add `clawdbot acp` for IDE integrations. https://docs.molt.bot/cli/acp - ACP: add `moltbot acp` for IDE integrations. https://docs.molt.bot/cli/acp
- ACP: add `clawdbot acp client` interactive harness for debugging. https://docs.molt.bot/cli/acp - ACP: add `moltbot acp client` interactive harness for debugging. https://docs.molt.bot/cli/acp
- Skills: add download installs with OS-filtered options. https://docs.molt.bot/tools/skills - Skills: add download installs with OS-filtered options. https://docs.molt.bot/tools/skills
- Skills: add the local sherpa-onnx-tts skill. https://docs.molt.bot/tools/skills - Skills: add the local sherpa-onnx-tts skill. https://docs.molt.bot/tools/skills
- Memory: add hybrid BM25 + vector search (FTS5) with weighted merging and fallback. https://docs.molt.bot/concepts/memory - Memory: add hybrid BM25 + vector search (FTS5) with weighted merging and fallback. https://docs.molt.bot/concepts/memory
@@ -374,8 +374,8 @@ Status: unreleased.
- Plugins: migrate the Zalo Personal plugin to the shared plugin SDK runtime. https://docs.molt.bot/plugins/zalouser - Plugins: migrate the Zalo Personal plugin to the shared plugin SDK runtime. https://docs.molt.bot/plugins/zalouser
- Plugins: allow optional agent tools with explicit allowlists and add the plugin tool authoring guide. https://docs.molt.bot/plugins/agent-tools - Plugins: allow optional agent tools with explicit allowlists and add the plugin tool authoring guide. https://docs.molt.bot/plugins/agent-tools
- Plugins: auto-enable bundled channel/provider plugins when configuration is present. - 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: sync plugin sources on channel switches and update npm-installed plugins during `moltbot update`.
- Plugins: share npm plugin update logic between `clawdbot update` and `clawdbot plugins update`. - Plugins: share npm plugin update logic between `moltbot update` and `moltbot plugins update`.
- Gateway/API: add `/v1/responses` (OpenResponses) with item-based input + semantic streaming events. (#1229) - 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) - Gateway/API: expand `/v1/responses` to support file/image inputs, tool_choice, usage, and output limits. (#1229)
@@ -384,7 +384,7 @@ Status: unreleased.
- Exec: add host/security/ask routing for gateway + node exec. https://docs.molt.bot/tools/exec - Exec: add host/security/ask routing for gateway + node exec. https://docs.molt.bot/tools/exec
- Exec: add `/exec` directive for per-session exec defaults (host/security/ask/node). https://docs.molt.bot/tools/exec - Exec: add `/exec` directive for per-session exec defaults (host/security/ask/node). https://docs.molt.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.molt.bot/tools/exec-approvals - 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.molt.bot/tools/exec-approvals
- Nodes: add headless node host (`clawdbot node start`) for `system.run`/`system.which`. https://docs.molt.bot/cli/node - Nodes: add headless node host (`moltbot node start`) for `system.run`/`system.which`. https://docs.molt.bot/cli/node
- Nodes: add node daemon service install/status/start/stop/restart. https://docs.molt.bot/cli/node - Nodes: add node daemon service install/status/start/stop/restart. https://docs.molt.bot/cli/node
- Bridge: add `skills.bins` RPC to support node host auto-allow skill bins. - 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.molt.bot/concepts/session - Sessions: add daily reset policy with per-type overrides and idle windows (default 4am local), preserving legacy idle-only configs. (#1146) https://docs.molt.bot/concepts/session
@@ -414,10 +414,10 @@ Status: unreleased.
- Swabble: use the tagged Commander Swift package release. - Swabble: use the tagged Commander Swift package release.
### Breaking ### 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. - **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety. Run `moltbot doctor --fix` to repair, then update plugins (`moltbot plugins update`) if you use any.
### Fixes ### Fixes
- Discovery: shorten Bonjour DNS-SD service type to `_clawdbot-gw._tcp` and update discovery clients/docs. - Discovery: shorten Bonjour DNS-SD service type to `_moltbot-gw._tcp` and update discovery clients/docs.
- Diagnostics: export OTLP logs, correct queue depth tracking, and document message-flow telemetry. - 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: emit message-flow diagnostics across channels via shared dispatch. (#1244)
- Diagnostics: gate heartbeat/webhook logging. (#1244) - Diagnostics: gate heartbeat/webhook logging. (#1244)
@@ -444,7 +444,7 @@ Status: unreleased.
- Plugins: add Nextcloud Talk manifest for plugin config validation. (#1297) - Plugins: add Nextcloud Talk manifest for plugin config validation. (#1297)
- Plugins: surface plugin load/register/config errors in gateway logs with plugin/source context. - 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: preserve cron delivery settings when editing message payloads. (#1322)
- CLI: keep `clawdbot logs` output resilient to broken pipes while preserving progress output. - CLI: keep `moltbot logs` output resilient to broken pipes while preserving progress output.
- CLI: avoid duplicating --profile/--dev flags when formatting commands. - 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: 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: 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)
@@ -462,7 +462,7 @@ Status: unreleased.
- TUI: show generic empty-state text for searchable pickers. (#1201) - TUI: show generic empty-state text for searchable pickers. (#1201)
- TUI: highlight model search matches and stabilize search ordering. - TUI: highlight model search matches and stabilize search ordering.
- Configure: hide OpenRouter auto routing model from the model picker. (#1182) - Configure: hide OpenRouter auto routing model from the model picker. (#1182)
- Memory: show total file counts + scan issues in `clawdbot memory status`. - Memory: show total file counts + scan issues in `moltbot memory status`.
- Memory: fall back to non-batch embeddings after repeated batch failures. - Memory: fall back to non-batch embeddings after repeated batch failures.
- Memory: apply OpenAI batch defaults even without explicit remote config. - Memory: apply OpenAI batch defaults even without explicit remote config.
- Memory: index atomically so failed reindex preserves the previous memory database. (#1151) - Memory: index atomically so failed reindex preserves the previous memory database. (#1151)
@@ -472,7 +472,7 @@ Status: unreleased.
- Memory: split overly long lines to keep embeddings under token limits. - Memory: split overly long lines to keep embeddings under token limits.
- Memory: skip empty chunks to avoid invalid embedding inputs. - Memory: skip empty chunks to avoid invalid embedding inputs.
- Memory: split embedding batches to avoid OpenAI token limits during indexing. - Memory: split embedding batches to avoid OpenAI token limits during indexing.
- Memory: probe sqlite-vec availability in `clawdbot memory status`. - Memory: probe sqlite-vec availability in `moltbot memory status`.
- Exec approvals: enforce allowlist when ask is off. - Exec approvals: enforce allowlist when ask is off.
- Exec approvals: prefer raw command for node approvals/events. - 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: show exec elevated flag before the command and keep it outside markdown in tool summaries.
@@ -524,18 +524,18 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
### Highlights ### Highlights
- Hooks: add hooks system with bundled hooks, CLI tooling, and docs. (#1028) — thanks @ThomsenDrake. https://docs.molt.bot/hooks - Hooks: add hooks system with bundled hooks, CLI tooling, and docs. (#1028) — thanks @ThomsenDrake. https://docs.molt.bot/hooks
- Media: add inbound media understanding (image/audio/video) with provider + CLI fallbacks. https://docs.molt.bot/nodes/media-understanding - Media: add inbound media understanding (image/audio/video) with provider + CLI fallbacks. https://docs.molt.bot/nodes/media-understanding
- Plugins: add Zalo Personal plugin (`@clawdbot/zalouser`) and unify channel directory for plugins. (#1032) — thanks @suminhthanh. https://docs.molt.bot/plugins/zalouser - Plugins: add Zalo Personal plugin (`@moltbot/zalouser`) and unify channel directory for plugins. (#1032) — thanks @suminhthanh. https://docs.molt.bot/plugins/zalouser
- Models: add Vercel AI Gateway auth choice + onboarding updates. (#1016) — thanks @timolins. https://docs.molt.bot/providers/vercel-ai-gateway - Models: add Vercel AI Gateway auth choice + onboarding updates. (#1016) — thanks @timolins. https://docs.molt.bot/providers/vercel-ai-gateway
- Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.molt.bot/concepts/session - Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.molt.bot/concepts/session
- Web search: add `country`/`language` parameters (schema + Brave API) and docs. (#1046) — thanks @YuriNachos. https://docs.molt.bot/tools/web - Web search: add `country`/`language` parameters (schema + Brave API) and docs. (#1046) — thanks @YuriNachos. https://docs.molt.bot/tools/web
### Breaking ### Breaking
- **BREAKING:** `clawdbot message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan. - **BREAKING:** `moltbot message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan.
- **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow. - **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow.
- **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`. - **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`.
- **BREAKING:** remove legacy provider-specific target resolution fallbacks; target resolution is centralized with plugin hints + directory lookups. - **BREAKING:** remove legacy provider-specific target resolution fallbacks; target resolution is centralized with plugin hints + directory lookups.
- **BREAKING:** `clawdbot hooks` is now `clawdbot webhooks`; hooks live under `clawdbot hooks`. https://docs.molt.bot/cli/webhooks - **BREAKING:** `moltbot hooks` is now `moltbot webhooks`; hooks live under `moltbot hooks`. https://docs.molt.bot/cli/webhooks
- **BREAKING:** `clawdbot plugins install <path>` now copies into `~/.clawdbot/extensions` (use `--link` to keep path-based loading). - **BREAKING:** `moltbot plugins install <path>` now copies into `~/.clawdbot/extensions` (use `--link` to keep path-based loading).
### Changes ### Changes
- Plugins: ship bundled plugins disabled by default and allow overrides by installed versions. (#1066) — thanks @ItzR3NO. - Plugins: ship bundled plugins disabled by default and allow overrides by installed versions. (#1066) — thanks @ItzR3NO.
@@ -553,7 +553,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Skills: update coding-agent guidance to prefer PTY-enabled exec runs and simplify tmux usage. - Skills: update coding-agent guidance to prefer PTY-enabled exec runs and simplify tmux usage.
- TUI: refresh session token counts after runs complete or fail. (#1079) — thanks @d-ploutarchos. - TUI: refresh session token counts after runs complete or fail. (#1079) — thanks @d-ploutarchos.
- Status: trim `/status` to current-provider usage only and drop the OAuth/token block. - Status: trim `/status` to current-provider usage only and drop the OAuth/token block.
- Directory: unify `clawdbot directory` across channels and plugin channels. - Directory: unify `moltbot directory` across channels and plugin channels.
- UI: allow deleting sessions from the Control UI. - UI: allow deleting sessions from the Control UI.
- Memory: add sqlite-vec vector acceleration with CLI status details. - Memory: add sqlite-vec vector acceleration with CLI status details.
- Memory: add experimental session transcript indexing for memory_search (opt-in via memorySearch.experimental.sessionMemory + sources). - Memory: add experimental session transcript indexing for memory_search (opt-in via memorySearch.experimental.sessionMemory + sources).
@@ -569,7 +569,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Docs: add `/help` hub, Node/npm PATH guide, and expand directory CLI docs. - Docs: add `/help` hub, Node/npm PATH guide, and expand directory CLI docs.
- Config: support env var substitution in config values. (#1044) — thanks @sebslight. - Config: support env var substitution in config values. (#1044) — thanks @sebslight.
- Health: add per-agent session summaries and account-level health details, and allow selective probes. (#1047) — thanks @gumadeiras. - Health: add per-agent session summaries and account-level health details, and allow selective probes. (#1047) — thanks @gumadeiras.
- Hooks: add hook pack installs (npm/path/zip/tar) with `clawdbot.hooks` manifests and `clawdbot hooks install/update`. - Hooks: add hook pack installs (npm/path/zip/tar) with `moltbot.hooks` manifests and `moltbot hooks install/update`.
- Plugins: add zip installs and `--link` to avoid copying local paths. - Plugins: add zip installs and `--link` to avoid copying local paths.
### Fixes ### Fixes
@@ -603,7 +603,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Security: default-deny slash/control commands unless a channel computed `CommandAuthorized` (fixes accidental “open” behavior), and ensure WhatsApp + Zalo plugin channels gate inline `/…` tokens correctly. https://docs.molt.bot/gateway/security - Security: default-deny slash/control commands unless a channel computed `CommandAuthorized` (fixes accidental “open” behavior), and ensure WhatsApp + Zalo plugin channels gate inline `/…` tokens correctly. https://docs.molt.bot/gateway/security
- Security: redact sensitive text in gateway WS logs. - Security: redact sensitive text in gateway WS logs.
- Tools: cap pending `exec` process output to avoid unbounded buffers. - Tools: cap pending `exec` process output to avoid unbounded buffers.
- CLI: speed up `clawdbot sandbox-explain` by avoiding heavy plugin imports when normalizing channel ids. - CLI: speed up `moltbot sandbox-explain` by avoiding heavy plugin imports when normalizing channel ids.
- Browser: remote profile tab operations prefer persistent Playwright and avoid silent HTTP fallbacks. (#1057) — thanks @mukhtharcm. - Browser: remote profile tab operations prefer persistent Playwright and avoid silent HTTP fallbacks. (#1057) — thanks @mukhtharcm.
- Browser: remote profile tab ops follow-up: shared Playwright loader, Playwright-based focus, and more coverage (incl. opt-in live Browserless test). (follow-up to #1057) — thanks @mukhtharcm. - Browser: remote profile tab ops follow-up: shared Playwright loader, Playwright-based focus, and more coverage (incl. opt-in live Browserless test). (follow-up to #1057) — thanks @mukhtharcm.
- Browser: refresh extension relay tab metadata after navigation so `/json/list` stays current. (#1073) — thanks @roshanasingh4. - Browser: refresh extension relay tab metadata after navigation so `/json/list` stays current. (#1073) — thanks @roshanasingh4.
@@ -629,19 +629,19 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
## 2026.1.15 ## 2026.1.15
### Highlights ### Highlights
- Plugins: add provider auth registry + `clawdbot models auth login` for plugin-driven OAuth/API key flows. - Plugins: add provider auth registry + `moltbot models auth login` for plugin-driven OAuth/API key flows.
- Browser: improve remote CDP/Browserless support (auth passthrough, `wss` upgrade, timeouts, clearer errors). - Browser: improve remote CDP/Browserless support (auth passthrough, `wss` upgrade, timeouts, clearer errors).
- Heartbeat: per-agent configuration + 24h duplicate suppression. (#980) — thanks @voidserf. - Heartbeat: per-agent configuration + 24h duplicate suppression. (#980) — thanks @voidserf.
- Security: audit warns on weak model tiers; app nodes store auth tokens encrypted (Keychain/SecurePrefs). - Security: audit warns on weak model tiers; app nodes store auth tokens encrypted (Keychain/SecurePrefs).
### Breaking ### Breaking
- **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702) - **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702)
- **BREAKING:** Microsoft Teams is now a plugin; install `@clawdbot/msteams` via `clawdbot plugins install @clawdbot/msteams`. - **BREAKING:** Microsoft Teams is now a plugin; install `@moltbot/msteams` via `moltbot plugins install @moltbot/msteams`.
- **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow. - **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow.
### Changes ### Changes
- UI/Apps: move channel/config settings to schema-driven forms and rename Connections → Channels. (#1040) — thanks @thewilloftheshadow. - UI/Apps: move channel/config settings to schema-driven forms and rename Connections → Channels. (#1040) — thanks @thewilloftheshadow.
- CLI: set process titles to `clawdbot-<command>` for clearer process listings. - CLI: set process titles to `moltbot-<command>` for clearer process listings.
- CLI/macOS: sync remote SSH target/identity to config and let `gateway status` auto-infer SSH targets (ssh-config aware). - CLI/macOS: sync remote SSH target/identity to config and let `gateway status` auto-infer SSH targets (ssh-config aware).
- Telegram: scope inline buttons with allowlist default + callback gating in DMs/groups. - Telegram: scope inline buttons with allowlist default + callback gating in DMs/groups.
- Telegram: default reaction notifications to own. - Telegram: default reaction notifications to own.
@@ -649,13 +649,13 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Heartbeat: tighten prompt guidance + suppress duplicate alerts for 24h. (#980) — thanks @voidserf. - Heartbeat: tighten prompt guidance + suppress duplicate alerts for 24h. (#980) — thanks @voidserf.
- Repo: ignore local identity files to avoid accidental commits. (#1001) — thanks @gerardward2007. - Repo: ignore local identity files to avoid accidental commits. (#1001) — thanks @gerardward2007.
- Sessions/Security: add `session.dmScope` for multi-user DM isolation and audit warnings. (#948) — thanks @Alphonse-arianee. - Sessions/Security: add `session.dmScope` for multi-user DM isolation and audit warnings. (#948) — thanks @Alphonse-arianee.
- Plugins: add provider auth registry + `clawdbot models auth login` for plugin-driven OAuth/API key flows. - Plugins: add provider auth registry + `moltbot models auth login` for plugin-driven OAuth/API key flows.
- Onboarding: switch channels setup to a single-select loop with per-channel actions and disabled hints in the picker. - Onboarding: switch channels setup to a single-select loop with per-channel actions and disabled hints in the picker.
- TUI: show provider/model labels for the active session and default model. - TUI: show provider/model labels for the active session and default model.
- Heartbeat: add per-agent heartbeat configuration and multi-agent docs example. - Heartbeat: add per-agent heartbeat configuration and multi-agent docs example.
- UI: show gateway auth guidance + doc link on unauthorized Control UI connections. - UI: show gateway auth guidance + doc link on unauthorized Control UI connections.
- UI: add session deletion action in Control UI sessions list. (#1017) — thanks @Szpadel. - UI: add session deletion action in Control UI sessions list. (#1017) — thanks @Szpadel.
- Security: warn on weak model tiers (Haiku, below GPT-5, below Claude 4.5) in `clawdbot security audit`. - Security: warn on weak model tiers (Haiku, below GPT-5, below Claude 4.5) in `moltbot security audit`.
- Apps: store node auth tokens encrypted (Keychain/SecurePrefs). - Apps: store node auth tokens encrypted (Keychain/SecurePrefs).
- Daemon: share profile/state-dir resolution across service helpers and honor `CLAWDBOT_STATE_DIR` for Windows task scripts. - Daemon: share profile/state-dir resolution across service helpers and honor `CLAWDBOT_STATE_DIR` for Windows task scripts.
- Docs: clarify multi-gateway rescue bot guidance. (#969) — thanks @bjesuiter. - Docs: clarify multi-gateway rescue bot guidance. (#969) — thanks @bjesuiter.
@@ -665,8 +665,8 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Docs: add Date & Time guide and update prompt/timezone configuration docs. - Docs: add Date & Time guide and update prompt/timezone configuration docs.
- Messages: debounce rapid inbound messages across channels with per-connector overrides. (#971) — thanks @juanpablodlc. - Messages: debounce rapid inbound messages across channels with per-connector overrides. (#971) — thanks @juanpablodlc.
- Messages: allow media-only sends (CLI/tool) and show Telegram voice recording status for voice notes. (#957) — thanks @rdev. - Messages: allow media-only sends (CLI/tool) and show Telegram voice recording status for voice notes. (#957) — thanks @rdev.
- Auth/Status: keep auth profiles sticky per session (rotate on compaction/new), surface provider usage headers in `/status` and `clawdbot models status`, and update docs. - Auth/Status: keep auth profiles sticky per session (rotate on compaction/new), surface provider usage headers in `/status` and `moltbot models status`, and update docs.
- CLI: add `--json` output for `clawdbot daemon` lifecycle/install commands. - CLI: add `--json` output for `moltbot daemon` lifecycle/install commands.
- Memory: make `node-llama-cpp` an optional dependency (avoid Node 25 install failures) and improve local-embeddings fallback/errors. - Memory: make `node-llama-cpp` an optional dependency (avoid Node 25 install failures) and improve local-embeddings fallback/errors.
- Browser: add `snapshot refs=aria` (Playwright aria-ref ids) for self-resolving refs across `snapshot``act`. - Browser: add `snapshot refs=aria` (Playwright aria-ref ids) for self-resolving refs across `snapshot``act`.
- Browser: `profile="chrome"` now defaults to host control and returns clearer “attach a tab” errors. - Browser: `profile="chrome"` now defaults to host control and returns clearer “attach a tab” errors.
@@ -689,10 +689,10 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- iMessage: treat missing `imsg rpc` support as fatal to avoid restart loops. - iMessage: treat missing `imsg rpc` support as fatal to avoid restart loops.
- Auth: merge main auth profiles into per-agent stores for sub-agents and document inheritance. (#1013) — thanks @marcmarg. - Auth: merge main auth profiles into per-agent stores for sub-agents and document inheritance. (#1013) — thanks @marcmarg.
- Agents: avoid JSON Schema `format` collisions in tool params by renaming snapshot format fields. (#1013) — thanks @marcmarg. - Agents: avoid JSON Schema `format` collisions in tool params by renaming snapshot format fields. (#1013) — thanks @marcmarg.
- Fix: make `clawdbot update` auto-update global installs when installed via a package manager. - Fix: make `moltbot update` auto-update global installs when installed via a package manager.
- Fix: list model picker entries as provider/model pairs for explicit selection. (#970) — thanks @mcinteerj. - Fix: list model picker entries as provider/model pairs for explicit selection. (#970) — thanks @mcinteerj.
- Fix: align OpenAI image-gen defaults with DALL-E 3 standard quality and document output formats. (#880) — thanks @mkbehr. - Fix: align OpenAI image-gen defaults with DALL-E 3 standard quality and document output formats. (#880) — thanks @mkbehr.
- Fix: persist `gateway.mode=local` after selecting Local run mode in `clawdbot configure`, even if no other sections are chosen. - Fix: persist `gateway.mode=local` after selecting Local run mode in `moltbot configure`, even if no other sections are chosen.
- Daemon: fix profile-aware service label resolution (env-driven) and add coverage for launchd/systemd/schtasks. (#969) — thanks @bjesuiter. - Daemon: fix profile-aware service label resolution (env-driven) and add coverage for launchd/systemd/schtasks. (#969) — thanks @bjesuiter.
- Agents: avoid false positives when logging unsupported Google tool schema keywords. - Agents: avoid false positives when logging unsupported Google tool schema keywords.
- Agents: skip Gemini history downgrades for google-antigravity to preserve tool calls. (#894) — thanks @mukhtharcm. - Agents: skip Gemini history downgrades for google-antigravity to preserve tool calls. (#894) — thanks @mukhtharcm.
@@ -718,13 +718,13 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Web search: `web_search`/`web_fetch` tools (Brave API) + first-time setup in onboarding/configure. - Web search: `web_search`/`web_fetch` tools (Brave API) + first-time setup in onboarding/configure.
- Browser control: Chrome extension relay takeover mode + remote browser control support. - Browser control: Chrome extension relay takeover mode + remote browser control support.
- Plugins: channel plugins (gateway HTTP hooks) + Zalo plugin + onboarding install flow. (#854) — thanks @longmaba. - Plugins: channel plugins (gateway HTTP hooks) + Zalo plugin + onboarding install flow. (#854) — thanks @longmaba.
- Security: expanded `clawdbot security audit` (+ `--fix`), detect-secrets CI scan, and a `SECURITY.md` reporting policy. - Security: expanded `moltbot security audit` (+ `--fix`), detect-secrets CI scan, and a `SECURITY.md` reporting policy.
### Changes ### Changes
- Docs: clarify per-agent auth stores, sandboxed skill binaries, and elevated semantics. - Docs: clarify per-agent auth stores, sandboxed skill binaries, and elevated semantics.
- Docs: add FAQ entries for missing provider auth after adding agents and Gemini thinking signature errors. - Docs: add FAQ entries for missing provider auth after adding agents and Gemini thinking signature errors.
- Agents: add optional auth-profile copy prompt on `agents add` and improve auth error messaging. - Agents: add optional auth-profile copy prompt on `agents add` and improve auth error messaging.
- Security: expand `clawdbot security audit` checks (model hygiene, config includes, plugin allowlists, exposure matrix) and extend `--fix` to tighten more sensitive state paths. - Security: expand `moltbot security audit` checks (model hygiene, config includes, plugin allowlists, exposure matrix) and extend `--fix` to tighten more sensitive state paths.
- Security: add `SECURITY.md` reporting policy. - Security: add `SECURITY.md` reporting policy.
- Channels: add Matrix plugin (external) with docs + onboarding hooks. - Channels: add Matrix plugin (external) with docs + onboarding hooks.
- Plugins: add Zalo channel plugin with gateway HTTP hooks and onboarding install prompt. (#854) — thanks @longmaba. - Plugins: add Zalo channel plugin with gateway HTTP hooks and onboarding install prompt. (#854) — thanks @longmaba.
@@ -734,7 +734,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Security: add detect-secrets CI scan and baseline guidance. (#227) — thanks @Hyaxia. - Security: add detect-secrets CI scan and baseline guidance. (#227) — thanks @Hyaxia.
- Tools: add `web_search`/`web_fetch` (Brave API), auto-enable `web_fetch` for sandboxed sessions, and remove the `brave-search` skill. - Tools: add `web_search`/`web_fetch` (Brave API), auto-enable `web_fetch` for sandboxed sessions, and remove the `brave-search` skill.
- CLI/Docs: add a web tools configure section for storing Brave API keys and update onboarding tips. - CLI/Docs: add a web tools configure section for storing Brave API keys and update onboarding tips.
- Browser: add Chrome extension relay takeover mode (toolbar button), plus `clawdbot browser extension install/path` and remote browser control (standalone server + token auth). - Browser: add Chrome extension relay takeover mode (toolbar button), plus `moltbot browser extension install/path` and remote browser control (standalone server + token auth).
### Fixes ### Fixes
- Sessions: refactor session store updates to lock + mutate per-entry, add chat.inject, and harden subagent cleanup flow. (#944) — thanks @tyler6204. - Sessions: refactor session store updates to lock + mutate per-entry, add chat.inject, and harden subagent cleanup flow. (#944) — thanks @tyler6204.
@@ -831,19 +831,19 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
### New & Improved ### New & Improved
- Memory: add custom OpenAI-compatible embedding endpoints; support OpenAI/local `node-llama-cpp` embeddings with per-agent overrides and provider metadata in tools/CLI. (#819) — thanks @mukhtharcm. - Memory: add custom OpenAI-compatible embedding endpoints; support OpenAI/local `node-llama-cpp` embeddings with per-agent overrides and provider metadata in tools/CLI. (#819) — thanks @mukhtharcm.
- Memory: new `clawdbot memory` CLI plus `memory_search`/`memory_get` tools with snippets + line ranges; index stored under `~/.clawdbot/memory/{agentId}.sqlite` with watch-on-by-default. - Memory: new `moltbot memory` CLI plus `memory_search`/`memory_get` tools with snippets + line ranges; index stored under `~/.clawdbot/memory/{agentId}.sqlite` with watch-on-by-default.
- Agents: strengthen memory recall guidance; make workspace bootstrap truncation configurable (default 20k) with warnings; add default sub-agent model config. - Agents: strengthen memory recall guidance; make workspace bootstrap truncation configurable (default 20k) with warnings; add default sub-agent model config.
- Tools/Sandbox: add tool profiles + group shorthands; support tool-policy groups in `tools.sandbox.tools`; drop legacy `memory` shorthand; allow Docker bind mounts via `docker.binds`. (#790) — thanks @akonyer. - Tools/Sandbox: add tool profiles + group shorthands; support tool-policy groups in `tools.sandbox.tools`; drop legacy `memory` shorthand; allow Docker bind mounts via `docker.binds`. (#790) — thanks @akonyer.
- Tools: add provider/model-specific tool policy overrides (`tools.byProvider`) to trim tool exposure per provider. - Tools: add provider/model-specific tool policy overrides (`tools.byProvider`) to trim tool exposure per provider.
- Tools: add browser `scrollintoview` action; allow Claude/Gemini tool param aliases; allow thinking `xhigh` for GPT-5.2/Codex with safe downgrades. (#793) — thanks @hsrvc; (#444) — thanks @grp06. - Tools: add browser `scrollintoview` action; allow Claude/Gemini tool param aliases; allow thinking `xhigh` for GPT-5.2/Codex with safe downgrades. (#793) — thanks @hsrvc; (#444) — thanks @grp06.
- Gateway/CLI: add Tailscale binary discovery, custom bind mode, and probe auth retry; add `clawdbot dashboard` auto-open flow; default native slash commands to `"auto"` with per-provider overrides. (#740) — thanks @jeffersonwarrior. - Gateway/CLI: add Tailscale binary discovery, custom bind mode, and probe auth retry; add `moltbot dashboard` auto-open flow; default native slash commands to `"auto"` with per-provider overrides. (#740) — thanks @jeffersonwarrior.
- Auth/Onboarding: add Chutes OAuth (PKCE + refresh + onboarding choice); normalize API key inputs; default TUI onboarding to `deliver: false`. (#726) — thanks @FrieSei; (#791) — thanks @roshanasingh4. - Auth/Onboarding: add Chutes OAuth (PKCE + refresh + onboarding choice); normalize API key inputs; default TUI onboarding to `deliver: false`. (#726) — thanks @FrieSei; (#791) — thanks @roshanasingh4.
- Providers: add `discord.allowBots`; trim legacy MiniMax M2 from default catalogs; route MiniMax vision to the Coding Plan VLM endpoint (also accepts `@/path/to/file.png` inputs). (#802) — thanks @zknicker. - Providers: add `discord.allowBots`; trim legacy MiniMax M2 from default catalogs; route MiniMax vision to the Coding Plan VLM endpoint (also accepts `@/path/to/file.png` inputs). (#802) — thanks @zknicker.
- Gateway: allow Tailscale Serve identity headers to satisfy token auth; rebuild Control UI assets when protocol schema is newer. (#823) — thanks @roshanasingh4; (#786) — thanks @meaningfool. - Gateway: allow Tailscale Serve identity headers to satisfy token auth; rebuild Control UI assets when protocol schema is newer. (#823) — thanks @roshanasingh4; (#786) — thanks @meaningfool.
- Heartbeat: default `ackMaxChars` to 300 so short `HEARTBEAT_OK` replies stay internal. - Heartbeat: default `ackMaxChars` to 300 so short `HEARTBEAT_OK` replies stay internal.
### Installer ### Installer
- Install: run `clawdbot doctor --non-interactive` after git installs/updates and nudge daemon restarts when detected. - Install: run `moltbot doctor --non-interactive` after git installs/updates and nudge daemon restarts when detected.
### Fixes ### Fixes
- Doctor: warn on pnpm workspace mismatches, missing Control UI assets, and missing tsx binaries; offer UI rebuilds. - Doctor: warn on pnpm workspace mismatches, missing Control UI assets, and missing tsx binaries; offer UI rebuilds.
@@ -864,7 +864,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Telegram: preserve forum topic thread ids, persist polling offsets, respect account bindings in webhook mode, and show typing indicator in General topics. (#727, #739) — thanks @thewilloftheshadow; (#821) — thanks @gumadeiras; (#779) — thanks @azade-c. - Telegram: preserve forum topic thread ids, persist polling offsets, respect account bindings in webhook mode, and show typing indicator in General topics. (#727, #739) — thanks @thewilloftheshadow; (#821) — thanks @gumadeiras; (#779) — thanks @azade-c.
- Slack: accept slash commands with or without leading `/` for custom command configs. (#798) — thanks @thewilloftheshadow. - Slack: accept slash commands with or without leading `/` for custom command configs. (#798) — thanks @thewilloftheshadow.
- Cron: persist disabled jobs correctly; accept `jobId` aliases for update/run/remove params. (#205, #252) — thanks @thewilloftheshadow. - Cron: persist disabled jobs correctly; accept `jobId` aliases for update/run/remove params. (#205, #252) — thanks @thewilloftheshadow.
- Gateway/CLI: honor `CLAWDBOT_LAUNCHD_LABEL` / `CLAWDBOT_SYSTEMD_UNIT` overrides; `agents.list` respects explicit config; reduce noisy loopback WS logs during tests; run `clawdbot doctor --non-interactive` during updates. (#781) — thanks @ronyrus. - Gateway/CLI: honor `CLAWDBOT_LAUNCHD_LABEL` / `CLAWDBOT_SYSTEMD_UNIT` overrides; `agents.list` respects explicit config; reduce noisy loopback WS logs during tests; run `moltbot doctor --non-interactive` during updates. (#781) — thanks @ronyrus.
- Onboarding/Control UI: refuse invalid configs (run doctor first); quote Windows browser URLs for OAuth; keep chat scroll position unless the user is near the bottom. (#764) — thanks @mukhtharcm; (#794) — thanks @roshanasingh4; (#217) — thanks @thewilloftheshadow. - Onboarding/Control UI: refuse invalid configs (run doctor first); quote Windows browser URLs for OAuth; keep chat scroll position unless the user is near the bottom. (#764) — thanks @mukhtharcm; (#794) — thanks @roshanasingh4; (#217) — thanks @thewilloftheshadow.
- Tools/UI: harden tool input schemas for strict providers; drop null-only union variants for Gemini schema cleanup; treat `maxChars: 0` as unlimited; keep TUI last streamed response instead of "(no output)". (#782) — thanks @AbhisekBasu1; (#796) — thanks @gabriel-trigo; (#747) — thanks @thewilloftheshadow. - Tools/UI: harden tool input schemas for strict providers; drop null-only union variants for Gemini schema cleanup; treat `maxChars: 0` as unlimited; keep TUI last streamed response instead of "(no output)". (#782) — thanks @AbhisekBasu1; (#796) — thanks @gabriel-trigo; (#747) — thanks @thewilloftheshadow.
- Connections UI: polish multi-account account cards. (#816) — thanks @steipete. - Connections UI: polish multi-account account cards. (#816) — thanks @steipete.
@@ -893,7 +893,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Auto-reply: add compact `/model` picker (models + available providers) and show provider endpoints in `/model status`. - Auto-reply: add compact `/model` picker (models + available providers) and show provider endpoints in `/model status`.
- Control UI: add Config tab model presets (MiniMax M2.1, GLM 4.7, Kimi) for one-click setup. - Control UI: add Config tab model presets (MiniMax M2.1, GLM 4.7, Kimi) for one-click setup.
- Plugins: add extension loader (tools/RPC/CLI/services), discovery paths, and config schema + Control UI labels (uiHints). - Plugins: add extension loader (tools/RPC/CLI/services), discovery paths, and config schema + Control UI labels (uiHints).
- Plugins: add `clawdbot plugins install` (path/tgz/npm), plus `list|info|enable|disable|doctor` UX. - Plugins: add `moltbot plugins install` (path/tgz/npm), plus `list|info|enable|disable|doctor` UX.
- Plugins: voice-call plugin now real (Twilio/log), adds start/status RPC/CLI/tool + tests. - Plugins: voice-call plugin now real (Twilio/log), adds start/status RPC/CLI/tool + tests.
- Docs: add plugins doc + cross-links from tools/skills/gateway config. - Docs: add plugins doc + cross-links from tools/skills/gateway config.
- Docs: add beginner-friendly plugin quick start + expand Voice Call plugin docs. - Docs: add beginner-friendly plugin quick start + expand Voice Call plugin docs.
@@ -906,7 +906,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Agents: add pre-compaction memory flush config (`agents.defaults.compaction.*`) with a soft threshold + system prompt. - Agents: add pre-compaction memory flush config (`agents.defaults.compaction.*`) with a soft threshold + system prompt.
- Config: add `$include` directive for modular config files. (#731) — thanks @pasogott. - Config: add `$include` directive for modular config files. (#731) — thanks @pasogott.
- Build: set pnpm minimum release age to 2880 minutes (2 days). (#718) — thanks @dan-dr. - Build: set pnpm minimum release age to 2880 minutes (2 days). (#718) — thanks @dan-dr.
- macOS: prompt to install the global `clawdbot` CLI when missing in local mode; install via `molt.bot/install-cli.sh` (no onboarding) and use external launchd/CLI instead of the embedded gateway runtime. - macOS: prompt to install the global `moltbot` CLI when missing in local mode; install via `molt.bot/install-cli.sh` (no onboarding) and use external launchd/CLI instead of the embedded gateway runtime.
- Docs: add gog calendar event color IDs from `gog calendar colors`. (#715) — thanks @mjrussell. - Docs: add gog calendar event color IDs from `gog calendar colors`. (#715) — thanks @mjrussell.
- Cron/CLI: add `--model` flag to cron add/edit commands. (#711) — thanks @mjrussell. - Cron/CLI: add `--model` flag to cron add/edit commands. (#711) — thanks @mjrussell.
- Cron/CLI: trim model overrides on cron edits and document main-session guidance. (#711) — thanks @mjrussell. - Cron/CLI: trim model overrides on cron edits and document main-session guidance. (#711) — thanks @mjrussell.
@@ -936,7 +936,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Auth: read Codex keychain credentials and make the lookup platform-aware. - Auth: read Codex keychain credentials and make the lookup platform-aware.
- macOS/Release: avoid bundling dist artifacts in relay builds and generate appcasts from zip-only sources. - macOS/Release: avoid bundling dist artifacts in relay builds and generate appcasts from zip-only sources.
- Doctor: surface plugin diagnostics in the report. - Doctor: surface plugin diagnostics in the report.
- Plugins: treat `plugins.load.paths` directory entries as package roots when they contain `package.json` + `clawdbot.extensions`; load plugin packages from config dirs; extract archives without system tar. - Plugins: treat `plugins.load.paths` directory entries as package roots when they contain `package.json` + `moltbot.extensions`; load plugin packages from config dirs; extract archives without system tar.
- Config: expand `~` in `CLAWDBOT_CONFIG_PATH` and common path-like config fields (including `plugins.load.paths`); guard invalid `$include` paths. (#731) — thanks @pasogott. - Config: expand `~` in `CLAWDBOT_CONFIG_PATH` and common path-like config fields (including `plugins.load.paths`); guard invalid `$include` paths. (#731) — thanks @pasogott.
- Agents: stop pre-creating session transcripts so first user messages persist in JSONL history. - Agents: stop pre-creating session transcripts so first user messages persist in JSONL history.
- Agents: skip pre-compaction memory flush when the session workspace is read-only. - Agents: skip pre-compaction memory flush when the session workspace is read-only.
@@ -965,9 +965,9 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
## 2026.1.10 ## 2026.1.10
### Highlights ### Highlights
- CLI: `clawdbot status` now table-based + shows OS/update/gateway/daemon/agents/sessions; `status --all` adds a full read-only debug report (tables, log tails, Tailscale summary, and scan progress via OSC-9 + spinner). - CLI: `moltbot status` now table-based + shows OS/update/gateway/daemon/agents/sessions; `status --all` adds a full read-only debug report (tables, log tails, Tailscale summary, and scan progress via OSC-9 + spinner).
- CLI Backends: add Codex CLI fallback with resume support (text output) and JSONL parsing for new runs, plus a live CLI resume probe. - CLI Backends: add Codex CLI fallback with resume support (text output) and JSONL parsing for new runs, plus a live CLI resume probe.
- CLI: add `clawdbot update` (safe-ish git checkout update) + `--update` shorthand. (#673) — thanks @fm1randa. - CLI: add `moltbot update` (safe-ish git checkout update) + `--update` shorthand. (#673) — thanks @fm1randa.
- Gateway: add OpenAI-compatible `/v1/chat/completions` HTTP endpoint (auth, SSE streaming, per-agent routing). (#680). - Gateway: add OpenAI-compatible `/v1/chat/completions` HTTP endpoint (auth, SSE streaming, per-agent routing). (#680).
### Changes ### Changes
@@ -977,7 +977,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Agents/Browser: add `browser.target` (sandbox/host/custom) with sandbox host-control gating via `agents.defaults.sandbox.browser.allowHostControl`, allowlists for custom control URLs/hosts/ports, and expand browser tool docs (remote control, profiles, internals). - Agents/Browser: add `browser.target` (sandbox/host/custom) with sandbox host-control gating via `agents.defaults.sandbox.browser.allowHostControl`, allowlists for custom control URLs/hosts/ports, and expand browser tool docs (remote control, profiles, internals).
- Onboarding/Models: add catalog-backed default model picker to onboarding + configure. (#611) — thanks @jonasjancarik. - Onboarding/Models: add catalog-backed default model picker to onboarding + configure. (#611) — thanks @jonasjancarik.
- Agents/OpenCode Zen: update fallback models + defaults, keep legacy alias mappings. (#669) — thanks @magimetal. - Agents/OpenCode Zen: update fallback models + defaults, keep legacy alias mappings. (#669) — thanks @magimetal.
- CLI: add `clawdbot reset` and `clawdbot uninstall` flows (interactive + non-interactive) plus docker cleanup smoke test. - CLI: add `moltbot reset` and `moltbot uninstall` flows (interactive + non-interactive) plus docker cleanup smoke test.
- Providers: move provider wiring to a plugin architecture. (#661). - Providers: move provider wiring to a plugin architecture. (#661).
- Providers: unify group history context wrappers across providers with per-provider/per-account `historyLimit` overrides (fallback to `messages.groupChat.historyLimit`). Set `0` to disable. (#672). - Providers: unify group history context wrappers across providers with per-provider/per-account `historyLimit` overrides (fallback to `messages.groupChat.historyLimit`). Set `0` to disable. (#672).
- Gateway/Heartbeat: optionally deliver heartbeat `Reasoning:` output (`agents.defaults.heartbeat.includeReasoning`). (#690) - Gateway/Heartbeat: optionally deliver heartbeat `Reasoning:` output (`agents.defaults.heartbeat.includeReasoning`). (#690)
@@ -986,7 +986,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
### Fixes ### Fixes
- Auto-reply: suppress draft/typing streaming for `NO_REPLY` (silent system ops) so it doesnt leak partial output. - Auto-reply: suppress draft/typing streaming for `NO_REPLY` (silent system ops) so it doesnt leak partial output.
- CLI/Status: expand tables to full terminal width; clarify provider setup vs runtime warnings; richer per-provider detail; token previews in `status` while keeping `status --all` redacted; add troubleshooting link footer; keep log tails pasteable; show gateway auth used when reachable; surface provider runtime errors (Signal/iMessage/Slack); harden `tailscale status --json` parsing; make `status --all` scan progress determinate; and replace the footer with a 3-line “Next steps” recommendation (share/debug/probe). - CLI/Status: expand tables to full terminal width; clarify provider setup vs runtime warnings; richer per-provider detail; token previews in `status` while keeping `status --all` redacted; add troubleshooting link footer; keep log tails pasteable; show gateway auth used when reachable; surface provider runtime errors (Signal/iMessage/Slack); harden `tailscale status --json` parsing; make `status --all` scan progress determinate; and replace the footer with a 3-line “Next steps” recommendation (share/debug/probe).
- CLI/Gateway: clarify that `clawdbot gateway status` reports RPC health (connect + RPC) and shows RPC failures separately from connect failures. - CLI/Gateway: clarify that `moltbot gateway status` reports RPC health (connect + RPC) and shows RPC failures separately from connect failures.
- CLI/Update: gate progress spinner on stdout TTY and align clean-check step label. (#701) — thanks @bjesuiter. - CLI/Update: gate progress spinner on stdout TTY and align clean-check step label. (#701) — thanks @bjesuiter.
- Telegram: add `/whoami` + `/id` commands to reveal sender id for allowlists; allow `@username` and prefixed ids in `allowFrom` prompts (with stability warning). - Telegram: add `/whoami` + `/id` commands to reveal sender id for allowlists; allow `@username` and prefixed ids in `allowFrom` prompts (with stability warning).
- Heartbeat: strip markup-wrapped `HEARTBEAT_OK` so acks dont leak to external providers (e.g., Telegram). - Heartbeat: strip markup-wrapped `HEARTBEAT_OK` so acks dont leak to external providers (e.g., Telegram).
@@ -999,7 +999,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Agents/Pi: inject config `temperature`/`maxTokens` into streaming without replacing the session streamFn; cover with live maxTokens probe. (#732) — thanks @peschee. - Agents/Pi: inject config `temperature`/`maxTokens` into streaming without replacing the session streamFn; cover with live maxTokens probe. (#732) — thanks @peschee.
- macOS: clear unsigned launchd overrides on signed restarts and warn via doctor when attach-only/disable markers are set. (#695) — thanks @jeffersonwarrior. - macOS: clear unsigned launchd overrides on signed restarts and warn via doctor when attach-only/disable markers are set. (#695) — thanks @jeffersonwarrior.
- Agents: enforce single-writer session locks and drop orphan tool results to prevent tool-call ID failures (MiniMax/Anthropic-compatible APIs). - Agents: enforce single-writer session locks and drop orphan tool results to prevent tool-call ID failures (MiniMax/Anthropic-compatible APIs).
- Docs: make `clawdbot status` the first diagnostic step, clarify `status --deep` behavior, and document `/whoami` + `/id`. - Docs: make `moltbot status` the first diagnostic step, clarify `status --deep` behavior, and document `/whoami` + `/id`.
- Docs/Testing: clarify live tool+image probes and how to list your testable `provider/model` ids. - Docs/Testing: clarify live tool+image probes and how to list your testable `provider/model` ids.
- Tests/Live: make gateway bash+read probes resilient to provider formatting while still validating real tool calls. - Tests/Live: make gateway bash+read probes resilient to provider formatting while still validating real tool calls.
- WhatsApp: detect @lid mentions in groups using authDir reverse mapping + resolve self JID E.164 for mention gating. (#692) — thanks @peschee. - WhatsApp: detect @lid mentions in groups using authDir reverse mapping + resolve self JID E.164 for mention gating. (#692) — thanks @peschee.
@@ -1019,7 +1019,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- WhatsApp: expose group participant IDs to the model so reactions can target the right sender. - WhatsApp: expose group participant IDs to the model so reactions can target the right sender.
- Cron: `wakeMode: "now"` waits for heartbeat completion (and retries when the main lane is busy). (#666) — thanks @roshanasingh4. - Cron: `wakeMode: "now"` waits for heartbeat completion (and retries when the main lane is busy). (#666) — thanks @roshanasingh4.
- Agents/OpenAI: fix Responses tool-only → follow-up turn handling (avoid standalone `reasoning` items that trigger 400 “required following item”) and replay reasoning items in Responses/Codex Responses history for tool-call-only turns. - Agents/OpenAI: fix Responses tool-only → follow-up turn handling (avoid standalone `reasoning` items that trigger 400 “required following item”) and replay reasoning items in Responses/Codex Responses history for tool-call-only turns.
- Sandbox: add `clawdbot sandbox explain` (effective policy inspector + fix-it keys); improve “sandbox jail” tool-policy/elevated errors with actionable config key paths; link to docs. - Sandbox: add `moltbot sandbox explain` (effective policy inspector + fix-it keys); improve “sandbox jail” tool-policy/elevated errors with actionable config key paths; link to docs.
- Hooks/Gmail: keep Tailscale serve path at `/` while preserving the public path. (#668) — thanks @antons. - Hooks/Gmail: keep Tailscale serve path at `/` while preserving the public path. (#668) — thanks @antons.
- Hooks/Gmail: allow Tailscale target URLs to preserve internal serve paths. - Hooks/Gmail: allow Tailscale target URLs to preserve internal serve paths.
- Auth: update Claude Code keychain credentials in-place during refresh sync; share JSON file helpers; add CLI fallback coverage. - Auth: update Claude Code keychain credentials in-place during refresh sync; share JSON file helpers; add CLI fallback coverage.
@@ -1031,12 +1031,12 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Gateway/Control UI: sniff image attachments for chat.send, drop non-images, and log mismatches. (#670) — thanks @cristip73. - Gateway/Control UI: sniff image attachments for chat.send, drop non-images, and log mismatches. (#670) — thanks @cristip73.
- macOS: force `restart-mac.sh --sign` to require identities and keep bundled Node signed for relay verification. (#580) — thanks @jeffersonwarrior. - macOS: force `restart-mac.sh --sign` to require identities and keep bundled Node signed for relay verification. (#580) — thanks @jeffersonwarrior.
- Gateway/Agent: accept image attachments on `agent` (multimodal message) and add live gateway image probe (`CLAWDBOT_LIVE_GATEWAY_IMAGE_PROBE=1`). - Gateway/Agent: accept image attachments on `agent` (multimodal message) and add live gateway image probe (`CLAWDBOT_LIVE_GATEWAY_IMAGE_PROBE=1`).
- CLI: `clawdbot sessions` now includes `elev:*` + `usage:*` flags in the table output. - CLI: `moltbot sessions` now includes `elev:*` + `usage:*` flags in the table output.
- CLI/Pairing: accept positional provider for `pairing list|approve` (npm-run compatible); update docs/bot hints. - CLI/Pairing: accept positional provider for `pairing list|approve` (npm-run compatible); update docs/bot hints.
- Branding: normalize user-facing “ClawdBot”/“CLAWDBOT” → “Clawdbot” (CLI, status, docs). - Branding: normalize legacy casing/branding to “Moltbot” (CLI, status, docs).
- Auto-reply: fix native `/model` not updating the actual chat session (Telegram/Slack/Discord). (#646) - Auto-reply: fix native `/model` not updating the actual chat session (Telegram/Slack/Discord). (#646)
- Doctor: offer to run `clawdbot update` first on git installs (keeps doctor output aligned with latest). - Doctor: offer to run `moltbot update` first on git installs (keeps doctor output aligned with latest).
- Doctor: avoid false legacy workspace warning when install dir is `~/clawdbot`. (#660) - Doctor: avoid false legacy workspace warning when install dir is `~/moltbot`. (#660)
- iMessage: fix reasoning persistence across DMs; avoid partial/duplicate replies when reasoning is enabled. (#655) — thanks @antons. - iMessage: fix reasoning persistence across DMs; avoid partial/duplicate replies when reasoning is enabled. (#655) — thanks @antons.
- Models/Auth: allow MiniMax API configs without `models.providers.minimax.apiKey` (auth profiles / `MINIMAX_API_KEY`). (#656) — thanks @mneves75. - Models/Auth: allow MiniMax API configs without `models.providers.minimax.apiKey` (auth profiles / `MINIMAX_API_KEY`). (#656) — thanks @mneves75.
- Agents: avoid duplicate replies when the message tool sends. (#659) — thanks @mickahouan. - Agents: avoid duplicate replies when the message tool sends. (#659) — thanks @mickahouan.
@@ -1067,12 +1067,12 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Control UI/TUI: queued messages, session links, reasoning view, mobile polish, logs UX. - Control UI/TUI: queued messages, session links, reasoning view, mobile polish, logs UX.
### Breaking ### Breaking
- CLI: `clawdbot message` now subcommands (`message send|poll|...`) and requires `--provider` unless only one provider configured. - CLI: `moltbot message` now subcommands (`message send|poll|...`) and requires `--provider` unless only one provider configured.
- Commands/Tools: `/restart` and gateway restart tool disabled by default; enable with `commands.restart=true`. - Commands/Tools: `/restart` and gateway restart tool disabled by default; enable with `commands.restart=true`.
### New Features and Changes ### New Features and Changes
- Models/Auth: OpenCode Zen onboarding (#623) — thanks @magimetal; MiniMax Anthropic-compatible API + hosted onboarding (#590, #495) — thanks @mneves75, @tobiasbischoff. - Models/Auth: OpenCode Zen onboarding (#623) — thanks @magimetal; MiniMax Anthropic-compatible API + hosted onboarding (#590, #495) — thanks @mneves75, @tobiasbischoff.
- Models/Auth: setup-token + token auth profiles; `clawdbot models auth order {get,set,clear}`; per-agent auth candidates in `/model status`; OAuth expiry checks in doctor/status. - Models/Auth: setup-token + token auth profiles; `moltbot models auth order {get,set,clear}`; per-agent auth candidates in `/model status`; OAuth expiry checks in doctor/status.
- Agent/System: claude-cli runner; `session_status` tool (and sandbox allow); adaptive context pruning default; system prompt messaging guidance + no auto self-update; eligible skills list injection; sub-agent context trimmed. - Agent/System: claude-cli runner; `session_status` tool (and sandbox allow); adaptive context pruning default; system prompt messaging guidance + no auto self-update; eligible skills list injection; sub-agent context trimmed.
- Commands: `/commands` list; `/models` alias; `/usage` alias; `/debug` runtime overrides + effective config view; `/config` chat updates + `/config get`; `config --section`. - Commands: `/commands` list; `/models` alias; `/usage` alias; `/debug` runtime overrides + effective config view; `/config` chat updates + `/config get`; `config --section`.
- CLI/Gateway: unified message tool + message subcommands; gateway discover (local + wide-area DNS-SD) with JSON/timeout; gateway status human-readable + JSON + SSH loopback; wide-area records include gatewayPort/sshPort/cliPath + tailnet DNS fallback. - CLI/Gateway: unified message tool + message subcommands; gateway discover (local + wide-area DNS-SD) with JSON/timeout; gateway status human-readable + JSON + SSH loopback; wide-area records include gatewayPort/sshPort/cliPath + tailnet DNS fallback.
@@ -1104,7 +1104,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Signal: reaction handling safety; own-reaction matching (uuid+phone); UUID-only senders accepted; ignore reaction-only messages. - Signal: reaction handling safety; own-reaction matching (uuid+phone); UUID-only senders accepted; ignore reaction-only messages.
- MS Teams: download image attachments reliably; fix top-level replies; stop on shutdown + honor chunk limits; normalize poll providers/deps; pairing label fixes. - MS Teams: download image attachments reliably; fix top-level replies; stop on shutdown + honor chunk limits; normalize poll providers/deps; pairing label fixes.
- iMessage: isolate group-ish threads by chat_id. - iMessage: isolate group-ish threads by chat_id.
- Gateway/Daemon/Doctor: atomic config writes; repair gateway service entrypoint + install switches; non-interactive legacy migrations; systemd unit alignment + KillMode=process; node bridge keepalive/pings; Launch at Login persistence; bundle ClawdbotKit resources + Swift 6.2 compat dylib; relay version check + remove smoke test; regen Swift GatewayModels + keep agent provider string; cron jobId alias + channel alias migration + main session key normalization; heartbeat Telegram accountId resolution; avoid WhatsApp fallback for internal runs; gateway listener error wording; serveBaseUrl param; honor gateway --dev; fix wide-area discovery updates; align agents.defaults schema; provider account metadata in daemon status; refresh Carbon patch for gateway fixes; restore doctor prompter initialValue handling. - Gateway/Daemon/Doctor: atomic config writes; repair gateway service entrypoint + install switches; non-interactive legacy migrations; systemd unit alignment + KillMode=process; node bridge keepalive/pings; Launch at Login persistence; bundle MoltbotKit resources + Swift 6.2 compat dylib; relay version check + remove smoke test; regen Swift GatewayModels + keep agent provider string; cron jobId alias + channel alias migration + main session key normalization; heartbeat Telegram accountId resolution; avoid WhatsApp fallback for internal runs; gateway listener error wording; serveBaseUrl param; honor gateway --dev; fix wide-area discovery updates; align agents.defaults schema; provider account metadata in daemon status; refresh Carbon patch for gateway fixes; restore doctor prompter initialValue handling.
- Control UI/TUI: persist per-session verbose off + hide tool cards; logs tab opens at bottom; relative asset paths + landing cleanup; session labels lookup/persistence; stop pinning main session in recents; start logs at bottom; TUI status bar refresh + timeout handling + hide reasoning label when off. - Control UI/TUI: persist per-session verbose off + hide tool cards; logs tab opens at bottom; relative asset paths + landing cleanup; session labels lookup/persistence; stop pinning main session in recents; start logs at bottom; TUI status bar refresh + timeout handling + hide reasoning label when off.
- Onboarding/Configure: QuickStart single-select provider picker; avoid Codex CLI false-expiry warnings; clarify WhatsApp owner prompt; fix Minimax hosted onboarding (agents.defaults + msteams heartbeat target); remove configure Control UI prompt; honor gateway --dev flag. - Onboarding/Configure: QuickStart single-select provider picker; avoid Codex CLI false-expiry warnings; clarify WhatsApp owner prompt; fix Minimax hosted onboarding (agents.defaults + msteams heartbeat target); remove configure Control UI prompt; honor gateway --dev flag.
@@ -1130,7 +1130,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Previously, if you didnt configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots). - Previously, if you didnt configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots).
- New default: DM pairing (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`). - New default: DM pairing (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`).
- To keep old “open to everyone” behavior: set `dmPolicy="open"` and include `"*"` in the relevant `allowFrom` (Discord/Slack: `discord.dm.allowFrom` / `slack.dm.allowFrom`). - To keep old “open to everyone” behavior: set `dmPolicy="open"` and include `"*"` in the relevant `allowFrom` (Discord/Slack: `discord.dm.allowFrom` / `slack.dm.allowFrom`).
- Approve requests via `clawdbot pairing list <provider>` + `clawdbot pairing approve <provider> <code>`. - Approve requests via `moltbot pairing list <provider>` + `moltbot pairing approve <provider> <code>`.
- Sandbox: default `agent.sandbox.scope` to `"agent"` (one container/workspace per agent). Use `"session"` for per-session isolation; `"shared"` disables cross-session isolation. - Sandbox: default `agent.sandbox.scope` to `"agent"` (one container/workspace per agent). Use `"session"` for per-session isolation; `"shared"` disables cross-session isolation.
- Timestamps in agent envelopes are now UTC (compact `YYYY-MM-DDTHH:mmZ`); removed `messages.timestampPrefix`. Add `agent.userTimezone` to tell the model the users local time (system prompt only). - Timestamps in agent envelopes are now UTC (compact `YYYY-MM-DDTHH:mmZ`); removed `messages.timestampPrefix`. Add `agent.userTimezone` to tell the model the users local time (system prompt only).
- Model config schema changes (auth profiles + model lists); doctor auto-migrates and the gateway rewrites legacy configs on startup. - Model config schema changes (auth profiles + model lists); doctor auto-migrates and the gateway rewrites legacy configs on startup.
@@ -1144,7 +1144,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- **Agent loop + compaction:** compaction/pruning tuning, overflow handling, safer bootstrap context, and per-provider threading/confirmations; opt-in tool-result pruning + compact tracking. - **Agent loop + compaction:** compaction/pruning tuning, overflow handling, safer bootstrap context, and per-provider threading/confirmations; opt-in tool-result pruning + compact tracking.
- **Sandbox + tools:** per-agent sandbox overrides, workspaceAccess controls, session tool visibility, tool policy overrides, process isolation, and tool schema/timeout/reaction unification. - **Sandbox + tools:** per-agent sandbox overrides, workspaceAccess controls, session tool visibility, tool policy overrides, process isolation, and tool schema/timeout/reaction unification.
- **Providers (Telegram/WhatsApp/Discord/Slack/Signal/iMessage):** retry/backoff, threading, reactions, media groups/attachments, mention gating, typing behavior, and error/log stability; long polling + forum topic isolation for Telegram. - **Providers (Telegram/WhatsApp/Discord/Slack/Signal/iMessage):** retry/backoff, threading, reactions, media groups/attachments, mention gating, typing behavior, and error/log stability; long polling + forum topic isolation for Telegram.
- **Gateway/CLI UX:** `clawdbot logs`, cron list colors/aliases, docs search, agents list/add/delete flows, status usage snapshots, runtime/auth source display, and `/status`/commands auth unification. - **Gateway/CLI UX:** `moltbot logs`, cron list colors/aliases, docs search, agents list/add/delete flows, status usage snapshots, runtime/auth source display, and `/status`/commands auth unification.
- **Control UI/Web:** logs tab, focus mode polish, config form resilience, streaming stability, tool output caps, windowed chat history, and reconnect/password URL auth. - **Control UI/Web:** logs tab, focus mode polish, config form resilience, streaming stability, tool output caps, windowed chat history, and reconnect/password URL auth.
- **macOS/Android/TUI/Build:** macOS gateway races, QR bundling, JSON5 config safety, Voice Wake hardening; Android EXIF rotation + APK naming/versioning; TUI key handling; tooling/bundling fixes. - **macOS/Android/TUI/Build:** macOS gateway races, QR bundling, JSON5 config safety, Voice Wake hardening; Android EXIF rotation + APK naming/versioning; TUI key handling; tooling/bundling fixes.
- **Packaging/compat:** npm dist folder coverage, Node 25 qrcode-terminal import fixes, Bun/Playwright/WebSocket patches, and Docker Bun install. - **Packaging/compat:** npm dist folder coverage, Node 25 qrcode-terminal import fixes, Bun/Playwright/WebSocket patches, and Docker Bun install.
@@ -1186,4 +1186,4 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
- Agent tools: honor `agent.tools` allow/deny policy even when sandbox is off. - Agent tools: honor `agent.tools` allow/deny policy even when sandbox is off.
- Discord: avoid duplicate replies when OpenAI emits repeated `message_end` events. - Discord: avoid duplicate replies when OpenAI emits repeated `message_end` events.
- Commands: unify /status (inline) and command auth across providers; group bypass for authorized control commands; remove Discord /clawd slash handler. - Commands: unify /status (inline) and command auth across providers; group bypass for authorized control commands; remove Discord /clawd slash handler.
- CLI: run `clawdbot agent` via the Gateway by default; use `--local` to force embedded mode. - CLI: run `moltbot agent` via the Gateway by default; use `--local` to force embedded mode.

View File

@@ -1,11 +1,11 @@
# Contributing to Clawdbot # Contributing to Moltbot
Welcome to the lobster tank! 🦞 Welcome to the lobster tank! 🦞
## Quick Links ## Quick Links
- **GitHub:** https://github.com/clawdbot/clawdbot - **GitHub:** https://github.com/moltbot/moltbot
- **Discord:** https://discord.gg/qkhbAGHRBT - **Discord:** https://discord.gg/qkhbAGHRBT
- **X/Twitter:** [@steipete](https://x.com/steipete) / [@clawdbot](https://x.com/clawdbot) - **X/Twitter:** [@steipete](https://x.com/steipete) / [@moltbot](https://x.com/moltbot)
## Maintainers ## Maintainers
@@ -20,11 +20,11 @@ Welcome to the lobster tank! 🦞
## How to Contribute ## How to Contribute
1. **Bugs & small fixes** → Open a PR! 1. **Bugs & small fixes** → Open a PR!
2. **New features / architecture** → Start a [GitHub Discussion](https://github.com/clawdbot/clawdbot/discussions) or ask in Discord first 2. **New features / architecture** → Start a [GitHub Discussion](https://github.com/moltbot/moltbot/discussions) or ask in Discord first
3. **Questions** → Discord #setup-help 3. **Questions** → Discord #setup-help
## Before You PR ## Before You PR
- Test locally with your Clawdbot instance - Test locally with your Moltbot instance
- Run linter: `npm run lint` - Run linter: `npm run lint`
- Keep PRs focused (one thing per PR) - Keep PRs focused (one thing per PR)
- Describe what & why - Describe what & why
@@ -49,4 +49,4 @@ We are currently prioritizing:
- **Skills**: Expanding the library of bundled skills and improving the Skill Creation developer experience. - **Skills**: Expanding the library of bundled skills and improving the Skill Creation developer experience.
- **Performance**: Optimizing token usage and compaction logic. - **Performance**: Optimizing token usage and compaction logic.
Check the [GitHub Issues](https://github.com/clawdbot/clawdbot/issues) for "good first issue" labels! Check the [GitHub Issues](https://github.com/moltbot/moltbot/issues) for "good first issue" labels!

View File

@@ -20,9 +20,9 @@ RUN apt-get update \
xvfb \ xvfb \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
COPY scripts/sandbox-browser-entrypoint.sh /usr/local/bin/clawdbot-sandbox-browser COPY scripts/sandbox-browser-entrypoint.sh /usr/local/bin/moltbot-sandbox-browser
RUN chmod +x /usr/local/bin/clawdbot-sandbox-browser RUN chmod +x /usr/local/bin/moltbot-sandbox-browser
EXPOSE 9222 5900 6080 EXPOSE 9222 5900 6080
CMD ["clawdbot-sandbox-browser"] CMD ["moltbot-sandbox-browser"]

View File

@@ -1,6 +1,6 @@
# Security Policy # Security Policy
If you believe you've found a security issue in Clawdbot, please report it privately. If you believe you've found a security issue in Moltbot, please report it privately.
## Reporting ## Reporting
@@ -9,19 +9,19 @@ If you believe you've found a security issue in Clawdbot, please report it priva
## Operational Guidance ## Operational Guidance
For threat model + hardening guidance (including `clawdbot security audit --deep` and `--fix`), see: For threat model + hardening guidance (including `moltbot security audit --deep` and `--fix`), see:
- `https://docs.molt.bot/gateway/security` - `https://docs.molt.bot/gateway/security`
### Web Interface Safety ### Web Interface Safety
Clawdbot's web interface is intended for local use only. Do **not** bind it to the public internet; it is not hardened for public exposure. Moltbot's web interface is intended for local use only. Do **not** bind it to the public internet; it is not hardened for public exposure.
## Runtime Requirements ## Runtime Requirements
### Node.js Version ### Node.js Version
Clawdbot requires **Node.js 22.12.0 or later** (LTS). This version includes important security patches: Moltbot requires **Node.js 22.12.0 or later** (LTS). This version includes important security patches:
- CVE-2025-59466: async_hooks DoS vulnerability - CVE-2025-59466: async_hooks DoS vulnerability
- CVE-2026-21636: Permission model bypass vulnerability - CVE-2026-21636: Permission model bypass vulnerability
@@ -34,7 +34,7 @@ node --version # Should be v22.12.0 or later
### Docker Security ### Docker Security
When running Clawdbot in Docker: When running Moltbot in Docker:
1. The official image runs as a non-root user (`node`) for reduced attack surface 1. The official image runs as a non-root user (`node`) for reduced attack surface
2. Use `--read-only` flag when possible for additional filesystem protection 2. Use `--read-only` flag when possible for additional filesystem protection
@@ -44,8 +44,8 @@ Example secure Docker run:
```bash ```bash
docker run --read-only --cap-drop=ALL \ docker run --read-only --cap-drop=ALL \
-v clawdbot-data:/app/data \ -v moltbot-data:/app/data \
clawdbot/clawdbot:latest moltbot/moltbot:latest
``` ```
## Security Scanning ## Security Scanning

View File

@@ -1,31 +1,31 @@
<?xml version="1.0" standalone="yes"?> <?xml version="1.0" standalone="yes"?>
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0"> <rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel> <channel>
<title>Clawdbot</title> <title>Moltbot</title>
<item> <item>
<title>2026.1.24-1</title> <title>2026.1.24-1</title>
<pubDate>Sun, 25 Jan 2026 14:05:25 +0000</pubDate> <pubDate>Sun, 25 Jan 2026 14:05:25 +0000</pubDate>
<link>https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml</link> <link>https://raw.githubusercontent.com/moltbot/moltbot/main/appcast.xml</link>
<sparkle:version>7952</sparkle:version> <sparkle:version>7952</sparkle:version>
<sparkle:shortVersionString>2026.1.24-1</sparkle:shortVersionString> <sparkle:shortVersionString>2026.1.24-1</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion> <sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>Clawdbot 2026.1.24-1</h2> <description><![CDATA[<h2>Moltbot 2026.1.24-1</h2>
<h3>Fixes</h3> <h3>Fixes</h3>
<ul> <ul>
<li>Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install).</li> <li>Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install).</li>
</ul> </ul>
<p><a href="https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md">View full changelog</a></p> <p><a href="https://github.com/moltbot/moltbot/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description> ]]></description>
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.24-1/Clawdbot-2026.1.24-1.zip" length="12396699" type="application/octet-stream" sparkle:edSignature="VaEdWIgEJBrZLIp2UmigoQ6vaq4P/jNFXpHYXvXHD5MsATS0CqBl6ugyyxRq+/GbpUqmdgdlht4dTUVbLRw6BA=="/> <enclosure url="https://github.com/moltbot/moltbot/releases/download/v2026.1.24-1/Moltbot-2026.1.24-1.zip" length="12396699" type="application/octet-stream" sparkle:edSignature="VaEdWIgEJBrZLIp2UmigoQ6vaq4P/jNFXpHYXvXHD5MsATS0CqBl6ugyyxRq+/GbpUqmdgdlht4dTUVbLRw6BA=="/>
</item> </item>
<item> <item>
<title>2026.1.24</title> <title>2026.1.24</title>
<pubDate>Sun, 25 Jan 2026 13:31:05 +0000</pubDate> <pubDate>Sun, 25 Jan 2026 13:31:05 +0000</pubDate>
<link>https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml</link> <link>https://raw.githubusercontent.com/moltbot/moltbot/main/appcast.xml</link>
<sparkle:version>7944</sparkle:version> <sparkle:version>7944</sparkle:version>
<sparkle:shortVersionString>2026.1.24</sparkle:shortVersionString> <sparkle:shortVersionString>2026.1.24</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion> <sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>Clawdbot 2026.1.24</h2> <description><![CDATA[<h2>Moltbot 2026.1.24</h2>
<h3>Highlights</h3> <h3>Highlights</h3>
<ul> <ul>
<li>Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.molt.bot/providers/ollama https://docs.molt.bot/providers/venice</li> <li>Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.molt.bot/providers/ollama https://docs.molt.bot/providers/venice</li>
@@ -93,18 +93,18 @@
<li>Tests: avoid fake-timer dependency in embedded runner stream mock to reduce CI flakes. (#1597) Thanks @rohannagpal.</li> <li>Tests: avoid fake-timer dependency in embedded runner stream mock to reduce CI flakes. (#1597) Thanks @rohannagpal.</li>
<li>Tests: increase embedded runner ordering test timeout to reduce CI flakes. (#1597) Thanks @rohannagpal.</li> <li>Tests: increase embedded runner ordering test timeout to reduce CI flakes. (#1597) Thanks @rohannagpal.</li>
</ul> </ul>
<p><a href="https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md">View full changelog</a></p> <p><a href="https://github.com/moltbot/moltbot/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description> ]]></description>
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.24/Clawdbot-2026.1.24.zip" length="12396700" type="application/octet-stream" sparkle:edSignature="u+XzKD3YwV8s79gIr7LK4OtDCcmp/b+cjNC6SHav3/1CVJegh02SsBKatrampox32XGx8P2+8c/+fHV+qpkHCA=="/> <enclosure url="https://github.com/moltbot/moltbot/releases/download/v2026.1.24/Moltbot-2026.1.24.zip" length="12396700" type="application/octet-stream" sparkle:edSignature="u+XzKD3YwV8s79gIr7LK4OtDCcmp/b+cjNC6SHav3/1CVJegh02SsBKatrampox32XGx8P2+8c/+fHV+qpkHCA=="/>
</item> </item>
<item> <item>
<title>2026.1.23</title> <title>2026.1.23</title>
<pubDate>Sat, 24 Jan 2026 13:02:18 +0000</pubDate> <pubDate>Sat, 24 Jan 2026 13:02:18 +0000</pubDate>
<link>https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml</link> <link>https://raw.githubusercontent.com/moltbot/moltbot/main/appcast.xml</link>
<sparkle:version>7750</sparkle:version> <sparkle:version>7750</sparkle:version>
<sparkle:shortVersionString>2026.1.23</sparkle:shortVersionString> <sparkle:shortVersionString>2026.1.23</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion> <sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>Clawdbot 2026.1.23</h2> <description><![CDATA[<h2>Moltbot 2026.1.23</h2>
<h3>Highlights</h3> <h3>Highlights</h3>
<ul> <ul>
<li>TTS: allow model-driven TTS tags by default for expressive audio replies (laughter, singing cues, etc.).</li> <li>TTS: allow model-driven TTS tags by default for expressive audio replies (laughter, singing cues, etc.).</li>
@@ -117,9 +117,9 @@
<li>Browser: add node-host proxy auto-routing for remote gateways (configurable per gateway/node).</li> <li>Browser: add node-host proxy auto-routing for remote gateways (configurable per gateway/node).</li>
<li>Heartbeat: add per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer.</li> <li>Heartbeat: add per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer.</li>
<li>Plugins: add optional llm-task JSON-only tool for workflows. (#1498) Thanks @vignesh07.</li> <li>Plugins: add optional llm-task JSON-only tool for workflows. (#1498) Thanks @vignesh07.</li>
<li>CLI: restart the gateway by default after <code>clawdbot update</code>; add <code>--no-restart</code> to skip it.</li> <li>CLI: restart the gateway by default after <code>moltbot update</code>; add <code>--no-restart</code> to skip it.</li>
<li>CLI: add live auth probes to <code>clawdbot models status</code> for per-profile verification.</li> <li>CLI: add live auth probes to <code>moltbot models status</code> for per-profile verification.</li>
<li>CLI: add <code>clawdbot system</code> for system events + heartbeat controls; remove standalone <code>wake</code>.</li> <li>CLI: add <code>moltbot system</code> for system events + heartbeat controls; remove standalone <code>wake</code>.</li>
<li>Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3.</li> <li>Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3.</li>
<li>Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc.</li> <li>Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc.</li>
<li>Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc.</li> <li>Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc.</li>
@@ -154,10 +154,10 @@
<li>Exec: honor tools.exec ask/security defaults for elevated approvals (avoid unwanted prompts).</li> <li>Exec: honor tools.exec ask/security defaults for elevated approvals (avoid unwanted prompts).</li>
<li>TUI: forward unknown slash commands (for example, <code>/context</code>) to the Gateway.</li> <li>TUI: forward unknown slash commands (for example, <code>/context</code>) to the Gateway.</li>
<li>TUI: include Gateway slash commands in autocomplete and <code>/help</code>.</li> <li>TUI: include Gateway slash commands in autocomplete and <code>/help</code>.</li>
<li>CLI: skip usage lines in <code>clawdbot models status</code> when provider usage is unavailable.</li> <li>CLI: skip usage lines in <code>moltbot models status</code> when provider usage is unavailable.</li>
<li>CLI: suppress diagnostic session/run noise during auth probes.</li> <li>CLI: suppress diagnostic session/run noise during auth probes.</li>
<li>CLI: hide auth probe timeout warnings from embedded runs.</li> <li>CLI: hide auth probe timeout warnings from embedded runs.</li>
<li>CLI: render auth probe results as a table in <code>clawdbot models status</code>.</li> <li>CLI: render auth probe results as a table in <code>moltbot models status</code>.</li>
<li>CLI: suppress probe-only embedded logs unless <code>--verbose</code> is set.</li> <li>CLI: suppress probe-only embedded logs unless <code>--verbose</code> is set.</li>
<li>CLI: move auth probe errors below the table to reduce wrapping.</li> <li>CLI: move auth probe errors below the table to reduce wrapping.</li>
<li>CLI: prevent ANSI color bleed when table cells wrap.</li> <li>CLI: prevent ANSI color bleed when table cells wrap.</li>
@@ -180,9 +180,9 @@
<li>Browser: keep extension relay tabs controllable when the extension reuses a session id after switching tabs. (#1160)</li> <li>Browser: keep extension relay tabs controllable when the extension reuses a session id after switching tabs. (#1160)</li>
<li>Agents: warn and ignore tool allowlists that only reference unknown or unloaded plugin tools. (#1566)</li> <li>Agents: warn and ignore tool allowlists that only reference unknown or unloaded plugin tools. (#1566)</li>
</ul> </ul>
<p><a href="https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md">View full changelog</a></p> <p><a href="https://github.com/moltbot/moltbot/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description> ]]></description>
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.23/Clawdbot-2026.1.23.zip" length="22326233" type="application/octet-stream" sparkle:edSignature="p40dFczUfmMpsif4BrEUYVqUPG2WiBXleWgefwu4WiqjuyXbw7CAaH5CpQKig/k2qRLlE59kX7AR/qJqmy+yCA=="/> <enclosure url="https://github.com/moltbot/moltbot/releases/download/v2026.1.23/Moltbot-2026.1.23.zip" length="22326233" type="application/octet-stream" sparkle:edSignature="p40dFczUfmMpsif4BrEUYVqUPG2WiBXleWgefwu4WiqjuyXbw7CAaH5CpQKig/k2qRLlE59kX7AR/qJqmy+yCA=="/>
</item> </item>
</channel> </channel>
</rss> </rss>

View File

@@ -65,7 +65,7 @@ androidComponents {
val versionName = output.versionName.orNull ?: "0" val versionName = output.versionName.orNull ?: "0"
val buildType = variant.buildType val buildType = variant.buildType
val outputFileName = "clawdbot-${versionName}-${buildType}.apk" val outputFileName = "moltbot-${versionName}-${buildType}.apk"
output.outputFileName = outputFileName output.outputFileName = outputFileName
} }
} }

View File

@@ -32,7 +32,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/Theme.ClawdbotNode"> android:theme="@style/Theme.MoltbotNode">
<service <service
android:name=".NodeForegroundService" android:name=".NodeForegroundService"
android:exported="false" android:exported="false"

View File

@@ -19,7 +19,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import com.clawdbot.android.ui.RootScreen import com.clawdbot.android.ui.RootScreen
import com.clawdbot.android.ui.ClawdbotTheme import com.clawdbot.android.ui.MoltbotTheme
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -56,7 +56,7 @@ class MainActivity : ComponentActivity() {
} }
setContent { setContent {
ClawdbotTheme { MoltbotTheme {
Surface(modifier = Modifier) { Surface(modifier = Modifier) {
RootScreen(viewModel = viewModel) RootScreen(viewModel = viewModel)
} }

View File

@@ -29,7 +29,7 @@ class NodeForegroundService : Service() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ensureChannel() ensureChannel()
val initial = buildNotification(title = "Clawdbot Node", text = "Starting…") val initial = buildNotification(title = "Moltbot Node", text = "Starting…")
startForegroundWithTypes(notification = initial, requiresMic = false) startForegroundWithTypes(notification = initial, requiresMic = false)
val runtime = (application as NodeApp).runtime val runtime = (application as NodeApp).runtime
@@ -44,7 +44,7 @@ class NodeForegroundService : Service() {
) { status, server, connected, voiceMode, voiceListening -> ) { status, server, connected, voiceMode, voiceListening ->
Quint(status, server, connected, voiceMode, voiceListening) Quint(status, server, connected, voiceMode, voiceListening)
}.collect { (status, server, connected, voiceMode, voiceListening) -> }.collect { (status, server, connected, voiceMode, voiceListening) ->
val title = if (connected) "Clawdbot Node · Connected" else "Clawdbot Node" val title = if (connected) "Moltbot Node · Connected" else "Moltbot Node"
val voiceSuffix = val voiceSuffix =
if (voiceMode == VoiceWakeMode.Always) { if (voiceMode == VoiceWakeMode.Always) {
if (voiceListening) " · Voice Wake: Listening" else " · Voice Wake: Paused" if (voiceListening) " · Voice Wake: Listening" else " · Voice Wake: Paused"
@@ -91,7 +91,7 @@ class NodeForegroundService : Service() {
"Connection", "Connection",
NotificationManager.IMPORTANCE_LOW, NotificationManager.IMPORTANCE_LOW,
).apply { ).apply {
description = "Clawdbot node connection status" description = "Moltbot node connection status"
setShowBadge(false) setShowBadge(false)
} }
mgr.createNotificationChannel(channel) mgr.createNotificationChannel(channel)

View File

@@ -26,14 +26,14 @@ import com.clawdbot.android.BuildConfig
import com.clawdbot.android.node.CanvasController import com.clawdbot.android.node.CanvasController
import com.clawdbot.android.node.ScreenRecordManager import com.clawdbot.android.node.ScreenRecordManager
import com.clawdbot.android.node.SmsManager import com.clawdbot.android.node.SmsManager
import com.clawdbot.android.protocol.ClawdbotCapability import com.clawdbot.android.protocol.MoltbotCapability
import com.clawdbot.android.protocol.ClawdbotCameraCommand import com.clawdbot.android.protocol.MoltbotCameraCommand
import com.clawdbot.android.protocol.ClawdbotCanvasA2UIAction import com.clawdbot.android.protocol.MoltbotCanvasA2UIAction
import com.clawdbot.android.protocol.ClawdbotCanvasA2UICommand import com.clawdbot.android.protocol.MoltbotCanvasA2UICommand
import com.clawdbot.android.protocol.ClawdbotCanvasCommand import com.clawdbot.android.protocol.MoltbotCanvasCommand
import com.clawdbot.android.protocol.ClawdbotScreenCommand import com.clawdbot.android.protocol.MoltbotScreenCommand
import com.clawdbot.android.protocol.ClawdbotLocationCommand import com.clawdbot.android.protocol.MoltbotLocationCommand
import com.clawdbot.android.protocol.ClawdbotSmsCommand import com.clawdbot.android.protocol.MoltbotSmsCommand
import com.clawdbot.android.voice.TalkModeManager import com.clawdbot.android.voice.TalkModeManager
import com.clawdbot.android.voice.VoiceWakeManager import com.clawdbot.android.voice.VoiceWakeManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -451,38 +451,38 @@ class NodeRuntime(context: Context) {
private fun buildInvokeCommands(): List<String> = private fun buildInvokeCommands(): List<String> =
buildList { buildList {
add(ClawdbotCanvasCommand.Present.rawValue) add(MoltbotCanvasCommand.Present.rawValue)
add(ClawdbotCanvasCommand.Hide.rawValue) add(MoltbotCanvasCommand.Hide.rawValue)
add(ClawdbotCanvasCommand.Navigate.rawValue) add(MoltbotCanvasCommand.Navigate.rawValue)
add(ClawdbotCanvasCommand.Eval.rawValue) add(MoltbotCanvasCommand.Eval.rawValue)
add(ClawdbotCanvasCommand.Snapshot.rawValue) add(MoltbotCanvasCommand.Snapshot.rawValue)
add(ClawdbotCanvasA2UICommand.Push.rawValue) add(MoltbotCanvasA2UICommand.Push.rawValue)
add(ClawdbotCanvasA2UICommand.PushJSONL.rawValue) add(MoltbotCanvasA2UICommand.PushJSONL.rawValue)
add(ClawdbotCanvasA2UICommand.Reset.rawValue) add(MoltbotCanvasA2UICommand.Reset.rawValue)
add(ClawdbotScreenCommand.Record.rawValue) add(MoltbotScreenCommand.Record.rawValue)
if (cameraEnabled.value) { if (cameraEnabled.value) {
add(ClawdbotCameraCommand.Snap.rawValue) add(MoltbotCameraCommand.Snap.rawValue)
add(ClawdbotCameraCommand.Clip.rawValue) add(MoltbotCameraCommand.Clip.rawValue)
} }
if (locationMode.value != LocationMode.Off) { if (locationMode.value != LocationMode.Off) {
add(ClawdbotLocationCommand.Get.rawValue) add(MoltbotLocationCommand.Get.rawValue)
} }
if (sms.canSendSms()) { if (sms.canSendSms()) {
add(ClawdbotSmsCommand.Send.rawValue) add(MoltbotSmsCommand.Send.rawValue)
} }
} }
private fun buildCapabilities(): List<String> = private fun buildCapabilities(): List<String> =
buildList { buildList {
add(ClawdbotCapability.Canvas.rawValue) add(MoltbotCapability.Canvas.rawValue)
add(ClawdbotCapability.Screen.rawValue) add(MoltbotCapability.Screen.rawValue)
if (cameraEnabled.value) add(ClawdbotCapability.Camera.rawValue) if (cameraEnabled.value) add(MoltbotCapability.Camera.rawValue)
if (sms.canSendSms()) add(ClawdbotCapability.Sms.rawValue) if (sms.canSendSms()) add(MoltbotCapability.Sms.rawValue)
if (voiceWakeMode.value != VoiceWakeMode.Off && hasRecordAudioPermission()) { if (voiceWakeMode.value != VoiceWakeMode.Off && hasRecordAudioPermission()) {
add(ClawdbotCapability.VoiceWake.rawValue) add(MoltbotCapability.VoiceWake.rawValue)
} }
if (locationMode.value != LocationMode.Off) { if (locationMode.value != LocationMode.Off) {
add(ClawdbotCapability.Location.rawValue) add(MoltbotCapability.Location.rawValue)
} }
} }
@@ -506,7 +506,7 @@ class NodeRuntime(context: Context) {
val version = resolvedVersionName() val version = resolvedVersionName()
val release = Build.VERSION.RELEASE?.trim().orEmpty() val release = Build.VERSION.RELEASE?.trim().orEmpty()
val releaseLabel = if (release.isEmpty()) "unknown" else release val releaseLabel = if (release.isEmpty()) "unknown" else release
return "ClawdbotAndroid/$version (Android $releaseLabel; SDK ${Build.VERSION.SDK_INT})" return "MoltbotAndroid/$version (Android $releaseLabel; SDK ${Build.VERSION.SDK_INT})"
} }
private fun buildClientInfo(clientId: String, clientMode: String): GatewayClientInfo { private fun buildClientInfo(clientId: String, clientMode: String): GatewayClientInfo {
@@ -529,7 +529,7 @@ class NodeRuntime(context: Context) {
caps = buildCapabilities(), caps = buildCapabilities(),
commands = buildInvokeCommands(), commands = buildInvokeCommands(),
permissions = emptyMap(), permissions = emptyMap(),
client = buildClientInfo(clientId = "clawdbot-android", clientMode = "node"), client = buildClientInfo(clientId = "moltbot-android", clientMode = "node"),
userAgent = buildUserAgent(), userAgent = buildUserAgent(),
) )
} }
@@ -541,7 +541,7 @@ class NodeRuntime(context: Context) {
caps = emptyList(), caps = emptyList(),
commands = emptyList(), commands = emptyList(),
permissions = emptyMap(), permissions = emptyMap(),
client = buildClientInfo(clientId = "clawdbot-control-ui", clientMode = "ui"), client = buildClientInfo(clientId = "moltbot-control-ui", clientMode = "ui"),
userAgent = buildUserAgent(), userAgent = buildUserAgent(),
) )
} }
@@ -665,7 +665,7 @@ class NodeRuntime(context: Context) {
val actionId = (userActionObj["id"] as? JsonPrimitive)?.content?.trim().orEmpty().ifEmpty { val actionId = (userActionObj["id"] as? JsonPrimitive)?.content?.trim().orEmpty().ifEmpty {
java.util.UUID.randomUUID().toString() java.util.UUID.randomUUID().toString()
} }
val name = ClawdbotCanvasA2UIAction.extractActionName(userActionObj) ?: return@launch val name = MoltbotCanvasA2UIAction.extractActionName(userActionObj) ?: return@launch
val surfaceId = val surfaceId =
(userActionObj["surfaceId"] as? JsonPrimitive)?.content?.trim().orEmpty().ifEmpty { "main" } (userActionObj["surfaceId"] as? JsonPrimitive)?.content?.trim().orEmpty().ifEmpty { "main" }
@@ -675,7 +675,7 @@ class NodeRuntime(context: Context) {
val sessionKey = resolveMainSessionKey() val sessionKey = resolveMainSessionKey()
val message = val message =
ClawdbotCanvasA2UIAction.formatAgentMessage( MoltbotCanvasA2UIAction.formatAgentMessage(
actionName = name, actionName = name,
sessionKey = sessionKey, sessionKey = sessionKey,
surfaceId = surfaceId, surfaceId = surfaceId,
@@ -709,7 +709,7 @@ class NodeRuntime(context: Context) {
try { try {
canvas.eval( canvas.eval(
ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus( MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(
actionId = actionId, actionId = actionId,
ok = connected && error == null, ok = connected && error == null,
error = error, error = error,
@@ -827,10 +827,10 @@ class NodeRuntime(context: Context) {
private suspend fun handleInvoke(command: String, paramsJson: String?): GatewaySession.InvokeResult { private suspend fun handleInvoke(command: String, paramsJson: String?): GatewaySession.InvokeResult {
if ( if (
command.startsWith(ClawdbotCanvasCommand.NamespacePrefix) || command.startsWith(MoltbotCanvasCommand.NamespacePrefix) ||
command.startsWith(ClawdbotCanvasA2UICommand.NamespacePrefix) || command.startsWith(MoltbotCanvasA2UICommand.NamespacePrefix) ||
command.startsWith(ClawdbotCameraCommand.NamespacePrefix) || command.startsWith(MoltbotCameraCommand.NamespacePrefix) ||
command.startsWith(ClawdbotScreenCommand.NamespacePrefix) command.startsWith(MoltbotScreenCommand.NamespacePrefix)
) { ) {
if (!isForeground.value) { if (!isForeground.value) {
return GatewaySession.InvokeResult.error( return GatewaySession.InvokeResult.error(
@@ -839,13 +839,13 @@ class NodeRuntime(context: Context) {
) )
} }
} }
if (command.startsWith(ClawdbotCameraCommand.NamespacePrefix) && !cameraEnabled.value) { if (command.startsWith(MoltbotCameraCommand.NamespacePrefix) && !cameraEnabled.value) {
return GatewaySession.InvokeResult.error( return GatewaySession.InvokeResult.error(
code = "CAMERA_DISABLED", code = "CAMERA_DISABLED",
message = "CAMERA_DISABLED: enable Camera in Settings", message = "CAMERA_DISABLED: enable Camera in Settings",
) )
} }
if (command.startsWith(ClawdbotLocationCommand.NamespacePrefix) && if (command.startsWith(MoltbotLocationCommand.NamespacePrefix) &&
locationMode.value == LocationMode.Off locationMode.value == LocationMode.Off
) { ) {
return GatewaySession.InvokeResult.error( return GatewaySession.InvokeResult.error(
@@ -855,18 +855,18 @@ class NodeRuntime(context: Context) {
} }
return when (command) { return when (command) {
ClawdbotCanvasCommand.Present.rawValue -> { MoltbotCanvasCommand.Present.rawValue -> {
val url = CanvasController.parseNavigateUrl(paramsJson) val url = CanvasController.parseNavigateUrl(paramsJson)
canvas.navigate(url) canvas.navigate(url)
GatewaySession.InvokeResult.ok(null) GatewaySession.InvokeResult.ok(null)
} }
ClawdbotCanvasCommand.Hide.rawValue -> GatewaySession.InvokeResult.ok(null) MoltbotCanvasCommand.Hide.rawValue -> GatewaySession.InvokeResult.ok(null)
ClawdbotCanvasCommand.Navigate.rawValue -> { MoltbotCanvasCommand.Navigate.rawValue -> {
val url = CanvasController.parseNavigateUrl(paramsJson) val url = CanvasController.parseNavigateUrl(paramsJson)
canvas.navigate(url) canvas.navigate(url)
GatewaySession.InvokeResult.ok(null) GatewaySession.InvokeResult.ok(null)
} }
ClawdbotCanvasCommand.Eval.rawValue -> { MoltbotCanvasCommand.Eval.rawValue -> {
val js = val js =
CanvasController.parseEvalJs(paramsJson) CanvasController.parseEvalJs(paramsJson)
?: return GatewaySession.InvokeResult.error( ?: return GatewaySession.InvokeResult.error(
@@ -884,7 +884,7 @@ class NodeRuntime(context: Context) {
} }
GatewaySession.InvokeResult.ok("""{"result":${result.toJsonString()}}""") GatewaySession.InvokeResult.ok("""{"result":${result.toJsonString()}}""")
} }
ClawdbotCanvasCommand.Snapshot.rawValue -> { MoltbotCanvasCommand.Snapshot.rawValue -> {
val snapshotParams = CanvasController.parseSnapshotParams(paramsJson) val snapshotParams = CanvasController.parseSnapshotParams(paramsJson)
val base64 = val base64 =
try { try {
@@ -901,7 +901,7 @@ class NodeRuntime(context: Context) {
} }
GatewaySession.InvokeResult.ok("""{"format":"${snapshotParams.format.rawValue}","base64":"$base64"}""") GatewaySession.InvokeResult.ok("""{"format":"${snapshotParams.format.rawValue}","base64":"$base64"}""")
} }
ClawdbotCanvasA2UICommand.Reset.rawValue -> { MoltbotCanvasA2UICommand.Reset.rawValue -> {
val a2uiUrl = resolveA2uiHostUrl() val a2uiUrl = resolveA2uiHostUrl()
?: return GatewaySession.InvokeResult.error( ?: return GatewaySession.InvokeResult.error(
code = "A2UI_HOST_NOT_CONFIGURED", code = "A2UI_HOST_NOT_CONFIGURED",
@@ -917,7 +917,7 @@ class NodeRuntime(context: Context) {
val res = canvas.eval(a2uiResetJS) val res = canvas.eval(a2uiResetJS)
GatewaySession.InvokeResult.ok(res) GatewaySession.InvokeResult.ok(res)
} }
ClawdbotCanvasA2UICommand.Push.rawValue, ClawdbotCanvasA2UICommand.PushJSONL.rawValue -> { MoltbotCanvasA2UICommand.Push.rawValue, MoltbotCanvasA2UICommand.PushJSONL.rawValue -> {
val messages = val messages =
try { try {
decodeA2uiMessages(command, paramsJson) decodeA2uiMessages(command, paramsJson)
@@ -940,7 +940,7 @@ class NodeRuntime(context: Context) {
val res = canvas.eval(js) val res = canvas.eval(js)
GatewaySession.InvokeResult.ok(res) GatewaySession.InvokeResult.ok(res)
} }
ClawdbotCameraCommand.Snap.rawValue -> { MoltbotCameraCommand.Snap.rawValue -> {
showCameraHud(message = "Taking photo…", kind = CameraHudKind.Photo) showCameraHud(message = "Taking photo…", kind = CameraHudKind.Photo)
triggerCameraFlash() triggerCameraFlash()
val res = val res =
@@ -954,7 +954,7 @@ class NodeRuntime(context: Context) {
showCameraHud(message = "Photo captured", kind = CameraHudKind.Success, autoHideMs = 1600) showCameraHud(message = "Photo captured", kind = CameraHudKind.Success, autoHideMs = 1600)
GatewaySession.InvokeResult.ok(res.payloadJson) GatewaySession.InvokeResult.ok(res.payloadJson)
} }
ClawdbotCameraCommand.Clip.rawValue -> { MoltbotCameraCommand.Clip.rawValue -> {
val includeAudio = paramsJson?.contains("\"includeAudio\":true") != false val includeAudio = paramsJson?.contains("\"includeAudio\":true") != false
if (includeAudio) externalAudioCaptureActive.value = true if (includeAudio) externalAudioCaptureActive.value = true
try { try {
@@ -973,7 +973,7 @@ class NodeRuntime(context: Context) {
if (includeAudio) externalAudioCaptureActive.value = false if (includeAudio) externalAudioCaptureActive.value = false
} }
} }
ClawdbotLocationCommand.Get.rawValue -> { MoltbotLocationCommand.Get.rawValue -> {
val mode = locationMode.value val mode = locationMode.value
if (!isForeground.value && mode != LocationMode.Always) { if (!isForeground.value && mode != LocationMode.Always) {
return GatewaySession.InvokeResult.error( return GatewaySession.InvokeResult.error(
@@ -1026,7 +1026,7 @@ class NodeRuntime(context: Context) {
GatewaySession.InvokeResult.error(code = "LOCATION_UNAVAILABLE", message = message) GatewaySession.InvokeResult.error(code = "LOCATION_UNAVAILABLE", message = message)
} }
} }
ClawdbotScreenCommand.Record.rawValue -> { MoltbotScreenCommand.Record.rawValue -> {
// Status pill mirrors screen recording state so it stays visible without overlay stacking. // Status pill mirrors screen recording state so it stays visible without overlay stacking.
_screenRecordActive.value = true _screenRecordActive.value = true
try { try {
@@ -1042,7 +1042,7 @@ class NodeRuntime(context: Context) {
_screenRecordActive.value = false _screenRecordActive.value = false
} }
} }
ClawdbotSmsCommand.Send.rawValue -> { MoltbotSmsCommand.Send.rawValue -> {
val res = sms.send(paramsJson) val res = sms.send(paramsJson)
if (res.ok) { if (res.ok) {
GatewaySession.InvokeResult.ok(res.payloadJson) GatewaySession.InvokeResult.ok(res.payloadJson)
@@ -1115,7 +1115,7 @@ class NodeRuntime(context: Context) {
val raw = if (nodeRaw.isNotBlank()) nodeRaw else operatorRaw val raw = if (nodeRaw.isNotBlank()) nodeRaw else operatorRaw
if (raw.isBlank()) return null if (raw.isBlank()) return null
val base = raw.trimEnd('/') val base = raw.trimEnd('/')
return "${base}/__clawdbot__/a2ui/?platform=android" return "${base}/__moltbot__/a2ui/?platform=android"
} }
private suspend fun ensureA2uiReady(a2uiUrl: String): Boolean { private suspend fun ensureA2uiReady(a2uiUrl: String): Boolean {
@@ -1150,7 +1150,7 @@ class NodeRuntime(context: Context) {
val jsonlField = (obj["jsonl"] as? JsonPrimitive)?.content?.trim().orEmpty() val jsonlField = (obj["jsonl"] as? JsonPrimitive)?.content?.trim().orEmpty()
val hasMessagesArray = obj["messages"] is JsonArray val hasMessagesArray = obj["messages"] is JsonArray
if (command == ClawdbotCanvasA2UICommand.PushJSONL.rawValue || (!hasMessagesArray && jsonlField.isNotBlank())) { if (command == MoltbotCanvasA2UICommand.PushJSONL.rawValue || (!hasMessagesArray && jsonlField.isNotBlank())) {
val jsonl = jsonlField val jsonl = jsonlField
if (jsonl.isBlank()) throw IllegalArgumentException("INVALID_REQUEST: jsonl required") if (jsonl.isBlank()) throw IllegalArgumentException("INVALID_REQUEST: jsonl required")
val messages = val messages =
@@ -1218,7 +1218,7 @@ private const val a2uiResetJS: String =
""" """
(() => { (() => {
try { try {
if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing clawdbotA2UI" }; if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing moltbotA2UI" };
return globalThis.clawdbotA2UI.reset(); return globalThis.clawdbotA2UI.reset();
} catch (e) { } catch (e) {
return { ok: false, error: String(e?.message ?? e) }; return { ok: false, error: String(e?.message ?? e) };
@@ -1230,7 +1230,7 @@ private fun a2uiApplyMessagesJS(messagesJson: String): String {
return """ return """
(() => { (() => {
try { try {
if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing clawdbotA2UI" }; if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing moltbotA2UI" };
const messages = $messagesJson; const messages = $messagesJson;
return globalThis.clawdbotA2UI.applyMessages(messages); return globalThis.clawdbotA2UI.applyMessages(messages);
} catch (e) { } catch (e) {

View File

@@ -115,7 +115,7 @@ class PermissionRequester(private val activity: ComponentActivity) {
private fun buildRationaleMessage(permissions: List<String>): String { private fun buildRationaleMessage(permissions: List<String>): String {
val labels = permissions.map { permissionLabel(it) } val labels = permissions.map { permissionLabel(it) }
return "Clawdbot needs ${labels.joinToString(", ")} permissions to continue." return "Moltbot needs ${labels.joinToString(", ")} permissions to continue."
} }
private fun buildSettingsMessage(permissions: List<String>): String { private fun buildSettingsMessage(permissions: List<String>): String {

View File

@@ -55,7 +55,7 @@ class ScreenCaptureRequester(private val activity: ComponentActivity) {
suspendCancellableCoroutine { cont -> suspendCancellableCoroutine { cont ->
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setTitle("Screen recording required") .setTitle("Screen recording required")
.setMessage("Clawdbot needs to record the screen for this command.") .setMessage("Moltbot needs to record the screen for this command.")
.setPositiveButton("Continue") { _, _ -> cont.resume(true) } .setPositiveButton("Continue") { _, _ -> cont.resume(true) }
.setNegativeButton("Not now") { _, _ -> cont.resume(false) } .setNegativeButton("Not now") { _, _ -> cont.resume(false) }
.setOnCancelListener { cont.resume(false) } .setOnCancelListener { cont.resume(false) }

View File

@@ -31,7 +31,7 @@ class SecurePrefs(context: Context) {
private val prefs = private val prefs =
EncryptedSharedPreferences.create( EncryptedSharedPreferences.create(
context, context,
"clawdbot.node.secure", "moltbot.node.secure",
masterKey, masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,

View File

@@ -21,7 +21,7 @@ data class DeviceIdentity(
class DeviceIdentityStore(context: Context) { class DeviceIdentityStore(context: Context) {
private val json = Json { ignoreUnknownKeys = true } private val json = Json { ignoreUnknownKeys = true }
private val identityFile = File(context.filesDir, "clawdbot/identity/device.json") private val identityFile = File(context.filesDir, "moltbot/identity/device.json")
@Synchronized @Synchronized
fun loadOrCreate(): DeviceIdentity { fun loadOrCreate(): DeviceIdentity {

View File

@@ -51,9 +51,9 @@ class GatewayDiscovery(
private val nsd = context.getSystemService(NsdManager::class.java) private val nsd = context.getSystemService(NsdManager::class.java)
private val connectivity = context.getSystemService(ConnectivityManager::class.java) private val connectivity = context.getSystemService(ConnectivityManager::class.java)
private val dns = DnsResolver.getInstance() private val dns = DnsResolver.getInstance()
private val serviceType = "_clawdbot-gw._tcp." private val serviceType = "_moltbot-gw._tcp."
private val wideAreaDomain = "clawdbot.internal." private val wideAreaDomain = "moltbot.internal."
private val logTag = "Clawdbot/GatewayDiscovery" private val logTag = "Moltbot/GatewayDiscovery"
private val localById = ConcurrentHashMap<String, GatewayEndpoint>() private val localById = ConcurrentHashMap<String, GatewayEndpoint>()
private val unicastById = ConcurrentHashMap<String, GatewayEndpoint>() private val unicastById = ConcurrentHashMap<String, GatewayEndpoint>()

View File

@@ -148,7 +148,7 @@ class GatewaySession(
try { try {
conn.request("node.event", params, timeoutMs = 8_000) conn.request("node.event", params, timeoutMs = 8_000)
} catch (err: Throwable) { } catch (err: Throwable) {
Log.w("ClawdbotGateway", "node.event failed: ${err.message ?: err::class.java.simpleName}") Log.w("MoltbotGateway", "node.event failed: ${err.message ?: err::class.java.simpleName}")
} }
} }
@@ -181,7 +181,7 @@ class GatewaySession(
private val connectNonceDeferred = CompletableDeferred<String?>() private val connectNonceDeferred = CompletableDeferred<String?>()
private val client: OkHttpClient = buildClient() private val client: OkHttpClient = buildClient()
private var socket: WebSocket? = null private var socket: WebSocket? = null
private val loggerTag = "ClawdbotGateway" private val loggerTag = "MoltbotGateway"
val remoteAddress: String = val remoteAddress: String =
if (endpoint.host.contains(":")) { if (endpoint.host.contains(":")) {

View File

@@ -155,7 +155,7 @@ class CameraCaptureManager(private val context: Context) {
provider.unbindAll() provider.unbindAll()
provider.bindToLifecycle(owner, selector, videoCapture) provider.bindToLifecycle(owner, selector, videoCapture)
val file = File.createTempFile("clawdbot-clip-", ".mp4") val file = File.createTempFile("moltbot-clip-", ".mp4")
val outputOptions = FileOutputOptions.Builder(file).build() val outputOptions = FileOutputOptions.Builder(file).build()
val finalized = kotlinx.coroutines.CompletableDeferred<VideoRecordEvent.Finalize>() val finalized = kotlinx.coroutines.CompletableDeferred<VideoRecordEvent.Finalize>()
@@ -285,7 +285,7 @@ private suspend fun Context.cameraProvider(): ProcessCameraProvider =
/** Returns (jpegBytes, exifOrientation) so caller can rotate the decoded bitmap. */ /** Returns (jpegBytes, exifOrientation) so caller can rotate the decoded bitmap. */
private suspend fun ImageCapture.takeJpegWithExif(executor: Executor): Pair<ByteArray, Int> = private suspend fun ImageCapture.takeJpegWithExif(executor: Executor): Pair<ByteArray, Int> =
suspendCancellableCoroutine { cont -> suspendCancellableCoroutine { cont ->
val file = File.createTempFile("clawdbot-snap-", ".jpg") val file = File.createTempFile("moltbot-snap-", ".jpg")
val options = ImageCapture.OutputFileOptions.Builder(file).build() val options = ImageCapture.OutputFileOptions.Builder(file).build()
takePicture( takePicture(
options, options,

View File

@@ -84,12 +84,12 @@ class CanvasController {
withWebViewOnMain { wv -> withWebViewOnMain { wv ->
if (currentUrl == null) { if (currentUrl == null) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.d("ClawdbotCanvas", "load scaffold: $scaffoldAssetUrl") Log.d("MoltbotCanvas", "load scaffold: $scaffoldAssetUrl")
} }
wv.loadUrl(scaffoldAssetUrl) wv.loadUrl(scaffoldAssetUrl)
} else { } else {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.d("ClawdbotCanvas", "load url: $currentUrl") Log.d("MoltbotCanvas", "load url: $currentUrl")
} }
wv.loadUrl(currentUrl) wv.loadUrl(currentUrl)
} }
@@ -106,7 +106,7 @@ class CanvasController {
val js = """ val js = """
(() => { (() => {
try { try {
const api = globalThis.__clawdbot; const api = globalThis.__moltbot;
if (!api) return; if (!api) return;
if (typeof api.setDebugStatusEnabled === 'function') { if (typeof api.setDebugStatusEnabled === 'function') {
api.setDebugStatusEnabled(${if (enabled) "true" else "false"}); api.setDebugStatusEnabled(${if (enabled) "true" else "false"});

View File

@@ -63,7 +63,7 @@ class ScreenRecordManager(private val context: Context) {
val height = metrics.heightPixels val height = metrics.heightPixels
val densityDpi = metrics.densityDpi val densityDpi = metrics.densityDpi
val file = File.createTempFile("clawdbot-screen-", ".mp4") val file = File.createTempFile("moltbot-screen-", ".mp4")
if (includeAudio) ensureMicPermission() if (includeAudio) ensureMicPermission()
val recorder = createMediaRecorder() val recorder = createMediaRecorder()
@@ -90,7 +90,7 @@ class ScreenRecordManager(private val context: Context) {
val surface = recorder.surface val surface = recorder.surface
virtualDisplay = virtualDisplay =
projection.createVirtualDisplay( projection.createVirtualDisplay(
"clawdbot-screen", "moltbot-screen",
width, width,
height, height,
densityDpi, densityDpi,

View File

@@ -3,7 +3,7 @@ package com.clawdbot.android.protocol
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
object ClawdbotCanvasA2UIAction { object MoltbotCanvasA2UIAction {
fun extractActionName(userAction: JsonObject): String? { fun extractActionName(userAction: JsonObject): String? {
val name = val name =
(userAction["name"] as? JsonPrimitive) (userAction["name"] as? JsonPrimitive)
@@ -61,6 +61,6 @@ object ClawdbotCanvasA2UIAction {
val err = (error ?: "").replace("\\", "\\\\").replace("\"", "\\\"") val err = (error ?: "").replace("\\", "\\\\").replace("\"", "\\\"")
val okLiteral = if (ok) "true" else "false" val okLiteral = if (ok) "true" else "false"
val idEscaped = actionId.replace("\\", "\\\\").replace("\"", "\\\"") val idEscaped = actionId.replace("\\", "\\\\").replace("\"", "\\\"")
return "window.dispatchEvent(new CustomEvent('clawdbot:a2ui-action-status', { detail: { id: \"${idEscaped}\", ok: ${okLiteral}, error: \"${err}\" } }));" return "window.dispatchEvent(new CustomEvent('moltbot:a2ui-action-status', { detail: { id: \"${idEscaped}\", ok: ${okLiteral}, error: \"${err}\" } }));"
} }
} }

View File

@@ -1,6 +1,6 @@
package com.clawdbot.android.protocol package com.clawdbot.android.protocol
enum class ClawdbotCapability(val rawValue: String) { enum class MoltbotCapability(val rawValue: String) {
Canvas("canvas"), Canvas("canvas"),
Camera("camera"), Camera("camera"),
Screen("screen"), Screen("screen"),
@@ -9,7 +9,7 @@ enum class ClawdbotCapability(val rawValue: String) {
Location("location"), Location("location"),
} }
enum class ClawdbotCanvasCommand(val rawValue: String) { enum class MoltbotCanvasCommand(val rawValue: String) {
Present("canvas.present"), Present("canvas.present"),
Hide("canvas.hide"), Hide("canvas.hide"),
Navigate("canvas.navigate"), Navigate("canvas.navigate"),
@@ -22,7 +22,7 @@ enum class ClawdbotCanvasCommand(val rawValue: String) {
} }
} }
enum class ClawdbotCanvasA2UICommand(val rawValue: String) { enum class MoltbotCanvasA2UICommand(val rawValue: String) {
Push("canvas.a2ui.push"), Push("canvas.a2ui.push"),
PushJSONL("canvas.a2ui.pushJSONL"), PushJSONL("canvas.a2ui.pushJSONL"),
Reset("canvas.a2ui.reset"), Reset("canvas.a2ui.reset"),
@@ -33,7 +33,7 @@ enum class ClawdbotCanvasA2UICommand(val rawValue: String) {
} }
} }
enum class ClawdbotCameraCommand(val rawValue: String) { enum class MoltbotCameraCommand(val rawValue: String) {
Snap("camera.snap"), Snap("camera.snap"),
Clip("camera.clip"), Clip("camera.clip"),
; ;
@@ -43,7 +43,7 @@ enum class ClawdbotCameraCommand(val rawValue: String) {
} }
} }
enum class ClawdbotScreenCommand(val rawValue: String) { enum class MoltbotScreenCommand(val rawValue: String) {
Record("screen.record"), Record("screen.record"),
; ;
@@ -52,7 +52,7 @@ enum class ClawdbotScreenCommand(val rawValue: String) {
} }
} }
enum class ClawdbotSmsCommand(val rawValue: String) { enum class MoltbotSmsCommand(val rawValue: String) {
Send("sms.send"), Send("sms.send"),
; ;
@@ -61,7 +61,7 @@ enum class ClawdbotSmsCommand(val rawValue: String) {
} }
} }
enum class ClawdbotLocationCommand(val rawValue: String) { enum class MoltbotLocationCommand(val rawValue: String) {
Get("location.get"), Get("location.get"),
; ;

View File

@@ -9,7 +9,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@Composable @Composable
fun ClawdbotTheme(content: @Composable () -> Unit) { fun MoltbotTheme(content: @Composable () -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val isDark = isSystemInDarkTheme() val isDark = isSystemInDarkTheme()
val colorScheme = if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) val colorScheme = if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)

View File

@@ -333,7 +333,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
disableForceDarkIfSupported(settings) disableForceDarkIfSupported(settings)
} }
if (isDebuggable) { if (isDebuggable) {
Log.d("ClawdbotWebView", "userAgent: ${settings.userAgentString}") Log.d("MoltbotWebView", "userAgent: ${settings.userAgentString}")
} }
isScrollContainer = true isScrollContainer = true
overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS
@@ -348,7 +348,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
) { ) {
if (!isDebuggable) return if (!isDebuggable) return
if (!request.isForMainFrame) return if (!request.isForMainFrame) return
Log.e("ClawdbotWebView", "onReceivedError: ${error.errorCode} ${error.description} ${request.url}") Log.e("MoltbotWebView", "onReceivedError: ${error.errorCode} ${error.description} ${request.url}")
} }
override fun onReceivedHttpError( override fun onReceivedHttpError(
@@ -359,14 +359,14 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
if (!isDebuggable) return if (!isDebuggable) return
if (!request.isForMainFrame) return if (!request.isForMainFrame) return
Log.e( Log.e(
"ClawdbotWebView", "MoltbotWebView",
"onReceivedHttpError: ${errorResponse.statusCode} ${errorResponse.reasonPhrase} ${request.url}", "onReceivedHttpError: ${errorResponse.statusCode} ${errorResponse.reasonPhrase} ${request.url}",
) )
} }
override fun onPageFinished(view: WebView, url: String?) { override fun onPageFinished(view: WebView, url: String?) {
if (isDebuggable) { if (isDebuggable) {
Log.d("ClawdbotWebView", "onPageFinished: $url") Log.d("MoltbotWebView", "onPageFinished: $url")
} }
viewModel.canvas.onPageFinished() viewModel.canvas.onPageFinished()
} }
@@ -377,7 +377,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
): Boolean { ): Boolean {
if (isDebuggable) { if (isDebuggable) {
Log.e( Log.e(
"ClawdbotWebView", "MoltbotWebView",
"onRenderProcessGone didCrash=${detail.didCrash()} priorityAtExit=${detail.rendererPriorityAtExit()}", "onRenderProcessGone didCrash=${detail.didCrash()} priorityAtExit=${detail.rendererPriorityAtExit()}",
) )
} }
@@ -390,7 +390,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
if (!isDebuggable) return false if (!isDebuggable) return false
val msg = consoleMessage ?: return false val msg = consoleMessage ?: return false
Log.d( Log.d(
"ClawdbotWebView", "MoltbotWebView",
"console ${msg.messageLevel()} @ ${msg.sourceId()}:${msg.lineNumber()} ${msg.message()}", "console ${msg.messageLevel()} @ ${msg.sourceId()}:${msg.lineNumber()} ${msg.message()}",
) )
return false return false
@@ -428,7 +428,7 @@ private class CanvasA2UIActionBridge(private val onMessage: (String) -> Unit) {
} }
companion object { companion object {
const val interfaceName: String = "clawdbotCanvasA2UIAction" const val interfaceName: String = "moltbotCanvasA2UIAction"
} }
} }

View File

@@ -457,7 +457,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
Column(verticalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth()) { Column(verticalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth()) {
ListItem( ListItem(
headlineContent = { Text("Foreground Only") }, headlineContent = { Text("Foreground Only") },
supportingContent = { Text("Listens only while Clawdbot is open.") }, supportingContent = { Text("Listens only while Moltbot is open.") },
trailingContent = { trailingContent = {
RadioButton( RadioButton(
selected = voiceWakeMode == VoiceWakeMode.Foreground, selected = voiceWakeMode == VoiceWakeMode.Foreground,
@@ -603,7 +603,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
) )
ListItem( ListItem(
headlineContent = { Text("While Using") }, headlineContent = { Text("While Using") },
supportingContent = { Text("Only while Clawdbot is open.") }, supportingContent = { Text("Only while Moltbot is open.") },
trailingContent = { trailingContent = {
RadioButton( RadioButton(
selected = locationMode == LocationMode.WhileUsing, selected = locationMode == LocationMode.WhileUsing,
@@ -650,7 +650,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
item { item {
ListItem( ListItem(
headlineContent = { Text("Prevent Sleep") }, headlineContent = { Text("Prevent Sleep") },
supportingContent = { Text("Keeps the screen awake while Clawdbot is open.") }, supportingContent = { Text("Keeps the screen awake while Moltbot is open.") },
trailingContent = { Switch(checked = preventSleep, onCheckedChange = viewModel::setPreventSleep) }, trailingContent = { Switch(checked = preventSleep, onCheckedChange = viewModel::setPreventSleep) },
) )
} }

View File

@@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">Clawdbot Node</string> <string name="app_name">Moltbot Node</string>
</resources> </resources>

View File

@@ -1,5 +1,5 @@
<resources> <resources>
<style name="Theme.ClawdbotNode" parent="Theme.Material3.DayNight.NoActionBar"> <style name="Theme.MoltbotNode" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">false</item> <item name="android:windowLightStatusBar">false</item>

View File

@@ -4,7 +4,7 @@
<base-config cleartextTrafficPermitted="true" tools:ignore="InsecureBaseConfiguration" /> <base-config cleartextTrafficPermitted="true" tools:ignore="InsecureBaseConfiguration" />
<!-- Allow HTTP for tailnet/local dev endpoints (e.g. canvas/background web). --> <!-- Allow HTTP for tailnet/local dev endpoints (e.g. canvas/background web). -->
<domain-config cleartextTrafficPermitted="true"> <domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">clawdbot.internal</domain> <domain includeSubdomains="true">moltbot.internal</domain>
</domain-config> </domain-config>
<domain-config cleartextTrafficPermitted="true"> <domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">ts.net</domain> <domain includeSubdomains="true">ts.net</domain>

View File

@@ -12,7 +12,7 @@ class BonjourEscapesTest {
@Test @Test
fun decodeDecodesDecimalEscapes() { fun decodeDecodesDecimalEscapes() {
assertEquals("Clawdbot Gateway", BonjourEscapes.decode("Clawdbot\\032Gateway")) assertEquals("Moltbot Gateway", BonjourEscapes.decode("Moltbot\\032Gateway"))
assertEquals("A B", BonjourEscapes.decode("A\\032B")) assertEquals("A B", BonjourEscapes.decode("A\\032B"))
assertEquals("Peter\u2019s Mac", BonjourEscapes.decode("Peter\\226\\128\\153s Mac")) assertEquals("Peter\u2019s Mac", BonjourEscapes.decode("Peter\\226\\128\\153s Mac"))
} }

View File

@@ -5,24 +5,24 @@ import kotlinx.serialization.json.jsonObject
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
class ClawdbotCanvasA2UIActionTest { class MoltbotCanvasA2UIActionTest {
@Test @Test
fun extractActionNameAcceptsNameOrAction() { fun extractActionNameAcceptsNameOrAction() {
val nameObj = Json.parseToJsonElement("{\"name\":\"Hello\"}").jsonObject val nameObj = Json.parseToJsonElement("{\"name\":\"Hello\"}").jsonObject
assertEquals("Hello", ClawdbotCanvasA2UIAction.extractActionName(nameObj)) assertEquals("Hello", MoltbotCanvasA2UIAction.extractActionName(nameObj))
val actionObj = Json.parseToJsonElement("{\"action\":\"Wave\"}").jsonObject val actionObj = Json.parseToJsonElement("{\"action\":\"Wave\"}").jsonObject
assertEquals("Wave", ClawdbotCanvasA2UIAction.extractActionName(actionObj)) assertEquals("Wave", MoltbotCanvasA2UIAction.extractActionName(actionObj))
val fallbackObj = val fallbackObj =
Json.parseToJsonElement("{\"name\":\" \",\"action\":\"Fallback\"}").jsonObject Json.parseToJsonElement("{\"name\":\" \",\"action\":\"Fallback\"}").jsonObject
assertEquals("Fallback", ClawdbotCanvasA2UIAction.extractActionName(fallbackObj)) assertEquals("Fallback", MoltbotCanvasA2UIAction.extractActionName(fallbackObj))
} }
@Test @Test
fun formatAgentMessageMatchesSharedSpec() { fun formatAgentMessageMatchesSharedSpec() {
val msg = val msg =
ClawdbotCanvasA2UIAction.formatAgentMessage( MoltbotCanvasA2UIAction.formatAgentMessage(
actionName = "Get Weather", actionName = "Get Weather",
sessionKey = "main", sessionKey = "main",
surfaceId = "main", surfaceId = "main",
@@ -40,9 +40,9 @@ class ClawdbotCanvasA2UIActionTest {
@Test @Test
fun jsDispatchA2uiStatusIsStable() { fun jsDispatchA2uiStatusIsStable() {
val js = ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId = "a1", ok = true, error = null) val js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId = "a1", ok = true, error = null)
assertEquals( assertEquals(
"window.dispatchEvent(new CustomEvent('clawdbot:a2ui-action-status', { detail: { id: \"a1\", ok: true, error: \"\" } }));", "window.dispatchEvent(new CustomEvent('moltbot:a2ui-action-status', { detail: { id: \"a1\", ok: true, error: \"\" } }));",
js, js,
) )
} }

View File

@@ -3,33 +3,33 @@ package com.clawdbot.android.protocol
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
class ClawdbotProtocolConstantsTest { class MoltbotProtocolConstantsTest {
@Test @Test
fun canvasCommandsUseStableStrings() { fun canvasCommandsUseStableStrings() {
assertEquals("canvas.present", ClawdbotCanvasCommand.Present.rawValue) assertEquals("canvas.present", MoltbotCanvasCommand.Present.rawValue)
assertEquals("canvas.hide", ClawdbotCanvasCommand.Hide.rawValue) assertEquals("canvas.hide", MoltbotCanvasCommand.Hide.rawValue)
assertEquals("canvas.navigate", ClawdbotCanvasCommand.Navigate.rawValue) assertEquals("canvas.navigate", MoltbotCanvasCommand.Navigate.rawValue)
assertEquals("canvas.eval", ClawdbotCanvasCommand.Eval.rawValue) assertEquals("canvas.eval", MoltbotCanvasCommand.Eval.rawValue)
assertEquals("canvas.snapshot", ClawdbotCanvasCommand.Snapshot.rawValue) assertEquals("canvas.snapshot", MoltbotCanvasCommand.Snapshot.rawValue)
} }
@Test @Test
fun a2uiCommandsUseStableStrings() { fun a2uiCommandsUseStableStrings() {
assertEquals("canvas.a2ui.push", ClawdbotCanvasA2UICommand.Push.rawValue) assertEquals("canvas.a2ui.push", MoltbotCanvasA2UICommand.Push.rawValue)
assertEquals("canvas.a2ui.pushJSONL", ClawdbotCanvasA2UICommand.PushJSONL.rawValue) assertEquals("canvas.a2ui.pushJSONL", MoltbotCanvasA2UICommand.PushJSONL.rawValue)
assertEquals("canvas.a2ui.reset", ClawdbotCanvasA2UICommand.Reset.rawValue) assertEquals("canvas.a2ui.reset", MoltbotCanvasA2UICommand.Reset.rawValue)
} }
@Test @Test
fun capabilitiesUseStableStrings() { fun capabilitiesUseStableStrings() {
assertEquals("canvas", ClawdbotCapability.Canvas.rawValue) assertEquals("canvas", MoltbotCapability.Canvas.rawValue)
assertEquals("camera", ClawdbotCapability.Camera.rawValue) assertEquals("camera", MoltbotCapability.Camera.rawValue)
assertEquals("screen", ClawdbotCapability.Screen.rawValue) assertEquals("screen", MoltbotCapability.Screen.rawValue)
assertEquals("voiceWake", ClawdbotCapability.VoiceWake.rawValue) assertEquals("voiceWake", MoltbotCapability.VoiceWake.rawValue)
} }
@Test @Test
fun screenCommandsUseStableStrings() { fun screenCommandsUseStableStrings() {
assertEquals("screen.record", ClawdbotScreenCommand.Record.rawValue) assertEquals("screen.record", MoltbotScreenCommand.Record.rawValue)
} }
} }

View File

@@ -14,5 +14,5 @@ dependencyResolutionManagement {
} }
} }
rootProject.name = "ClawdbotNodeAndroid" rootProject.name = "MoltbotNodeAndroid"
include(":app") include(":app")

View File

@@ -1,5 +1,5 @@
import AVFoundation import AVFoundation
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
actor CameraController { actor CameraController {
@@ -36,7 +36,7 @@ actor CameraController {
} }
} }
func snap(params: ClawdbotCameraSnapParams) async throws -> ( func snap(params: MoltbotCameraSnapParams) async throws -> (
format: String, format: String,
base64: String, base64: String,
width: Int, width: Int,
@@ -109,7 +109,7 @@ actor CameraController {
height: res.heightPx) height: res.heightPx)
} }
func clip(params: ClawdbotCameraClipParams) async throws -> ( func clip(params: MoltbotCameraClipParams) async throws -> (
format: String, format: String,
base64: String, base64: String,
durationMs: Int, durationMs: Int,
@@ -161,9 +161,9 @@ actor CameraController {
await Self.warmUpCaptureSession() await Self.warmUpCaptureSession()
let movURL = FileManager().temporaryDirectory let movURL = FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mov") .appendingPathComponent("moltbot-camera-\(UUID().uuidString).mov")
let mp4URL = FileManager().temporaryDirectory let mp4URL = FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mp4") .appendingPathComponent("moltbot-camera-\(UUID().uuidString).mp4")
defer { defer {
try? FileManager().removeItem(at: movURL) try? FileManager().removeItem(at: movURL)
@@ -221,7 +221,7 @@ actor CameraController {
} }
private nonisolated static func pickCamera( private nonisolated static func pickCamera(
facing: ClawdbotCameraFacing, facing: MoltbotCameraFacing,
deviceId: String?) -> AVCaptureDevice? deviceId: String?) -> AVCaptureDevice?
{ {
if let deviceId, !deviceId.isEmpty { if let deviceId, !deviceId.isEmpty {

View File

@@ -1,16 +1,16 @@
import ClawdbotChatUI import MoltbotChatUI
import ClawdbotKit import MoltbotKit
import SwiftUI import SwiftUI
struct ChatSheet: View { struct ChatSheet: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var viewModel: ClawdbotChatViewModel @State private var viewModel: MoltbotChatViewModel
private let userAccent: Color? private let userAccent: Color?
init(gateway: GatewayNodeSession, sessionKey: String, userAccent: Color? = nil) { init(gateway: GatewayNodeSession, sessionKey: String, userAccent: Color? = nil) {
let transport = IOSGatewayChatTransport(gateway: gateway) let transport = IOSGatewayChatTransport(gateway: gateway)
self._viewModel = State( self._viewModel = State(
initialValue: ClawdbotChatViewModel( initialValue: MoltbotChatViewModel(
sessionKey: sessionKey, sessionKey: sessionKey,
transport: transport)) transport: transport))
self.userAccent = userAccent self.userAccent = userAccent
@@ -18,7 +18,7 @@ struct ChatSheet: View {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ClawdbotChatView( MoltbotChatView(
viewModel: self.viewModel, viewModel: self.viewModel,
showsSessionSwitcher: true, showsSessionSwitcher: true,
userAccent: self.userAccent) userAccent: self.userAccent)

View File

@@ -1,9 +1,9 @@
import ClawdbotChatUI import MoltbotChatUI
import ClawdbotKit import MoltbotKit
import ClawdbotProtocol import MoltbotProtocol
import Foundation import Foundation
struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable { struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
private let gateway: GatewayNodeSession private let gateway: GatewayNodeSession
init(gateway: GatewayNodeSession) { init(gateway: GatewayNodeSession) {
@@ -20,7 +20,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
_ = try await self.gateway.request(method: "chat.abort", paramsJSON: json, timeoutSeconds: 10) _ = try await self.gateway.request(method: "chat.abort", paramsJSON: json, timeoutSeconds: 10)
} }
func listSessions(limit: Int?) async throws -> ClawdbotChatSessionsListResponse { func listSessions(limit: Int?) async throws -> MoltbotChatSessionsListResponse {
struct Params: Codable { struct Params: Codable {
var includeGlobal: Bool var includeGlobal: Bool
var includeUnknown: Bool var includeUnknown: Bool
@@ -29,7 +29,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
let data = try JSONEncoder().encode(Params(includeGlobal: true, includeUnknown: false, limit: limit)) let data = try JSONEncoder().encode(Params(includeGlobal: true, includeUnknown: false, limit: limit))
let json = String(data: data, encoding: .utf8) let json = String(data: data, encoding: .utf8)
let res = try await self.gateway.request(method: "sessions.list", paramsJSON: json, timeoutSeconds: 15) let res = try await self.gateway.request(method: "sessions.list", paramsJSON: json, timeoutSeconds: 15)
return try JSONDecoder().decode(ClawdbotChatSessionsListResponse.self, from: res) return try JSONDecoder().decode(MoltbotChatSessionsListResponse.self, from: res)
} }
func setActiveSessionKey(_ sessionKey: String) async throws { func setActiveSessionKey(_ sessionKey: String) async throws {
@@ -39,12 +39,12 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
await self.gateway.sendEvent(event: "chat.subscribe", payloadJSON: json) await self.gateway.sendEvent(event: "chat.subscribe", payloadJSON: json)
} }
func requestHistory(sessionKey: String) async throws -> ClawdbotChatHistoryPayload { func requestHistory(sessionKey: String) async throws -> MoltbotChatHistoryPayload {
struct Params: Codable { var sessionKey: String } struct Params: Codable { var sessionKey: String }
let data = try JSONEncoder().encode(Params(sessionKey: sessionKey)) let data = try JSONEncoder().encode(Params(sessionKey: sessionKey))
let json = String(data: data, encoding: .utf8) let json = String(data: data, encoding: .utf8)
let res = try await self.gateway.request(method: "chat.history", paramsJSON: json, timeoutSeconds: 15) let res = try await self.gateway.request(method: "chat.history", paramsJSON: json, timeoutSeconds: 15)
return try JSONDecoder().decode(ClawdbotChatHistoryPayload.self, from: res) return try JSONDecoder().decode(MoltbotChatHistoryPayload.self, from: res)
} }
func sendMessage( func sendMessage(
@@ -52,13 +52,13 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
message: String, message: String,
thinking: String, thinking: String,
idempotencyKey: String, idempotencyKey: String,
attachments: [ClawdbotChatAttachmentPayload]) async throws -> ClawdbotChatSendResponse attachments: [MoltbotChatAttachmentPayload]) async throws -> MoltbotChatSendResponse
{ {
struct Params: Codable { struct Params: Codable {
var sessionKey: String var sessionKey: String
var message: String var message: String
var thinking: String var thinking: String
var attachments: [ClawdbotChatAttachmentPayload]? var attachments: [MoltbotChatAttachmentPayload]?
var timeoutMs: Int var timeoutMs: Int
var idempotencyKey: String var idempotencyKey: String
} }
@@ -73,16 +73,16 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
let data = try JSONEncoder().encode(params) let data = try JSONEncoder().encode(params)
let json = String(data: data, encoding: .utf8) let json = String(data: data, encoding: .utf8)
let res = try await self.gateway.request(method: "chat.send", paramsJSON: json, timeoutSeconds: 35) let res = try await self.gateway.request(method: "chat.send", paramsJSON: json, timeoutSeconds: 35)
return try JSONDecoder().decode(ClawdbotChatSendResponse.self, from: res) return try JSONDecoder().decode(MoltbotChatSendResponse.self, from: res)
} }
func requestHealth(timeoutMs: Int) async throws -> Bool { func requestHealth(timeoutMs: Int) async throws -> Bool {
let seconds = max(1, Int(ceil(Double(timeoutMs) / 1000.0))) let seconds = max(1, Int(ceil(Double(timeoutMs) / 1000.0)))
let res = try await self.gateway.request(method: "health", paramsJSON: nil, timeoutSeconds: seconds) let res = try await self.gateway.request(method: "health", paramsJSON: nil, timeoutSeconds: seconds)
return (try? JSONDecoder().decode(ClawdbotGatewayHealthOK.self, from: res))?.ok ?? true return (try? JSONDecoder().decode(MoltbotGatewayHealthOK.self, from: res))?.ok ?? true
} }
func events() -> AsyncStream<ClawdbotChatTransportEvent> { func events() -> AsyncStream<MoltbotChatTransportEvent> {
AsyncStream { continuation in AsyncStream { continuation in
let task = Task { let task = Task {
let stream = await self.gateway.subscribeServerEvents() let stream = await self.gateway.subscribeServerEvents()
@@ -97,13 +97,13 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
guard let payload = evt.payload else { break } guard let payload = evt.payload else { break }
let ok = (try? GatewayPayloadDecoding.decode( let ok = (try? GatewayPayloadDecoding.decode(
payload, payload,
as: ClawdbotGatewayHealthOK.self))?.ok ?? true as: MoltbotGatewayHealthOK.self))?.ok ?? true
continuation.yield(.health(ok: ok)) continuation.yield(.health(ok: ok))
case "chat": case "chat":
guard let payload = evt.payload else { break } guard let payload = evt.payload else { break }
if let chatPayload = try? GatewayPayloadDecoding.decode( if let chatPayload = try? GatewayPayloadDecoding.decode(
payload, payload,
as: ClawdbotChatEventPayload.self) as: MoltbotChatEventPayload.self)
{ {
continuation.yield(.chat(chatPayload)) continuation.yield(.chat(chatPayload))
} }
@@ -111,7 +111,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
guard let payload = evt.payload else { break } guard let payload = evt.payload else { break }
if let agentPayload = try? GatewayPayloadDecoding.decode( if let agentPayload = try? GatewayPayloadDecoding.decode(
payload, payload,
as: ClawdbotAgentEventPayload.self) as: MoltbotAgentEventPayload.self)
{ {
continuation.yield(.agent(agentPayload)) continuation.yield(.agent(agentPayload))
} }

View File

@@ -1,7 +1,7 @@
import SwiftUI import SwiftUI
@main @main
struct ClawdbotApp: App { struct MoltbotApp: App {
@State private var appModel: NodeAppModel @State private var appModel: NodeAppModel
@State private var gatewayController: GatewayConnectionController @State private var gatewayController: GatewayConnectionController
@Environment(\.scenePhase) private var scenePhase @Environment(\.scenePhase) private var scenePhase

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import Darwin import Darwin
import Foundation import Foundation
import Network import Network
@@ -283,7 +283,7 @@ final class GatewayConnectionController {
caps: self.currentCaps(), caps: self.currentCaps(),
commands: self.currentCommands(), commands: self.currentCommands(),
permissions: [:], permissions: [:],
clientId: "clawdbot-ios", clientId: "moltbot-ios",
clientMode: "node", clientMode: "node",
clientDisplayName: displayName) clientDisplayName: displayName)
} }
@@ -304,51 +304,51 @@ final class GatewayConnectionController {
} }
private func currentCaps() -> [String] { private func currentCaps() -> [String] {
var caps = [ClawdbotCapability.canvas.rawValue, ClawdbotCapability.screen.rawValue] var caps = [MoltbotCapability.canvas.rawValue, MoltbotCapability.screen.rawValue]
// Default-on: if the key doesn't exist yet, treat it as enabled. // Default-on: if the key doesn't exist yet, treat it as enabled.
let cameraEnabled = let cameraEnabled =
UserDefaults.standard.object(forKey: "camera.enabled") == nil UserDefaults.standard.object(forKey: "camera.enabled") == nil
? true ? true
: UserDefaults.standard.bool(forKey: "camera.enabled") : UserDefaults.standard.bool(forKey: "camera.enabled")
if cameraEnabled { caps.append(ClawdbotCapability.camera.rawValue) } if cameraEnabled { caps.append(MoltbotCapability.camera.rawValue) }
let voiceWakeEnabled = UserDefaults.standard.bool(forKey: VoiceWakePreferences.enabledKey) let voiceWakeEnabled = UserDefaults.standard.bool(forKey: VoiceWakePreferences.enabledKey)
if voiceWakeEnabled { caps.append(ClawdbotCapability.voiceWake.rawValue) } if voiceWakeEnabled { caps.append(MoltbotCapability.voiceWake.rawValue) }
let locationModeRaw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off" let locationModeRaw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off"
let locationMode = ClawdbotLocationMode(rawValue: locationModeRaw) ?? .off let locationMode = MoltbotLocationMode(rawValue: locationModeRaw) ?? .off
if locationMode != .off { caps.append(ClawdbotCapability.location.rawValue) } if locationMode != .off { caps.append(MoltbotCapability.location.rawValue) }
return caps return caps
} }
private func currentCommands() -> [String] { private func currentCommands() -> [String] {
var commands: [String] = [ var commands: [String] = [
ClawdbotCanvasCommand.present.rawValue, MoltbotCanvasCommand.present.rawValue,
ClawdbotCanvasCommand.hide.rawValue, MoltbotCanvasCommand.hide.rawValue,
ClawdbotCanvasCommand.navigate.rawValue, MoltbotCanvasCommand.navigate.rawValue,
ClawdbotCanvasCommand.evalJS.rawValue, MoltbotCanvasCommand.evalJS.rawValue,
ClawdbotCanvasCommand.snapshot.rawValue, MoltbotCanvasCommand.snapshot.rawValue,
ClawdbotCanvasA2UICommand.push.rawValue, MoltbotCanvasA2UICommand.push.rawValue,
ClawdbotCanvasA2UICommand.pushJSONL.rawValue, MoltbotCanvasA2UICommand.pushJSONL.rawValue,
ClawdbotCanvasA2UICommand.reset.rawValue, MoltbotCanvasA2UICommand.reset.rawValue,
ClawdbotScreenCommand.record.rawValue, MoltbotScreenCommand.record.rawValue,
ClawdbotSystemCommand.notify.rawValue, MoltbotSystemCommand.notify.rawValue,
ClawdbotSystemCommand.which.rawValue, MoltbotSystemCommand.which.rawValue,
ClawdbotSystemCommand.run.rawValue, MoltbotSystemCommand.run.rawValue,
ClawdbotSystemCommand.execApprovalsGet.rawValue, MoltbotSystemCommand.execApprovalsGet.rawValue,
ClawdbotSystemCommand.execApprovalsSet.rawValue, MoltbotSystemCommand.execApprovalsSet.rawValue,
] ]
let caps = Set(self.currentCaps()) let caps = Set(self.currentCaps())
if caps.contains(ClawdbotCapability.camera.rawValue) { if caps.contains(MoltbotCapability.camera.rawValue) {
commands.append(ClawdbotCameraCommand.list.rawValue) commands.append(MoltbotCameraCommand.list.rawValue)
commands.append(ClawdbotCameraCommand.snap.rawValue) commands.append(MoltbotCameraCommand.snap.rawValue)
commands.append(ClawdbotCameraCommand.clip.rawValue) commands.append(MoltbotCameraCommand.clip.rawValue)
} }
if caps.contains(ClawdbotCapability.location.rawValue) { if caps.contains(MoltbotCapability.location.rawValue) {
commands.append(ClawdbotLocationCommand.get.rawValue) commands.append(MoltbotLocationCommand.get.rawValue)
} }
return commands return commands

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
import Network import Network
import Observation import Observation
@@ -52,11 +52,11 @@ final class GatewayDiscoveryModel {
if !self.browsers.isEmpty { return } if !self.browsers.isEmpty { return }
self.appendDebugLog("start()") self.appendDebugLog("start()")
for domain in ClawdbotBonjour.gatewayServiceDomains { for domain in MoltbotBonjour.gatewayServiceDomains {
let params = NWParameters.tcp let params = NWParameters.tcp
params.includePeerToPeer = true params.includePeerToPeer = true
let browser = NWBrowser( let browser = NWBrowser(
for: .bonjour(type: ClawdbotBonjour.gatewayServiceType, domain: domain), for: .bonjour(type: MoltbotBonjour.gatewayServiceType, domain: domain),
using: params) using: params)
browser.stateUpdateHandler = { [weak self] state in browser.stateUpdateHandler = { [weak self] state in
@@ -202,7 +202,7 @@ final class GatewayDiscoveryModel {
private static func prettifyInstanceName(_ decodedName: String) -> String { private static func prettifyInstanceName(_ decodedName: String) -> String {
let normalized = decodedName.split(whereSeparator: \.isWhitespace).joined(separator: " ") let normalized = decodedName.split(whereSeparator: \.isWhitespace).joined(separator: " ")
let stripped = normalized.replacingOccurrences(of: " (Clawdbot)", with: "") let stripped = normalized.replacingOccurrences(of: " (Moltbot)", with: "")
.replacingOccurrences(of: #"\s+\(\d+\)$"#, with: "", options: .regularExpression) .replacingOccurrences(of: #"\s+\(\d+\)$"#, with: "", options: .regularExpression)
return stripped.trimmingCharacters(in: .whitespacesAndNewlines) return stripped.trimmingCharacters(in: .whitespacesAndNewlines)
} }

View File

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Clawdbot</string> <string>Moltbot</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconName</key> <key>CFBundleIconName</key>
@@ -29,20 +29,20 @@
</dict> </dict>
<key>NSBonjourServices</key> <key>NSBonjourServices</key>
<array> <array>
<string>_clawdbot-gw._tcp</string> <string>_moltbot-gw._tcp</string>
</array> </array>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Clawdbot can capture photos or short video clips when requested via the gateway.</string> <string>Moltbot can capture photos or short video clips when requested via the gateway.</string>
<key>NSLocalNetworkUsageDescription</key> <key>NSLocalNetworkUsageDescription</key>
<string>Clawdbot discovers and connects to your Clawdbot gateway on the local network.</string> <string>Moltbot discovers and connects to your Moltbot gateway on the local network.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Clawdbot can share your location in the background when you enable Always.</string> <string>Moltbot can share your location in the background when you enable Always.</string>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string>Clawdbot uses your location when you allow location sharing.</string> <string>Moltbot uses your location when you allow location sharing.</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>Clawdbot needs microphone access for voice wake.</string> <string>Moltbot needs microphone access for voice wake.</string>
<key>NSSpeechRecognitionUsageDescription</key> <key>NSSpeechRecognitionUsageDescription</key>
<string>Clawdbot uses on-device speech recognition for voice wake.</string> <string>Moltbot uses on-device speech recognition for voice wake.</string>
<key>UIApplicationSceneManifest</key> <key>UIApplicationSceneManifest</key>
<dict> <dict>
<key>UIApplicationSupportsMultipleScenes</key> <key>UIApplicationSupportsMultipleScenes</key>

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import CoreLocation import CoreLocation
import Foundation import Foundation
@@ -30,7 +30,7 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
return .fullAccuracy return .fullAccuracy
} }
func ensureAuthorization(mode: ClawdbotLocationMode) async -> CLAuthorizationStatus { func ensureAuthorization(mode: MoltbotLocationMode) async -> CLAuthorizationStatus {
guard CLLocationManager.locationServicesEnabled() else { return .denied } guard CLLocationManager.locationServicesEnabled() else { return .denied }
let status = self.manager.authorizationStatus let status = self.manager.authorizationStatus
@@ -53,8 +53,8 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
} }
func currentLocation( func currentLocation(
params: ClawdbotLocationGetParams, params: MoltbotLocationGetParams,
desiredAccuracy: ClawdbotLocationAccuracy, desiredAccuracy: MoltbotLocationAccuracy,
maxAgeMs: Int?, maxAgeMs: Int?,
timeoutMs: Int?) async throws -> CLLocation timeoutMs: Int?) async throws -> CLLocation
{ {
@@ -93,7 +93,7 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
try await AsyncTimeout.withTimeoutMs(timeoutMs: timeoutMs, onTimeout: { Error.timeout }, operation: operation) try await AsyncTimeout.withTimeoutMs(timeoutMs: timeoutMs, onTimeout: { Error.timeout }, operation: operation)
} }
private static func accuracyValue(_ accuracy: ClawdbotLocationAccuracy) -> CLLocationAccuracy { private static func accuracyValue(_ accuracy: MoltbotLocationAccuracy) -> CLLocationAccuracy {
switch accuracy { switch accuracy {
case .coarse: case .coarse:
kCLLocationAccuracyKilometer kCLLocationAccuracyKilometer

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import Network import Network
import Observation import Observation
import SwiftUI import SwiftUI
@@ -90,7 +90,7 @@ final class NodeAppModel {
}() }()
guard !userAction.isEmpty else { return } guard !userAction.isEmpty else { return }
guard let name = ClawdbotCanvasA2UIAction.extractActionName(userAction) else { return } guard let name = MoltbotCanvasA2UIAction.extractActionName(userAction) else { return }
let actionId: String = { let actionId: String = {
let id = (userAction["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let id = (userAction["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return id.isEmpty ? UUID().uuidString : id return id.isEmpty ? UUID().uuidString : id
@@ -109,15 +109,15 @@ final class NodeAppModel {
let host = UserDefaults.standard.string(forKey: "node.displayName") ?? UIDevice.current.name let host = UserDefaults.standard.string(forKey: "node.displayName") ?? UIDevice.current.name
let instanceId = (UserDefaults.standard.string(forKey: "node.instanceId") ?? "ios-node").lowercased() let instanceId = (UserDefaults.standard.string(forKey: "node.instanceId") ?? "ios-node").lowercased()
let contextJSON = ClawdbotCanvasA2UIAction.compactJSON(userAction["context"]) let contextJSON = MoltbotCanvasA2UIAction.compactJSON(userAction["context"])
let sessionKey = self.mainSessionKey let sessionKey = self.mainSessionKey
let messageContext = ClawdbotCanvasA2UIAction.AgentMessageContext( let messageContext = MoltbotCanvasA2UIAction.AgentMessageContext(
actionName: name, actionName: name,
session: .init(key: sessionKey, surfaceId: surfaceId), session: .init(key: sessionKey, surfaceId: surfaceId),
component: .init(id: sourceComponentId, host: host, instanceId: instanceId), component: .init(id: sourceComponentId, host: host, instanceId: instanceId),
contextJSON: contextJSON) contextJSON: contextJSON)
let message = ClawdbotCanvasA2UIAction.formatAgentMessage(messageContext) let message = MoltbotCanvasA2UIAction.formatAgentMessage(messageContext)
let ok: Bool let ok: Bool
var errorText: String? var errorText: String?
@@ -142,7 +142,7 @@ final class NodeAppModel {
} }
} }
let js = ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId: actionId, ok: ok, error: errorText) let js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId: actionId, ok: ok, error: errorText)
do { do {
_ = try await self.screen.eval(javaScript: js) _ = try await self.screen.eval(javaScript: js)
} catch { } catch {
@@ -154,7 +154,7 @@ final class NodeAppModel {
guard let raw = await self.gateway.currentCanvasHostUrl() else { return nil } guard let raw = await self.gateway.currentCanvasHostUrl() else { return nil }
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty, let base = URL(string: trimmed) else { return nil } guard !trimmed.isEmpty, let base = URL(string: trimmed) else { return nil }
return base.appendingPathComponent("__clawdbot__/a2ui/").absoluteString + "?platform=ios" return base.appendingPathComponent("__moltbot__/a2ui/").absoluteString + "?platform=ios"
} }
private func showA2UIOnConnectIfNeeded() async { private func showA2UIOnConnectIfNeeded() async {
@@ -190,7 +190,7 @@ final class NodeAppModel {
self.talkMode.setEnabled(enabled) self.talkMode.setEnabled(enabled)
} }
func requestLocationPermissions(mode: ClawdbotLocationMode) async -> Bool { func requestLocationPermissions(mode: MoltbotLocationMode) async -> Bool {
guard mode != .off else { return true } guard mode != .off else { return true }
let status = await self.locationService.ensureAuthorization(mode: mode) let status = await self.locationService.ensureAuthorization(mode: mode)
switch status { switch status {
@@ -272,7 +272,7 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "UNAVAILABLE: node not ready")) message: "UNAVAILABLE: node not ready"))
} }
@@ -487,7 +487,7 @@ final class NodeAppModel {
} }
// iOS gateway forwards to the gateway; no local auth prompts here. // iOS gateway forwards to the gateway; no local auth prompts here.
// (Key-based unattended auth is handled on macOS for clawdbot:// links.) // (Key-based unattended auth is handled on macOS for moltbot:// links.)
let data = try JSONEncoder().encode(link) let data = try JSONEncoder().encode(link)
guard let json = String(bytes: data, encoding: .utf8) else { guard let json = String(bytes: data, encoding: .utf8) else {
throw NSError(domain: "NodeAppModel", code: 2, userInfo: [ throw NSError(domain: "NodeAppModel", code: 2, userInfo: [
@@ -508,7 +508,7 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .backgroundUnavailable, code: .backgroundUnavailable,
message: "NODE_BACKGROUND_UNAVAILABLE: canvas/camera/screen commands require foreground")) message: "NODE_BACKGROUND_UNAVAILABLE: canvas/camera/screen commands require foreground"))
} }
@@ -517,36 +517,36 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "CAMERA_DISABLED: enable Camera in iOS Settings → Camera → Allow Camera")) message: "CAMERA_DISABLED: enable Camera in iOS Settings → Camera → Allow Camera"))
} }
do { do {
switch command { switch command {
case ClawdbotLocationCommand.get.rawValue: case MoltbotLocationCommand.get.rawValue:
return try await self.handleLocationInvoke(req) return try await self.handleLocationInvoke(req)
case ClawdbotCanvasCommand.present.rawValue, case MoltbotCanvasCommand.present.rawValue,
ClawdbotCanvasCommand.hide.rawValue, MoltbotCanvasCommand.hide.rawValue,
ClawdbotCanvasCommand.navigate.rawValue, MoltbotCanvasCommand.navigate.rawValue,
ClawdbotCanvasCommand.evalJS.rawValue, MoltbotCanvasCommand.evalJS.rawValue,
ClawdbotCanvasCommand.snapshot.rawValue: MoltbotCanvasCommand.snapshot.rawValue:
return try await self.handleCanvasInvoke(req) return try await self.handleCanvasInvoke(req)
case ClawdbotCanvasA2UICommand.reset.rawValue, case MoltbotCanvasA2UICommand.reset.rawValue,
ClawdbotCanvasA2UICommand.push.rawValue, MoltbotCanvasA2UICommand.push.rawValue,
ClawdbotCanvasA2UICommand.pushJSONL.rawValue: MoltbotCanvasA2UICommand.pushJSONL.rawValue:
return try await self.handleCanvasA2UIInvoke(req) return try await self.handleCanvasA2UIInvoke(req)
case ClawdbotCameraCommand.list.rawValue, case MoltbotCameraCommand.list.rawValue,
ClawdbotCameraCommand.snap.rawValue, MoltbotCameraCommand.snap.rawValue,
ClawdbotCameraCommand.clip.rawValue: MoltbotCameraCommand.clip.rawValue:
return try await self.handleCameraInvoke(req) return try await self.handleCameraInvoke(req)
case ClawdbotScreenCommand.record.rawValue: case MoltbotScreenCommand.record.rawValue:
return try await self.handleScreenRecordInvoke(req) return try await self.handleScreenRecordInvoke(req)
default: default:
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command")) error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
} }
} catch { } catch {
if command.hasPrefix("camera.") { if command.hasPrefix("camera.") {
@@ -556,7 +556,7 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError(code: .unavailable, message: error.localizedDescription)) error: MoltbotNodeError(code: .unavailable, message: error.localizedDescription))
} }
} }
@@ -570,7 +570,7 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "LOCATION_DISABLED: enable Location in Settings")) message: "LOCATION_DISABLED: enable Location in Settings"))
} }
@@ -578,12 +578,12 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .backgroundUnavailable, code: .backgroundUnavailable,
message: "LOCATION_BACKGROUND_UNAVAILABLE: background location requires Always")) message: "LOCATION_BACKGROUND_UNAVAILABLE: background location requires Always"))
} }
let params = (try? Self.decodeParams(ClawdbotLocationGetParams.self, from: req.paramsJSON)) ?? let params = (try? Self.decodeParams(MoltbotLocationGetParams.self, from: req.paramsJSON)) ??
ClawdbotLocationGetParams() MoltbotLocationGetParams()
let desired = params.desiredAccuracy ?? let desired = params.desiredAccuracy ??
(self.isLocationPreciseEnabled() ? .precise : .balanced) (self.isLocationPreciseEnabled() ? .precise : .balanced)
let status = self.locationService.authorizationStatus() let status = self.locationService.authorizationStatus()
@@ -591,7 +591,7 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "LOCATION_PERMISSION_REQUIRED: grant Location permission")) message: "LOCATION_PERMISSION_REQUIRED: grant Location permission"))
} }
@@ -599,7 +599,7 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "LOCATION_PERMISSION_REQUIRED: enable Always for background access")) message: "LOCATION_PERMISSION_REQUIRED: enable Always for background access"))
} }
@@ -609,7 +609,7 @@ final class NodeAppModel {
maxAgeMs: params.maxAgeMs, maxAgeMs: params.maxAgeMs,
timeoutMs: params.timeoutMs) timeoutMs: params.timeoutMs)
let isPrecise = self.locationService.accuracyAuthorization() == .fullAccuracy let isPrecise = self.locationService.accuracyAuthorization() == .fullAccuracy
let payload = ClawdbotLocationPayload( let payload = MoltbotLocationPayload(
lat: location.coordinate.latitude, lat: location.coordinate.latitude,
lon: location.coordinate.longitude, lon: location.coordinate.longitude,
accuracyMeters: location.horizontalAccuracy, accuracyMeters: location.horizontalAccuracy,
@@ -625,9 +625,9 @@ final class NodeAppModel {
private func handleCanvasInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse { private func handleCanvasInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
switch req.command { switch req.command {
case ClawdbotCanvasCommand.present.rawValue: case MoltbotCanvasCommand.present.rawValue:
let params = (try? Self.decodeParams(ClawdbotCanvasPresentParams.self, from: req.paramsJSON)) ?? let params = (try? Self.decodeParams(MoltbotCanvasPresentParams.self, from: req.paramsJSON)) ??
ClawdbotCanvasPresentParams() MoltbotCanvasPresentParams()
let url = params.url?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let url = params.url?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if url.isEmpty { if url.isEmpty {
self.screen.showDefaultCanvas() self.screen.showDefaultCanvas()
@@ -635,19 +635,19 @@ final class NodeAppModel {
self.screen.navigate(to: url) self.screen.navigate(to: url)
} }
return BridgeInvokeResponse(id: req.id, ok: true) return BridgeInvokeResponse(id: req.id, ok: true)
case ClawdbotCanvasCommand.hide.rawValue: case MoltbotCanvasCommand.hide.rawValue:
return BridgeInvokeResponse(id: req.id, ok: true) return BridgeInvokeResponse(id: req.id, ok: true)
case ClawdbotCanvasCommand.navigate.rawValue: case MoltbotCanvasCommand.navigate.rawValue:
let params = try Self.decodeParams(ClawdbotCanvasNavigateParams.self, from: req.paramsJSON) let params = try Self.decodeParams(MoltbotCanvasNavigateParams.self, from: req.paramsJSON)
self.screen.navigate(to: params.url) self.screen.navigate(to: params.url)
return BridgeInvokeResponse(id: req.id, ok: true) return BridgeInvokeResponse(id: req.id, ok: true)
case ClawdbotCanvasCommand.evalJS.rawValue: case MoltbotCanvasCommand.evalJS.rawValue:
let params = try Self.decodeParams(ClawdbotCanvasEvalParams.self, from: req.paramsJSON) let params = try Self.decodeParams(MoltbotCanvasEvalParams.self, from: req.paramsJSON)
let result = try await self.screen.eval(javaScript: params.javaScript) let result = try await self.screen.eval(javaScript: params.javaScript)
let payload = try Self.encodePayload(["result": result]) let payload = try Self.encodePayload(["result": result])
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload) return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
case ClawdbotCanvasCommand.snapshot.rawValue: case MoltbotCanvasCommand.snapshot.rawValue:
let params = try? Self.decodeParams(ClawdbotCanvasSnapshotParams.self, from: req.paramsJSON) let params = try? Self.decodeParams(MoltbotCanvasSnapshotParams.self, from: req.paramsJSON)
let format = params?.format ?? .jpeg let format = params?.format ?? .jpeg
let maxWidth: CGFloat? = { let maxWidth: CGFloat? = {
if let raw = params?.maxWidth, raw > 0 { return CGFloat(raw) } if let raw = params?.maxWidth, raw > 0 { return CGFloat(raw) }
@@ -671,19 +671,19 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command")) error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
} }
} }
private func handleCanvasA2UIInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse { private func handleCanvasA2UIInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
let command = req.command let command = req.command
switch command { switch command {
case ClawdbotCanvasA2UICommand.reset.rawValue: case MoltbotCanvasA2UICommand.reset.rawValue:
guard let a2uiUrl = await self.resolveA2UIHostURL() else { guard let a2uiUrl = await self.resolveA2UIHostURL() else {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host")) message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host"))
} }
@@ -692,31 +692,31 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable")) message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable"))
} }
let json = try await self.screen.eval(javaScript: """ let json = try await self.screen.eval(javaScript: """
(() => { (() => {
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing clawdbotA2UI" }); if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing moltbotA2UI" });
return JSON.stringify(globalThis.clawdbotA2UI.reset()); return JSON.stringify(globalThis.clawdbotA2UI.reset());
})() })()
""") """)
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: json) return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: json)
case ClawdbotCanvasA2UICommand.push.rawValue, ClawdbotCanvasA2UICommand.pushJSONL.rawValue: case MoltbotCanvasA2UICommand.push.rawValue, MoltbotCanvasA2UICommand.pushJSONL.rawValue:
let messages: [AnyCodable] let messages: [AnyCodable]
if command == ClawdbotCanvasA2UICommand.pushJSONL.rawValue { if command == MoltbotCanvasA2UICommand.pushJSONL.rawValue {
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON) let params = try Self.decodeParams(MoltbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl) messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
} else { } else {
do { do {
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushParams.self, from: req.paramsJSON) let params = try Self.decodeParams(MoltbotCanvasA2UIPushParams.self, from: req.paramsJSON)
messages = params.messages messages = params.messages
} catch { } catch {
// Be forgiving: some clients still send JSONL payloads to `canvas.a2ui.push`. // Be forgiving: some clients still send JSONL payloads to `canvas.a2ui.push`.
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON) let params = try Self.decodeParams(MoltbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl) messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
} }
} }
@@ -724,7 +724,7 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host")) message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host"))
} }
@@ -733,16 +733,16 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError( error: MoltbotNodeError(
code: .unavailable, code: .unavailable,
message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable")) message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable"))
} }
let messagesJSON = try ClawdbotCanvasA2UIJSONL.encodeMessagesJSONArray(messages) let messagesJSON = try MoltbotCanvasA2UIJSONL.encodeMessagesJSONArray(messages)
let js = """ let js = """
(() => { (() => {
try { try {
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing clawdbotA2UI" }); if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing moltbotA2UI" });
const messages = \(messagesJSON); const messages = \(messagesJSON);
return JSON.stringify(globalThis.clawdbotA2UI.applyMessages(messages)); return JSON.stringify(globalThis.clawdbotA2UI.applyMessages(messages));
} catch (e) { } catch (e) {
@@ -756,24 +756,24 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command")) error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
} }
} }
private func handleCameraInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse { private func handleCameraInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
switch req.command { switch req.command {
case ClawdbotCameraCommand.list.rawValue: case MoltbotCameraCommand.list.rawValue:
let devices = await self.camera.listDevices() let devices = await self.camera.listDevices()
struct Payload: Codable { struct Payload: Codable {
var devices: [CameraController.CameraDeviceInfo] var devices: [CameraController.CameraDeviceInfo]
} }
let payload = try Self.encodePayload(Payload(devices: devices)) let payload = try Self.encodePayload(Payload(devices: devices))
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload) return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
case ClawdbotCameraCommand.snap.rawValue: case MoltbotCameraCommand.snap.rawValue:
self.showCameraHUD(text: "Taking photo…", kind: .photo) self.showCameraHUD(text: "Taking photo…", kind: .photo)
self.triggerCameraFlash() self.triggerCameraFlash()
let params = (try? Self.decodeParams(ClawdbotCameraSnapParams.self, from: req.paramsJSON)) ?? let params = (try? Self.decodeParams(MoltbotCameraSnapParams.self, from: req.paramsJSON)) ??
ClawdbotCameraSnapParams() MoltbotCameraSnapParams()
let res = try await self.camera.snap(params: params) let res = try await self.camera.snap(params: params)
struct Payload: Codable { struct Payload: Codable {
@@ -789,9 +789,9 @@ final class NodeAppModel {
height: res.height)) height: res.height))
self.showCameraHUD(text: "Photo captured", kind: .success, autoHideSeconds: 1.6) self.showCameraHUD(text: "Photo captured", kind: .success, autoHideSeconds: 1.6)
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload) return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
case ClawdbotCameraCommand.clip.rawValue: case MoltbotCameraCommand.clip.rawValue:
let params = (try? Self.decodeParams(ClawdbotCameraClipParams.self, from: req.paramsJSON)) ?? let params = (try? Self.decodeParams(MoltbotCameraClipParams.self, from: req.paramsJSON)) ??
ClawdbotCameraClipParams() MoltbotCameraClipParams()
let suspended = (params.includeAudio ?? true) ? self.voiceWake.suspendForExternalAudioCapture() : false let suspended = (params.includeAudio ?? true) ? self.voiceWake.suspendForExternalAudioCapture() : false
defer { self.voiceWake.resumeAfterExternalAudioCapture(wasSuspended: suspended) } defer { self.voiceWake.resumeAfterExternalAudioCapture(wasSuspended: suspended) }
@@ -816,13 +816,13 @@ final class NodeAppModel {
return BridgeInvokeResponse( return BridgeInvokeResponse(
id: req.id, id: req.id,
ok: false, ok: false,
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command")) error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
} }
} }
private func handleScreenRecordInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse { private func handleScreenRecordInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
let params = (try? Self.decodeParams(ClawdbotScreenRecordParams.self, from: req.paramsJSON)) ?? let params = (try? Self.decodeParams(MoltbotScreenRecordParams.self, from: req.paramsJSON)) ??
ClawdbotScreenRecordParams() MoltbotScreenRecordParams()
if let format = params.format, format.lowercased() != "mp4" { if let format = params.format, format.lowercased() != "mp4" {
throw NSError(domain: "Screen", code: 30, userInfo: [ throw NSError(domain: "Screen", code: 30, userInfo: [
NSLocalizedDescriptionKey: "INVALID_REQUEST: screen format must be mp4", NSLocalizedDescriptionKey: "INVALID_REQUEST: screen format must be mp4",
@@ -860,9 +860,9 @@ final class NodeAppModel {
} }
private extension NodeAppModel { private extension NodeAppModel {
func locationMode() -> ClawdbotLocationMode { func locationMode() -> MoltbotLocationMode {
let raw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off" let raw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off"
return ClawdbotLocationMode(rawValue: raw) ?? .off return MoltbotLocationMode(rawValue: raw) ?? .off
} }
func isLocationPreciseEnabled() -> Bool { func isLocationPreciseEnabled() -> Bool {

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import Observation import Observation
import SwiftUI import SwiftUI
import WebKit import WebKit
@@ -13,7 +13,7 @@ final class ScreenController {
var urlString: String = "" var urlString: String = ""
var errorText: String? var errorText: String?
/// Callback invoked when a clawdbot:// deep link is tapped in the canvas /// Callback invoked when a moltbot:// deep link is tapped in the canvas
var onDeepLink: ((URL) -> Void)? var onDeepLink: ((URL) -> Void)?
/// Callback invoked when the user clicks an A2UI action (e.g. button) inside the canvas web UI. /// Callback invoked when the user clicks an A2UI action (e.g. button) inside the canvas web UI.
@@ -101,7 +101,7 @@ final class ScreenController {
let js = """ let js = """
(() => { (() => {
try { try {
const api = globalThis.__clawdbot; const api = globalThis.__moltbot;
if (!api) return; if (!api) return;
if (typeof api.setDebugStatusEnabled === 'function') { if (typeof api.setDebugStatusEnabled === 'function') {
api.setDebugStatusEnabled(\(enabled ? "true" : "false")); api.setDebugStatusEnabled(\(enabled ? "true" : "false"));
@@ -184,7 +184,7 @@ final class ScreenController {
func snapshotBase64( func snapshotBase64(
maxWidth: CGFloat? = nil, maxWidth: CGFloat? = nil,
format: ClawdbotCanvasSnapshotFormat, format: MoltbotCanvasSnapshotFormat,
quality: Double? = nil) async throws -> String quality: Double? = nil) async throws -> String
{ {
let config = WKSnapshotConfiguration() let config = WKSnapshotConfiguration()
@@ -229,7 +229,7 @@ final class ScreenController {
subdirectory: String) subdirectory: String)
-> URL? -> URL?
{ {
let bundle = ClawdbotKitResources.bundle let bundle = MoltbotKitResources.bundle
return bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory) return bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory)
?? bundle.url(forResource: name, withExtension: ext) ?? bundle.url(forResource: name, withExtension: ext)
} }
@@ -342,7 +342,7 @@ extension Double {
// MARK: - Navigation Delegate // MARK: - Navigation Delegate
/// Handles navigation policy to intercept clawdbot:// deep links from canvas /// Handles navigation policy to intercept moltbot:// deep links from canvas
@MainActor @MainActor
private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate { private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
weak var controller: ScreenController? weak var controller: ScreenController?
@@ -357,8 +357,8 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
return return
} }
// Intercept clawdbot:// deep links // Intercept moltbot:// deep links
if url.scheme == "clawdbot" { if url.scheme == "moltbot" {
decisionHandler(.cancel) decisionHandler(.cancel)
self.controller?.onDeepLink?(url) self.controller?.onDeepLink?(url)
return return
@@ -386,7 +386,7 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
} }
private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler { private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
static let messageName = "clawdbotCanvasA2UIAction" static let messageName = "moltbotCanvasA2UIAction"
static let legacyMessageNames = ["canvas", "a2ui", "userAction", "action"] static let legacyMessageNames = ["canvas", "a2ui", "userAction", "action"]
static let handlerNames = [messageName] + legacyMessageNames static let handlerNames = [messageName] + legacyMessageNames

View File

@@ -105,7 +105,7 @@ final class ScreenRecordService: @unchecked Sendable {
return URL(fileURLWithPath: outPath) return URL(fileURLWithPath: outPath)
} }
return FileManager().temporaryDirectory return FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-screen-record-\(UUID().uuidString).mp4") .appendingPathComponent("moltbot-screen-record-\(UUID().uuidString).mp4")
} }
private func startCapture( private func startCapture(

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import SwiftUI import SwiftUI
struct ScreenTab: View { struct ScreenTab: View {

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import SwiftUI import SwiftUI
import WebKit import WebKit

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import Network import Network
import Observation import Observation
import SwiftUI import SwiftUI
@@ -23,7 +23,7 @@ struct SettingsTab: View {
@AppStorage("talk.enabled") private var talkEnabled: Bool = false @AppStorage("talk.enabled") private var talkEnabled: Bool = false
@AppStorage("talk.button.enabled") private var talkButtonEnabled: Bool = true @AppStorage("talk.button.enabled") private var talkButtonEnabled: Bool = true
@AppStorage("camera.enabled") private var cameraEnabled: Bool = true @AppStorage("camera.enabled") private var cameraEnabled: Bool = true
@AppStorage("location.enabledMode") private var locationEnabledModeRaw: String = ClawdbotLocationMode.off.rawValue @AppStorage("location.enabledMode") private var locationEnabledModeRaw: String = MoltbotLocationMode.off.rawValue
@AppStorage("location.preciseEnabled") private var locationPreciseEnabled: Bool = true @AppStorage("location.preciseEnabled") private var locationPreciseEnabled: Bool = true
@AppStorage("screen.preventSleep") private var preventSleep: Bool = true @AppStorage("screen.preventSleep") private var preventSleep: Bool = true
@AppStorage("gateway.preferredStableID") private var preferredGatewayStableID: String = "" @AppStorage("gateway.preferredStableID") private var preferredGatewayStableID: String = ""
@@ -37,7 +37,7 @@ struct SettingsTab: View {
@State private var connectStatus = ConnectStatusStore() @State private var connectStatus = ConnectStatusStore()
@State private var connectingGatewayID: String? @State private var connectingGatewayID: String?
@State private var localIPAddress: String? @State private var localIPAddress: String?
@State private var lastLocationModeRaw: String = ClawdbotLocationMode.off.rawValue @State private var lastLocationModeRaw: String = MoltbotLocationMode.off.rawValue
@State private var gatewayToken: String = "" @State private var gatewayToken: String = ""
@State private var gatewayPassword: String = "" @State private var gatewayPassword: String = ""
@@ -197,9 +197,9 @@ struct SettingsTab: View {
Section("Location") { Section("Location") {
Picker("Location Access", selection: self.$locationEnabledModeRaw) { Picker("Location Access", selection: self.$locationEnabledModeRaw) {
Text("Off").tag(ClawdbotLocationMode.off.rawValue) Text("Off").tag(MoltbotLocationMode.off.rawValue)
Text("While Using").tag(ClawdbotLocationMode.whileUsing.rawValue) Text("While Using").tag(MoltbotLocationMode.whileUsing.rawValue)
Text("Always").tag(ClawdbotLocationMode.always.rawValue) Text("Always").tag(MoltbotLocationMode.always.rawValue)
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)
@@ -213,7 +213,7 @@ struct SettingsTab: View {
Section("Screen") { Section("Screen") {
Toggle("Prevent Sleep", isOn: self.$preventSleep) Toggle("Prevent Sleep", isOn: self.$preventSleep)
Text("Keeps the screen awake while Clawdbot is open.") Text("Keeps the screen awake while Moltbot is open.")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
@@ -261,7 +261,7 @@ struct SettingsTab: View {
.onChange(of: self.locationEnabledModeRaw) { _, newValue in .onChange(of: self.locationEnabledModeRaw) { _, newValue in
let previous = self.lastLocationModeRaw let previous = self.lastLocationModeRaw
self.lastLocationModeRaw = newValue self.lastLocationModeRaw = newValue
guard let mode = ClawdbotLocationMode(rawValue: newValue) else { return } guard let mode = MoltbotLocationMode(rawValue: newValue) else { return }
Task { Task {
let granted = await self.appModel.requestLocationPermissions(mode: mode) let granted = await self.appModel.requestLocationPermissions(mode: mode)
if !granted { if !granted {
@@ -336,8 +336,8 @@ struct SettingsTab: View {
return "iOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)" return "iOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)"
} }
private var locationMode: ClawdbotLocationMode { private var locationMode: MoltbotLocationMode {
ClawdbotLocationMode(rawValue: self.locationEnabledModeRaw) ?? .off MoltbotLocationMode(rawValue: self.locationEnabledModeRaw) ?? .off
} }
private func appVersion() -> String { private func appVersion() -> String {

View File

@@ -36,7 +36,7 @@ struct VoiceWakeWordsSettingsView: View {
Text("Wake Words") Text("Wake Words")
} footer: { } footer: {
Text( Text(
"Clawdbot reacts when any trigger appears in a transcription. " "Moltbot reacts when any trigger appears in a transcription. "
+ "Keep them short to avoid false positives.") + "Keep them short to avoid false positives.")
} }
} }

View File

@@ -1,6 +1,6 @@
import AVFAudio import AVFAudio
import ClawdbotKit import MoltbotKit
import ClawdbotProtocol import MoltbotProtocol
import Foundation import Foundation
import Observation import Observation
import OSLog import OSLog

View File

@@ -1,6 +1,6 @@
import SwiftUI import SwiftUI
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct AppCoverageTests { @Suite struct AppCoverageTests {
@Test @MainActor func nodeAppModelUpdatesBackgroundedState() { @Test @MainActor func nodeAppModelUpdatesBackgroundedState() {

View File

@@ -1,5 +1,5 @@
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct CameraControllerClampTests { @Suite struct CameraControllerClampTests {
@Test func clampQualityDefaultsAndBounds() { @Test func clampQualityDefaultsAndBounds() {

View File

@@ -1,5 +1,5 @@
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct CameraControllerErrorTests { @Suite struct CameraControllerErrorTests {
@Test func errorDescriptionsAreStable() { @Test func errorDescriptionsAreStable() {

View File

@@ -1,15 +1,15 @@
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
import Testing import Testing
@Suite struct DeepLinkParserTests { @Suite struct DeepLinkParserTests {
@Test func parseRejectsUnknownHost() { @Test func parseRejectsUnknownHost() {
let url = URL(string: "clawdbot://nope?message=hi")! let url = URL(string: "moltbot://nope?message=hi")!
#expect(DeepLinkParser.parse(url) == nil) #expect(DeepLinkParser.parse(url) == nil)
} }
@Test func parseHostIsCaseInsensitive() { @Test func parseHostIsCaseInsensitive() {
let url = URL(string: "clawdbot://AGENT?message=Hello")! let url = URL(string: "moltbot://AGENT?message=Hello")!
#expect(DeepLinkParser.parse(url) == .agent(.init( #expect(DeepLinkParser.parse(url) == .agent(.init(
message: "Hello", message: "Hello",
sessionKey: nil, sessionKey: nil,
@@ -21,19 +21,19 @@ import Testing
key: nil))) key: nil)))
} }
@Test func parseRejectsNonClawdbotScheme() { @Test func parseRejectsNonMoltbotScheme() {
let url = URL(string: "https://example.com/agent?message=hi")! let url = URL(string: "https://example.com/agent?message=hi")!
#expect(DeepLinkParser.parse(url) == nil) #expect(DeepLinkParser.parse(url) == nil)
} }
@Test func parseRejectsEmptyMessage() { @Test func parseRejectsEmptyMessage() {
let url = URL(string: "clawdbot://agent?message=%20%20%0A")! let url = URL(string: "moltbot://agent?message=%20%20%0A")!
#expect(DeepLinkParser.parse(url) == nil) #expect(DeepLinkParser.parse(url) == nil)
} }
@Test func parseAgentLinkParsesCommonFields() { @Test func parseAgentLinkParsesCommonFields() {
let url = let url =
URL(string: "clawdbot://agent?message=Hello&deliver=1&sessionKey=node-test&thinking=low&timeoutSeconds=30")! URL(string: "moltbot://agent?message=Hello&deliver=1&sessionKey=node-test&thinking=low&timeoutSeconds=30")!
#expect( #expect(
DeepLinkParser.parse(url) == .agent( DeepLinkParser.parse(url) == .agent(
.init( .init(
@@ -50,7 +50,7 @@ import Testing
@Test func parseAgentLinkParsesTargetRoutingFields() { @Test func parseAgentLinkParsesTargetRoutingFields() {
let url = let url =
URL( URL(
string: "clawdbot://agent?message=Hello%20World&deliver=1&to=%2B15551234567&channel=whatsapp&key=secret")! string: "moltbot://agent?message=Hello%20World&deliver=1&to=%2B15551234567&channel=whatsapp&key=secret")!
#expect( #expect(
DeepLinkParser.parse(url) == .agent( DeepLinkParser.parse(url) == .agent(
.init( .init(
@@ -65,7 +65,7 @@ import Testing
} }
@Test func parseRejectsNegativeTimeoutSeconds() { @Test func parseRejectsNegativeTimeoutSeconds() {
let url = URL(string: "clawdbot://agent?message=Hello&timeoutSeconds=-1")! let url = URL(string: "moltbot://agent?message=Hello&timeoutSeconds=-1")!
#expect(DeepLinkParser.parse(url) == .agent(.init( #expect(DeepLinkParser.parse(url) == .agent(.init(
message: "Hello", message: "Hello",
sessionKey: nil, sessionKey: nil,

View File

@@ -1,8 +1,8 @@
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
import Testing import Testing
import UIKit import UIKit
@testable import Clawdbot @testable import Moltbot
private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T { private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
@@ -49,31 +49,31 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
"node.instanceId": "ios-test", "node.instanceId": "ios-test",
"node.displayName": "Test Node", "node.displayName": "Test Node",
"camera.enabled": true, "camera.enabled": true,
"location.enabledMode": ClawdbotLocationMode.always.rawValue, "location.enabledMode": MoltbotLocationMode.always.rawValue,
VoiceWakePreferences.enabledKey: true, VoiceWakePreferences.enabledKey: true,
]) { ]) {
let appModel = NodeAppModel() let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false) let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
let caps = Set(controller._test_currentCaps()) let caps = Set(controller._test_currentCaps())
#expect(caps.contains(ClawdbotCapability.canvas.rawValue)) #expect(caps.contains(MoltbotCapability.canvas.rawValue))
#expect(caps.contains(ClawdbotCapability.screen.rawValue)) #expect(caps.contains(MoltbotCapability.screen.rawValue))
#expect(caps.contains(ClawdbotCapability.camera.rawValue)) #expect(caps.contains(MoltbotCapability.camera.rawValue))
#expect(caps.contains(ClawdbotCapability.location.rawValue)) #expect(caps.contains(MoltbotCapability.location.rawValue))
#expect(caps.contains(ClawdbotCapability.voiceWake.rawValue)) #expect(caps.contains(MoltbotCapability.voiceWake.rawValue))
} }
} }
@Test @MainActor func currentCommandsIncludeLocationWhenEnabled() { @Test @MainActor func currentCommandsIncludeLocationWhenEnabled() {
withUserDefaults([ withUserDefaults([
"node.instanceId": "ios-test", "node.instanceId": "ios-test",
"location.enabledMode": ClawdbotLocationMode.whileUsing.rawValue, "location.enabledMode": MoltbotLocationMode.whileUsing.rawValue,
]) { ]) {
let appModel = NodeAppModel() let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false) let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
let commands = Set(controller._test_currentCommands()) let commands = Set(controller._test_currentCommands())
#expect(commands.contains(ClawdbotLocationCommand.get.rawValue)) #expect(commands.contains(MoltbotLocationCommand.get.rawValue))
} }
} }
} }

View File

@@ -1,5 +1,5 @@
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite(.serialized) struct GatewayDiscoveryModelTests { @Suite(.serialized) struct GatewayDiscoveryModelTests {
@Test @MainActor func debugLoggingCapturesLifecycleAndResets() { @Test @MainActor func debugLoggingCapturesLifecycleAndResets() {

View File

@@ -1,17 +1,17 @@
import ClawdbotKit import MoltbotKit
import Network import Network
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct GatewayEndpointIDTests { @Suite struct GatewayEndpointIDTests {
@Test func stableIDForServiceDecodesAndNormalizesName() { @Test func stableIDForServiceDecodesAndNormalizesName() {
let endpoint = NWEndpoint.service( let endpoint = NWEndpoint.service(
name: "Clawdbot\\032Gateway \\032 Node\n", name: "Moltbot\\032Gateway \\032 Node\n",
type: "_clawdbot-gw._tcp", type: "_moltbot-gw._tcp",
domain: "local.", domain: "local.",
interface: nil) interface: nil)
#expect(GatewayEndpointID.stableID(endpoint) == "_clawdbot-gw._tcp|local.|Clawdbot Gateway Node") #expect(GatewayEndpointID.stableID(endpoint) == "_moltbot-gw._tcp|local.|Moltbot Gateway Node")
} }
@Test func stableIDForNonServiceUsesEndpointDescription() { @Test func stableIDForNonServiceUsesEndpointDescription() {
@@ -21,8 +21,8 @@ import Testing
@Test func prettyDescriptionDecodesBonjourEscapes() { @Test func prettyDescriptionDecodesBonjourEscapes() {
let endpoint = NWEndpoint.service( let endpoint = NWEndpoint.service(
name: "Clawdbot\\032Gateway", name: "Moltbot\\032Gateway",
type: "_clawdbot-gw._tcp", type: "_moltbot-gw._tcp",
domain: "local.", domain: "local.",
interface: nil) interface: nil)

View File

@@ -1,6 +1,6 @@
import Foundation import Foundation
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
private struct KeychainEntry: Hashable { private struct KeychainEntry: Hashable {
let service: String let service: String

View File

@@ -1,6 +1,6 @@
import ClawdbotKit import MoltbotKit
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct IOSGatewayChatTransportTests { @Suite struct IOSGatewayChatTransportTests {
@Test func requestsFailFastWhenGatewayNotConnected() async { @Test func requestsFailFastWhenGatewayNotConnected() async {

View File

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>ClawdbotTests</string> <string>MoltbotTests</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

@@ -1,6 +1,6 @@
import Foundation import Foundation
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct KeychainStoreTests { @Suite struct KeychainStoreTests {
@Test func saveLoadUpdateDeleteRoundTrip() { @Test func saveLoadUpdateDeleteRoundTrip() {

View File

@@ -1,8 +1,8 @@
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
import Testing import Testing
import UIKit import UIKit
@testable import Clawdbot @testable import Moltbot
private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T { private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
@@ -32,7 +32,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Suite(.serialized) struct NodeAppModelInvokeTests { @Suite(.serialized) struct NodeAppModelInvokeTests {
@Test @MainActor func decodeParamsFailsWithoutJSON() { @Test @MainActor func decodeParamsFailsWithoutJSON() {
#expect(throws: Error.self) { #expect(throws: Error.self) {
_ = try NodeAppModel._test_decodeParams(ClawdbotCanvasNavigateParams.self, from: nil) _ = try NodeAppModel._test_decodeParams(MoltbotCanvasNavigateParams.self, from: nil)
} }
} }
@@ -48,7 +48,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
let appModel = NodeAppModel() let appModel = NodeAppModel()
appModel.setScenePhase(.background) appModel.setScenePhase(.background)
let req = BridgeInvokeRequest(id: "bg", command: ClawdbotCanvasCommand.present.rawValue) let req = BridgeInvokeRequest(id: "bg", command: MoltbotCanvasCommand.present.rawValue)
let res = await appModel._test_handleInvoke(req) let res = await appModel._test_handleInvoke(req)
#expect(res.ok == false) #expect(res.ok == false)
#expect(res.error?.code == .backgroundUnavailable) #expect(res.error?.code == .backgroundUnavailable)
@@ -56,7 +56,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleInvokeRejectsCameraWhenDisabled() async { @Test @MainActor func handleInvokeRejectsCameraWhenDisabled() async {
let appModel = NodeAppModel() let appModel = NodeAppModel()
let req = BridgeInvokeRequest(id: "cam", command: ClawdbotCameraCommand.snap.rawValue) let req = BridgeInvokeRequest(id: "cam", command: MoltbotCameraCommand.snap.rawValue)
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
let key = "camera.enabled" let key = "camera.enabled"
@@ -78,13 +78,13 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleInvokeRejectsInvalidScreenFormat() async { @Test @MainActor func handleInvokeRejectsInvalidScreenFormat() async {
let appModel = NodeAppModel() let appModel = NodeAppModel()
let params = ClawdbotScreenRecordParams(format: "gif") let params = MoltbotScreenRecordParams(format: "gif")
let data = try? JSONEncoder().encode(params) let data = try? JSONEncoder().encode(params)
let json = data.flatMap { String(data: $0, encoding: .utf8) } let json = data.flatMap { String(data: $0, encoding: .utf8) }
let req = BridgeInvokeRequest( let req = BridgeInvokeRequest(
id: "screen", id: "screen",
command: ClawdbotScreenCommand.record.rawValue, command: MoltbotScreenCommand.record.rawValue,
paramsJSON: json) paramsJSON: json)
let res = await appModel._test_handleInvoke(req) let res = await appModel._test_handleInvoke(req)
@@ -96,28 +96,28 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
let appModel = NodeAppModel() let appModel = NodeAppModel()
appModel.screen.navigate(to: "http://example.com") appModel.screen.navigate(to: "http://example.com")
let present = BridgeInvokeRequest(id: "present", command: ClawdbotCanvasCommand.present.rawValue) let present = BridgeInvokeRequest(id: "present", command: MoltbotCanvasCommand.present.rawValue)
let presentRes = await appModel._test_handleInvoke(present) let presentRes = await appModel._test_handleInvoke(present)
#expect(presentRes.ok == true) #expect(presentRes.ok == true)
#expect(appModel.screen.urlString.isEmpty) #expect(appModel.screen.urlString.isEmpty)
let navigateParams = ClawdbotCanvasNavigateParams(url: "http://localhost:18789/") let navigateParams = MoltbotCanvasNavigateParams(url: "http://localhost:18789/")
let navData = try JSONEncoder().encode(navigateParams) let navData = try JSONEncoder().encode(navigateParams)
let navJSON = String(decoding: navData, as: UTF8.self) let navJSON = String(decoding: navData, as: UTF8.self)
let navigate = BridgeInvokeRequest( let navigate = BridgeInvokeRequest(
id: "nav", id: "nav",
command: ClawdbotCanvasCommand.navigate.rawValue, command: MoltbotCanvasCommand.navigate.rawValue,
paramsJSON: navJSON) paramsJSON: navJSON)
let navRes = await appModel._test_handleInvoke(navigate) let navRes = await appModel._test_handleInvoke(navigate)
#expect(navRes.ok == true) #expect(navRes.ok == true)
#expect(appModel.screen.urlString == "http://localhost:18789/") #expect(appModel.screen.urlString == "http://localhost:18789/")
let evalParams = ClawdbotCanvasEvalParams(javaScript: "1+1") let evalParams = MoltbotCanvasEvalParams(javaScript: "1+1")
let evalData = try JSONEncoder().encode(evalParams) let evalData = try JSONEncoder().encode(evalParams)
let evalJSON = String(decoding: evalData, as: UTF8.self) let evalJSON = String(decoding: evalData, as: UTF8.self)
let eval = BridgeInvokeRequest( let eval = BridgeInvokeRequest(
id: "eval", id: "eval",
command: ClawdbotCanvasCommand.evalJS.rawValue, command: MoltbotCanvasCommand.evalJS.rawValue,
paramsJSON: evalJSON) paramsJSON: evalJSON)
let evalRes = await appModel._test_handleInvoke(eval) let evalRes = await appModel._test_handleInvoke(eval)
#expect(evalRes.ok == true) #expect(evalRes.ok == true)
@@ -129,18 +129,18 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleInvokeA2UICommandsFailWhenHostMissing() async throws { @Test @MainActor func handleInvokeA2UICommandsFailWhenHostMissing() async throws {
let appModel = NodeAppModel() let appModel = NodeAppModel()
let reset = BridgeInvokeRequest(id: "reset", command: ClawdbotCanvasA2UICommand.reset.rawValue) let reset = BridgeInvokeRequest(id: "reset", command: MoltbotCanvasA2UICommand.reset.rawValue)
let resetRes = await appModel._test_handleInvoke(reset) let resetRes = await appModel._test_handleInvoke(reset)
#expect(resetRes.ok == false) #expect(resetRes.ok == false)
#expect(resetRes.error?.message.contains("A2UI_HOST_NOT_CONFIGURED") == true) #expect(resetRes.error?.message.contains("A2UI_HOST_NOT_CONFIGURED") == true)
let jsonl = "{\"beginRendering\":{}}" let jsonl = "{\"beginRendering\":{}}"
let pushParams = ClawdbotCanvasA2UIPushJSONLParams(jsonl: jsonl) let pushParams = MoltbotCanvasA2UIPushJSONLParams(jsonl: jsonl)
let pushData = try JSONEncoder().encode(pushParams) let pushData = try JSONEncoder().encode(pushParams)
let pushJSON = String(decoding: pushData, as: UTF8.self) let pushJSON = String(decoding: pushData, as: UTF8.self)
let push = BridgeInvokeRequest( let push = BridgeInvokeRequest(
id: "push", id: "push",
command: ClawdbotCanvasA2UICommand.pushJSONL.rawValue, command: MoltbotCanvasA2UICommand.pushJSONL.rawValue,
paramsJSON: pushJSON) paramsJSON: pushJSON)
let pushRes = await appModel._test_handleInvoke(push) let pushRes = await appModel._test_handleInvoke(push)
#expect(pushRes.ok == false) #expect(pushRes.ok == false)
@@ -157,7 +157,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleDeepLinkSetsErrorWhenNotConnected() async { @Test @MainActor func handleDeepLinkSetsErrorWhenNotConnected() async {
let appModel = NodeAppModel() let appModel = NodeAppModel()
let url = URL(string: "clawdbot://agent?message=hello")! let url = URL(string: "moltbot://agent?message=hello")!
await appModel.handleDeepLink(url: url) await appModel.handleDeepLink(url: url)
#expect(appModel.screen.errorText?.contains("Gateway not connected") == true) #expect(appModel.screen.errorText?.contains("Gateway not connected") == true)
} }
@@ -165,7 +165,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleDeepLinkRejectsOversizedMessage() async { @Test @MainActor func handleDeepLinkRejectsOversizedMessage() async {
let appModel = NodeAppModel() let appModel = NodeAppModel()
let msg = String(repeating: "a", count: 20001) let msg = String(repeating: "a", count: 20001)
let url = URL(string: "clawdbot://agent?message=\(msg)")! let url = URL(string: "moltbot://agent?message=\(msg)")!
await appModel.handleDeepLink(url: url) await appModel.handleDeepLink(url: url)
#expect(appModel.screen.errorText?.contains("Deep link too large") == true) #expect(appModel.screen.errorText?.contains("Deep link too large") == true)
} }

View File

@@ -1,6 +1,6 @@
import Testing import Testing
import WebKit import WebKit
@testable import Clawdbot @testable import Moltbot
@Suite struct ScreenControllerTests { @Suite struct ScreenControllerTests {
@Test @MainActor func canvasModeConfiguresWebViewForTouch() { @Test @MainActor func canvasModeConfiguresWebViewForTouch() {

View File

@@ -1,5 +1,5 @@
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite(.serialized) struct ScreenRecordServiceTests { @Suite(.serialized) struct ScreenRecordServiceTests {
@Test func clampDefaultsAndBounds() { @Test func clampDefaultsAndBounds() {

View File

@@ -1,5 +1,5 @@
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct SettingsNetworkingHelpersTests { @Suite struct SettingsNetworkingHelpersTests {
@Test func parseHostPortParsesIPv4() { @Test func parseHostPortParsesIPv4() {

View File

@@ -1,8 +1,8 @@
import ClawdbotKit import MoltbotKit
import SwiftUI import SwiftUI
import Testing import Testing
import UIKit import UIKit
@testable import Clawdbot @testable import Moltbot
@Suite struct SwiftUIRenderSmokeTests { @Suite struct SwiftUIRenderSmokeTests {
@MainActor private static func host(_ view: some View) -> UIWindow { @MainActor private static func host(_ view: some View) -> UIWindow {
@@ -75,7 +75,7 @@ import UIKit
} }
@Test @MainActor func voiceWakeToastBuildsAViewHierarchy() { @Test @MainActor func voiceWakeToastBuildsAViewHierarchy() {
let root = VoiceWakeToast(command: "clawdbot: do something") let root = VoiceWakeToast(command: "moltbot: do something")
_ = Self.host(root) _ = Self.host(root)
} }
} }

View File

@@ -1,6 +1,6 @@
import Foundation import Foundation
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct VoiceWakeGatewaySyncTests { @Suite struct VoiceWakeGatewaySyncTests {
@Test func decodeGatewayTriggersFromJSONSanitizes() { @Test func decodeGatewayTriggersFromJSONSanitizes() {

View File

@@ -1,7 +1,7 @@
import Foundation import Foundation
import SwabbleKit import SwabbleKit
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct VoiceWakeManagerExtractCommandTests { @Suite struct VoiceWakeManagerExtractCommandTests {
@Test func extractCommandReturnsNilWhenNoTriggerFound() { @Test func extractCommandReturnsNilWhenNoTriggerFound() {

View File

@@ -1,7 +1,7 @@
import Foundation import Foundation
import SwabbleKit import SwabbleKit
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite(.serialized) struct VoiceWakeManagerStateTests { @Suite(.serialized) struct VoiceWakeManagerStateTests {
@Test @MainActor func suspendAndResumeCycleUpdatesState() async { @Test @MainActor func suspendAndResumeCycleUpdatesState() async {

View File

@@ -1,6 +1,6 @@
import Foundation import Foundation
import Testing import Testing
@testable import Clawdbot @testable import Moltbot
@Suite struct VoiceWakePreferencesTests { @Suite struct VoiceWakePreferencesTests {
@Test func sanitizeTriggerWordsTrimsAndDropsEmpty() { @Test func sanitizeTriggerWordsTrimsAndDropsEmpty() {

View File

@@ -72,8 +72,8 @@ platform :ios do
UI.user_error!("Missing IOS_DEVELOPMENT_TEAM (Apple Team ID). Add it to fastlane/.env or export it in your shell.") if team_id.nil? || team_id.strip.empty? UI.user_error!("Missing IOS_DEVELOPMENT_TEAM (Apple Team ID). Add it to fastlane/.env or export it in your shell.") if team_id.nil? || team_id.strip.empty?
build_app( build_app(
project: "Clawdbot.xcodeproj", project: "Moltbot.xcodeproj",
scheme: "Clawdbot", scheme: "Moltbot",
export_method: "app-store", export_method: "app-store",
clean: true, clean: true,
xcargs: "DEVELOPMENT_TEAM=#{team_id} -allowProvisioningUpdates", xcargs: "DEVELOPMENT_TEAM=#{team_id} -allowProvisioningUpdates",

View File

@@ -1,4 +1,4 @@
# fastlane setup (Clawdbot iOS) # fastlane setup (Moltbot iOS)
Install: Install:

View File

@@ -1,4 +1,4 @@
name: Clawdbot name: Moltbot
options: options:
bundleIdPrefix: com.clawdbot bundleIdPrefix: com.clawdbot
deploymentTarget: deploymentTarget:
@@ -10,33 +10,33 @@ settings:
SWIFT_VERSION: "6.0" SWIFT_VERSION: "6.0"
packages: packages:
ClawdbotKit: MoltbotKit:
path: ../shared/ClawdbotKit path: ../shared/ClawdbotKit
Swabble: Swabble:
path: ../../Swabble path: ../../Swabble
schemes: schemes:
Clawdbot: Moltbot:
shared: true shared: true
build: build:
targets: targets:
Clawdbot: all Moltbot: all
test: test:
targets: targets:
- ClawdbotTests - MoltbotTests
targets: targets:
Clawdbot: Moltbot:
type: application type: application
platform: iOS platform: iOS
sources: sources:
- path: Sources - path: Sources
dependencies: dependencies:
- package: ClawdbotKit - package: MoltbotKit
- package: ClawdbotKit - package: MoltbotKit
product: ClawdbotChatUI product: MoltbotChatUI
- package: ClawdbotKit - package: MoltbotKit
product: ClawdbotProtocol product: MoltbotProtocol
- package: Swabble - package: Swabble
product: SwabbleKit product: SwabbleKit
- sdk: AppIntents.framework - sdk: AppIntents.framework
@@ -79,7 +79,7 @@ targets:
info: info:
path: Sources/Info.plist path: Sources/Info.plist
properties: properties:
CFBundleDisplayName: Clawdbot CFBundleDisplayName: Moltbot
CFBundleIconName: AppIcon CFBundleIconName: AppIcon
CFBundleShortVersionString: "2026.1.26" CFBundleShortVersionString: "2026.1.26"
CFBundleVersion: "20260126" CFBundleVersion: "20260126"
@@ -88,16 +88,16 @@ targets:
UIApplicationSupportsMultipleScenes: false UIApplicationSupportsMultipleScenes: false
UIBackgroundModes: UIBackgroundModes:
- audio - audio
NSLocalNetworkUsageDescription: Clawdbot discovers and connects to your Clawdbot gateway on the local network. NSLocalNetworkUsageDescription: Moltbot discovers and connects to your Moltbot gateway on the local network.
NSAppTransportSecurity: NSAppTransportSecurity:
NSAllowsArbitraryLoadsInWebContent: true NSAllowsArbitraryLoadsInWebContent: true
NSBonjourServices: NSBonjourServices:
- _clawdbot-gw._tcp - _moltbot-gw._tcp
NSCameraUsageDescription: Clawdbot can capture photos or short video clips when requested via the gateway. NSCameraUsageDescription: Moltbot can capture photos or short video clips when requested via the gateway.
NSLocationWhenInUseUsageDescription: Clawdbot uses your location when you allow location sharing. NSLocationWhenInUseUsageDescription: Moltbot uses your location when you allow location sharing.
NSLocationAlwaysAndWhenInUseUsageDescription: Clawdbot can share your location in the background when you enable Always. NSLocationAlwaysAndWhenInUseUsageDescription: Moltbot can share your location in the background when you enable Always.
NSMicrophoneUsageDescription: Clawdbot needs microphone access for voice wake. NSMicrophoneUsageDescription: Moltbot needs microphone access for voice wake.
NSSpeechRecognitionUsageDescription: Clawdbot uses on-device speech recognition for voice wake. NSSpeechRecognitionUsageDescription: Moltbot uses on-device speech recognition for voice wake.
UISupportedInterfaceOrientations: UISupportedInterfaceOrientations:
- UIInterfaceOrientationPortrait - UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationPortraitUpsideDown
@@ -109,13 +109,13 @@ targets:
- UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationLandscapeRight
ClawdbotTests: MoltbotTests:
type: bundle.unit-test type: bundle.unit-test
platform: iOS platform: iOS
sources: sources:
- path: Tests - path: Tests
dependencies: dependencies:
- target: Clawdbot - target: Moltbot
- package: Swabble - package: Swabble
product: SwabbleKit product: SwabbleKit
- sdk: AppIntents.framework - sdk: AppIntents.framework
@@ -124,11 +124,11 @@ targets:
PRODUCT_BUNDLE_IDENTIFIER: com.clawdbot.ios.tests PRODUCT_BUNDLE_IDENTIFIER: com.clawdbot.ios.tests
SWIFT_VERSION: "6.0" SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete SWIFT_STRICT_CONCURRENCY: complete
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Clawdbot.app/Clawdbot" TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Moltbot.app/Moltbot"
BUNDLE_LOADER: "$(TEST_HOST)" BUNDLE_LOADER: "$(TEST_HOST)"
info: info:
path: Tests/Info.plist path: Tests/Info.plist
properties: properties:
CFBundleDisplayName: ClawdbotTests CFBundleDisplayName: MoltbotTests
CFBundleShortVersionString: "2026.1.26" CFBundleShortVersionString: "2026.1.26"
CFBundleVersion: "20260126" CFBundleVersion: "20260126"

View File

@@ -6,8 +6,8 @@
{ {
"layers" : [ "layers" : [
{ {
"image-name" : "clawdbot-mac.png", "image-name" : "moltbot-mac.png",
"name" : "clawdbot-mac", "name" : "moltbot-mac",
"position" : { "position" : {
"scale" : 1.07, "scale" : 1.07,
"translation-in-points" : [ "translation-in-points" : [

View File

@@ -1,18 +1,18 @@
// swift-tools-version: 6.2 // swift-tools-version: 6.2
// Package manifest for the Clawdbot macOS companion (menu bar app + IPC library). // Package manifest for the Moltbot macOS companion (menu bar app + IPC library).
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "Clawdbot", name: "Moltbot",
platforms: [ platforms: [
.macOS(.v15), .macOS(.v15),
], ],
products: [ products: [
.library(name: "ClawdbotIPC", targets: ["ClawdbotIPC"]), .library(name: "MoltbotIPC", targets: ["MoltbotIPC"]),
.library(name: "ClawdbotDiscovery", targets: ["ClawdbotDiscovery"]), .library(name: "MoltbotDiscovery", targets: ["MoltbotDiscovery"]),
.executable(name: "Clawdbot", targets: ["Clawdbot"]), .executable(name: "Moltbot", targets: ["Moltbot"]),
.executable(name: "clawdbot-mac", targets: ["ClawdbotMacCLI"]), .executable(name: "moltbot-mac", targets: ["MoltbotMacCLI"]),
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/orchetect/MenuBarExtraAccess", exact: "1.2.2"), .package(url: "https://github.com/orchetect/MenuBarExtraAccess", exact: "1.2.2"),
@@ -25,28 +25,28 @@ let package = Package(
], ],
targets: [ targets: [
.target( .target(
name: "ClawdbotIPC", name: "MoltbotIPC",
dependencies: [], dependencies: [],
swiftSettings: [ swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"), .enableUpcomingFeature("StrictConcurrency"),
]), ]),
.target( .target(
name: "ClawdbotDiscovery", name: "MoltbotDiscovery",
dependencies: [ dependencies: [
.product(name: "ClawdbotKit", package: "ClawdbotKit"), .product(name: "MoltbotKit", package: "MoltbotKit"),
], ],
path: "Sources/ClawdbotDiscovery", path: "Sources/MoltbotDiscovery",
swiftSettings: [ swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"), .enableUpcomingFeature("StrictConcurrency"),
]), ]),
.executableTarget( .executableTarget(
name: "Clawdbot", name: "Moltbot",
dependencies: [ dependencies: [
"ClawdbotIPC", "MoltbotIPC",
"ClawdbotDiscovery", "MoltbotDiscovery",
.product(name: "ClawdbotKit", package: "ClawdbotKit"), .product(name: "MoltbotKit", package: "MoltbotKit"),
.product(name: "ClawdbotChatUI", package: "ClawdbotKit"), .product(name: "MoltbotChatUI", package: "MoltbotKit"),
.product(name: "ClawdbotProtocol", package: "ClawdbotKit"), .product(name: "MoltbotProtocol", package: "MoltbotKit"),
.product(name: "SwabbleKit", package: "swabble"), .product(name: "SwabbleKit", package: "swabble"),
.product(name: "MenuBarExtraAccess", package: "MenuBarExtraAccess"), .product(name: "MenuBarExtraAccess", package: "MenuBarExtraAccess"),
.product(name: "Subprocess", package: "swift-subprocess"), .product(name: "Subprocess", package: "swift-subprocess"),
@@ -59,30 +59,30 @@ let package = Package(
"Resources/Info.plist", "Resources/Info.plist",
], ],
resources: [ resources: [
.copy("Resources/Clawdbot.icns"), .copy("Resources/Moltbot.icns"),
.copy("Resources/DeviceModels"), .copy("Resources/DeviceModels"),
], ],
swiftSettings: [ swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"), .enableUpcomingFeature("StrictConcurrency"),
]), ]),
.executableTarget( .executableTarget(
name: "ClawdbotMacCLI", name: "MoltbotMacCLI",
dependencies: [ dependencies: [
"ClawdbotDiscovery", "MoltbotDiscovery",
.product(name: "ClawdbotKit", package: "ClawdbotKit"), .product(name: "MoltbotKit", package: "MoltbotKit"),
.product(name: "ClawdbotProtocol", package: "ClawdbotKit"), .product(name: "MoltbotProtocol", package: "MoltbotKit"),
], ],
path: "Sources/ClawdbotMacCLI", path: "Sources/MoltbotMacCLI",
swiftSettings: [ swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"), .enableUpcomingFeature("StrictConcurrency"),
]), ]),
.testTarget( .testTarget(
name: "ClawdbotIPCTests", name: "MoltbotIPCTests",
dependencies: [ dependencies: [
"ClawdbotIPC", "MoltbotIPC",
"Clawdbot", "Moltbot",
"ClawdbotDiscovery", "MoltbotDiscovery",
.product(name: "ClawdbotProtocol", package: "ClawdbotKit"), .product(name: "MoltbotProtocol", package: "MoltbotKit"),
.product(name: "SwabbleKit", package: "swabble"), .product(name: "SwabbleKit", package: "swabble"),
], ],
swiftSettings: [ swiftSettings: [

View File

@@ -10,7 +10,7 @@ struct AboutSettings: View {
VStack(spacing: 8) { VStack(spacing: 8) {
let appIcon = NSApplication.shared.applicationIconImage ?? CritterIconRenderer.makeIcon(blink: 0) let appIcon = NSApplication.shared.applicationIconImage ?? CritterIconRenderer.makeIcon(blink: 0)
Button { Button {
if let url = URL(string: "https://github.com/clawdbot/clawdbot") { if let url = URL(string: "https://github.com/moltbot/moltbot") {
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
} label: { } label: {
@@ -29,7 +29,7 @@ struct AboutSettings: View {
} }
VStack(spacing: 3) { VStack(spacing: 3) {
Text("Clawdbot") Text("Moltbot")
.font(.title3.bold()) .font(.title3.bold())
Text("Version \(self.versionString)") Text("Version \(self.versionString)")
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
@@ -49,7 +49,7 @@ struct AboutSettings: View {
AboutLinkRow( AboutLinkRow(
icon: "chevron.left.slash.chevron.right", icon: "chevron.left.slash.chevron.right",
title: "GitHub", title: "GitHub",
url: "https://github.com/clawdbot/clawdbot") url: "https://github.com/moltbot/moltbot")
AboutLinkRow(icon: "globe", title: "Website", url: "https://steipete.me") AboutLinkRow(icon: "globe", title: "Website", url: "https://steipete.me")
AboutLinkRow(icon: "bird", title: "Twitter", url: "https://twitter.com/steipete") AboutLinkRow(icon: "bird", title: "Twitter", url: "https://twitter.com/steipete")
AboutLinkRow(icon: "envelope", title: "Email", url: "mailto:peter@steipete.me") AboutLinkRow(icon: "envelope", title: "Email", url: "mailto:peter@steipete.me")
@@ -108,7 +108,7 @@ struct AboutSettings: View {
} }
private var buildTimestamp: String? { private var buildTimestamp: String? {
guard let raw = Bundle.main.object(forInfoDictionaryKey: "ClawdbotBuildTimestamp") as? String guard let raw = Bundle.main.object(forInfoDictionaryKey: "MoltbotBuildTimestamp") as? String
else { return nil } else { return nil }
let parser = ISO8601DateFormatter() let parser = ISO8601DateFormatter()
parser.formatOptions = [.withInternetDateTime] parser.formatOptions = [.withInternetDateTime]
@@ -122,7 +122,7 @@ struct AboutSettings: View {
} }
private var gitCommit: String { private var gitCommit: String {
Bundle.main.object(forInfoDictionaryKey: "ClawdbotGitCommit") as? String ?? "unknown" Bundle.main.object(forInfoDictionaryKey: "MoltbotGitCommit") as? String ?? "unknown"
} }
private var bundleID: String { private var bundleID: String {

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol import MoltbotProtocol
import SwiftUI import SwiftUI
@MainActor @MainActor
@@ -81,7 +81,7 @@ private struct EventRow: View {
return f.string(from: date) return f.string(from: date)
} }
private func prettyJSON(_ dict: [String: ClawdbotProtocol.AnyCodable]) -> String? { private func prettyJSON(_ dict: [String: MoltbotProtocol.AnyCodable]) -> String? {
let normalized = dict.mapValues { $0.value } let normalized = dict.mapValues { $0.value }
guard JSONSerialization.isValidJSONObject(normalized), guard JSONSerialization.isValidJSONObject(normalized),
let data = try? JSONSerialization.data(withJSONObject: normalized, options: [.prettyPrinted]), let data = try? JSONSerialization.data(withJSONObject: normalized, options: [.prettyPrinted]),
@@ -99,8 +99,8 @@ struct AgentEventsWindow_Previews: PreviewProvider {
stream: "tool", stream: "tool",
ts: Date().timeIntervalSince1970 * 1000, ts: Date().timeIntervalSince1970 * 1000,
data: [ data: [
"phase": ClawdbotProtocol.AnyCodable("start"), "phase": MoltbotProtocol.AnyCodable("start"),
"name": ClawdbotProtocol.AnyCodable("bash"), "name": MoltbotProtocol.AnyCodable("bash"),
], ],
summary: nil) summary: nil)
AgentEventStore.shared.append(sample) AgentEventStore.shared.append(sample)

View File

@@ -34,7 +34,7 @@ enum AgentWorkspace {
static func resolveWorkspaceURL(from userInput: String?) -> URL { static func resolveWorkspaceURL(from userInput: String?) -> URL {
let trimmed = userInput?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let trimmed = userInput?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if trimmed.isEmpty { return ClawdbotConfigFile.defaultWorkspaceURL() } if trimmed.isEmpty { return MoltbotConfigFile.defaultWorkspaceURL() }
let expanded = (trimmed as NSString).expandingTildeInPath let expanded = (trimmed as NSString).expandingTildeInPath
return URL(fileURLWithPath: expanded, isDirectory: true) return URL(fileURLWithPath: expanded, isDirectory: true)
} }
@@ -154,7 +154,7 @@ enum AgentWorkspace {
static func defaultTemplate() -> String { static func defaultTemplate() -> String {
let fallback = """ let fallback = """
# AGENTS.md - Clawdbot Workspace # AGENTS.md - Moltbot Workspace
This folder is the assistant's working directory. This folder is the assistant's working directory.
@@ -265,7 +265,7 @@ enum AgentWorkspace {
- Timezone (optional) - Timezone (optional)
- Notes - Notes
3) ~/.clawdbot/clawdbot.json 3) ~/.clawdbot/moltbot.json
Set identity.name, identity.theme, identity.emoji to match IDENTITY.md. Set identity.name, identity.theme, identity.emoji to match IDENTITY.md.
## Cleanup ## Cleanup

View File

@@ -6,7 +6,7 @@ import SwiftUI
struct AnthropicAuthControls: View { struct AnthropicAuthControls: View {
let connectionMode: AppState.ConnectionMode let connectionMode: AppState.ConnectionMode
@State private var oauthStatus: ClawdbotOAuthStore.AnthropicOAuthStatus = ClawdbotOAuthStore.anthropicOAuthStatus() @State private var oauthStatus: MoltbotOAuthStore.AnthropicOAuthStatus = MoltbotOAuthStore.anthropicOAuthStatus()
@State private var pkce: AnthropicOAuth.PKCE? @State private var pkce: AnthropicOAuth.PKCE?
@State private var code: String = "" @State private var code: String = ""
@State private var busy = false @State private var busy = false
@@ -42,10 +42,10 @@ struct AnthropicAuthControls: View {
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
Spacer() Spacer()
Button("Reveal") { Button("Reveal") {
NSWorkspace.shared.activateFileViewerSelecting([ClawdbotOAuthStore.oauthURL()]) NSWorkspace.shared.activateFileViewerSelecting([MoltbotOAuthStore.oauthURL()])
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.disabled(!FileManager().fileExists(atPath: ClawdbotOAuthStore.oauthURL().path)) .disabled(!FileManager().fileExists(atPath: MoltbotOAuthStore.oauthURL().path))
Button("Refresh") { Button("Refresh") {
self.refresh() self.refresh()
@@ -53,7 +53,7 @@ struct AnthropicAuthControls: View {
.buttonStyle(.bordered) .buttonStyle(.bordered)
} }
Text(ClawdbotOAuthStore.oauthURL().path) Text(MoltbotOAuthStore.oauthURL().path)
.font(.caption.monospaced()) .font(.caption.monospaced())
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.lineLimit(1) .lineLimit(1)
@@ -130,8 +130,8 @@ struct AnthropicAuthControls: View {
} }
private func refresh() { private func refresh() {
let imported = ClawdbotOAuthStore.importLegacyAnthropicOAuthIfNeeded() let imported = MoltbotOAuthStore.importLegacyAnthropicOAuthIfNeeded()
self.oauthStatus = ClawdbotOAuthStore.anthropicOAuthStatus() self.oauthStatus = MoltbotOAuthStore.anthropicOAuthStatus()
if imported != nil { if imported != nil {
self.statusText = "Imported existing OAuth credentials." self.statusText = "Imported existing OAuth credentials."
} }
@@ -172,11 +172,11 @@ struct AnthropicAuthControls: View {
code: parsed.code, code: parsed.code,
state: parsed.state, state: parsed.state,
verifier: pkce.verifier) verifier: pkce.verifier)
try ClawdbotOAuthStore.saveAnthropicOAuth(creds) try MoltbotOAuthStore.saveAnthropicOAuth(creds)
self.refresh() self.refresh()
self.pkce = nil self.pkce = nil
self.code = "" self.code = ""
self.statusText = "Connected. Clawdbot can now use Claude via OAuth." self.statusText = "Connected. Moltbot can now use Claude via OAuth."
} catch { } catch {
self.statusText = "OAuth failed: \(error.localizedDescription)" self.statusText = "OAuth failed: \(error.localizedDescription)"
} }
@@ -212,7 +212,7 @@ struct AnthropicAuthControls: View {
extension AnthropicAuthControls { extension AnthropicAuthControls {
init( init(
connectionMode: AppState.ConnectionMode, connectionMode: AppState.ConnectionMode,
oauthStatus: ClawdbotOAuthStore.AnthropicOAuthStatus, oauthStatus: MoltbotOAuthStore.AnthropicOAuthStatus,
pkce: AnthropicOAuth.PKCE? = nil, pkce: AnthropicOAuth.PKCE? = nil,
code: String = "", code: String = "",
busy: Bool = false, busy: Bool = false,

View File

@@ -18,7 +18,7 @@ enum AnthropicAuthMode: Equatable {
var shortLabel: String { var shortLabel: String {
switch self { switch self {
case .oauthFile: "OAuth (Clawdbot token file)" case .oauthFile: "OAuth (Moltbot token file)"
case .oauthEnv: "OAuth (env var)" case .oauthEnv: "OAuth (env var)"
case .apiKeyEnv: "API key (env var)" case .apiKeyEnv: "API key (env var)"
case .missing: "Missing credentials" case .missing: "Missing credentials"
@@ -36,7 +36,7 @@ enum AnthropicAuthMode: Equatable {
enum AnthropicAuthResolver { enum AnthropicAuthResolver {
static func resolve( static func resolve(
environment: [String: String] = ProcessInfo.processInfo.environment, environment: [String: String] = ProcessInfo.processInfo.environment,
oauthStatus: ClawdbotOAuthStore.AnthropicOAuthStatus = ClawdbotOAuthStore oauthStatus: MoltbotOAuthStore.AnthropicOAuthStatus = MoltbotOAuthStore
.anthropicOAuthStatus()) -> AnthropicAuthMode .anthropicOAuthStatus()) -> AnthropicAuthMode
{ {
if oauthStatus.isConnected { return .oauthFile } if oauthStatus.isConnected { return .oauthFile }
@@ -194,10 +194,10 @@ enum AnthropicOAuth {
} }
} }
enum ClawdbotOAuthStore { enum MoltbotOAuthStore {
static let oauthFilename = "oauth.json" static let oauthFilename = "oauth.json"
private static let providerKey = "anthropic" private static let providerKey = "anthropic"
private static let clawdbotOAuthDirEnv = "CLAWDBOT_OAUTH_DIR" private static let moltbotOAuthDirEnv = "CLAWDBOT_OAUTH_DIR"
private static let legacyPiDirEnv = "PI_CODING_AGENT_DIR" private static let legacyPiDirEnv = "PI_CODING_AGENT_DIR"
enum AnthropicOAuthStatus: Equatable { enum AnthropicOAuthStatus: Equatable {
@@ -215,12 +215,12 @@ enum ClawdbotOAuthStore {
var shortDescription: String { var shortDescription: String {
switch self { switch self {
case .missingFile: "Clawdbot OAuth token file not found" case .missingFile: "Moltbot OAuth token file not found"
case .unreadableFile: "Clawdbot OAuth token file not readable" case .unreadableFile: "Moltbot OAuth token file not readable"
case .invalidJSON: "Clawdbot OAuth token file invalid" case .invalidJSON: "Moltbot OAuth token file invalid"
case .missingProviderEntry: "No Anthropic entry in Clawdbot OAuth token file" case .missingProviderEntry: "No Anthropic entry in Moltbot OAuth token file"
case .missingTokens: "Anthropic entry missing tokens" case .missingTokens: "Anthropic entry missing tokens"
case .connected: "Clawdbot OAuth credentials found" case .connected: "Moltbot OAuth credentials found"
} }
} }
} }

View File

@@ -1,10 +1,10 @@
import ClawdbotKit import MoltbotKit
import ClawdbotProtocol import MoltbotProtocol
import Foundation import Foundation
// Prefer the ClawdbotKit wrapper to keep gateway request payloads consistent. // Prefer the MoltbotKit wrapper to keep gateway request payloads consistent.
typealias AnyCodable = ClawdbotKit.AnyCodable typealias AnyCodable = MoltbotKit.AnyCodable
typealias InstanceIdentity = ClawdbotKit.InstanceIdentity typealias InstanceIdentity = MoltbotKit.InstanceIdentity
extension AnyCodable { extension AnyCodable {
var stringValue: String? { self.value as? String } var stringValue: String? { self.value as? String }
@@ -26,19 +26,19 @@ extension AnyCodable {
} }
} }
extension ClawdbotProtocol.AnyCodable { extension MoltbotProtocol.AnyCodable {
var stringValue: String? { self.value as? String } var stringValue: String? { self.value as? String }
var boolValue: Bool? { self.value as? Bool } var boolValue: Bool? { self.value as? Bool }
var intValue: Int? { self.value as? Int } var intValue: Int? { self.value as? Int }
var doubleValue: Double? { self.value as? Double } var doubleValue: Double? { self.value as? Double }
var dictionaryValue: [String: ClawdbotProtocol.AnyCodable]? { self.value as? [String: ClawdbotProtocol.AnyCodable] } var dictionaryValue: [String: MoltbotProtocol.AnyCodable]? { self.value as? [String: MoltbotProtocol.AnyCodable] }
var arrayValue: [ClawdbotProtocol.AnyCodable]? { self.value as? [ClawdbotProtocol.AnyCodable] } var arrayValue: [MoltbotProtocol.AnyCodable]? { self.value as? [MoltbotProtocol.AnyCodable] }
var foundationValue: Any { var foundationValue: Any {
switch self.value { switch self.value {
case let dict as [String: ClawdbotProtocol.AnyCodable]: case let dict as [String: MoltbotProtocol.AnyCodable]:
dict.mapValues { $0.foundationValue } dict.mapValues { $0.foundationValue }
case let array as [ClawdbotProtocol.AnyCodable]: case let array as [MoltbotProtocol.AnyCodable]:
array.map(\.foundationValue) array.map(\.foundationValue)
default: default:
self.value self.value

View File

@@ -41,13 +41,13 @@ final class AppState {
} }
var onboardingSeen: Bool { var onboardingSeen: Bool {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.onboardingSeen, forKey: "clawdbot.onboardingSeen") } didSet { self.ifNotPreview { UserDefaults.standard.set(self.onboardingSeen, forKey: "moltbot.onboardingSeen") }
} }
} }
var debugPaneEnabled: Bool { var debugPaneEnabled: Bool {
didSet { didSet {
self.ifNotPreview { UserDefaults.standard.set(self.debugPaneEnabled, forKey: "clawdbot.debugPaneEnabled") } self.ifNotPreview { UserDefaults.standard.set(self.debugPaneEnabled, forKey: "moltbot.debugPaneEnabled") }
CanvasManager.shared.refreshDebugStatus() CanvasManager.shared.refreshDebugStatus()
} }
} }
@@ -229,11 +229,11 @@ final class AppState {
init(preview: Bool = false) { init(preview: Bool = false) {
self.isPreview = preview || ProcessInfo.processInfo.isRunningTests self.isPreview = preview || ProcessInfo.processInfo.isRunningTests
let onboardingSeen = UserDefaults.standard.bool(forKey: "clawdbot.onboardingSeen") let onboardingSeen = UserDefaults.standard.bool(forKey: "moltbot.onboardingSeen")
self.isPaused = UserDefaults.standard.bool(forKey: pauseDefaultsKey) self.isPaused = UserDefaults.standard.bool(forKey: pauseDefaultsKey)
self.launchAtLogin = false self.launchAtLogin = false
self.onboardingSeen = onboardingSeen self.onboardingSeen = onboardingSeen
self.debugPaneEnabled = UserDefaults.standard.bool(forKey: "clawdbot.debugPaneEnabled") self.debugPaneEnabled = UserDefaults.standard.bool(forKey: "moltbot.debugPaneEnabled")
let savedVoiceWake = UserDefaults.standard.bool(forKey: swabbleEnabledKey) let savedVoiceWake = UserDefaults.standard.bool(forKey: swabbleEnabledKey)
self.swabbleEnabled = voiceWakeSupported ? savedVoiceWake : false self.swabbleEnabled = voiceWakeSupported ? savedVoiceWake : false
self.swabbleTriggerWords = UserDefaults.standard self.swabbleTriggerWords = UserDefaults.standard
@@ -275,7 +275,7 @@ final class AppState {
UserDefaults.standard.set(IconOverrideSelection.system.rawValue, forKey: iconOverrideKey) UserDefaults.standard.set(IconOverrideSelection.system.rawValue, forKey: iconOverrideKey)
} }
let configRoot = ClawdbotConfigFile.loadDict() let configRoot = MoltbotConfigFile.loadDict()
let configRemoteUrl = GatewayRemoteConfig.resolveUrlString(root: configRoot) let configRemoteUrl = GatewayRemoteConfig.resolveUrlString(root: configRoot)
let configRemoteTransport = GatewayRemoteConfig.resolveTransport(root: configRoot) let configRemoteTransport = GatewayRemoteConfig.resolveTransport(root: configRoot)
let resolvedConnectionMode = ConnectionModeResolver.resolve(root: configRoot).mode let resolvedConnectionMode = ConnectionModeResolver.resolve(root: configRoot).mode
@@ -353,7 +353,7 @@ final class AppState {
} }
private func startConfigWatcher() { private func startConfigWatcher() {
let configUrl = ClawdbotConfigFile.url() let configUrl = MoltbotConfigFile.url()
self.configWatcher = ConfigFileWatcher(url: configUrl) { [weak self] in self.configWatcher = ConfigFileWatcher(url: configUrl) { [weak self] in
Task { @MainActor in Task { @MainActor in
self?.applyConfigFromDisk() self?.applyConfigFromDisk()
@@ -363,7 +363,7 @@ final class AppState {
} }
private func applyConfigFromDisk() { private func applyConfigFromDisk() {
let root = ClawdbotConfigFile.loadDict() let root = MoltbotConfigFile.loadDict()
self.applyConfigOverrides(root) self.applyConfigOverrides(root)
} }
@@ -451,7 +451,7 @@ final class AppState {
Task { @MainActor in Task { @MainActor in
// Keep app-only connection settings local to avoid overwriting remote gateway config. // Keep app-only connection settings local to avoid overwriting remote gateway config.
var root = ClawdbotConfigFile.loadDict() var root = MoltbotConfigFile.loadDict()
var gateway = root["gateway"] as? [String: Any] ?? [:] var gateway = root["gateway"] as? [String: Any] ?? [:]
var changed = false var changed = false
@@ -541,7 +541,7 @@ final class AppState {
} else { } else {
root["gateway"] = gateway root["gateway"] = gateway
} }
ClawdbotConfigFile.saveDict(root) MoltbotConfigFile.saveDict(root)
} }
} }
@@ -685,7 +685,7 @@ extension AppState {
state.remoteTarget = "user@example.com" state.remoteTarget = "user@example.com"
state.remoteUrl = "wss://gateway.example.ts.net" state.remoteUrl = "wss://gateway.example.ts.net"
state.remoteIdentity = "~/.ssh/id_ed25519" state.remoteIdentity = "~/.ssh/id_ed25519"
state.remoteProjectRoot = "~/Projects/clawdbot" state.remoteProjectRoot = "~/Projects/moltbot"
state.remoteCliPath = "" state.remoteCliPath = ""
return state return state
} }

View File

@@ -15,7 +15,7 @@ final class CLIInstallPrompter {
UserDefaults.standard.set(version, forKey: cliInstallPromptedVersionKey) UserDefaults.standard.set(version, forKey: cliInstallPromptedVersionKey)
let alert = NSAlert() let alert = NSAlert()
alert.messageText = "Install Clawdbot CLI?" alert.messageText = "Install Moltbot CLI?"
alert.informativeText = "Local mode needs the CLI so launchd can run the gateway." alert.informativeText = "Local mode needs the CLI so launchd can run the gateway."
alert.addButton(withTitle: "Install CLI") alert.addButton(withTitle: "Install CLI")
alert.addButton(withTitle: "Not now") alert.addButton(withTitle: "Not now")

View File

@@ -13,7 +13,7 @@ enum CLIInstaller {
fileManager: FileManager) -> String? fileManager: FileManager) -> String?
{ {
for basePath in searchPaths { for basePath in searchPaths {
let candidate = URL(fileURLWithPath: basePath).appendingPathComponent("clawdbot").path let candidate = URL(fileURLWithPath: basePath).appendingPathComponent("moltbot").path
var isDirectory: ObjCBool = false var isDirectory: ObjCBool = false
guard fileManager.fileExists(atPath: candidate, isDirectory: &isDirectory), guard fileManager.fileExists(atPath: candidate, isDirectory: &isDirectory),
@@ -37,14 +37,14 @@ enum CLIInstaller {
static func install(statusHandler: @escaping @MainActor @Sendable (String) async -> Void) async { static func install(statusHandler: @escaping @MainActor @Sendable (String) async -> Void) async {
let expected = GatewayEnvironment.expectedGatewayVersionString() ?? "latest" let expected = GatewayEnvironment.expectedGatewayVersionString() ?? "latest"
let prefix = Self.installPrefix() let prefix = Self.installPrefix()
await statusHandler("Installing clawdbot CLI…") await statusHandler("Installing moltbot CLI…")
let cmd = self.installScriptCommand(version: expected, prefix: prefix) let cmd = self.installScriptCommand(version: expected, prefix: prefix)
let response = await ShellExecutor.runDetailed(command: cmd, cwd: nil, env: nil, timeout: 900) let response = await ShellExecutor.runDetailed(command: cmd, cwd: nil, env: nil, timeout: 900)
if response.success { if response.success {
let parsed = self.parseInstallEvents(response.stdout) let parsed = self.parseInstallEvents(response.stdout)
let installedVersion = parsed.last { $0.event == "done" }?.version let installedVersion = parsed.last { $0.event == "done" }?.version
let summary = installedVersion.map { "Installed clawdbot \($0)." } ?? "Installed clawdbot." let summary = installedVersion.map { "Installed moltbot \($0)." } ?? "Installed moltbot."
await statusHandler(summary) await statusHandler(summary)
return return
} }

View File

@@ -1,6 +1,6 @@
import AVFoundation import AVFoundation
import ClawdbotIPC import MoltbotIPC
import ClawdbotKit import MoltbotKit
import CoreGraphics import CoreGraphics
import Foundation import Foundation
import OSLog import OSLog
@@ -168,7 +168,7 @@ actor CameraCaptureService {
await Self.warmUpCaptureSession() await Self.warmUpCaptureSession()
let tmpMovURL = FileManager().temporaryDirectory let tmpMovURL = FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mov") .appendingPathComponent("moltbot-camera-\(UUID().uuidString).mov")
defer { try? FileManager().removeItem(at: tmpMovURL) } defer { try? FileManager().removeItem(at: tmpMovURL) }
let outputURL: URL = { let outputURL: URL = {
@@ -176,7 +176,7 @@ actor CameraCaptureService {
return URL(fileURLWithPath: outPath) return URL(fileURLWithPath: outPath)
} }
return FileManager().temporaryDirectory return FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mp4") .appendingPathComponent("moltbot-camera-\(UUID().uuidString).mp4")
}() }()
// Ensure we don't fail exporting due to an existing file. // Ensure we don't fail exporting due to an existing file.

View File

@@ -1,11 +1,11 @@
import AppKit import AppKit
import ClawdbotIPC import MoltbotIPC
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
import WebKit import WebKit
final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler { final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
static let messageName = "clawdbotCanvasA2UIAction" static let messageName = "moltbotCanvasA2UIAction"
private let sessionKey: String private let sessionKey: String
@@ -52,7 +52,7 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
}() }()
guard !userAction.isEmpty else { return } guard !userAction.isEmpty else { return }
guard let name = ClawdbotCanvasA2UIAction.extractActionName(userAction) else { return } guard let name = MoltbotCanvasA2UIAction.extractActionName(userAction) else { return }
let actionId = let actionId =
(userAction["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty (userAction["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
?? UUID().uuidString ?? UUID().uuidString
@@ -64,15 +64,15 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
let sourceComponentId = (userAction["sourceComponentId"] as? String)? let sourceComponentId = (userAction["sourceComponentId"] as? String)?
.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "-" .trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "-"
let instanceId = InstanceIdentity.instanceId.lowercased() let instanceId = InstanceIdentity.instanceId.lowercased()
let contextJSON = ClawdbotCanvasA2UIAction.compactJSON(userAction["context"]) let contextJSON = MoltbotCanvasA2UIAction.compactJSON(userAction["context"])
// Token-efficient and unambiguous. The agent should treat this as a UI event and (by default) update Canvas. // Token-efficient and unambiguous. The agent should treat this as a UI event and (by default) update Canvas.
let messageContext = ClawdbotCanvasA2UIAction.AgentMessageContext( let messageContext = MoltbotCanvasA2UIAction.AgentMessageContext(
actionName: name, actionName: name,
session: .init(key: self.sessionKey, surfaceId: surfaceId), session: .init(key: self.sessionKey, surfaceId: surfaceId),
component: .init(id: sourceComponentId, host: InstanceIdentity.displayName, instanceId: instanceId), component: .init(id: sourceComponentId, host: InstanceIdentity.displayName, instanceId: instanceId),
contextJSON: contextJSON) contextJSON: contextJSON)
let text = ClawdbotCanvasA2UIAction.formatAgentMessage(messageContext) let text = MoltbotCanvasA2UIAction.formatAgentMessage(messageContext)
Task { [weak webView] in Task { [weak webView] in
if AppStateStore.shared.connectionMode == .local { if AppStateStore.shared.connectionMode == .local {
@@ -91,7 +91,7 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
await MainActor.run { await MainActor.run {
guard let webView else { return } guard let webView else { return }
let js = ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus( let js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(
actionId: actionId, actionId: actionId,
ok: result.ok, ok: result.ok,
error: result.error) error: result.error)
@@ -144,5 +144,5 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
return false return false
} }
// Formatting helpers live in ClawdbotKit (`ClawdbotCanvasA2UIAction`). // Formatting helpers live in MoltbotKit (`MoltbotCanvasA2UIAction`).
} }

View File

@@ -1,6 +1,6 @@
import AppKit import AppKit
import ClawdbotIPC import MoltbotIPC
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
import OSLog import OSLog
@@ -26,7 +26,7 @@ final class CanvasManager {
private nonisolated static let canvasRoot: URL = { private nonisolated static let canvasRoot: URL = {
let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first! let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
return base.appendingPathComponent("Clawdbot/canvas", isDirectory: true) return base.appendingPathComponent("Moltbot/canvas", isDirectory: true)
}() }()
func show(sessionKey: String, path: String? = nil, placement: CanvasPlacement? = nil) throws -> String { func show(sessionKey: String, path: String? = nil, placement: CanvasPlacement? = nil) throws -> String {
@@ -231,7 +231,7 @@ final class CanvasManager {
private static func resolveA2UIHostUrl(from raw: String?) -> String? { private static func resolveA2UIHostUrl(from raw: String?) -> String? {
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty, let base = URL(string: trimmed) else { return nil } guard !trimmed.isEmpty, let base = URL(string: trimmed) else { return nil }
return base.appendingPathComponent("__clawdbot__/a2ui/").absoluteString + "?platform=macos" return base.appendingPathComponent("__moltbot__/a2ui/").absoluteString + "?platform=macos"
} }
// MARK: - Anchoring // MARK: - Anchoring

View File

@@ -1,7 +1,7 @@
import Foundation import Foundation
enum CanvasScheme { enum CanvasScheme {
static let scheme = "clawdbot-canvas" static let scheme = "moltbot-canvas"
static func makeURL(session: String, path: String? = nil) -> URL? { static func makeURL(session: String, path: String? = nil) -> URL? {
var comps = URLComponents() var comps = URLComponents()

View File

@@ -1,4 +1,4 @@
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
import OSLog import OSLog
import WebKit import WebKit
@@ -222,7 +222,7 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
let name = fileURL.deletingPathExtension().lastPathComponent let name = fileURL.deletingPathExtension().lastPathComponent
guard !name.isEmpty, !ext.isEmpty else { return nil } guard !name.isEmpty, !ext.isEmpty else { return nil }
let bundle = ClawdbotKitResources.bundle let bundle = MoltbotKitResources.bundle
let resourceURL = let resourceURL =
bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory) bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory)
?? bundle.url(forResource: name, withExtension: ext) ?? bundle.url(forResource: name, withExtension: ext)

View File

@@ -23,7 +23,7 @@ extension CanvasWindowController {
} }
static func storedFrameDefaultsKey(sessionKey: String) -> String { static func storedFrameDefaultsKey(sessionKey: String) -> String {
"clawdbot.canvas.frame.\(self.sanitizeSessionKey(sessionKey))" "moltbot.canvas.frame.\(self.sanitizeSessionKey(sessionKey))"
} }
static func loadRestoredFrame(sessionKey: String) -> NSRect? { static func loadRestoredFrame(sessionKey: String) -> NSRect? {

View File

@@ -17,7 +17,7 @@ extension CanvasWindowController {
let scheme = url.scheme?.lowercased() let scheme = url.scheme?.lowercased()
// Deep links: allow local Canvas content to invoke the agent without bouncing through NSWorkspace. // Deep links: allow local Canvas content to invoke the agent without bouncing through NSWorkspace.
if scheme == "clawdbot" { if scheme == "moltbot" {
if self.webView.url?.scheme == CanvasScheme.scheme { if self.webView.url?.scheme == CanvasScheme.scheme {
Task { await DeepLinkHandler.shared.handle(url: url) } Task { await DeepLinkHandler.shared.handle(url: url) }
} else { } else {

View File

@@ -1,5 +1,5 @@
import AppKit import AppKit
import ClawdbotIPC import MoltbotIPC
extension CanvasWindowController { extension CanvasWindowController {
// MARK: - Window // MARK: - Window
@@ -12,7 +12,7 @@ extension CanvasWindowController {
styleMask: [.titled, .closable, .resizable, .miniaturizable], styleMask: [.titled, .closable, .resizable, .miniaturizable],
backing: .buffered, backing: .buffered,
defer: false) defer: false)
window.title = "Clawdbot Canvas" window.title = "Moltbot Canvas"
window.isReleasedWhenClosed = false window.isReleasedWhenClosed = false
window.contentView = contentView window.contentView = contentView
window.center() window.center()

View File

@@ -1,6 +1,6 @@
import AppKit import AppKit
import ClawdbotIPC import MoltbotIPC
import ClawdbotKit import MoltbotKit
import Foundation import Foundation
import WebKit import WebKit
@@ -57,8 +57,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
(() => { (() => {
try { try {
if (location.protocol !== '\(CanvasScheme.scheme):') return; if (location.protocol !== '\(CanvasScheme.scheme):') return;
if (globalThis.__clawdbotA2UIBridgeInstalled) return; if (globalThis.__moltbotA2UIBridgeInstalled) return;
globalThis.__clawdbotA2UIBridgeInstalled = true; globalThis.__moltbotA2UIBridgeInstalled = true;
const deepLinkKey = \(Self.jsStringLiteral(deepLinkKey)); const deepLinkKey = \(Self.jsStringLiteral(deepLinkKey));
const sessionKey = \(Self.jsStringLiteral(injectedSessionKey)); const sessionKey = \(Self.jsStringLiteral(injectedSessionKey));
@@ -89,7 +89,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
// If the bundled A2UI shell is present, let it forward actions so we keep its richer // If the bundled A2UI shell is present, let it forward actions so we keep its richer
// context resolution (data model path lookups, surface detection, etc.). // context resolution (data model path lookups, surface detection, etc.).
const hasBundledA2UIHost = !!globalThis.clawdbotA2UI || !!document.querySelector('clawdbot-a2ui-host'); const hasBundledA2UIHost = !!globalThis.clawdbotA2UI || !!document.querySelector('moltbot-a2ui-host');
if (hasBundledA2UIHost && handler?.postMessage) return; if (hasBundledA2UIHost && handler?.postMessage) return;
// Otherwise, forward directly when possible. // Otherwise, forward directly when possible.
@@ -115,7 +115,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
params.set('deliver', 'false'); params.set('deliver', 'false');
params.set('channel', 'last'); params.set('channel', 'last');
params.set('key', deepLinkKey); params.set('key', deepLinkKey);
location.href = 'clawdbot://agent?' + params.toString(); location.href = 'moltbot://agent?' + params.toString();
} catch {} } catch {}
}, true); }, true);
} catch {} } catch {}
@@ -268,7 +268,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
let js = """ let js = """
(() => { (() => {
try { try {
const api = globalThis.__clawdbot; const api = globalThis.__moltbot;
if (!api) return; if (!api) return;
if (typeof api.setDebugStatusEnabled === 'function') { if (typeof api.setDebugStatusEnabled === 'function') {
api.setDebugStatusEnabled(\(enabled ? "true" : "false")); api.setDebugStatusEnabled(\(enabled ? "true" : "false"));
@@ -336,7 +336,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
path = outPath path = outPath
} else { } else {
let ts = Int(Date().timeIntervalSince1970) let ts = Int(Date().timeIntervalSince1970)
path = "/tmp/clawdbot-canvas-\(CanvasWindowController.sanitizeSessionKey(self.sessionKey))-\(ts).png" path = "/tmp/moltbot-canvas-\(CanvasWindowController.sanitizeSessionKey(self.sessionKey))-\(ts).png"
} }
try png.write(to: URL(fileURLWithPath: path), options: [.atomic]) try png.write(to: URL(fileURLWithPath: path), options: [.atomic])

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol import MoltbotProtocol
import SwiftUI import SwiftUI
extension ChannelsSettings { extension ChannelsSettings {

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol import MoltbotProtocol
import Foundation import Foundation
extension ChannelsStore { extension ChannelsStore {
@@ -28,7 +28,7 @@ extension ChannelsStore {
params: nil, params: nil,
timeoutMs: 10000) timeoutMs: 10000)
self.configStatus = snap.valid == false self.configStatus = snap.valid == false
? "Config invalid; fix it in ~/.clawdbot/clawdbot.json." ? "Config invalid; fix it in ~/.clawdbot/moltbot.json."
: nil : nil
self.configRoot = snap.config?.mapValues { $0.foundationValue } ?? [:] self.configRoot = snap.config?.mapValues { $0.foundationValue } ?? [:]
self.configDraft = cloneConfigValue(self.configRoot) as? [String: Any] ?? self.configRoot self.configDraft = cloneConfigValue(self.configRoot) as? [String: Any] ?? self.configRoot

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol import MoltbotProtocol
import Foundation import Foundation
extension ChannelsStore { extension ChannelsStore {

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol import MoltbotProtocol
import Foundation import Foundation
import Observation import Observation

View File

@@ -1,19 +1,19 @@
import ClawdbotProtocol import MoltbotProtocol
import Foundation import Foundation
enum ClawdbotConfigFile { enum MoltbotConfigFile {
private static let logger = Logger(subsystem: "com.clawdbot", category: "config") private static let logger = Logger(subsystem: "com.clawdbot", category: "config")
static func url() -> URL { static func url() -> URL {
ClawdbotPaths.configURL MoltbotPaths.configURL
} }
static func stateDirURL() -> URL { static func stateDirURL() -> URL {
ClawdbotPaths.stateDirURL MoltbotPaths.stateDirURL
} }
static func defaultWorkspaceURL() -> URL { static func defaultWorkspaceURL() -> URL {
ClawdbotPaths.workspaceURL MoltbotPaths.workspaceURL
} }
static func loadDict() -> [String: Any] { static func loadDict() -> [String: Any] {

View File

@@ -1,6 +1,6 @@
import Foundation import Foundation
enum ClawdbotEnv { enum MoltbotEnv {
static func path(_ key: String) -> String? { static func path(_ key: String) -> String? {
// Normalize env overrides once so UI + file IO stay consistent. // Normalize env overrides once so UI + file IO stay consistent.
guard let raw = getenv(key) else { return nil } guard let raw = getenv(key) else { return nil }
@@ -13,12 +13,12 @@ enum ClawdbotEnv {
} }
} }
enum ClawdbotPaths { enum MoltbotPaths {
private static let configPathEnv = "CLAWDBOT_CONFIG_PATH" private static let configPathEnv = "CLAWDBOT_CONFIG_PATH"
private static let stateDirEnv = "CLAWDBOT_STATE_DIR" private static let stateDirEnv = "CLAWDBOT_STATE_DIR"
static var stateDirURL: URL { static var stateDirURL: URL {
if let override = ClawdbotEnv.path(self.stateDirEnv) { if let override = MoltbotEnv.path(self.stateDirEnv) {
return URL(fileURLWithPath: override, isDirectory: true) return URL(fileURLWithPath: override, isDirectory: true)
} }
return FileManager().homeDirectoryForCurrentUser return FileManager().homeDirectoryForCurrentUser
@@ -26,10 +26,10 @@ enum ClawdbotPaths {
} }
static var configURL: URL { static var configURL: URL {
if let override = ClawdbotEnv.path(self.configPathEnv) { if let override = MoltbotEnv.path(self.configPathEnv) {
return URL(fileURLWithPath: override) return URL(fileURLWithPath: override)
} }
return self.stateDirURL.appendingPathComponent("clawdbot.json") return self.stateDirURL.appendingPathComponent("moltbot.json")
} }
static var workspaceURL: URL { static var workspaceURL: URL {

View File

@@ -1,13 +1,13 @@
import Foundation import Foundation
enum CommandResolver { enum CommandResolver {
private static let projectRootDefaultsKey = "clawdbot.gatewayProjectRootPath" private static let projectRootDefaultsKey = "moltbot.gatewayProjectRootPath"
private static let helperName = "clawdbot" private static let helperName = "moltbot"
static func gatewayEntrypoint(in root: URL) -> String? { static func gatewayEntrypoint(in root: URL) -> String? {
let distEntry = root.appendingPathComponent("dist/index.js").path let distEntry = root.appendingPathComponent("dist/index.js").path
if FileManager().isReadableFile(atPath: distEntry) { return distEntry } if FileManager().isReadableFile(atPath: distEntry) { return distEntry }
let binEntry = root.appendingPathComponent("bin/clawdbot.js").path let binEntry = root.appendingPathComponent("bin/moltbot.js").path
if FileManager().isReadableFile(atPath: binEntry) { return binEntry } if FileManager().isReadableFile(atPath: binEntry) { return binEntry }
return nil return nil
} }
@@ -52,7 +52,7 @@ enum CommandResolver {
return url return url
} }
let fallback = FileManager().homeDirectoryForCurrentUser let fallback = FileManager().homeDirectoryForCurrentUser
.appendingPathComponent("Projects/clawdbot") .appendingPathComponent("Projects/moltbot")
if FileManager().fileExists(atPath: fallback.path) { if FileManager().fileExists(atPath: fallback.path) {
return fallback return fallback
} }
@@ -87,17 +87,17 @@ enum CommandResolver {
// Dev-only convenience. Avoid project-local PATH hijacking in release builds. // Dev-only convenience. Avoid project-local PATH hijacking in release builds.
extras.insert(projectRoot.appendingPathComponent("node_modules/.bin").path, at: 0) extras.insert(projectRoot.appendingPathComponent("node_modules/.bin").path, at: 0)
#endif #endif
let clawdbotPaths = self.clawdbotManagedPaths(home: home) let moltbotPaths = self.clawdbotManagedPaths(home: home)
if !clawdbotPaths.isEmpty { if !moltbotPaths.isEmpty {
extras.insert(contentsOf: clawdbotPaths, at: 1) extras.insert(contentsOf: moltbotPaths, at: 1)
} }
extras.insert(contentsOf: self.nodeManagerBinPaths(home: home), at: 1 + clawdbotPaths.count) extras.insert(contentsOf: self.nodeManagerBinPaths(home: home), at: 1 + moltbotPaths.count)
var seen = Set<String>() var seen = Set<String>()
// Preserve order while stripping duplicates so PATH lookups remain deterministic. // Preserve order while stripping duplicates so PATH lookups remain deterministic.
return (extras + current).filter { seen.insert($0).inserted } return (extras + current).filter { seen.insert($0).inserted }
} }
private static func clawdbotManagedPaths(home: URL) -> [String] { private static func moltbotManagedPaths(home: URL) -> [String] {
let base = home.appendingPathComponent(".clawdbot") let base = home.appendingPathComponent(".clawdbot")
let bin = base.appendingPathComponent("bin") let bin = base.appendingPathComponent("bin")
let nodeBin = base.appendingPathComponent("tools/node/bin") let nodeBin = base.appendingPathComponent("tools/node/bin")
@@ -187,11 +187,11 @@ enum CommandResolver {
return nil return nil
} }
static func clawdbotExecutable(searchPaths: [String]? = nil) -> String? { static func moltbotExecutable(searchPaths: [String]? = nil) -> String? {
self.findExecutable(named: self.helperName, searchPaths: searchPaths) self.findExecutable(named: self.helperName, searchPaths: searchPaths)
} }
static func projectClawdbotExecutable(projectRoot: URL? = nil) -> String? { static func projectMoltbotExecutable(projectRoot: URL? = nil) -> String? {
#if DEBUG #if DEBUG
let root = projectRoot ?? self.projectRoot() let root = projectRoot ?? self.projectRoot()
let candidate = root.appendingPathComponent("node_modules/.bin").appendingPathComponent(self.helperName).path let candidate = root.appendingPathComponent("node_modules/.bin").appendingPathComponent(self.helperName).path
@@ -202,11 +202,11 @@ enum CommandResolver {
} }
static func nodeCliPath() -> String? { static func nodeCliPath() -> String? {
let candidate = self.projectRoot().appendingPathComponent("bin/clawdbot.js").path let candidate = self.projectRoot().appendingPathComponent("bin/moltbot.js").path
return FileManager().isReadableFile(atPath: candidate) ? candidate : nil return FileManager().isReadableFile(atPath: candidate) ? candidate : nil
} }
static func hasAnyClawdbotInvoker(searchPaths: [String]? = nil) -> Bool { static func hasAnyMoltbotInvoker(searchPaths: [String]? = nil) -> Bool {
if self.clawdbotExecutable(searchPaths: searchPaths) != nil { return true } if self.clawdbotExecutable(searchPaths: searchPaths) != nil { return true }
if self.findExecutable(named: "pnpm", searchPaths: searchPaths) != nil { return true } if self.findExecutable(named: "pnpm", searchPaths: searchPaths) != nil { return true }
if self.findExecutable(named: "node", searchPaths: searchPaths) != nil, if self.findExecutable(named: "node", searchPaths: searchPaths) != nil,
@@ -217,7 +217,7 @@ enum CommandResolver {
return false return false
} }
static func clawdbotNodeCommand( static func moltbotNodeCommand(
subcommand: String, subcommand: String,
extraArgs: [String] = [], extraArgs: [String] = [],
defaults: UserDefaults = .standard, defaults: UserDefaults = .standard,
@@ -238,8 +238,8 @@ enum CommandResolver {
switch runtimeResult { switch runtimeResult {
case let .success(runtime): case let .success(runtime):
let root = self.projectRoot() let root = self.projectRoot()
if let clawdbotPath = self.projectClawdbotExecutable(projectRoot: root) { if let moltbotPath = self.projectMoltbotExecutable(projectRoot: root) {
return [clawdbotPath, subcommand] + extraArgs return [moltbotPath, subcommand] + extraArgs
} }
if let entry = self.gatewayEntrypoint(in: root) { if let entry = self.gatewayEntrypoint(in: root) {
@@ -251,14 +251,14 @@ enum CommandResolver {
} }
if let pnpm = self.findExecutable(named: "pnpm", searchPaths: searchPaths) { if let pnpm = self.findExecutable(named: "pnpm", searchPaths: searchPaths) {
// Use --silent to avoid pnpm lifecycle banners that would corrupt JSON outputs. // Use --silent to avoid pnpm lifecycle banners that would corrupt JSON outputs.
return [pnpm, "--silent", "clawdbot", subcommand] + extraArgs return [pnpm, "--silent", "moltbot", subcommand] + extraArgs
} }
if let clawdbotPath = self.clawdbotExecutable(searchPaths: searchPaths) { if let moltbotPath = self.clawdbotExecutable(searchPaths: searchPaths) {
return [clawdbotPath, subcommand] + extraArgs return [moltbotPath, subcommand] + extraArgs
} }
let missingEntry = """ let missingEntry = """
clawdbot entrypoint missing (looked for dist/index.js or bin/clawdbot.js); run pnpm build. moltbot entrypoint missing (looked for dist/index.js or bin/moltbot.js); run pnpm build.
""" """
return self.errorCommand(with: missingEntry) return self.errorCommand(with: missingEntry)
@@ -267,8 +267,8 @@ enum CommandResolver {
} }
} }
// Existing callers still refer to clawdbotCommand; keep it as node alias. // Existing callers still refer to moltbotCommand; keep it as node alias.
static func clawdbotCommand( static func moltbotCommand(
subcommand: String, subcommand: String,
extraArgs: [String] = [], extraArgs: [String] = [],
defaults: UserDefaults = .standard, defaults: UserDefaults = .standard,
@@ -289,7 +289,7 @@ enum CommandResolver {
guard !settings.target.isEmpty else { return nil } guard !settings.target.isEmpty else { return nil }
guard let parsed = self.parseSSHTarget(settings.target) else { return nil } guard let parsed = self.parseSSHTarget(settings.target) else { return nil }
// Run the real clawdbot CLI on the remote host. // Run the real moltbot CLI on the remote host.
let exportedPath = [ let exportedPath = [
"/opt/homebrew/bin", "/opt/homebrew/bin",
"/usr/local/bin", "/usr/local/bin",
@@ -306,7 +306,7 @@ enum CommandResolver {
let projectSection = if userPRJ.isEmpty { let projectSection = if userPRJ.isEmpty {
""" """
DEFAULT_PRJ="$HOME/Projects/clawdbot" DEFAULT_PRJ="$HOME/Projects/moltbot"
if [ -d "$DEFAULT_PRJ" ]; then if [ -d "$DEFAULT_PRJ" ]; then
PRJ="$DEFAULT_PRJ" PRJ="$DEFAULT_PRJ"
cd "$PRJ" || { echo "Project root not found: $PRJ"; exit 127; } cd "$PRJ" || { echo "Project root not found: $PRJ"; exit 127; }
@@ -345,9 +345,9 @@ enum CommandResolver {
CLI=""; CLI="";
\(cliSection) \(cliSection)
\(projectSection) \(projectSection)
if command -v clawdbot >/dev/null 2>&1; then if command -v moltbot >/dev/null 2>&1; then
CLI="$(command -v clawdbot)" CLI="$(command -v moltbot)"
clawdbot \(quotedArgs); moltbot \(quotedArgs);
elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/dist/index.js" ]; then elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/dist/index.js" ]; then
if command -v node >/dev/null 2>&1; then if command -v node >/dev/null 2>&1; then
CLI="node $PRJ/dist/index.js" CLI="node $PRJ/dist/index.js"
@@ -355,18 +355,18 @@ enum CommandResolver {
else else
echo "Node >=22 required on remote host"; exit 127; echo "Node >=22 required on remote host"; exit 127;
fi fi
elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/bin/clawdbot.js" ]; then elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/bin/moltbot.js" ]; then
if command -v node >/dev/null 2>&1; then if command -v node >/dev/null 2>&1; then
CLI="node $PRJ/bin/clawdbot.js" CLI="node $PRJ/bin/moltbot.js"
node "$PRJ/bin/clawdbot.js" \(quotedArgs); node "$PRJ/bin/moltbot.js" \(quotedArgs);
else else
echo "Node >=22 required on remote host"; exit 127; echo "Node >=22 required on remote host"; exit 127;
fi fi
elif command -v pnpm >/dev/null 2>&1; then elif command -v pnpm >/dev/null 2>&1; then
CLI="pnpm --silent clawdbot" CLI="pnpm --silent moltbot"
pnpm --silent clawdbot \(quotedArgs); pnpm --silent moltbot \(quotedArgs);
else else
echo "clawdbot CLI missing on remote host"; exit 127; echo "moltbot CLI missing on remote host"; exit 127;
fi fi
""" """
let options: [String] = [ let options: [String] = [
@@ -394,7 +394,7 @@ enum CommandResolver {
defaults: UserDefaults = .standard, defaults: UserDefaults = .standard,
configRoot: [String: Any]? = nil) -> RemoteSettings configRoot: [String: Any]? = nil) -> RemoteSettings
{ {
let root = configRoot ?? ClawdbotConfigFile.loadDict() let root = configRoot ?? MoltbotConfigFile.loadDict()
let mode = ConnectionModeResolver.resolve(root: root, defaults: defaults).mode let mode = ConnectionModeResolver.resolve(root: root, defaults: defaults).mode
let target = defaults.string(forKey: remoteTargetKey) ?? "" let target = defaults.string(forKey: remoteTargetKey) ?? ""
let identity = defaults.string(forKey: remoteIdentityKey) ?? "" let identity = defaults.string(forKey: remoteIdentityKey) ?? ""

View File

@@ -153,7 +153,7 @@ extension ConfigSettings {
.font(.title3.weight(.semibold)) .font(.title3.weight(.semibold))
Text(self.isNixMode Text(self.isNixMode
? "This tab is read-only in Nix mode. Edit config via Nix and rebuild." ? "This tab is read-only in Nix mode. Edit config via Nix and rebuild."
: "Edit ~/.clawdbot/clawdbot.json using the schema-driven form.") : "Edit ~/.clawdbot/moltbot.json using the schema-driven form.")
.font(.callout) .font(.callout)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol import MoltbotProtocol
import Foundation import Foundation
enum ConfigStore { enum ConfigStore {
@@ -44,7 +44,7 @@ enum ConfigStore {
if let gateway = await self.loadFromGateway() { if let gateway = await self.loadFromGateway() {
return gateway return gateway
} }
return ClawdbotConfigFile.loadDict() return MoltbotConfigFile.loadDict()
} }
@MainActor @MainActor
@@ -63,7 +63,7 @@ enum ConfigStore {
do { do {
try await self.saveToGateway(root) try await self.saveToGateway(root)
} catch { } catch {
ClawdbotConfigFile.saveDict(root) MoltbotConfigFile.saveDict(root)
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More