mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
refactor: rename clawdbot to moltbot with legacy compat
This commit is contained in:
36
AGENTS.md
36
AGENTS.md
@@ -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 + what’s covered: `docs/testing.md`.
|
- Full kit + what’s covered: `docs/testing.md`.
|
||||||
- Pure test additions/fixes generally do **not** need a changelog entry unless they alter user-facing behavior or the user asks for one.
|
- 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); don’t hand-roll spinners/bars.
|
- CLI progress: use `src/cli/progress.ts` (`osc-progress` + `@clack/prompts` spinner); don’t 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`; don’t 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`; don’t 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}`. Don’t add extra quotes.
|
- Command template should stay `moltbot-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Don’t add extra quotes.
|
||||||
- launchd PATH is minimal; ensure the app’s 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 app’s 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 tool’s escaping.
|
- For manual `moltbot message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tool’s escaping.
|
||||||
- Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step.
|
- Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step.
|
||||||
|
|
||||||
## NPM + 1Password (publish/verify)
|
## NPM + 1Password (publish/verify)
|
||||||
|
|||||||
120
CHANGELOG.md
120
CHANGELOG.md
@@ -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 doesn’t leak partial output.
|
- Auto-reply: suppress draft/typing streaming for `NO_REPLY` (silent system ops) so it doesn’t 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 don’t leak to external providers (e.g., Telegram).
|
- Heartbeat: strip markup-wrapped `HEARTBEAT_OK` so acks don’t 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 didn’t configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots).
|
- Previously, if you didn’t 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 user’s 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 user’s 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.
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
14
SECURITY.md
14
SECURITY.md
@@ -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
|
||||||
|
|||||||
36
appcast.xml
36
appcast.xml
@@ -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>
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>()
|
||||||
|
|||||||
@@ -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(":")) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}\" } }));"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"),
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Clawdbot Node</string>
|
<string name="app_name">Moltbot Node</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,5 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "ClawdbotNodeAndroid"
|
rootProject.name = "MoltbotNodeAndroid"
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ClawdbotKit
|
import MoltbotKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ScreenTab: View {
|
struct ScreenTab: View {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ClawdbotKit
|
import MoltbotKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WebKit
|
import WebKit
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# fastlane setup (Clawdbot iOS)
|
# fastlane setup (Moltbot iOS)
|
||||||
|
|
||||||
Install:
|
Install:
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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" : [
|
||||||
|
|||||||
@@ -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: [
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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`).
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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? {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ClawdbotProtocol
|
import MoltbotProtocol
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ChannelsSettings {
|
extension ChannelsSettings {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ClawdbotProtocol
|
import MoltbotProtocol
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension ChannelsStore {
|
extension ChannelsStore {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ClawdbotProtocol
|
import MoltbotProtocol
|
||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
|
|
||||||
|
|||||||
@@ -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] {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) ?? ""
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user