Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3b5190b
Add binding descriptions for Deploy to Cloudflare button
Jan 29, 2026
578843f
Better descriptions for Deploy to Cloudflare button
Jan 29, 2026
fc67fdd
Hide debug env vars from Deploy to Cloudflare button
Jan 29, 2026
39dcc11
docs: add “use” for grammar
et0and Jan 29, 2026
5b071d7
Rename dm.policy to dmPolicy in Telegram config
JackCloudman Jan 29, 2026
22593bf
URL safe token
jillesme Jan 30, 2026
58104a9
rename to OpenClaw
celso Jan 30, 2026
9035f67
fix: truncate trailing slashes in AIG base URL
roerohan Jan 30, 2026
d6fa1ad
Add E2E tests for device pairing and conversation flow
Feb 1, 2026
c4060c3
Add GitHub Actions workflow for E2E tests with video recording
Feb 2, 2026
f7eb13d
test: add e2e test matrix for telegram and discord channel configurat…
Feb 2, 2026
ca29355
fix: remove invalid dm config key for Telegram, fix Discord dm.policy
Feb 2, 2026
1ea55ed
fix: use unique branches per matrix job for e2e video uploads
Feb 2, 2026
d2069d1
feat: add TELEGRAM_DM_ALLOW_FROM env var for allowlist policy
Feb 2, 2026
7ae34f7
fix(logging): redact sensitive query params from request logs
Feb 2, 2026
42b54cc
fix(logging): gate WebSocket payload logs behind DEBUG_ROUTES
Feb 2, 2026
6fad432
test: add unit tests for redactSensitiveParams
Feb 2, 2026
7ea27ef
refactor: move redactSensitiveParams to utils/logging.ts
Feb 2, 2026
a508cd5
refactor: import redactSensitiveParams from utils/logging
Feb 2, 2026
1765e93
refactor: remove duplicate redactSensitiveParams from index.ts
Feb 2, 2026
ebaa297
test: update import path for redactSensitiveParams
Feb 2, 2026
04e2d74
test(cctr): add e2e tests for log redaction (fixes #85, #117)
Feb 2, 2026
f2e88fb
fix(test): adjust log_redaction tests to not depend on CDP endpoint
Feb 2, 2026
7cdc217
fix(test): check for URL-encoded [REDACTED] in logs
Feb 2, 2026
814af1d
add LICENSE + CONTRIBUTING
elithrar Feb 2, 2026
8901b46
package.json LICENSE fix
elithrar Feb 2, 2026
46af86b
fix: Dockerfile supports arm64 Node download
sd0xdev Jan 30, 2026
d72caca
feat: cloud-based e2e tests with Terraform + wrangler deploy
Feb 3, 2026
be89351
fix(e2e): remove broken wrangler r2 object list, add sleep for output…
Feb 3, 2026
2623164
fix README TYPO
cf-jongsik Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
ANTHROPIC_API_KEY=sk-ant-...

# Local development mode - skips Cloudflare Access auth and bypasses device pairing
DEV_MODE=true
# DEV_MODE=true

# E2E test mode - skips Cloudflare Access auth but keeps device pairing enabled
# Use this for automated tests that need to test the real pairing flow
# E2E_TEST_MODE=true

# Enable debug routes at /debug/* (optional)
DEBUG_ROUTES=true
# DEBUG_ROUTES=true

# Optional - set a fixed token instead of auto-generated
MOLTBOT_GATEWAY_TOKEN=dev-token-change-in-prod
Expand Down
184 changes: 183 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ on:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
test:
unit:
runs-on: ubuntu-latest

steps:
Expand All @@ -27,3 +28,184 @@ jobs:

- name: Run tests
run: npm test

e2e:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: write
pull-requests: write

strategy:
fail-fast: false
matrix:
config:
- name: base
env: {}
- name: telegram
env:
TELEGRAM_BOT_TOKEN: "fake-telegram-bot-token-for-e2e"
TELEGRAM_DM_POLICY: "pairing"
- name: discord
env:
DISCORD_BOT_TOKEN: "fake-discord-bot-token-for-e2e"
DISCORD_DM_POLICY: "pairing"

name: e2e (${{ matrix.config.name }})

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false

- name: Install Playwright
run: npx playwright install --with-deps chromium

- name: Install playwright-cli
run: npm install -g @playwright/cli

- name: Install cctr
uses: taiki-e/install-action@v2
with:
tool: cctr

- name: Run E2E tests (${{ matrix.config.name }})
id: e2e
continue-on-error: true
env:
# Cloud infrastructure credentials (from repo secrets with E2E_ prefix)
CLOUDFLARE_API_TOKEN: ${{ secrets.E2E_CLOUDFLARE_API_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.E2E_CF_ACCOUNT_ID }}
WORKERS_SUBDOMAIN: ${{ secrets.E2E_WORKERS_SUBDOMAIN }}
CF_ACCESS_TEAM_DOMAIN: ${{ secrets.E2E_CF_ACCESS_TEAM_DOMAIN }}
R2_ACCESS_KEY_ID: ${{ secrets.E2E_R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.E2E_R2_SECRET_ACCESS_KEY }}
# AI provider (optional, for chat tests)
AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }}
AI_GATEWAY_BASE_URL: ${{ secrets.AI_GATEWAY_BASE_URL }}
# Unique test run ID for parallel isolation
E2E_TEST_RUN_ID: ${{ github.run_id }}-${{ matrix.config.name }}
# Matrix-specific config
TELEGRAM_BOT_TOKEN: ${{ matrix.config.env.TELEGRAM_BOT_TOKEN }}
TELEGRAM_DM_POLICY: ${{ matrix.config.env.TELEGRAM_DM_POLICY }}
DISCORD_BOT_TOKEN: ${{ matrix.config.env.DISCORD_BOT_TOKEN }}
DISCORD_DM_POLICY: ${{ matrix.config.env.DISCORD_DM_POLICY }}
run: cctr -vv test/e2e

- name: Convert video and generate thumbnail
id: convert
if: always()
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq ffmpeg imagemagick bc
if ls /tmp/moltworker-e2e-videos/*.webm 1>/dev/null 2>&1; then
for webm in /tmp/moltworker-e2e-videos/*.webm; do
mp4="${webm%.webm}.mp4"
thumb="${webm%.webm}.png"

# Convert to mp4
ffmpeg -y -i "$webm" -c:v libx264 -preset fast -crf 22 -c:a aac "$mp4"

# Extract middle frame as thumbnail
duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$mp4")
midpoint=$(echo "$duration / 2" | bc -l)
ffmpeg -y -ss "$midpoint" -i "$mp4" -vframes 1 -update 1 -q:v 2 "$thumb"

# Add play button overlay using ImageMagick
width=$(identify -format '%w' "$thumb")
height=$(identify -format '%h' "$thumb")
cx=$((width / 2))
cy=$((height / 2))
convert "$thumb" \
-fill 'rgba(0,0,0,0.6)' -draw "circle ${cx},${cy} $((cx+50)),${cy}" \
-fill 'white' -draw "polygon $((cx-15)),$((cy-25)) $((cx-15)),$((cy+25)) $((cx+30)),${cy}" \
"$thumb"

echo "video_path=$mp4" >> $GITHUB_OUTPUT
echo "video_name=$(basename $mp4)" >> $GITHUB_OUTPUT
echo "thumb_path=$thumb" >> $GITHUB_OUTPUT
echo "thumb_name=$(basename $thumb)" >> $GITHUB_OUTPUT
done
echo "has_video=true" >> $GITHUB_OUTPUT
else
echo "has_video=false" >> $GITHUB_OUTPUT
fi

- name: Prepare video for upload
id: prepare
if: always() && steps.convert.outputs.has_video == 'true'
run: |
mkdir -p /tmp/e2e-video-upload/videos/${{ github.run_id }}-${{ matrix.config.name }}
cp "${{ steps.convert.outputs.video_path }}" /tmp/e2e-video-upload/videos/${{ github.run_id }}-${{ matrix.config.name }}/
cp "${{ steps.convert.outputs.thumb_path }}" /tmp/e2e-video-upload/videos/${{ github.run_id }}-${{ matrix.config.name }}/
echo "video_url=https://github.com/${{ github.repository }}/raw/e2e-artifacts-${{ matrix.config.name }}/videos/${{ github.run_id }}-${{ matrix.config.name }}/${{ steps.convert.outputs.video_name }}" >> $GITHUB_OUTPUT
echo "thumb_url=https://github.com/${{ github.repository }}/raw/e2e-artifacts-${{ matrix.config.name }}/videos/${{ github.run_id }}-${{ matrix.config.name }}/${{ steps.convert.outputs.thumb_name }}" >> $GITHUB_OUTPUT

- name: Upload video to e2e-artifacts branch
if: always() && steps.convert.outputs.has_video == 'true'
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: /tmp/e2e-video-upload
publish_branch: e2e-artifacts-${{ matrix.config.name }}
keep_files: true

- name: Delete old video comments
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const marker = '<!-- e2e-video-${{ matrix.config.name }} -->';
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
for (const comment of comments) {
if (comment.body.includes(marker)) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
});
}
}

- name: Comment on PR with video
if: always() && github.event_name == 'pull_request' && steps.prepare.outputs.video_url
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
<!-- e2e-video-${{ matrix.config.name }} -->
## E2E Test Recording (${{ matrix.config.name }})

${{ steps.e2e.outcome == 'success' && '✅ Tests passed' || '❌ Tests failed' }}

[![E2E Test Video](${{ steps.prepare.outputs.thumb_url }})](${{ steps.prepare.outputs.video_url }})

- name: Add video link to summary
if: always()
run: |
echo "## E2E Test Recording" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.convert.outputs.has_video }}" == "true" ]; then
echo "📹 [Download video](${{ steps.prepare.outputs.video_url }})" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ No video recording found" >> $GITHUB_STEP_SUMMARY
fi

- name: Fail if E2E tests failed
if: steps.e2e.outcome == 'failure'
run: exit 1
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,25 @@ Thumbs.db

# Docker build artifacts
*.tar

# Veta agent memory
.veta/

# greger.el conversation
*.greger

# playwright-cli
.playwright-cli/

# Terraform
*.tfstate
*.tfstate.*
.terraform/
.terraform.lock.hcl
terraform.tfvars

# E2E test credentials
test/e2e/.dev.vars

# Temporary e2e wrangler configs
.wrangler-e2e-*.jsonc
37 changes: 37 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## Contributing

We welcome contributions, but with a few short rules:

- **Create issues first** for anything non-trivial (typos, doc fixes, glaring errors). Explain why, the impact, and how you intend to solve it _first_, before putting time and energy (or tokens) into a PR.

- **You cannot be offended if we close a PR** or otherwise decide not to merge your work. We're maintaining Moltworker for (many, many) others, and we're ultimately the ones that have to maintain the code. This is especially true if we believe your PR to be AI driven without any human-in-the-loop review or explanation. Not all ideas or work makes it. If it's critical to your workflow, you can fork it!

- **Demonstrate that you've tested your work** - whether via manual testing, automated tests, or a mix of both. You may be quizzed here.

## AI Contributions

> Heavily inspired and influenced by [Ghostty's AI policy](https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md)

**First**: AI tooling is incredibly powerful, and enabled much of Moltworker itself to exist! But it's a tool: and the wielder of the tool is ultimately responsible for their output.

We have a few rules regarding AI usage:

- **All AI usage in any form must be disclosed.** You must state the tool you used (e.g. Claude Code, Cursor, Amp) along with the extent that the work was AI-assisted.

- **Pull requests created in any way by AI can only be for accepted issues.** Drive-by pull requests that do not reference an accepted issue will be closed. If AI isn't disclosed but a maintainer suspects its use, the PR will be closed. If you want to share code for a non-accepted issue, open a discussion or attach it to an existing discussion.

- **Pull requests created by AI must have been fully verified with human use.** AI must not create hypothetically correct code that hasn't been tested. Importantly, you must not allow AI to write code for platforms or environments you don't have access to manually test on.

- **Issues and discussions can use AI assistance but must have a full human-in-the-loop.** This means that any content generated with AI must have been reviewed _and edited_ by a human before submission. AI is very good at being overly verbose and including noise that distracts from the main point. Humans must do their research and trim this down.

- **No AI-generated media is allowed (art, images, videos, audio, etc.).** Text and code are the only acceptable AI-generated content, per the other rules in this policy.

These rules apply only to outside contributions to Ghostty. Maintainers are exempt from these rules and may use AI tools at their discretion; they've proven themselves trustworthy to apply good judgment.

## There are Humans Here

Please remember that this software is ultimately maintained by humans.

Every discussion, issue, and pull request is read and reviewed by humans (and sometimes machines, too). It is a boundary point at which people interact with each other and the work done. It is rude and disrespectful to approach this boundary with low-effort, unqualified work, since it puts the burden of validation on the maintainer.

In a perfect world, AI would produce high-quality, accurate work every time. But today, that reality depends on the driver of the AI. And today, most drivers of AI are just not good enough. So, until either the people get better, the AI gets better, or both, we have to have strict rules to protect maintainers.
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ FROM docker.io/cloudflare/sandbox:0.7.0
# The base image has Node 20, we need to replace it with Node 22
# Using direct binary download for reliability
ENV NODE_VERSION=22.13.1
RUN apt-get update && apt-get install -y xz-utils ca-certificates rsync \
&& curl -fsSLk https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz -o /tmp/node.tar.xz \
RUN ARCH="$(dpkg --print-architecture)" \
&& case "${ARCH}" in \
amd64) NODE_ARCH="x64" ;; \
arm64) NODE_ARCH="arm64" ;; \
*) echo "Unsupported architecture: ${ARCH}" >&2; exit 1 ;; \
esac \
&& apt-get update && apt-get install -y xz-utils ca-certificates rsync \
&& curl -fsSLk https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz -o /tmp/node.tar.xz \
&& tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \
&& rm /tmp/node.tar.xz \
&& node --version \
Expand Down
Loading