clawdbot 2026.1.4-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/README-header.png +0 -0
- package/README.md +297 -0
- package/dist/agents/agent-paths.js +17 -0
- package/dist/agents/bash-process-registry.js +126 -0
- package/dist/agents/bash-tools.js +837 -0
- package/dist/agents/clawdbot-tools.js +30 -0
- package/dist/agents/clawdis-tools.js +27 -0
- package/dist/agents/context.js +34 -0
- package/dist/agents/defaults.js +6 -0
- package/dist/agents/model-auth.js +112 -0
- package/dist/agents/model-catalog.js +55 -0
- package/dist/agents/model-fallback.js +191 -0
- package/dist/agents/model-scan.js +263 -0
- package/dist/agents/model-selection.js +116 -0
- package/dist/agents/models-config.js +49 -0
- package/dist/agents/pi-embedded-helpers.js +74 -0
- package/dist/agents/pi-embedded-runner.js +407 -0
- package/dist/agents/pi-embedded-subscribe.js +568 -0
- package/dist/agents/pi-embedded-utils.js +20 -0
- package/dist/agents/pi-embedded.js +1 -0
- package/dist/agents/pi-oauth.js +88 -0
- package/dist/agents/pi-tools.js +433 -0
- package/dist/agents/sandbox-paths.js +68 -0
- package/dist/agents/sandbox.js +644 -0
- package/dist/agents/shell-utils.js +53 -0
- package/dist/agents/skills-install.js +244 -0
- package/dist/agents/skills-status.js +157 -0
- package/dist/agents/skills.js +470 -0
- package/dist/agents/steerable-agent-loop.js +338 -0
- package/dist/agents/steerable-provider-transport.js +48 -0
- package/dist/agents/system-prompt.js +104 -0
- package/dist/agents/tool-display.js +162 -0
- package/dist/agents/tool-images.js +138 -0
- package/dist/agents/tools/browser-tool.js +339 -0
- package/dist/agents/tools/canvas-tool.js +193 -0
- package/dist/agents/tools/common.js +88 -0
- package/dist/agents/tools/cron-tool.js +124 -0
- package/dist/agents/tools/discord-actions-guild.js +186 -0
- package/dist/agents/tools/discord-actions-messaging.js +285 -0
- package/dist/agents/tools/discord-actions-moderation.js +70 -0
- package/dist/agents/tools/discord-actions.js +56 -0
- package/dist/agents/tools/discord-schema.js +199 -0
- package/dist/agents/tools/discord-tool.js +16 -0
- package/dist/agents/tools/gateway-tool.js +46 -0
- package/dist/agents/tools/gateway.js +27 -0
- package/dist/agents/tools/image-tool.js +132 -0
- package/dist/agents/tools/nodes-tool.js +413 -0
- package/dist/agents/tools/nodes-utils.js +92 -0
- package/dist/agents/tools/sessions-helpers.js +88 -0
- package/dist/agents/tools/sessions-history-tool.js +53 -0
- package/dist/agents/tools/sessions-list-tool.js +143 -0
- package/dist/agents/tools/sessions-send-helpers.js +100 -0
- package/dist/agents/tools/sessions-send-tool.js +347 -0
- package/dist/agents/tools/slack-actions.js +129 -0
- package/dist/agents/tools/slack-schema.js +59 -0
- package/dist/agents/tools/slack-tool.js +16 -0
- package/dist/agents/usage.js +39 -0
- package/dist/agents/workspace.js +241 -0
- package/dist/auto-reply/chunk.js +76 -0
- package/dist/auto-reply/envelope.js +38 -0
- package/dist/auto-reply/group-activation.js +20 -0
- package/dist/auto-reply/heartbeat.js +57 -0
- package/dist/auto-reply/model.js +14 -0
- package/dist/auto-reply/reply/abort.js +14 -0
- package/dist/auto-reply/reply/agent-runner.js +371 -0
- package/dist/auto-reply/reply/block-streaming.js +34 -0
- package/dist/auto-reply/reply/body.js +29 -0
- package/dist/auto-reply/reply/commands.js +207 -0
- package/dist/auto-reply/reply/directive-handling.js +361 -0
- package/dist/auto-reply/reply/directives.js +47 -0
- package/dist/auto-reply/reply/followup-runner.js +149 -0
- package/dist/auto-reply/reply/groups.js +91 -0
- package/dist/auto-reply/reply/mentions.js +38 -0
- package/dist/auto-reply/reply/model-selection.js +114 -0
- package/dist/auto-reply/reply/queue.js +399 -0
- package/dist/auto-reply/reply/reply-tags.js +26 -0
- package/dist/auto-reply/reply/session-updates.js +87 -0
- package/dist/auto-reply/reply/session.js +160 -0
- package/dist/auto-reply/reply/typing.js +75 -0
- package/dist/auto-reply/reply.js +535 -0
- package/dist/auto-reply/send-policy.js +28 -0
- package/dist/auto-reply/status.js +158 -0
- package/dist/auto-reply/templating.js +9 -0
- package/dist/auto-reply/thinking.js +49 -0
- package/dist/auto-reply/tokens.js +2 -0
- package/dist/auto-reply/tool-meta.js +74 -0
- package/dist/auto-reply/transcription.js +57 -0
- package/dist/auto-reply/types.js +1 -0
- package/dist/browser/bridge-server.js +37 -0
- package/dist/browser/cdp.js +382 -0
- package/dist/browser/chrome.js +432 -0
- package/dist/browser/client-actions-core.js +67 -0
- package/dist/browser/client-actions-observe.js +24 -0
- package/dist/browser/client-actions-types.js +1 -0
- package/dist/browser/client-actions.js +3 -0
- package/dist/browser/client-fetch.js +43 -0
- package/dist/browser/client.js +105 -0
- package/dist/browser/config.js +140 -0
- package/dist/browser/constants.js +4 -0
- package/dist/browser/profiles-service.js +122 -0
- package/dist/browser/profiles.js +85 -0
- package/dist/browser/pw-ai.js +2 -0
- package/dist/browser/pw-session.js +144 -0
- package/dist/browser/pw-tools-core.js +363 -0
- package/dist/browser/routes/agent.js +535 -0
- package/dist/browser/routes/basic.js +155 -0
- package/dist/browser/routes/index.js +8 -0
- package/dist/browser/routes/tabs.js +105 -0
- package/dist/browser/routes/utils.js +62 -0
- package/dist/browser/screenshot.js +40 -0
- package/dist/browser/server-context.js +377 -0
- package/dist/browser/server.js +81 -0
- package/dist/browser/target-id.js +18 -0
- package/dist/browser/trash.js +21 -0
- package/dist/canvas-host/a2ui/.bundle.hash +1 -0
- package/dist/canvas-host/a2ui/a2ui.bundle.js +17768 -0
- package/dist/canvas-host/a2ui/index.html +246 -0
- package/dist/canvas-host/a2ui.js +187 -0
- package/dist/canvas-host/server.js +382 -0
- package/dist/cli/browser-cli-actions-input.js +459 -0
- package/dist/cli/browser-cli-actions-observe.js +56 -0
- package/dist/cli/browser-cli-examples.js +31 -0
- package/dist/cli/browser-cli-inspect.js +97 -0
- package/dist/cli/browser-cli-manage.js +286 -0
- package/dist/cli/browser-cli-shared.js +1 -0
- package/dist/cli/browser-cli.js +26 -0
- package/dist/cli/canvas-cli.js +416 -0
- package/dist/cli/cron-cli.js +454 -0
- package/dist/cli/deps.js +17 -0
- package/dist/cli/dns-cli.js +180 -0
- package/dist/cli/gateway-cli.js +489 -0
- package/dist/cli/gateway-rpc.js +20 -0
- package/dist/cli/hooks-cli.js +135 -0
- package/dist/cli/models-cli.js +248 -0
- package/dist/cli/nodes-camera.js +57 -0
- package/dist/cli/nodes-canvas.js +26 -0
- package/dist/cli/nodes-cli.js +946 -0
- package/dist/cli/nodes-screen.js +37 -0
- package/dist/cli/parse-duration.js +20 -0
- package/dist/cli/ports.js +97 -0
- package/dist/cli/program.js +406 -0
- package/dist/cli/prompt.js +19 -0
- package/dist/cli/tui-cli.js +35 -0
- package/dist/cli/wait.js +8 -0
- package/dist/commands/agent.js +645 -0
- package/dist/commands/antigravity-oauth.js +327 -0
- package/dist/commands/configure.js +480 -0
- package/dist/commands/doctor.js +484 -0
- package/dist/commands/health.js +108 -0
- package/dist/commands/models/aliases.js +64 -0
- package/dist/commands/models/fallbacks.js +99 -0
- package/dist/commands/models/image-fallbacks.js +99 -0
- package/dist/commands/models/list.js +323 -0
- package/dist/commands/models/scan.js +266 -0
- package/dist/commands/models/set-image.js +23 -0
- package/dist/commands/models/set.js +23 -0
- package/dist/commands/models/shared.js +72 -0
- package/dist/commands/models.js +7 -0
- package/dist/commands/onboard-auth.js +70 -0
- package/dist/commands/onboard-helpers.js +295 -0
- package/dist/commands/onboard-interactive.js +17 -0
- package/dist/commands/onboard-non-interactive.js +202 -0
- package/dist/commands/onboard-providers.js +634 -0
- package/dist/commands/onboard-remote.js +120 -0
- package/dist/commands/onboard-skills.js +148 -0
- package/dist/commands/onboard-types.js +1 -0
- package/dist/commands/onboard.js +12 -0
- package/dist/commands/send.js +124 -0
- package/dist/commands/sessions.js +212 -0
- package/dist/commands/setup.js +58 -0
- package/dist/commands/signal-install.js +135 -0
- package/dist/commands/status.js +207 -0
- package/dist/commands/update.js +16 -0
- package/dist/config/config.js +6 -0
- package/dist/config/defaults.js +61 -0
- package/dist/config/io.js +147 -0
- package/dist/config/legacy-migrate.js +13 -0
- package/dist/config/legacy.js +159 -0
- package/dist/config/paths.js +71 -0
- package/dist/config/schema.js +150 -0
- package/dist/config/sessions.js +282 -0
- package/dist/config/talk.js +31 -0
- package/dist/config/types.js +1 -0
- package/dist/config/validation.js +29 -0
- package/dist/config/zod-schema.js +831 -0
- package/dist/control-ui/assets/index-BFID3yAA.css +1 -0
- package/dist/control-ui/assets/index-CE_axlTS.js +2235 -0
- package/dist/control-ui/assets/index-CE_axlTS.js.map +1 -0
- package/dist/control-ui/index.html +15 -0
- package/dist/cron/isolated-agent.js +499 -0
- package/dist/cron/run-log.js +72 -0
- package/dist/cron/schedule.js +24 -0
- package/dist/cron/service.js +471 -0
- package/dist/cron/store.js +43 -0
- package/dist/cron/types.js +1 -0
- package/dist/daemon/constants.js +10 -0
- package/dist/daemon/launchd.js +276 -0
- package/dist/daemon/legacy.js +63 -0
- package/dist/daemon/program-args.js +76 -0
- package/dist/daemon/schtasks.js +257 -0
- package/dist/daemon/service.js +60 -0
- package/dist/daemon/systemd.js +266 -0
- package/dist/discord/index.js +2 -0
- package/dist/discord/monitor.js +1188 -0
- package/dist/discord/probe.js +54 -0
- package/dist/discord/send.js +577 -0
- package/dist/discord/token.js +8 -0
- package/dist/gateway/auth.js +121 -0
- package/dist/gateway/call.js +94 -0
- package/dist/gateway/chat-attachments.js +41 -0
- package/dist/gateway/client.js +180 -0
- package/dist/gateway/config-reload.js +274 -0
- package/dist/gateway/control-ui.js +184 -0
- package/dist/gateway/hooks-mapping.js +282 -0
- package/dist/gateway/hooks.js +168 -0
- package/dist/gateway/net.js +29 -0
- package/dist/gateway/protocol/index.js +61 -0
- package/dist/gateway/protocol/schema.js +560 -0
- package/dist/gateway/server-bridge-subscriptions.js +93 -0
- package/dist/gateway/server-bridge.js +1013 -0
- package/dist/gateway/server-browser.js +12 -0
- package/dist/gateway/server-chat.js +159 -0
- package/dist/gateway/server-constants.js +8 -0
- package/dist/gateway/server-discovery.js +62 -0
- package/dist/gateway/server-http.js +165 -0
- package/dist/gateway/server-methods/agent-job.js +125 -0
- package/dist/gateway/server-methods/agent.js +250 -0
- package/dist/gateway/server-methods/chat.js +200 -0
- package/dist/gateway/server-methods/config.js +50 -0
- package/dist/gateway/server-methods/connect.js +6 -0
- package/dist/gateway/server-methods/cron.js +83 -0
- package/dist/gateway/server-methods/health.js +28 -0
- package/dist/gateway/server-methods/models.js +16 -0
- package/dist/gateway/server-methods/nodes.js +294 -0
- package/dist/gateway/server-methods/providers.js +217 -0
- package/dist/gateway/server-methods/send.js +166 -0
- package/dist/gateway/server-methods/sessions.js +305 -0
- package/dist/gateway/server-methods/skills.js +83 -0
- package/dist/gateway/server-methods/system.js +118 -0
- package/dist/gateway/server-methods/talk.js +22 -0
- package/dist/gateway/server-methods/types.js +1 -0
- package/dist/gateway/server-methods/voicewake.js +30 -0
- package/dist/gateway/server-methods/web.js +58 -0
- package/dist/gateway/server-methods/wizard.js +100 -0
- package/dist/gateway/server-methods.js +53 -0
- package/dist/gateway/server-providers.js +644 -0
- package/dist/gateway/server-shared.js +1 -0
- package/dist/gateway/server-utils.js +35 -0
- package/dist/gateway/server.js +1437 -0
- package/dist/gateway/session-utils.js +216 -0
- package/dist/gateway/ws-log.js +349 -0
- package/dist/gateway/ws-logging.js +8 -0
- package/dist/globals.js +41 -0
- package/dist/hooks/gmail-ops.js +236 -0
- package/dist/hooks/gmail-setup-utils.js +278 -0
- package/dist/hooks/gmail-watcher.js +175 -0
- package/dist/hooks/gmail.js +177 -0
- package/dist/imessage/client.js +165 -0
- package/dist/imessage/index.js +3 -0
- package/dist/imessage/monitor.js +272 -0
- package/dist/imessage/probe.js +26 -0
- package/dist/imessage/send.js +83 -0
- package/dist/imessage/targets.js +176 -0
- package/dist/index.js +50 -0
- package/dist/infra/agent-events.js +46 -0
- package/dist/infra/binaries.js +9 -0
- package/dist/infra/bonjour-discovery.js +163 -0
- package/dist/infra/bonjour.js +200 -0
- package/dist/infra/bridge/server.js +562 -0
- package/dist/infra/canvas-host-url.js +54 -0
- package/dist/infra/env.js +8 -0
- package/dist/infra/errors.js +28 -0
- package/dist/infra/gateway-lock.js +8 -0
- package/dist/infra/heartbeat-events.js +21 -0
- package/dist/infra/heartbeat-runner.js +453 -0
- package/dist/infra/heartbeat-wake.js +61 -0
- package/dist/infra/is-main.js +37 -0
- package/dist/infra/machine-name.js +40 -0
- package/dist/infra/node-pairing.js +211 -0
- package/dist/infra/pam.js +42 -0
- package/dist/infra/path-env.js +92 -0
- package/dist/infra/ports.js +87 -0
- package/dist/infra/provider-summary.js +80 -0
- package/dist/infra/restart.js +29 -0
- package/dist/infra/retry.js +16 -0
- package/dist/infra/runtime-guard.js +59 -0
- package/dist/infra/system-events.js +44 -0
- package/dist/infra/system-presence.js +216 -0
- package/dist/infra/tailnet.js +46 -0
- package/dist/infra/tailscale.js +149 -0
- package/dist/infra/voicewake.js +77 -0
- package/dist/infra/widearea-dns.js +123 -0
- package/dist/infra/ws.js +13 -0
- package/dist/logger.js +52 -0
- package/dist/logging.js +490 -0
- package/dist/macos/gateway-daemon.js +141 -0
- package/dist/macos/relay.js +46 -0
- package/dist/media/constants.js +33 -0
- package/dist/media/host.js +42 -0
- package/dist/media/image-ops.js +121 -0
- package/dist/media/mime.js +115 -0
- package/dist/media/parse.js +81 -0
- package/dist/media/server.js +64 -0
- package/dist/media/store.js +139 -0
- package/dist/process/command-queue.js +97 -0
- package/dist/process/exec.js +75 -0
- package/dist/protocol.schema.json +2918 -0
- package/dist/provider-web.js +8 -0
- package/dist/providers/web/index.js +2 -0
- package/dist/runtime.js +8 -0
- package/dist/sessions/send-policy.js +68 -0
- package/dist/signal/client.js +134 -0
- package/dist/signal/daemon.js +69 -0
- package/dist/signal/index.js +3 -0
- package/dist/signal/monitor.js +336 -0
- package/dist/signal/probe.js +46 -0
- package/dist/signal/send.js +91 -0
- package/dist/slack/actions.js +97 -0
- package/dist/slack/index.js +5 -0
- package/dist/slack/monitor.js +1029 -0
- package/dist/slack/probe.js +47 -0
- package/dist/slack/send.js +131 -0
- package/dist/slack/token.js +10 -0
- package/dist/telegram/bot.js +394 -0
- package/dist/telegram/download.js +34 -0
- package/dist/telegram/index.js +4 -0
- package/dist/telegram/monitor.js +47 -0
- package/dist/telegram/probe.js +63 -0
- package/dist/telegram/proxy.js +9 -0
- package/dist/telegram/send.js +138 -0
- package/dist/telegram/token.js +30 -0
- package/dist/telegram/webhook-set.js +12 -0
- package/dist/telegram/webhook.js +56 -0
- package/dist/tui/commands.js +74 -0
- package/dist/tui/components/assistant-message.js +16 -0
- package/dist/tui/components/chat-log.js +92 -0
- package/dist/tui/components/custom-editor.js +53 -0
- package/dist/tui/components/selectors.js +8 -0
- package/dist/tui/components/tool-execution.js +111 -0
- package/dist/tui/components/user-message.js +17 -0
- package/dist/tui/gateway-chat.js +140 -0
- package/dist/tui/layout.js +41 -0
- package/dist/tui/message-list.js +57 -0
- package/dist/tui/theme/theme.js +80 -0
- package/dist/tui/theme.js +25 -0
- package/dist/tui/tui.js +708 -0
- package/dist/utils.js +133 -0
- package/dist/version.js +18 -0
- package/dist/web/active-listener.js +7 -0
- package/dist/web/auto-reply.js +1203 -0
- package/dist/web/inbound.js +481 -0
- package/dist/web/login-qr.js +204 -0
- package/dist/web/login.js +59 -0
- package/dist/web/media.js +148 -0
- package/dist/web/outbound.js +67 -0
- package/dist/web/qr-image.js +97 -0
- package/dist/web/reconnect.js +60 -0
- package/dist/web/reply-heartbeat-wake.js +61 -0
- package/dist/web/session.js +346 -0
- package/dist/wizard/clack-prompter.js +56 -0
- package/dist/wizard/onboarding.js +452 -0
- package/dist/wizard/prompts.js +6 -0
- package/dist/wizard/session.js +203 -0
- package/docs/AGENTS.default.md +116 -0
- package/docs/CNAME +1 -0
- package/docs/RELEASING.md +64 -0
- package/docs/_config.yml +51 -0
- package/docs/_layouts/default.html +145 -0
- package/docs/agent-send.md +21 -0
- package/docs/agent.md +104 -0
- package/docs/android/connect.md +131 -0
- package/docs/architecture.md +89 -0
- package/docs/assets/markdown.css +130 -0
- package/docs/assets/pixel-lobster.svg +60 -0
- package/docs/assets/terminal.css +497 -0
- package/docs/assets/theme.js +55 -0
- package/docs/audio.md +50 -0
- package/docs/background-process.md +74 -0
- package/docs/bash.md +32 -0
- package/docs/bonjour.md +159 -0
- package/docs/browser.md +289 -0
- package/docs/camera.md +152 -0
- package/docs/clawd.md +199 -0
- package/docs/clawdbot-mac.md +104 -0
- package/docs/configuration.md +1177 -0
- package/docs/control-api.md +49 -0
- package/docs/control-ui.md +83 -0
- package/docs/cron.md +374 -0
- package/docs/dashboard.md +17 -0
- package/docs/device-models.md +46 -0
- package/docs/discord.md +293 -0
- package/docs/discovery.md +112 -0
- package/docs/docker.md +251 -0
- package/docs/docs.json +86 -0
- package/docs/doctor.md +47 -0
- package/docs/elevated.md +31 -0
- package/docs/faq.md +640 -0
- package/docs/gateway/pairing.md +109 -0
- package/docs/gateway-lock.md +28 -0
- package/docs/gateway.md +174 -0
- package/docs/gmail-pubsub.md +191 -0
- package/docs/grammy.md +27 -0
- package/docs/group-messages.md +71 -0
- package/docs/groups.md +78 -0
- package/docs/health.md +28 -0
- package/docs/heartbeat.md +64 -0
- package/docs/images.md +52 -0
- package/docs/imessage.md +63 -0
- package/docs/index.md +182 -0
- package/docs/ios/connect.md +177 -0
- package/docs/ios/spec.md +236 -0
- package/docs/location-command.md +95 -0
- package/docs/logging.md +99 -0
- package/docs/lore.md +131 -0
- package/docs/mac/bun.md +133 -0
- package/docs/mac/canvas.md +161 -0
- package/docs/mac/child-process.md +72 -0
- package/docs/mac/dev-setup.md +81 -0
- package/docs/mac/health.md +28 -0
- package/docs/mac/icon.md +26 -0
- package/docs/mac/logging.md +51 -0
- package/docs/mac/menu-bar.md +69 -0
- package/docs/mac/peekaboo.md +170 -0
- package/docs/mac/permissions.md +40 -0
- package/docs/mac/release.md +76 -0
- package/docs/mac/remote.md +57 -0
- package/docs/mac/signing.md +41 -0
- package/docs/mac/skills.md +27 -0
- package/docs/mac/voice-overlay.md +52 -0
- package/docs/mac/voicewake.md +56 -0
- package/docs/mac/webchat.md +27 -0
- package/docs/mac/xpc.md +40 -0
- package/docs/models.md +90 -0
- package/docs/nix.md +49 -0
- package/docs/nodes.md +157 -0
- package/docs/onboarding-config-protocol.md +29 -0
- package/docs/onboarding.md +185 -0
- package/docs/presence.md +133 -0
- package/docs/queue.md +78 -0
- package/docs/refactor/browser-control-simplification.md +58 -0
- package/docs/refactor/canvas-a2ui.md +93 -0
- package/docs/refactor/cli-unification.md +64 -0
- package/docs/refactor/gateway-client.md +31 -0
- package/docs/refactor/gateway.md +99 -0
- package/docs/refactor/new-arch.md +171 -0
- package/docs/refactor/tui.md +26 -0
- package/docs/refactor/web-gateway-troubleshooting.md +37 -0
- package/docs/refactor/webagent-session.md +46 -0
- package/docs/remote-gateway-readme.md +148 -0
- package/docs/remote.md +66 -0
- package/docs/research/memory.md +227 -0
- package/docs/rpc.md +35 -0
- package/docs/security.md +168 -0
- package/docs/session-tool.md +119 -0
- package/docs/session.md +84 -0
- package/docs/sessions.md +8 -0
- package/docs/setup.md +118 -0
- package/docs/signal.md +113 -0
- package/docs/skills-config.md +58 -0
- package/docs/skills.md +149 -0
- package/docs/slack.md +158 -0
- package/docs/surface.md +20 -0
- package/docs/tailscale.md +71 -0
- package/docs/talk.md +79 -0
- package/docs/telegram.md +90 -0
- package/docs/templates/AGENTS.md +126 -0
- package/docs/templates/BOOTSTRAP.md +53 -0
- package/docs/templates/IDENTITY.md +17 -0
- package/docs/templates/SOUL.md +41 -0
- package/docs/templates/TOOLS.md +41 -0
- package/docs/templates/USER.md +22 -0
- package/docs/test.md +35 -0
- package/docs/thinking.md +46 -0
- package/docs/tools.md +248 -0
- package/docs/troubleshooting.md +227 -0
- package/docs/tui.md +69 -0
- package/docs/typebox.md +42 -0
- package/docs/voicewake.md +61 -0
- package/docs/web.md +115 -0
- package/docs/webchat.md +34 -0
- package/docs/webhook.md +132 -0
- package/docs/whatsapp-clawd.jpg +0 -0
- package/docs/whatsapp.md +142 -0
- package/docs/wizard.md +158 -0
- package/package.json +186 -0
- package/skills/apple-notes/SKILL.md +50 -0
- package/skills/apple-reminders/SKILL.md +67 -0
- package/skills/bear-notes/SKILL.md +79 -0
- package/skills/bird/SKILL.md +25 -0
- package/skills/blogwatcher/SKILL.md +46 -0
- package/skills/blucli/SKILL.md +27 -0
- package/skills/brave-search/SKILL.md +30 -0
- package/skills/brave-search/scripts/content.mjs +53 -0
- package/skills/brave-search/scripts/search.mjs +79 -0
- package/skills/camsnap/SKILL.md +25 -0
- package/skills/clawdhub/SKILL.md +53 -0
- package/skills/coding-agent/SKILL.md +275 -0
- package/skills/discord/SKILL.md +369 -0
- package/skills/eightctl/SKILL.md +29 -0
- package/skills/food-order/SKILL.md +41 -0
- package/skills/gemini/SKILL.md +23 -0
- package/skills/gifgrep/SKILL.md +47 -0
- package/skills/github/SKILL.md +47 -0
- package/skills/gog/SKILL.md +36 -0
- package/skills/goplaces/SKILL.md +30 -0
- package/skills/imsg/SKILL.md +25 -0
- package/skills/local-places/SERVER_README.md +101 -0
- package/skills/local-places/SKILL.md +91 -0
- package/skills/local-places/pyproject.toml +27 -0
- package/skills/local-places/src/local_places/__init__.py +2 -0
- package/skills/local-places/src/local_places/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/google_places.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/main.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/schemas.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/google_places.py +314 -0
- package/skills/local-places/src/local_places/main.py +65 -0
- package/skills/local-places/src/local_places/schemas.py +107 -0
- package/skills/mcporter/SKILL.md +38 -0
- package/skills/nano-banana-pro/SKILL.md +29 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +167 -0
- package/skills/nano-pdf/SKILL.md +20 -0
- package/skills/notion/SKILL.md +156 -0
- package/skills/obsidian/SKILL.md +55 -0
- package/skills/openai-image-gen/SKILL.md +31 -0
- package/skills/openai-image-gen/scripts/gen.py +173 -0
- package/skills/openai-whisper/SKILL.md +19 -0
- package/skills/openai-whisper-api/SKILL.md +43 -0
- package/skills/openai-whisper-api/scripts/transcribe.sh +85 -0
- package/skills/openhue/SKILL.md +30 -0
- package/skills/oracle/SKILL.md +105 -0
- package/skills/ordercli/SKILL.md +47 -0
- package/skills/peekaboo/SKILL.md +153 -0
- package/skills/qmd/SKILL.md +26 -0
- package/skills/sag/SKILL.md +62 -0
- package/skills/slack/SKILL.md +143 -0
- package/skills/songsee/SKILL.md +29 -0
- package/skills/sonoscli/SKILL.md +26 -0
- package/skills/spotify-player/SKILL.md +34 -0
- package/skills/summarize/SKILL.md +49 -0
- package/skills/things-mac/SKILL.md +61 -0
- package/skills/tmux/SKILL.md +121 -0
- package/skills/tmux/scripts/find-sessions.sh +112 -0
- package/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/skills/trello/SKILL.md +84 -0
- package/skills/video-frames/SKILL.md +29 -0
- package/skills/video-frames/scripts/frame.sh +81 -0
- package/skills/wacli/SKILL.md +42 -0
- package/skills/weather/SKILL.md +49 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { startBrowserBridgeServer, stopBrowserBridgeServer, } from "../browser/bridge-server.js";
|
|
7
|
+
import { resolveProfile, } from "../browser/config.js";
|
|
8
|
+
import { DEFAULT_CLAWD_BROWSER_COLOR } from "../browser/constants.js";
|
|
9
|
+
import { STATE_DIR_CLAWDBOT } from "../config/config.js";
|
|
10
|
+
import { defaultRuntime } from "../runtime.js";
|
|
11
|
+
import { resolveUserPath } from "../utils.js";
|
|
12
|
+
import { DEFAULT_AGENT_WORKSPACE_DIR, DEFAULT_AGENTS_FILENAME, DEFAULT_BOOTSTRAP_FILENAME, DEFAULT_IDENTITY_FILENAME, DEFAULT_SOUL_FILENAME, DEFAULT_TOOLS_FILENAME, DEFAULT_USER_FILENAME, ensureAgentWorkspace, } from "./workspace.js";
|
|
13
|
+
const DEFAULT_SANDBOX_WORKSPACE_ROOT = path.join(os.homedir(), ".clawdbot", "sandboxes");
|
|
14
|
+
export const DEFAULT_SANDBOX_IMAGE = "clawdbot-sandbox:bookworm-slim";
|
|
15
|
+
const DEFAULT_SANDBOX_CONTAINER_PREFIX = "clawdbot-sbx-";
|
|
16
|
+
const DEFAULT_SANDBOX_WORKDIR = "/workspace";
|
|
17
|
+
const DEFAULT_SANDBOX_IDLE_HOURS = 24;
|
|
18
|
+
const DEFAULT_SANDBOX_MAX_AGE_DAYS = 7;
|
|
19
|
+
const DEFAULT_TOOL_ALLOW = ["bash", "process", "read", "write", "edit"];
|
|
20
|
+
const DEFAULT_TOOL_DENY = [
|
|
21
|
+
"browser",
|
|
22
|
+
"canvas",
|
|
23
|
+
"nodes",
|
|
24
|
+
"cron",
|
|
25
|
+
"discord",
|
|
26
|
+
"gateway",
|
|
27
|
+
];
|
|
28
|
+
export const DEFAULT_SANDBOX_BROWSER_IMAGE = "clawdbot-sandbox-browser:bookworm-slim";
|
|
29
|
+
export const DEFAULT_SANDBOX_COMMON_IMAGE = "clawdbot-sandbox-common:bookworm-slim";
|
|
30
|
+
const DEFAULT_SANDBOX_BROWSER_PREFIX = "clawdbot-sbx-browser-";
|
|
31
|
+
const DEFAULT_SANDBOX_BROWSER_CDP_PORT = 9222;
|
|
32
|
+
const DEFAULT_SANDBOX_BROWSER_VNC_PORT = 5900;
|
|
33
|
+
const DEFAULT_SANDBOX_BROWSER_NOVNC_PORT = 6080;
|
|
34
|
+
const SANDBOX_STATE_DIR = path.join(STATE_DIR_CLAWDBOT, "sandbox");
|
|
35
|
+
const SANDBOX_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "containers.json");
|
|
36
|
+
const SANDBOX_BROWSER_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "browsers.json");
|
|
37
|
+
let lastPruneAtMs = 0;
|
|
38
|
+
const BROWSER_BRIDGES = new Map();
|
|
39
|
+
function normalizeToolList(values) {
|
|
40
|
+
if (!values)
|
|
41
|
+
return [];
|
|
42
|
+
return values
|
|
43
|
+
.map((value) => value.trim())
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.map((value) => value.toLowerCase());
|
|
46
|
+
}
|
|
47
|
+
function isToolAllowed(policy, name) {
|
|
48
|
+
const deny = new Set(normalizeToolList(policy.deny));
|
|
49
|
+
if (deny.has(name.toLowerCase()))
|
|
50
|
+
return false;
|
|
51
|
+
const allow = normalizeToolList(policy.allow);
|
|
52
|
+
if (allow.length === 0)
|
|
53
|
+
return true;
|
|
54
|
+
return allow.includes(name.toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
function defaultSandboxConfig(cfg) {
|
|
57
|
+
const agent = cfg?.agent?.sandbox;
|
|
58
|
+
return {
|
|
59
|
+
mode: agent?.mode ?? "off",
|
|
60
|
+
perSession: agent?.perSession ?? true,
|
|
61
|
+
workspaceRoot: agent?.workspaceRoot ?? DEFAULT_SANDBOX_WORKSPACE_ROOT,
|
|
62
|
+
docker: {
|
|
63
|
+
image: agent?.docker?.image ?? DEFAULT_SANDBOX_IMAGE,
|
|
64
|
+
containerPrefix: agent?.docker?.containerPrefix ?? DEFAULT_SANDBOX_CONTAINER_PREFIX,
|
|
65
|
+
workdir: agent?.docker?.workdir ?? DEFAULT_SANDBOX_WORKDIR,
|
|
66
|
+
readOnlyRoot: agent?.docker?.readOnlyRoot ?? true,
|
|
67
|
+
tmpfs: agent?.docker?.tmpfs ?? ["/tmp", "/var/tmp", "/run"],
|
|
68
|
+
network: agent?.docker?.network ?? "none",
|
|
69
|
+
user: agent?.docker?.user,
|
|
70
|
+
capDrop: agent?.docker?.capDrop ?? ["ALL"],
|
|
71
|
+
env: agent?.docker?.env ?? { LANG: "C.UTF-8" },
|
|
72
|
+
setupCommand: agent?.docker?.setupCommand,
|
|
73
|
+
pidsLimit: agent?.docker?.pidsLimit,
|
|
74
|
+
memory: agent?.docker?.memory,
|
|
75
|
+
memorySwap: agent?.docker?.memorySwap,
|
|
76
|
+
cpus: agent?.docker?.cpus,
|
|
77
|
+
ulimits: agent?.docker?.ulimits,
|
|
78
|
+
seccompProfile: agent?.docker?.seccompProfile,
|
|
79
|
+
apparmorProfile: agent?.docker?.apparmorProfile,
|
|
80
|
+
dns: agent?.docker?.dns,
|
|
81
|
+
extraHosts: agent?.docker?.extraHosts,
|
|
82
|
+
},
|
|
83
|
+
browser: {
|
|
84
|
+
enabled: agent?.browser?.enabled ?? false,
|
|
85
|
+
image: agent?.browser?.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE,
|
|
86
|
+
containerPrefix: agent?.browser?.containerPrefix ?? DEFAULT_SANDBOX_BROWSER_PREFIX,
|
|
87
|
+
cdpPort: agent?.browser?.cdpPort ?? DEFAULT_SANDBOX_BROWSER_CDP_PORT,
|
|
88
|
+
vncPort: agent?.browser?.vncPort ?? DEFAULT_SANDBOX_BROWSER_VNC_PORT,
|
|
89
|
+
noVncPort: agent?.browser?.noVncPort ?? DEFAULT_SANDBOX_BROWSER_NOVNC_PORT,
|
|
90
|
+
headless: agent?.browser?.headless ?? false,
|
|
91
|
+
enableNoVnc: agent?.browser?.enableNoVnc ?? true,
|
|
92
|
+
},
|
|
93
|
+
tools: {
|
|
94
|
+
allow: agent?.tools?.allow ?? DEFAULT_TOOL_ALLOW,
|
|
95
|
+
deny: agent?.tools?.deny ?? DEFAULT_TOOL_DENY,
|
|
96
|
+
},
|
|
97
|
+
prune: {
|
|
98
|
+
idleHours: agent?.prune?.idleHours ?? DEFAULT_SANDBOX_IDLE_HOURS,
|
|
99
|
+
maxAgeDays: agent?.prune?.maxAgeDays ?? DEFAULT_SANDBOX_MAX_AGE_DAYS,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function shouldSandboxSession(cfg, sessionKey, mainKey) {
|
|
104
|
+
if (cfg.mode === "off")
|
|
105
|
+
return false;
|
|
106
|
+
if (cfg.mode === "all")
|
|
107
|
+
return true;
|
|
108
|
+
return sessionKey.trim() !== mainKey.trim();
|
|
109
|
+
}
|
|
110
|
+
function slugifySessionKey(value) {
|
|
111
|
+
const trimmed = value.trim() || "session";
|
|
112
|
+
const hash = crypto
|
|
113
|
+
.createHash("sha1")
|
|
114
|
+
.update(trimmed)
|
|
115
|
+
.digest("hex")
|
|
116
|
+
.slice(0, 8);
|
|
117
|
+
const safe = trimmed
|
|
118
|
+
.toLowerCase()
|
|
119
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
120
|
+
.replace(/^-+|-+$/g, "");
|
|
121
|
+
const base = safe.slice(0, 32) || "session";
|
|
122
|
+
return `${base}-${hash}`;
|
|
123
|
+
}
|
|
124
|
+
function resolveSandboxWorkspaceDir(root, sessionKey) {
|
|
125
|
+
const resolvedRoot = resolveUserPath(root);
|
|
126
|
+
const slug = slugifySessionKey(sessionKey);
|
|
127
|
+
return path.join(resolvedRoot, slug);
|
|
128
|
+
}
|
|
129
|
+
async function readRegistry() {
|
|
130
|
+
try {
|
|
131
|
+
const raw = await fs.readFile(SANDBOX_REGISTRY_PATH, "utf-8");
|
|
132
|
+
const parsed = JSON.parse(raw);
|
|
133
|
+
if (parsed && Array.isArray(parsed.entries))
|
|
134
|
+
return parsed;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// ignore
|
|
138
|
+
}
|
|
139
|
+
return { entries: [] };
|
|
140
|
+
}
|
|
141
|
+
async function writeRegistry(registry) {
|
|
142
|
+
await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
|
|
143
|
+
await fs.writeFile(SANDBOX_REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
|
|
144
|
+
}
|
|
145
|
+
async function updateRegistry(entry) {
|
|
146
|
+
const registry = await readRegistry();
|
|
147
|
+
const existing = registry.entries.find((item) => item.containerName === entry.containerName);
|
|
148
|
+
const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
|
|
149
|
+
next.push({
|
|
150
|
+
...entry,
|
|
151
|
+
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
|
|
152
|
+
image: existing?.image ?? entry.image,
|
|
153
|
+
});
|
|
154
|
+
await writeRegistry({ entries: next });
|
|
155
|
+
}
|
|
156
|
+
async function removeRegistryEntry(containerName) {
|
|
157
|
+
const registry = await readRegistry();
|
|
158
|
+
const next = registry.entries.filter((item) => item.containerName !== containerName);
|
|
159
|
+
if (next.length === registry.entries.length)
|
|
160
|
+
return;
|
|
161
|
+
await writeRegistry({ entries: next });
|
|
162
|
+
}
|
|
163
|
+
async function readBrowserRegistry() {
|
|
164
|
+
try {
|
|
165
|
+
const raw = await fs.readFile(SANDBOX_BROWSER_REGISTRY_PATH, "utf-8");
|
|
166
|
+
const parsed = JSON.parse(raw);
|
|
167
|
+
if (parsed && Array.isArray(parsed.entries))
|
|
168
|
+
return parsed;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// ignore
|
|
172
|
+
}
|
|
173
|
+
return { entries: [] };
|
|
174
|
+
}
|
|
175
|
+
async function writeBrowserRegistry(registry) {
|
|
176
|
+
await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
|
|
177
|
+
await fs.writeFile(SANDBOX_BROWSER_REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
|
|
178
|
+
}
|
|
179
|
+
async function updateBrowserRegistry(entry) {
|
|
180
|
+
const registry = await readBrowserRegistry();
|
|
181
|
+
const existing = registry.entries.find((item) => item.containerName === entry.containerName);
|
|
182
|
+
const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
|
|
183
|
+
next.push({
|
|
184
|
+
...entry,
|
|
185
|
+
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
|
|
186
|
+
image: existing?.image ?? entry.image,
|
|
187
|
+
});
|
|
188
|
+
await writeBrowserRegistry({ entries: next });
|
|
189
|
+
}
|
|
190
|
+
async function removeBrowserRegistryEntry(containerName) {
|
|
191
|
+
const registry = await readBrowserRegistry();
|
|
192
|
+
const next = registry.entries.filter((item) => item.containerName !== containerName);
|
|
193
|
+
if (next.length === registry.entries.length)
|
|
194
|
+
return;
|
|
195
|
+
await writeBrowserRegistry({ entries: next });
|
|
196
|
+
}
|
|
197
|
+
function execDocker(args, opts) {
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
const child = spawn("docker", args, {
|
|
200
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
201
|
+
});
|
|
202
|
+
let stdout = "";
|
|
203
|
+
let stderr = "";
|
|
204
|
+
child.stdout?.on("data", (chunk) => {
|
|
205
|
+
stdout += chunk.toString();
|
|
206
|
+
});
|
|
207
|
+
child.stderr?.on("data", (chunk) => {
|
|
208
|
+
stderr += chunk.toString();
|
|
209
|
+
});
|
|
210
|
+
child.on("close", (code) => {
|
|
211
|
+
const exitCode = code ?? 0;
|
|
212
|
+
if (exitCode !== 0 && !opts?.allowFailure) {
|
|
213
|
+
reject(new Error(stderr.trim() || `docker ${args.join(" ")} failed`));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
resolve({ stdout, stderr, code: exitCode });
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function readDockerPort(containerName, port) {
|
|
221
|
+
const result = await execDocker(["port", containerName, `${port}/tcp`], {
|
|
222
|
+
allowFailure: true,
|
|
223
|
+
});
|
|
224
|
+
if (result.code !== 0)
|
|
225
|
+
return null;
|
|
226
|
+
const line = result.stdout.trim().split(/\r?\n/)[0] ?? "";
|
|
227
|
+
const match = line.match(/:(\d+)\s*$/);
|
|
228
|
+
if (!match)
|
|
229
|
+
return null;
|
|
230
|
+
const mapped = Number.parseInt(match[1] ?? "", 10);
|
|
231
|
+
return Number.isFinite(mapped) ? mapped : null;
|
|
232
|
+
}
|
|
233
|
+
async function dockerImageExists(image) {
|
|
234
|
+
const result = await execDocker(["image", "inspect", image], {
|
|
235
|
+
allowFailure: true,
|
|
236
|
+
});
|
|
237
|
+
return result.code === 0;
|
|
238
|
+
}
|
|
239
|
+
async function ensureDockerImage(image) {
|
|
240
|
+
const exists = await dockerImageExists(image);
|
|
241
|
+
if (exists)
|
|
242
|
+
return;
|
|
243
|
+
if (image === DEFAULT_SANDBOX_IMAGE) {
|
|
244
|
+
await execDocker(["pull", "debian:bookworm-slim"]);
|
|
245
|
+
await execDocker(["tag", "debian:bookworm-slim", DEFAULT_SANDBOX_IMAGE]);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
throw new Error(`Sandbox image not found: ${image}. Build or pull it first.`);
|
|
249
|
+
}
|
|
250
|
+
async function dockerContainerState(name) {
|
|
251
|
+
const result = await execDocker(["inspect", "-f", "{{.State.Running}}", name], { allowFailure: true });
|
|
252
|
+
if (result.code !== 0)
|
|
253
|
+
return { exists: false, running: false };
|
|
254
|
+
return { exists: true, running: result.stdout.trim() === "true" };
|
|
255
|
+
}
|
|
256
|
+
async function ensureSandboxWorkspace(workspaceDir, seedFrom) {
|
|
257
|
+
await fs.mkdir(workspaceDir, { recursive: true });
|
|
258
|
+
if (seedFrom) {
|
|
259
|
+
const seed = resolveUserPath(seedFrom);
|
|
260
|
+
const files = [
|
|
261
|
+
DEFAULT_AGENTS_FILENAME,
|
|
262
|
+
DEFAULT_SOUL_FILENAME,
|
|
263
|
+
DEFAULT_TOOLS_FILENAME,
|
|
264
|
+
DEFAULT_IDENTITY_FILENAME,
|
|
265
|
+
DEFAULT_USER_FILENAME,
|
|
266
|
+
DEFAULT_BOOTSTRAP_FILENAME,
|
|
267
|
+
];
|
|
268
|
+
for (const name of files) {
|
|
269
|
+
const src = path.join(seed, name);
|
|
270
|
+
const dest = path.join(workspaceDir, name);
|
|
271
|
+
try {
|
|
272
|
+
await fs.access(dest);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
try {
|
|
276
|
+
const content = await fs.readFile(src, "utf-8");
|
|
277
|
+
await fs.writeFile(dest, content, { encoding: "utf-8", flag: "wx" });
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// ignore missing seed file
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
await ensureAgentWorkspace({ dir: workspaceDir, ensureBootstrapFiles: true });
|
|
286
|
+
}
|
|
287
|
+
function normalizeDockerLimit(value) {
|
|
288
|
+
if (value === undefined || value === null)
|
|
289
|
+
return undefined;
|
|
290
|
+
if (typeof value === "number") {
|
|
291
|
+
return Number.isFinite(value) ? String(value) : undefined;
|
|
292
|
+
}
|
|
293
|
+
const trimmed = value.trim();
|
|
294
|
+
return trimmed ? trimmed : undefined;
|
|
295
|
+
}
|
|
296
|
+
function formatUlimitValue(name, value) {
|
|
297
|
+
if (!name.trim())
|
|
298
|
+
return null;
|
|
299
|
+
if (typeof value === "number" || typeof value === "string") {
|
|
300
|
+
const raw = String(value).trim();
|
|
301
|
+
return raw ? `${name}=${raw}` : null;
|
|
302
|
+
}
|
|
303
|
+
const soft = typeof value.soft === "number" ? Math.max(0, value.soft) : undefined;
|
|
304
|
+
const hard = typeof value.hard === "number" ? Math.max(0, value.hard) : undefined;
|
|
305
|
+
if (soft === undefined && hard === undefined)
|
|
306
|
+
return null;
|
|
307
|
+
if (soft === undefined)
|
|
308
|
+
return `${name}=${hard}`;
|
|
309
|
+
if (hard === undefined)
|
|
310
|
+
return `${name}=${soft}`;
|
|
311
|
+
return `${name}=${soft}:${hard}`;
|
|
312
|
+
}
|
|
313
|
+
export function buildSandboxCreateArgs(params) {
|
|
314
|
+
const createdAtMs = params.createdAtMs ?? Date.now();
|
|
315
|
+
const args = ["create", "--name", params.name];
|
|
316
|
+
args.push("--label", "clawdbot.sandbox=1");
|
|
317
|
+
args.push("--label", `clawdbot.sessionKey=${params.sessionKey}`);
|
|
318
|
+
args.push("--label", `clawdbot.createdAtMs=${createdAtMs}`);
|
|
319
|
+
for (const [key, value] of Object.entries(params.labels ?? {})) {
|
|
320
|
+
if (key && value)
|
|
321
|
+
args.push("--label", `${key}=${value}`);
|
|
322
|
+
}
|
|
323
|
+
if (params.cfg.readOnlyRoot)
|
|
324
|
+
args.push("--read-only");
|
|
325
|
+
for (const entry of params.cfg.tmpfs) {
|
|
326
|
+
args.push("--tmpfs", entry);
|
|
327
|
+
}
|
|
328
|
+
if (params.cfg.network)
|
|
329
|
+
args.push("--network", params.cfg.network);
|
|
330
|
+
if (params.cfg.user)
|
|
331
|
+
args.push("--user", params.cfg.user);
|
|
332
|
+
for (const cap of params.cfg.capDrop) {
|
|
333
|
+
args.push("--cap-drop", cap);
|
|
334
|
+
}
|
|
335
|
+
args.push("--security-opt", "no-new-privileges");
|
|
336
|
+
if (params.cfg.seccompProfile) {
|
|
337
|
+
args.push("--security-opt", `seccomp=${params.cfg.seccompProfile}`);
|
|
338
|
+
}
|
|
339
|
+
if (params.cfg.apparmorProfile) {
|
|
340
|
+
args.push("--security-opt", `apparmor=${params.cfg.apparmorProfile}`);
|
|
341
|
+
}
|
|
342
|
+
for (const entry of params.cfg.dns ?? []) {
|
|
343
|
+
if (entry.trim())
|
|
344
|
+
args.push("--dns", entry);
|
|
345
|
+
}
|
|
346
|
+
for (const entry of params.cfg.extraHosts ?? []) {
|
|
347
|
+
if (entry.trim())
|
|
348
|
+
args.push("--add-host", entry);
|
|
349
|
+
}
|
|
350
|
+
if (typeof params.cfg.pidsLimit === "number" && params.cfg.pidsLimit > 0) {
|
|
351
|
+
args.push("--pids-limit", String(params.cfg.pidsLimit));
|
|
352
|
+
}
|
|
353
|
+
const memory = normalizeDockerLimit(params.cfg.memory);
|
|
354
|
+
if (memory)
|
|
355
|
+
args.push("--memory", memory);
|
|
356
|
+
const memorySwap = normalizeDockerLimit(params.cfg.memorySwap);
|
|
357
|
+
if (memorySwap)
|
|
358
|
+
args.push("--memory-swap", memorySwap);
|
|
359
|
+
if (typeof params.cfg.cpus === "number" && params.cfg.cpus > 0) {
|
|
360
|
+
args.push("--cpus", String(params.cfg.cpus));
|
|
361
|
+
}
|
|
362
|
+
for (const [name, value] of Object.entries(params.cfg.ulimits ?? {})) {
|
|
363
|
+
const formatted = formatUlimitValue(name, value);
|
|
364
|
+
if (formatted)
|
|
365
|
+
args.push("--ulimit", formatted);
|
|
366
|
+
}
|
|
367
|
+
return args;
|
|
368
|
+
}
|
|
369
|
+
async function createSandboxContainer(params) {
|
|
370
|
+
const { name, cfg, workspaceDir, sessionKey } = params;
|
|
371
|
+
await ensureDockerImage(cfg.image);
|
|
372
|
+
const args = buildSandboxCreateArgs({
|
|
373
|
+
name,
|
|
374
|
+
cfg,
|
|
375
|
+
sessionKey,
|
|
376
|
+
});
|
|
377
|
+
args.push("--workdir", cfg.workdir);
|
|
378
|
+
args.push("-v", `${workspaceDir}:${cfg.workdir}`);
|
|
379
|
+
args.push(cfg.image, "sleep", "infinity");
|
|
380
|
+
await execDocker(args);
|
|
381
|
+
await execDocker(["start", name]);
|
|
382
|
+
if (cfg.setupCommand?.trim()) {
|
|
383
|
+
await execDocker(["exec", "-i", name, "sh", "-lc", cfg.setupCommand]);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function ensureSandboxContainer(params) {
|
|
387
|
+
const slug = params.cfg.perSession
|
|
388
|
+
? slugifySessionKey(params.sessionKey)
|
|
389
|
+
: "shared";
|
|
390
|
+
const name = `${params.cfg.docker.containerPrefix}${slug}`;
|
|
391
|
+
const containerName = name.slice(0, 63);
|
|
392
|
+
const state = await dockerContainerState(containerName);
|
|
393
|
+
if (!state.exists) {
|
|
394
|
+
await createSandboxContainer({
|
|
395
|
+
name: containerName,
|
|
396
|
+
cfg: params.cfg.docker,
|
|
397
|
+
workspaceDir: params.workspaceDir,
|
|
398
|
+
sessionKey: params.sessionKey,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
else if (!state.running) {
|
|
402
|
+
await execDocker(["start", containerName]);
|
|
403
|
+
}
|
|
404
|
+
const now = Date.now();
|
|
405
|
+
await updateRegistry({
|
|
406
|
+
containerName,
|
|
407
|
+
sessionKey: params.sessionKey,
|
|
408
|
+
createdAtMs: now,
|
|
409
|
+
lastUsedAtMs: now,
|
|
410
|
+
image: params.cfg.docker.image,
|
|
411
|
+
});
|
|
412
|
+
return containerName;
|
|
413
|
+
}
|
|
414
|
+
async function ensureSandboxBrowserImage(image) {
|
|
415
|
+
const exists = await dockerImageExists(image);
|
|
416
|
+
if (exists)
|
|
417
|
+
return;
|
|
418
|
+
throw new Error(`Sandbox browser image not found: ${image}. Build it with scripts/sandbox-browser-setup.sh.`);
|
|
419
|
+
}
|
|
420
|
+
function buildSandboxBrowserResolvedConfig(params) {
|
|
421
|
+
const controlHost = "127.0.0.1";
|
|
422
|
+
const controlUrl = `http://${controlHost}:${params.controlPort}`;
|
|
423
|
+
const cdpHost = "127.0.0.1";
|
|
424
|
+
return {
|
|
425
|
+
enabled: true,
|
|
426
|
+
controlUrl,
|
|
427
|
+
controlHost,
|
|
428
|
+
controlPort: params.controlPort,
|
|
429
|
+
cdpProtocol: "http",
|
|
430
|
+
cdpHost,
|
|
431
|
+
cdpIsLoopback: true,
|
|
432
|
+
color: DEFAULT_CLAWD_BROWSER_COLOR,
|
|
433
|
+
executablePath: undefined,
|
|
434
|
+
headless: params.headless,
|
|
435
|
+
noSandbox: false,
|
|
436
|
+
attachOnly: true,
|
|
437
|
+
defaultProfile: "clawd",
|
|
438
|
+
profiles: {
|
|
439
|
+
clawd: { cdpPort: params.cdpPort, color: DEFAULT_CLAWD_BROWSER_COLOR },
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
async function ensureSandboxBrowser(params) {
|
|
444
|
+
if (!params.cfg.browser.enabled)
|
|
445
|
+
return null;
|
|
446
|
+
if (!isToolAllowed(params.cfg.tools, "browser"))
|
|
447
|
+
return null;
|
|
448
|
+
const slug = params.cfg.perSession
|
|
449
|
+
? slugifySessionKey(params.sessionKey)
|
|
450
|
+
: "shared";
|
|
451
|
+
const name = `${params.cfg.browser.containerPrefix}${slug}`;
|
|
452
|
+
const containerName = name.slice(0, 63);
|
|
453
|
+
const state = await dockerContainerState(containerName);
|
|
454
|
+
if (!state.exists) {
|
|
455
|
+
await ensureSandboxBrowserImage(params.cfg.browser.image);
|
|
456
|
+
const args = buildSandboxCreateArgs({
|
|
457
|
+
name: containerName,
|
|
458
|
+
cfg: params.cfg.docker,
|
|
459
|
+
sessionKey: params.sessionKey,
|
|
460
|
+
labels: { "clawdbot.sandboxBrowser": "1" },
|
|
461
|
+
});
|
|
462
|
+
args.push("-v", `${params.workspaceDir}:${params.cfg.docker.workdir}`);
|
|
463
|
+
args.push("-p", `127.0.0.1::${params.cfg.browser.cdpPort}`);
|
|
464
|
+
if (params.cfg.browser.enableNoVnc && !params.cfg.browser.headless) {
|
|
465
|
+
args.push("-p", `127.0.0.1::${params.cfg.browser.noVncPort}`);
|
|
466
|
+
}
|
|
467
|
+
args.push("-e", `CLAWDBOT_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`);
|
|
468
|
+
args.push("-e", `CLAWDBOT_BROWSER_ENABLE_NOVNC=${params.cfg.browser.enableNoVnc ? "1" : "0"}`);
|
|
469
|
+
args.push("-e", `CLAWDBOT_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
|
|
470
|
+
args.push("-e", `CLAWDBOT_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
|
|
471
|
+
args.push("-e", `CLAWDBOT_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`);
|
|
472
|
+
args.push(params.cfg.browser.image);
|
|
473
|
+
await execDocker(args);
|
|
474
|
+
await execDocker(["start", containerName]);
|
|
475
|
+
}
|
|
476
|
+
else if (!state.running) {
|
|
477
|
+
await execDocker(["start", containerName]);
|
|
478
|
+
}
|
|
479
|
+
const mappedCdp = await readDockerPort(containerName, params.cfg.browser.cdpPort);
|
|
480
|
+
if (!mappedCdp) {
|
|
481
|
+
throw new Error(`Failed to resolve CDP port mapping for ${containerName}.`);
|
|
482
|
+
}
|
|
483
|
+
const mappedNoVnc = params.cfg.browser.enableNoVnc && !params.cfg.browser.headless
|
|
484
|
+
? await readDockerPort(containerName, params.cfg.browser.noVncPort)
|
|
485
|
+
: null;
|
|
486
|
+
const existing = BROWSER_BRIDGES.get(params.sessionKey);
|
|
487
|
+
const existingProfile = existing
|
|
488
|
+
? resolveProfile(existing.bridge.state.resolved, "clawd")
|
|
489
|
+
: null;
|
|
490
|
+
const shouldReuse = existing &&
|
|
491
|
+
existing.containerName === containerName &&
|
|
492
|
+
existingProfile?.cdpPort === mappedCdp;
|
|
493
|
+
if (existing && !shouldReuse) {
|
|
494
|
+
await stopBrowserBridgeServer(existing.bridge.server).catch(() => undefined);
|
|
495
|
+
BROWSER_BRIDGES.delete(params.sessionKey);
|
|
496
|
+
}
|
|
497
|
+
let bridge;
|
|
498
|
+
if (shouldReuse && existing) {
|
|
499
|
+
bridge = existing.bridge;
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
bridge = await startBrowserBridgeServer({
|
|
503
|
+
resolved: buildSandboxBrowserResolvedConfig({
|
|
504
|
+
controlPort: 0,
|
|
505
|
+
cdpPort: mappedCdp,
|
|
506
|
+
headless: params.cfg.browser.headless,
|
|
507
|
+
}),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (!shouldReuse) {
|
|
511
|
+
BROWSER_BRIDGES.set(params.sessionKey, { bridge, containerName });
|
|
512
|
+
}
|
|
513
|
+
const now = Date.now();
|
|
514
|
+
await updateBrowserRegistry({
|
|
515
|
+
containerName,
|
|
516
|
+
sessionKey: params.sessionKey,
|
|
517
|
+
createdAtMs: now,
|
|
518
|
+
lastUsedAtMs: now,
|
|
519
|
+
image: params.cfg.browser.image,
|
|
520
|
+
cdpPort: mappedCdp,
|
|
521
|
+
noVncPort: mappedNoVnc ?? undefined,
|
|
522
|
+
});
|
|
523
|
+
const noVncUrl = mappedNoVnc &&
|
|
524
|
+
params.cfg.browser.enableNoVnc &&
|
|
525
|
+
!params.cfg.browser.headless
|
|
526
|
+
? `http://127.0.0.1:${mappedNoVnc}/vnc.html?autoconnect=1&resize=remote`
|
|
527
|
+
: undefined;
|
|
528
|
+
return {
|
|
529
|
+
controlUrl: bridge.baseUrl,
|
|
530
|
+
noVncUrl,
|
|
531
|
+
containerName,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
async function pruneSandboxContainers(cfg) {
|
|
535
|
+
const now = Date.now();
|
|
536
|
+
const idleHours = cfg.prune.idleHours;
|
|
537
|
+
const maxAgeDays = cfg.prune.maxAgeDays;
|
|
538
|
+
if (idleHours === 0 && maxAgeDays === 0)
|
|
539
|
+
return;
|
|
540
|
+
const registry = await readRegistry();
|
|
541
|
+
for (const entry of registry.entries) {
|
|
542
|
+
const idleMs = now - entry.lastUsedAtMs;
|
|
543
|
+
const ageMs = now - entry.createdAtMs;
|
|
544
|
+
if ((idleHours > 0 && idleMs > idleHours * 60 * 60 * 1000) ||
|
|
545
|
+
(maxAgeDays > 0 && ageMs > maxAgeDays * 24 * 60 * 60 * 1000)) {
|
|
546
|
+
try {
|
|
547
|
+
await execDocker(["rm", "-f", entry.containerName], {
|
|
548
|
+
allowFailure: true,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
// ignore prune failures
|
|
553
|
+
}
|
|
554
|
+
finally {
|
|
555
|
+
await removeRegistryEntry(entry.containerName);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async function pruneSandboxBrowsers(cfg) {
|
|
561
|
+
const now = Date.now();
|
|
562
|
+
const idleHours = cfg.prune.idleHours;
|
|
563
|
+
const maxAgeDays = cfg.prune.maxAgeDays;
|
|
564
|
+
if (idleHours === 0 && maxAgeDays === 0)
|
|
565
|
+
return;
|
|
566
|
+
const registry = await readBrowserRegistry();
|
|
567
|
+
for (const entry of registry.entries) {
|
|
568
|
+
const idleMs = now - entry.lastUsedAtMs;
|
|
569
|
+
const ageMs = now - entry.createdAtMs;
|
|
570
|
+
if ((idleHours > 0 && idleMs > idleHours * 60 * 60 * 1000) ||
|
|
571
|
+
(maxAgeDays > 0 && ageMs > maxAgeDays * 24 * 60 * 60 * 1000)) {
|
|
572
|
+
try {
|
|
573
|
+
await execDocker(["rm", "-f", entry.containerName], {
|
|
574
|
+
allowFailure: true,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
// ignore prune failures
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
await removeBrowserRegistryEntry(entry.containerName);
|
|
582
|
+
const bridge = BROWSER_BRIDGES.get(entry.sessionKey);
|
|
583
|
+
if (bridge?.containerName === entry.containerName) {
|
|
584
|
+
await stopBrowserBridgeServer(bridge.bridge.server).catch(() => undefined);
|
|
585
|
+
BROWSER_BRIDGES.delete(entry.sessionKey);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async function maybePruneSandboxes(cfg) {
|
|
592
|
+
const now = Date.now();
|
|
593
|
+
if (now - lastPruneAtMs < 5 * 60 * 1000)
|
|
594
|
+
return;
|
|
595
|
+
lastPruneAtMs = now;
|
|
596
|
+
try {
|
|
597
|
+
await pruneSandboxContainers(cfg);
|
|
598
|
+
await pruneSandboxBrowsers(cfg);
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
const message = error instanceof Error
|
|
602
|
+
? error.message
|
|
603
|
+
: typeof error === "string"
|
|
604
|
+
? error
|
|
605
|
+
: JSON.stringify(error);
|
|
606
|
+
defaultRuntime.error?.(`Sandbox prune failed: ${message ?? "unknown error"}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
export async function resolveSandboxContext(params) {
|
|
610
|
+
const rawSessionKey = params.sessionKey?.trim();
|
|
611
|
+
if (!rawSessionKey)
|
|
612
|
+
return null;
|
|
613
|
+
const cfg = defaultSandboxConfig(params.config);
|
|
614
|
+
const mainKey = params.config?.session?.mainKey?.trim() || "main";
|
|
615
|
+
if (!shouldSandboxSession(cfg, rawSessionKey, mainKey))
|
|
616
|
+
return null;
|
|
617
|
+
await maybePruneSandboxes(cfg);
|
|
618
|
+
const workspaceRoot = resolveUserPath(cfg.workspaceRoot);
|
|
619
|
+
const workspaceDir = cfg.perSession
|
|
620
|
+
? resolveSandboxWorkspaceDir(workspaceRoot, rawSessionKey)
|
|
621
|
+
: workspaceRoot;
|
|
622
|
+
const seedWorkspace = params.workspaceDir?.trim() || DEFAULT_AGENT_WORKSPACE_DIR;
|
|
623
|
+
await ensureSandboxWorkspace(workspaceDir, seedWorkspace);
|
|
624
|
+
const containerName = await ensureSandboxContainer({
|
|
625
|
+
sessionKey: rawSessionKey,
|
|
626
|
+
workspaceDir,
|
|
627
|
+
cfg,
|
|
628
|
+
});
|
|
629
|
+
const browser = await ensureSandboxBrowser({
|
|
630
|
+
sessionKey: rawSessionKey,
|
|
631
|
+
workspaceDir,
|
|
632
|
+
cfg,
|
|
633
|
+
});
|
|
634
|
+
return {
|
|
635
|
+
enabled: true,
|
|
636
|
+
sessionKey: rawSessionKey,
|
|
637
|
+
workspaceDir,
|
|
638
|
+
containerName,
|
|
639
|
+
containerWorkdir: cfg.docker.workdir,
|
|
640
|
+
docker: cfg.docker,
|
|
641
|
+
tools: cfg.tools,
|
|
642
|
+
browser: browser ?? undefined,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
export function getShellConfig() {
|
|
3
|
+
if (process.platform === "win32") {
|
|
4
|
+
const shell = process.env.COMSPEC?.trim() || "cmd.exe";
|
|
5
|
+
return { shell, args: ["/d", "/s", "/c"] };
|
|
6
|
+
}
|
|
7
|
+
const shell = process.env.SHELL?.trim() || "sh";
|
|
8
|
+
return { shell, args: ["-c"] };
|
|
9
|
+
}
|
|
10
|
+
export function sanitizeBinaryOutput(text) {
|
|
11
|
+
const scrubbed = text.replace(/[\p{Format}\p{Surrogate}]/gu, "");
|
|
12
|
+
if (!scrubbed)
|
|
13
|
+
return scrubbed;
|
|
14
|
+
const chunks = [];
|
|
15
|
+
for (const char of scrubbed) {
|
|
16
|
+
const code = char.codePointAt(0);
|
|
17
|
+
if (code == null)
|
|
18
|
+
continue;
|
|
19
|
+
if (code === 0x09 || code === 0x0a || code === 0x0d) {
|
|
20
|
+
chunks.push(char);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (code < 0x20)
|
|
24
|
+
continue;
|
|
25
|
+
chunks.push(char);
|
|
26
|
+
}
|
|
27
|
+
return chunks.join("");
|
|
28
|
+
}
|
|
29
|
+
export function killProcessTree(pid) {
|
|
30
|
+
if (process.platform === "win32") {
|
|
31
|
+
try {
|
|
32
|
+
spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
33
|
+
stdio: "ignore",
|
|
34
|
+
detached: true,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// ignore errors if taskkill fails
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
process.kill(-pid, "SIGKILL");
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
try {
|
|
47
|
+
process.kill(pid, "SIGKILL");
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// process already dead
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|