Merge upstream OpenClaw 2026.2.6 with timeout fixes
Merging upstream fixes for: - Context overflow compaction improvements (#11664) - Subagent announce flow hardening (#11641) - Memory/QMD timeout and recovery fixes - Multiple other stability improvements Resolved pnpm-lock.yaml conflict by accepting upstream version. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
126
.agents/skills/PR_WORKFLOW.md
Normal file
126
.agents/skills/PR_WORKFLOW.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# PR Review Instructions
|
||||||
|
|
||||||
|
Please read this in full and do not skip sections.
|
||||||
|
|
||||||
|
## Working rule
|
||||||
|
|
||||||
|
Skills execute workflow, maintainers provide judgment.
|
||||||
|
Always pause between skills to evaluate technical direction, not just command success.
|
||||||
|
|
||||||
|
These three skills must be used in order:
|
||||||
|
|
||||||
|
1. `review-pr`
|
||||||
|
2. `prepare-pr`
|
||||||
|
3. `merge-pr`
|
||||||
|
|
||||||
|
They are necessary, but not sufficient. Maintainers must steer between steps and understand the code before moving forward.
|
||||||
|
|
||||||
|
Treat PRs as reports first, code second.
|
||||||
|
If submitted code is low quality, ignore it and implement the best solution for the problem.
|
||||||
|
|
||||||
|
Do not continue if you cannot verify the problem is real or test the fix.
|
||||||
|
|
||||||
|
## PR quality bar
|
||||||
|
|
||||||
|
- Do not trust PR code by default.
|
||||||
|
- Do not merge changes you cannot validate with a reproducible problem and a tested fix.
|
||||||
|
- Keep types strict. Do not use `any` in implementation code.
|
||||||
|
- Keep external-input boundaries typed and validated, including CLI input, environment variables, network payloads, and tool output.
|
||||||
|
- Keep implementations properly scoped. Fix root causes, not local symptoms.
|
||||||
|
- Identify and reuse canonical sources of truth so behavior does not drift across the codebase.
|
||||||
|
- Harden changes. Always evaluate security impact and abuse paths.
|
||||||
|
- Understand the system before changing it. Never make the codebase messier just to clear a PR queue.
|
||||||
|
|
||||||
|
## Unified workflow
|
||||||
|
|
||||||
|
Entry criteria:
|
||||||
|
|
||||||
|
- PR URL/number is known.
|
||||||
|
- Problem statement is clear enough to attempt reproduction.
|
||||||
|
- A realistic verification path exists (tests, integration checks, or explicit manual validation).
|
||||||
|
|
||||||
|
### 1) `review-pr`
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
- Review only: correctness, value, security risk, tests, docs, and changelog impact.
|
||||||
|
- Produce structured findings and a recommendation.
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
|
||||||
|
- Recommendation: ready, needs work, needs discussion, or close.
|
||||||
|
- `.local/review.md` with actionable findings.
|
||||||
|
|
||||||
|
Maintainer checkpoint before `prepare-pr`:
|
||||||
|
|
||||||
|
```
|
||||||
|
What problem are they trying to solve?
|
||||||
|
What is the most optimal implementation?
|
||||||
|
Is the code properly scoped?
|
||||||
|
Can we fix up everything?
|
||||||
|
Do we have any questions?
|
||||||
|
```
|
||||||
|
|
||||||
|
Stop and escalate instead of continuing if:
|
||||||
|
|
||||||
|
- The problem cannot be reproduced or confirmed.
|
||||||
|
- The proposed PR scope does not match the stated problem.
|
||||||
|
- The design introduces unresolved security or trust-boundary concerns.
|
||||||
|
|
||||||
|
### 2) `prepare-pr`
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
- Make the PR merge-ready on its head branch.
|
||||||
|
- Rebase onto current `main`, fix blocker/important findings, and run gates.
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
|
||||||
|
- Updated code and tests on the PR head branch.
|
||||||
|
- `.local/prep.md` with changes, verification, and current HEAD SHA.
|
||||||
|
- Final status: `PR is ready for /mergepr`.
|
||||||
|
|
||||||
|
Maintainer checkpoint before `merge-pr`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Is this the most optimal implementation?
|
||||||
|
Is the code properly scoped?
|
||||||
|
Is the code properly typed?
|
||||||
|
Is the code hardened?
|
||||||
|
Do we have enough tests?
|
||||||
|
Are tests using fake timers where relevant? (e.g., debounce/throttle, retry backoff, timeout branches, delayed callbacks, polling loops)
|
||||||
|
Do not add performative tests, ensure tests are real and there are no regressions.
|
||||||
|
Take your time, fix it properly, refactor if necessary.
|
||||||
|
Do you see any follow-up refactors we should do?
|
||||||
|
Did any changes introduce any potential security vulnerabilities?
|
||||||
|
```
|
||||||
|
|
||||||
|
Stop and escalate instead of continuing if:
|
||||||
|
|
||||||
|
- You cannot verify behavior changes with meaningful tests or validation.
|
||||||
|
- Fixing findings requires broad architecture changes outside safe PR scope.
|
||||||
|
- Security hardening requirements remain unresolved.
|
||||||
|
|
||||||
|
### 3) `merge-pr`
|
||||||
|
|
||||||
|
Purpose:
|
||||||
|
|
||||||
|
- Merge only after review and prep artifacts are present and checks are green.
|
||||||
|
- Use squash merge flow and verify the PR ends in `MERGED` state.
|
||||||
|
|
||||||
|
Go or no-go checklist before merge:
|
||||||
|
|
||||||
|
- All BLOCKER and IMPORTANT findings are resolved.
|
||||||
|
- Verification is meaningful and regression risk is acceptably low.
|
||||||
|
- Docs and changelog are updated when required.
|
||||||
|
- Required CI checks are green and the branch is not behind `main`.
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
|
||||||
|
- Successful merge commit and recorded merge SHA.
|
||||||
|
- Worktree cleanup after successful merge.
|
||||||
|
|
||||||
|
Maintainer checkpoint after merge:
|
||||||
|
|
||||||
|
- Were any refactors intentionally deferred and now need follow-up issue(s)?
|
||||||
|
- Did this reveal broader architecture or test gaps we should address?
|
||||||
185
.agents/skills/merge-pr/SKILL.md
Normal file
185
.agents/skills/merge-pr/SKILL.md
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
---
|
||||||
|
name: merge-pr
|
||||||
|
description: Merge a GitHub PR via squash after /preparepr. Use when asked to merge a ready PR. Do not push to main or modify code. Ensure the PR ends in MERGED state and clean up worktrees after success.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Merge PR
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Merge a prepared PR via `gh pr merge --squash` and clean up the worktree after success.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- Ask for PR number or URL.
|
||||||
|
- If missing, auto-detect from conversation.
|
||||||
|
- If ambiguous, ask.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Use `gh pr merge --squash` as the only path to `main`.
|
||||||
|
- Do not run `git push` at all during merge.
|
||||||
|
- Do not run gateway stop commands. Do not kill processes. Do not touch port 18792.
|
||||||
|
|
||||||
|
## Execution Rule
|
||||||
|
|
||||||
|
- Execute the workflow. Do not stop after printing the TODO checklist.
|
||||||
|
- If delegating, require the delegate to run commands and capture outputs.
|
||||||
|
|
||||||
|
## Known Footguns
|
||||||
|
|
||||||
|
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/dev/openclaw` if available; otherwise ask user.
|
||||||
|
- Read `.local/review.md` and `.local/prep.md` in the worktree. Do not skip.
|
||||||
|
- Clean up the real worktree directory `.worktrees/pr-<PR>` only after a successful merge.
|
||||||
|
- Expect cleanup to remove `.local/` artifacts.
|
||||||
|
|
||||||
|
## Completion Criteria
|
||||||
|
|
||||||
|
- Ensure `gh pr merge` succeeds.
|
||||||
|
- Ensure PR state is `MERGED`, never `CLOSED`.
|
||||||
|
- Record the merge SHA.
|
||||||
|
- Run cleanup only after merge success.
|
||||||
|
|
||||||
|
## First: Create a TODO Checklist
|
||||||
|
|
||||||
|
Create a checklist of all merge steps, print it, then continue and execute the commands.
|
||||||
|
|
||||||
|
## Setup: Use a Worktree
|
||||||
|
|
||||||
|
Use an isolated worktree for all merge work.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/dev/openclaw
|
||||||
|
# Sanity: confirm you are in the repo
|
||||||
|
git rev-parse --show-toplevel
|
||||||
|
|
||||||
|
WORKTREE_DIR=".worktrees/pr-<PR>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run all commands inside the worktree directory.
|
||||||
|
|
||||||
|
## Load Local Artifacts (Mandatory)
|
||||||
|
|
||||||
|
Expect these files from earlier steps:
|
||||||
|
|
||||||
|
- `.local/review.md` from `/reviewpr`
|
||||||
|
- `.local/prep.md` from `/preparepr`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ls -la .local || true
|
||||||
|
|
||||||
|
if [ -f .local/review.md ]; then
|
||||||
|
echo "Found .local/review.md"
|
||||||
|
sed -n '1,120p' .local/review.md
|
||||||
|
else
|
||||||
|
echo "Missing .local/review.md. Stop and run /reviewpr, then /preparepr."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f .local/prep.md ]; then
|
||||||
|
echo "Found .local/prep.md"
|
||||||
|
sed -n '1,120p' .local/prep.md
|
||||||
|
else
|
||||||
|
echo "Missing .local/prep.md. Stop and run /preparepr first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Identify PR meta
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr view <PR> --json number,title,state,isDraft,author,headRefName,baseRefName,headRepository,body --jq '{number,title,state,isDraft,author:.author.login,head:.headRefName,base:.baseRefName,headRepo:.headRepository.nameWithOwner,body}'
|
||||||
|
contrib=$(gh pr view <PR> --json author --jq .author.login)
|
||||||
|
head=$(gh pr view <PR> --json headRefName --jq .headRefName)
|
||||||
|
head_repo_url=$(gh pr view <PR> --json headRepository --jq .headRepository.url)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run sanity checks
|
||||||
|
|
||||||
|
Stop if any are true:
|
||||||
|
|
||||||
|
- PR is a draft.
|
||||||
|
- Required checks are failing.
|
||||||
|
- Branch is behind main.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Checks
|
||||||
|
gh pr checks <PR>
|
||||||
|
|
||||||
|
# Check behind main
|
||||||
|
git fetch origin main
|
||||||
|
git fetch origin pull/<PR>/head:pr-<PR>
|
||||||
|
git merge-base --is-ancestor origin/main pr-<PR> || echo "PR branch is behind main, run /preparepr"
|
||||||
|
```
|
||||||
|
|
||||||
|
If anything is failing or behind, stop and say to run `/preparepr`.
|
||||||
|
|
||||||
|
3. Merge PR and delete branch
|
||||||
|
|
||||||
|
If checks are still running, use `--auto` to queue the merge.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Check status first
|
||||||
|
check_status=$(gh pr checks <PR> 2>&1)
|
||||||
|
if echo "$check_status" | grep -q "pending\|queued"; then
|
||||||
|
echo "Checks still running, using --auto to queue merge"
|
||||||
|
gh pr merge <PR> --squash --delete-branch --auto
|
||||||
|
echo "Merge queued. Monitor with: gh pr checks <PR> --watch"
|
||||||
|
else
|
||||||
|
gh pr merge <PR> --squash --delete-branch
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
If merge fails, report the error and stop. Do not retry in a loop.
|
||||||
|
If the PR needs changes beyond what `/preparepr` already did, stop and say to run `/preparepr` again.
|
||||||
|
|
||||||
|
4. Get merge SHA
|
||||||
|
|
||||||
|
```sh
|
||||||
|
merge_sha=$(gh pr view <PR> --json mergeCommit --jq '.mergeCommit.oid')
|
||||||
|
echo "merge_sha=$merge_sha"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Optional comment
|
||||||
|
|
||||||
|
Use a literal multiline string or heredoc for newlines.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr comment <PR> -F - <<'EOF'
|
||||||
|
Merged via squash.
|
||||||
|
|
||||||
|
- Merge commit: $merge_sha
|
||||||
|
|
||||||
|
Thanks @$contrib!
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Verify PR state is MERGED
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr view <PR> --json state --jq .state
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Clean up worktree only on success
|
||||||
|
|
||||||
|
Run cleanup only if step 6 returned `MERGED`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/dev/openclaw
|
||||||
|
|
||||||
|
git worktree remove ".worktrees/pr-<PR>" --force
|
||||||
|
|
||||||
|
git branch -D temp/pr-<PR> 2>/dev/null || true
|
||||||
|
git branch -D pr-<PR> 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- Worktree only.
|
||||||
|
- Do not close PRs.
|
||||||
|
- End in MERGED state.
|
||||||
|
- Clean up only after merge success.
|
||||||
|
- Never push to main. Use `gh pr merge --squash` only.
|
||||||
|
- Do not run `git push` at all in this command.
|
||||||
4
.agents/skills/merge-pr/agents/openai.yaml
Normal file
4
.agents/skills/merge-pr/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Merge PR"
|
||||||
|
short_description: "Merge GitHub PRs via squash"
|
||||||
|
default_prompt: "Use $merge-pr to merge a GitHub PR via squash after preparation."
|
||||||
248
.agents/skills/prepare-pr/SKILL.md
Normal file
248
.agents/skills/prepare-pr/SKILL.md
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
---
|
||||||
|
name: prepare-pr
|
||||||
|
description: Prepare a GitHub PR for merge by rebasing onto main, fixing review findings, running gates, committing fixes, and pushing to the PR head branch. Use after /reviewpr. Never merge or push to main.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Prepare PR
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Prepare a PR branch for merge with review fixes, green gates, and an updated head branch.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- Ask for PR number or URL.
|
||||||
|
- If missing, auto-detect from conversation.
|
||||||
|
- If ambiguous, ask.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Never push to `main` or `origin/main`. Push only to the PR head branch.
|
||||||
|
- Never run `git push` without specifying remote and branch explicitly. Do not run bare `git push`.
|
||||||
|
- Do not run gateway stop commands. Do not kill processes. Do not touch port 18792.
|
||||||
|
- Do not run `git clean -fdx`.
|
||||||
|
- Do not run `git add -A` or `git add .`. Stage only specific files changed.
|
||||||
|
|
||||||
|
## Execution Rule
|
||||||
|
|
||||||
|
- Execute the workflow. Do not stop after printing the TODO checklist.
|
||||||
|
- If delegating, require the delegate to run commands and capture outputs.
|
||||||
|
|
||||||
|
## Known Footguns
|
||||||
|
|
||||||
|
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/dev/openclaw` if available; otherwise ask user.
|
||||||
|
- Do not run `git clean -fdx`.
|
||||||
|
- Do not run `git add -A` or `git add .`.
|
||||||
|
|
||||||
|
## Completion Criteria
|
||||||
|
|
||||||
|
- Rebase PR commits onto `origin/main`.
|
||||||
|
- Fix all BLOCKER and IMPORTANT items from `.local/review.md`.
|
||||||
|
- Run gates and pass.
|
||||||
|
- Commit prep changes.
|
||||||
|
- Push the updated HEAD back to the PR head branch.
|
||||||
|
- Write `.local/prep.md` with a prep summary.
|
||||||
|
- Output exactly: `PR is ready for /mergepr`.
|
||||||
|
|
||||||
|
## First: Create a TODO Checklist
|
||||||
|
|
||||||
|
Create a checklist of all prep steps, print it, then continue and execute the commands.
|
||||||
|
|
||||||
|
## Setup: Use a Worktree
|
||||||
|
|
||||||
|
Use an isolated worktree for all prep work.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/openclaw
|
||||||
|
# Sanity: confirm you are in the repo
|
||||||
|
git rev-parse --show-toplevel
|
||||||
|
|
||||||
|
WORKTREE_DIR=".worktrees/pr-<PR>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run all commands inside the worktree directory.
|
||||||
|
|
||||||
|
## Load Review Findings (Mandatory)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
if [ -f .local/review.md ]; then
|
||||||
|
echo "Found review findings from /reviewpr"
|
||||||
|
else
|
||||||
|
echo "Missing .local/review.md. Run /reviewpr first and save findings."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read it
|
||||||
|
sed -n '1,200p' .local/review.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Identify PR meta (author, head branch, head repo URL)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr view <PR> --json number,title,author,headRefName,baseRefName,headRepository,body --jq '{number,title,author:.author.login,head:.headRefName,base:.baseRefName,headRepo:.headRepository.nameWithOwner,body}'
|
||||||
|
contrib=$(gh pr view <PR> --json author --jq .author.login)
|
||||||
|
head=$(gh pr view <PR> --json headRefName --jq .headRefName)
|
||||||
|
head_repo_url=$(gh pr view <PR> --json headRepository --jq .headRepository.url)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Fetch the PR branch tip into a local ref
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git fetch origin pull/<PR>/head:pr-<PR>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Rebase PR commits onto latest main
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Move worktree to the PR tip first
|
||||||
|
git reset --hard pr-<PR>
|
||||||
|
|
||||||
|
# Rebase onto current main
|
||||||
|
git fetch origin main
|
||||||
|
git rebase origin/main
|
||||||
|
```
|
||||||
|
|
||||||
|
If conflicts happen:
|
||||||
|
|
||||||
|
- Resolve each conflicted file.
|
||||||
|
- Run `git add <resolved_file>` for each file.
|
||||||
|
- Run `git rebase --continue`.
|
||||||
|
|
||||||
|
If the rebase gets confusing or you resolve conflicts 3 or more times, stop and report.
|
||||||
|
|
||||||
|
4. Fix issues from `.local/review.md`
|
||||||
|
|
||||||
|
- Fix all BLOCKER and IMPORTANT items.
|
||||||
|
- NITs are optional.
|
||||||
|
- Keep scope tight.
|
||||||
|
|
||||||
|
Keep a running log in `.local/prep.md`:
|
||||||
|
|
||||||
|
- List which review items you fixed.
|
||||||
|
- List which files you touched.
|
||||||
|
- Note behavior changes.
|
||||||
|
|
||||||
|
5. Update `CHANGELOG.md` if flagged in review
|
||||||
|
|
||||||
|
Check `.local/review.md` section H for guidance.
|
||||||
|
If flagged and user-facing:
|
||||||
|
|
||||||
|
- Check if `CHANGELOG.md` exists.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ls CHANGELOG.md 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
- Follow existing format.
|
||||||
|
- Add a concise entry with PR number and contributor.
|
||||||
|
|
||||||
|
6. Update docs if flagged in review
|
||||||
|
|
||||||
|
Check `.local/review.md` section G for guidance.
|
||||||
|
If flagged, update only docs related to the PR changes.
|
||||||
|
|
||||||
|
7. Commit prep fixes
|
||||||
|
|
||||||
|
Stage only specific files:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git add <file1> <file2> ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Preferred commit tool:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
committer "fix: <summary> (#<PR>) (thanks @$contrib)" <changed files>
|
||||||
|
```
|
||||||
|
|
||||||
|
If `committer` is not found:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git commit -m "fix: <summary> (#<PR>) (thanks @$contrib)"
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Run full gates before pushing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm install
|
||||||
|
pnpm build
|
||||||
|
pnpm ui:build
|
||||||
|
pnpm check
|
||||||
|
pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Require all to pass. If something fails, fix, commit, and rerun. Allow at most 3 fix and rerun cycles. If gates still fail after 3 attempts, stop and report the failures. Do not loop indefinitely.
|
||||||
|
|
||||||
|
9. Push updates back to the PR head branch
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Ensure remote for PR head exists
|
||||||
|
git remote add prhead "$head_repo_url.git" 2>/dev/null || git remote set-url prhead "$head_repo_url.git"
|
||||||
|
|
||||||
|
# Use force with lease after rebase
|
||||||
|
# Double check: $head must NOT be "main" or "master"
|
||||||
|
echo "Pushing to branch: $head"
|
||||||
|
if [ "$head" = "main" ] || [ "$head" = "master" ]; then
|
||||||
|
echo "ERROR: head branch is main/master. This is wrong. Stopping."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
git push --force-with-lease prhead HEAD:$head
|
||||||
|
```
|
||||||
|
|
||||||
|
10. Verify PR is not behind main (Mandatory)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git fetch origin main
|
||||||
|
git fetch origin pull/<PR>/head:pr-<PR>-verify --force
|
||||||
|
git merge-base --is-ancestor origin/main pr-<PR>-verify && echo "PR is up to date with main" || echo "ERROR: PR is still behind main, rebase again"
|
||||||
|
git branch -D pr-<PR>-verify 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
If still behind main, repeat steps 2 through 9.
|
||||||
|
|
||||||
|
11. Write prep summary artifacts (Mandatory)
|
||||||
|
|
||||||
|
Update `.local/prep.md` with:
|
||||||
|
|
||||||
|
- Current HEAD sha from `git rev-parse HEAD`.
|
||||||
|
- Short bullet list of changes.
|
||||||
|
- Gate results.
|
||||||
|
- Push confirmation.
|
||||||
|
- Rebase verification result.
|
||||||
|
|
||||||
|
Create or overwrite `.local/prep.md` and verify it exists and is non-empty:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git rev-parse HEAD
|
||||||
|
ls -la .local/prep.md
|
||||||
|
wc -l .local/prep.md
|
||||||
|
```
|
||||||
|
|
||||||
|
12. Output
|
||||||
|
|
||||||
|
Include a diff stat summary:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git diff --stat origin/main..HEAD
|
||||||
|
git diff --shortstat origin/main..HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
Report totals: X files changed, Y insertions(+), Z deletions(-).
|
||||||
|
|
||||||
|
If gates passed and push succeeded, print exactly:
|
||||||
|
|
||||||
|
```
|
||||||
|
PR is ready for /mergepr
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise, list remaining failures and stop.
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- Worktree only.
|
||||||
|
- Do not delete the worktree on success. `/mergepr` may reuse it.
|
||||||
|
- Do not run `gh pr merge`.
|
||||||
|
- Never push to main. Only push to the PR head branch.
|
||||||
|
- Run and pass all gates before pushing.
|
||||||
4
.agents/skills/prepare-pr/agents/openai.yaml
Normal file
4
.agents/skills/prepare-pr/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Prepare PR"
|
||||||
|
short_description: "Prepare GitHub PRs for merge"
|
||||||
|
default_prompt: "Use $prepare-pr to prep a GitHub PR for merge without merging."
|
||||||
228
.agents/skills/review-pr/SKILL.md
Normal file
228
.agents/skills/review-pr/SKILL.md
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
---
|
||||||
|
name: review-pr
|
||||||
|
description: Review-only GitHub pull request analysis with the gh CLI. Use when asked to review a PR, provide structured feedback, or assess readiness to land. Do not merge, push, or make code changes you intend to keep.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Review PR
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Perform a thorough review-only PR assessment and return a structured recommendation on readiness for /preparepr.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- Ask for PR number or URL.
|
||||||
|
- If missing, always ask. Never auto-detect from conversation.
|
||||||
|
- If ambiguous, ask.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
- Never push to `main` or `origin/main`, not during review, not ever.
|
||||||
|
- Do not run `git push` at all during review. Treat review as read only.
|
||||||
|
- Do not stop or kill the gateway. Do not run gateway stop commands. Do not kill processes on port 18792.
|
||||||
|
|
||||||
|
## Execution Rule
|
||||||
|
|
||||||
|
- Execute the workflow. Do not stop after printing the TODO checklist.
|
||||||
|
- If delegating, require the delegate to run commands and capture outputs, not a plan.
|
||||||
|
|
||||||
|
## Known Failure Modes
|
||||||
|
|
||||||
|
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/dev/openclaw` if available; otherwise ask user.
|
||||||
|
- Do not stop after printing the checklist. That is not completion.
|
||||||
|
|
||||||
|
## Writing Style for Output
|
||||||
|
|
||||||
|
- Write casual and direct.
|
||||||
|
- Avoid em dashes and en dashes. Use commas or separate sentences.
|
||||||
|
|
||||||
|
## Completion Criteria
|
||||||
|
|
||||||
|
- Run the commands in the worktree and inspect the PR directly.
|
||||||
|
- Produce the structured review sections A through J.
|
||||||
|
- Save the full review to `.local/review.md` inside the worktree.
|
||||||
|
|
||||||
|
## First: Create a TODO Checklist
|
||||||
|
|
||||||
|
Create a checklist of all review steps, print it, then continue and execute the commands.
|
||||||
|
|
||||||
|
## Setup: Use a Worktree
|
||||||
|
|
||||||
|
Use an isolated worktree for all review work.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/dev/openclaw
|
||||||
|
# Sanity: confirm you are in the repo
|
||||||
|
git rev-parse --show-toplevel
|
||||||
|
|
||||||
|
WORKTREE_DIR=".worktrees/pr-<PR>"
|
||||||
|
git fetch origin main
|
||||||
|
|
||||||
|
# Reuse existing worktree if it exists, otherwise create new
|
||||||
|
if [ -d "$WORKTREE_DIR" ]; then
|
||||||
|
cd "$WORKTREE_DIR"
|
||||||
|
git checkout temp/pr-<PR> 2>/dev/null || git checkout -b temp/pr-<PR>
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
else
|
||||||
|
git worktree add "$WORKTREE_DIR" -b temp/pr-<PR> origin/main
|
||||||
|
cd "$WORKTREE_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create local scratch space that persists across /reviewpr to /preparepr to /mergepr
|
||||||
|
mkdir -p .local
|
||||||
|
```
|
||||||
|
|
||||||
|
Run all commands inside the worktree directory.
|
||||||
|
Start on `origin/main` so you can check for existing implementations before looking at PR code.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Identify PR meta and context
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr view <PR> --json number,title,state,isDraft,author,baseRefName,headRefName,headRepository,url,body,labels,assignees,reviewRequests,files,additions,deletions --jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headRepo:.headRepository.nameWithOwner,additions,deletions,files:.files|length,body}'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Check if this already exists in main before looking at the PR branch
|
||||||
|
|
||||||
|
- Identify the core feature or fix from the PR title and description.
|
||||||
|
- Search for existing implementations using keywords from the PR title, changed file paths, and function or component names from the diff.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Use keywords from the PR title and changed files
|
||||||
|
rg -n "<keyword_from_pr_title>" -S src packages apps ui || true
|
||||||
|
rg -n "<function_or_component_name>" -S src packages apps ui || true
|
||||||
|
|
||||||
|
git log --oneline --all --grep="<keyword_from_pr_title>" | head -20
|
||||||
|
```
|
||||||
|
|
||||||
|
If it already exists, call it out as a BLOCKER or at least IMPORTANT.
|
||||||
|
|
||||||
|
3. Claim the PR
|
||||||
|
|
||||||
|
Assign yourself so others know someone is reviewing. Skip if the PR looks like spam or is a draft you plan to recommend closing.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh_user=$(gh api user --jq .login)
|
||||||
|
gh pr edit <PR> --add-assignee "$gh_user"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Read the PR description carefully
|
||||||
|
|
||||||
|
Use the body from step 1. Summarize goal, scope, and missing context.
|
||||||
|
|
||||||
|
5. Read the diff thoroughly
|
||||||
|
|
||||||
|
Minimum:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr diff <PR>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need full code context locally, fetch the PR head to a local ref and diff it. Do not create a merge commit.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git fetch origin pull/<PR>/head:pr-<PR>
|
||||||
|
# Show changes without modifying the working tree
|
||||||
|
|
||||||
|
git diff --stat origin/main..pr-<PR>
|
||||||
|
git diff origin/main..pr-<PR>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to browse the PR version of files directly, temporarily check out `pr-<PR>` in the worktree. Do not commit or push. Return to `temp/pr-<PR>` and reset to `origin/main` afterward.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Use only if needed
|
||||||
|
# git checkout pr-<PR>
|
||||||
|
# ...inspect files...
|
||||||
|
|
||||||
|
git checkout temp/pr-<PR>
|
||||||
|
git reset --hard origin/main
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Validate the change is needed and valuable
|
||||||
|
|
||||||
|
Be honest. Call out low value AI slop.
|
||||||
|
|
||||||
|
7. Evaluate implementation quality
|
||||||
|
|
||||||
|
Review correctness, design, performance, and ergonomics.
|
||||||
|
|
||||||
|
8. Perform a security review
|
||||||
|
|
||||||
|
Assume OpenClaw subagents run with full disk access, including git, gh, and shell. Check auth, input validation, secrets, dependencies, tool safety, and privacy.
|
||||||
|
|
||||||
|
9. Review tests and verification
|
||||||
|
|
||||||
|
Identify what exists, what is missing, and what would be a minimal regression test.
|
||||||
|
|
||||||
|
10. Check docs
|
||||||
|
|
||||||
|
Check if the PR touches code with related documentation such as README, docs, inline API docs, or config examples.
|
||||||
|
|
||||||
|
- If docs exist for the changed area and the PR does not update them, flag as IMPORTANT.
|
||||||
|
- If the PR adds a new feature or config option with no docs, flag as IMPORTANT.
|
||||||
|
- If the change is purely internal with no user-facing impact, skip this.
|
||||||
|
|
||||||
|
11. Check changelog
|
||||||
|
|
||||||
|
Check if `CHANGELOG.md` exists and whether the PR warrants an entry.
|
||||||
|
|
||||||
|
- If the project has a changelog and the PR is user-facing, flag missing entry as IMPORTANT.
|
||||||
|
- Leave the change for /preparepr, only flag it here.
|
||||||
|
|
||||||
|
12. Answer the key question
|
||||||
|
|
||||||
|
Decide if /preparepr can fix issues or the contributor must update the PR.
|
||||||
|
|
||||||
|
13. Save findings to the worktree
|
||||||
|
|
||||||
|
Write the full structured review sections A through J to `.local/review.md`.
|
||||||
|
Create or overwrite the file and verify it exists and is non-empty.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ls -la .local/review.md
|
||||||
|
wc -l .local/review.md
|
||||||
|
```
|
||||||
|
|
||||||
|
14. Output the structured review
|
||||||
|
|
||||||
|
Produce a review that matches what you saved to `.local/review.md`.
|
||||||
|
|
||||||
|
A) TL;DR recommendation
|
||||||
|
|
||||||
|
- One of: READY FOR /preparepr | NEEDS WORK | NEEDS DISCUSSION | NOT USEFUL (CLOSE)
|
||||||
|
- 1 to 3 sentences.
|
||||||
|
|
||||||
|
B) What changed
|
||||||
|
|
||||||
|
C) What is good
|
||||||
|
|
||||||
|
D) Security findings
|
||||||
|
|
||||||
|
E) Concerns or questions (actionable)
|
||||||
|
|
||||||
|
- Numbered list.
|
||||||
|
- Mark each item as BLOCKER, IMPORTANT, or NIT.
|
||||||
|
- For each, point to file or area and propose a concrete fix.
|
||||||
|
|
||||||
|
F) Tests
|
||||||
|
|
||||||
|
G) Docs status
|
||||||
|
|
||||||
|
- State if related docs are up to date, missing, or not applicable.
|
||||||
|
|
||||||
|
H) Changelog
|
||||||
|
|
||||||
|
- State if `CHANGELOG.md` needs an entry and which category.
|
||||||
|
|
||||||
|
I) Follow ups (optional)
|
||||||
|
|
||||||
|
J) Suggested PR comment (optional)
|
||||||
|
|
||||||
|
## Guardrails
|
||||||
|
|
||||||
|
- Worktree only.
|
||||||
|
- Do not delete the worktree after review.
|
||||||
|
- Review only, do not merge, do not push.
|
||||||
4
.agents/skills/review-pr/agents/openai.yaml
Normal file
4
.agents/skills/review-pr/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Review PR"
|
||||||
|
short_description: "Review GitHub PRs without merging"
|
||||||
|
default_prompt: "Use $review-pr to perform a thorough, review-only GitHub PR review."
|
||||||
4
.github/ISSUE_TEMPLATE/config.yml
vendored
4
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -2,7 +2,7 @@ blank_issues_enabled: true
|
|||||||
contact_links:
|
contact_links:
|
||||||
- name: Onboarding
|
- name: Onboarding
|
||||||
url: https://discord.gg/clawd
|
url: https://discord.gg/clawd
|
||||||
about: New to Clawdbot? Join Discord for setup guidance from Krill in #help.
|
about: New to Clawdbot? Join Discord for setup guidance from Krill in \#help.
|
||||||
- name: Support
|
- name: Support
|
||||||
url: https://discord.gg/clawd
|
url: https://discord.gg/clawd
|
||||||
about: Get help from Krill and the community on Discord in #help.
|
about: Get help from Krill and the community on Discord in \#help.
|
||||||
|
|||||||
41
.github/actions/detect-docs-only/action.yml
vendored
Normal file
41
.github/actions/detect-docs-only/action.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: Detect docs-only changes
|
||||||
|
description: >
|
||||||
|
Outputs docs_only=true when all changed files are under docs/ or are
|
||||||
|
markdown (.md/.mdx). Fail-safe: if detection fails, outputs false (run
|
||||||
|
everything). Uses git diff — no API calls, no extra permissions needed.
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
docs_only:
|
||||||
|
description: "'true' if all changes are docs/markdown, 'false' otherwise"
|
||||||
|
value: ${{ steps.check.outputs.docs_only }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Detect docs-only changes
|
||||||
|
id: check
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
|
BASE="${{ github.event.before }}"
|
||||||
|
else
|
||||||
|
# Use the exact base SHA from the event payload — stable regardless
|
||||||
|
# of base branch movement (avoids origin/<ref> drift).
|
||||||
|
BASE="${{ github.event.pull_request.base.sha }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fail-safe: if we can't diff, assume non-docs (run everything)
|
||||||
|
CHANGED=$(git diff --name-only "$BASE" HEAD 2>/dev/null || echo "UNKNOWN")
|
||||||
|
if [ "$CHANGED" = "UNKNOWN" ] || [ -z "$CHANGED" ]; then
|
||||||
|
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if all changed files are docs or markdown
|
||||||
|
NON_DOCS=$(echo "$CHANGED" | grep -vE '^docs/|\.md$|\.mdx$' || true)
|
||||||
|
if [ -z "$NON_DOCS" ]; then
|
||||||
|
echo "docs_only=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Docs-only change detected — skipping heavy jobs"
|
||||||
|
else
|
||||||
|
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
6
.github/labeler.yml
vendored
6
.github/labeler.yml
vendored
@@ -9,6 +9,12 @@
|
|||||||
- "src/discord/**"
|
- "src/discord/**"
|
||||||
- "extensions/discord/**"
|
- "extensions/discord/**"
|
||||||
- "docs/channels/discord.md"
|
- "docs/channels/discord.md"
|
||||||
|
"channel: feishu":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/feishu/**"
|
||||||
|
- "extensions/feishu/**"
|
||||||
|
- "docs/channels/feishu.md"
|
||||||
"channel: googlechat":
|
"channel: googlechat":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
|
|||||||
303
.github/workflows/ci.yml
vendored
303
.github/workflows/ci.yml
vendored
@@ -2,10 +2,34 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.event.pull_request.number || github.sha }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# Detect docs-only changes to skip heavy jobs (test, build, Windows, macOS, Android).
|
||||||
|
# Lint and format always run. Fail-safe: if detection fails, run everything.
|
||||||
|
docs-scope:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
docs_only: ${{ steps.check.outputs.docs_only }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: false
|
||||||
|
|
||||||
|
- name: Detect docs-only changes
|
||||||
|
id: check
|
||||||
|
uses: ./.github/actions/detect-docs-only
|
||||||
|
|
||||||
install-check:
|
install-check:
|
||||||
|
needs: [docs-scope]
|
||||||
|
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -66,6 +90,8 @@ jobs:
|
|||||||
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
||||||
|
|
||||||
checks:
|
checks:
|
||||||
|
needs: [docs-scope]
|
||||||
|
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -74,18 +100,12 @@ jobs:
|
|||||||
- runtime: node
|
- runtime: node
|
||||||
task: tsgo
|
task: tsgo
|
||||||
command: pnpm tsgo
|
command: pnpm tsgo
|
||||||
- runtime: node
|
|
||||||
task: lint
|
|
||||||
command: pnpm build && pnpm lint
|
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: test
|
task: test
|
||||||
command: pnpm canvas:a2ui:bundle && pnpm test
|
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: protocol
|
task: protocol
|
||||||
command: pnpm protocol:check
|
command: pnpm protocol:check
|
||||||
- runtime: node
|
|
||||||
task: format
|
|
||||||
command: pnpm format
|
|
||||||
- runtime: bun
|
- runtime: bun
|
||||||
task: test
|
task: test
|
||||||
command: pnpm canvas:a2ui:bundle && bunx vitest run
|
command: pnpm canvas:a2ui:bundle && bunx vitest run
|
||||||
@@ -156,52 +176,17 @@ jobs:
|
|||||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||||
run: ${{ matrix.command }}
|
run: ${{ matrix.command }}
|
||||||
|
|
||||||
secrets:
|
# Lint and format always run, even on docs-only changes.
|
||||||
|
checks-lint:
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: false
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Install detect-secrets
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install detect-secrets==1.5.0
|
|
||||||
|
|
||||||
- name: Detect secrets
|
|
||||||
run: |
|
|
||||||
if ! detect-secrets scan --baseline .secrets.baseline; then
|
|
||||||
echo "::error::Secret scanning failed. See docs/gateway/security.md#secret-scanning-detect-secrets"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
checks-windows:
|
|
||||||
runs-on: blacksmith-4vcpu-windows-2025
|
|
||||||
env:
|
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
|
||||||
CLAWDBOT_TEST_WORKERS: 1
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- runtime: node
|
- task: lint
|
||||||
task: build & lint
|
|
||||||
command: pnpm build && pnpm lint
|
command: pnpm build && pnpm lint
|
||||||
- runtime: node
|
- task: format
|
||||||
task: test
|
command: pnpm format
|
||||||
command: pnpm canvas:a2ui:bundle && pnpm test
|
|
||||||
- runtime: node
|
|
||||||
task: protocol
|
|
||||||
command: pnpm protocol:check
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -266,18 +251,153 @@ jobs:
|
|||||||
pnpm -v
|
pnpm -v
|
||||||
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
||||||
|
|
||||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
- name: Run ${{ matrix.task }}
|
||||||
run: ${{ matrix.command }}
|
run: ${{ matrix.command }}
|
||||||
|
|
||||||
checks-macos:
|
secrets:
|
||||||
if: github.event_name == 'pull_request'
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
runs-on: macos-latest
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: false
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install detect-secrets
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install detect-secrets==1.5.0
|
||||||
|
|
||||||
|
- name: Detect secrets
|
||||||
|
run: |
|
||||||
|
if ! detect-secrets scan --baseline .secrets.baseline; then
|
||||||
|
echo "::error::Secret scanning failed. See docs/gateway/security.md#secret-scanning-detect-secrets"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
checks-windows:
|
||||||
|
needs: [docs-scope]
|
||||||
|
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||||
|
runs-on: blacksmith-4vcpu-windows-2025
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
|
# Keep total concurrency predictable on the 4 vCPU runner:
|
||||||
|
# `scripts/test-parallel.mjs` runs some vitest suites in parallel processes.
|
||||||
|
OPENCLAW_TEST_WORKERS: 2
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- task: test
|
- runtime: node
|
||||||
command: pnpm test
|
task: build & lint
|
||||||
|
command: pnpm build && pnpm lint
|
||||||
|
- runtime: node
|
||||||
|
task: test
|
||||||
|
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||||
|
- runtime: node
|
||||||
|
task: protocol
|
||||||
|
command: pnpm protocol:check
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: false
|
||||||
|
|
||||||
|
- name: Try to exclude workspace from Windows Defender (best-effort)
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$cmd = Get-Command Add-MpPreference -ErrorAction SilentlyContinue
|
||||||
|
if (-not $cmd) {
|
||||||
|
Write-Host "Add-MpPreference not available, skipping Defender exclusions."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Defender sometimes intercepts process spawning (vitest workers). If this fails
|
||||||
|
# (eg hardened images), keep going and rely on worker limiting above.
|
||||||
|
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction Stop
|
||||||
|
Add-MpPreference -ExclusionProcess "node.exe" -ErrorAction Stop
|
||||||
|
Write-Host "Defender exclusions applied."
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Failed to apply Defender exclusions, continuing. $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Checkout submodules (retry)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git submodule sync --recursive
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
if git -c protocol.version=2 submodule update --init --force --depth=1 --recursive; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Submodule update failed (attempt $attempt/5). Retrying…"
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22.x
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Setup pnpm (corepack retry)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
corepack enable
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
if corepack prepare pnpm@10.23.0 --activate; then
|
||||||
|
pnpm -v
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
|
||||||
|
- name: Runtime versions
|
||||||
|
run: |
|
||||||
|
node -v
|
||||||
|
npm -v
|
||||||
|
bun -v
|
||||||
|
pnpm -v
|
||||||
|
|
||||||
|
- name: Capture node path
|
||||||
|
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
run: |
|
||||||
|
export PATH="$NODE_BIN:$PATH"
|
||||||
|
which node
|
||||||
|
node -v
|
||||||
|
pnpm -v
|
||||||
|
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
||||||
|
|
||||||
|
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||||
|
run: ${{ matrix.command }}
|
||||||
|
|
||||||
|
# Consolidated macOS job: runs TS tests + Swift lint/build/test sequentially
|
||||||
|
# on a single runner. GitHub limits macOS concurrent jobs to 5 per org;
|
||||||
|
# running 4 separate jobs per PR (as before) starved the queue. One job
|
||||||
|
# per PR allows 5 PRs to run macOS checks simultaneously.
|
||||||
|
macos:
|
||||||
|
needs: [docs-scope]
|
||||||
|
if: github.event_name == 'pull_request' && needs.docs-scope.outputs.docs_only != 'true'
|
||||||
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -297,6 +417,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
# --- Node/pnpm setup (for TS tests) ---
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@@ -336,24 +457,34 @@ jobs:
|
|||||||
pnpm -v
|
pnpm -v
|
||||||
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
||||||
|
|
||||||
- name: Run ${{ matrix.task }}
|
# --- Run all checks sequentially (fast gates first) ---
|
||||||
|
- name: TS tests (macOS)
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
run: ${{ matrix.command }}
|
run: pnpm test
|
||||||
|
|
||||||
macos-app:
|
# --- Xcode/Swift setup ---
|
||||||
if: github.event_name == 'pull_request'
|
- name: Select Xcode 26.1
|
||||||
runs-on: macos-latest
|
run: |
|
||||||
strategy:
|
sudo xcode-select -s /Applications/Xcode_26.1.app
|
||||||
fail-fast: false
|
xcodebuild -version
|
||||||
matrix:
|
|
||||||
include:
|
- name: Install XcodeGen / SwiftLint / SwiftFormat
|
||||||
- task: lint
|
run: brew install xcodegen swiftlint swiftformat
|
||||||
command: |
|
|
||||||
|
- name: Show toolchain
|
||||||
|
run: |
|
||||||
|
sw_vers
|
||||||
|
xcodebuild -version
|
||||||
|
swift --version
|
||||||
|
|
||||||
|
- name: Swift lint
|
||||||
|
run: |
|
||||||
swiftlint --config .swiftlint.yml
|
swiftlint --config .swiftlint.yml
|
||||||
swiftformat --lint apps/macos/Sources --config .swiftformat
|
swiftformat --lint apps/macos/Sources --config .swiftformat
|
||||||
- task: build
|
|
||||||
command: |
|
- name: Swift build (release)
|
||||||
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
if swift build --package-path apps/macos --configuration release; then
|
if swift build --package-path apps/macos --configuration release; then
|
||||||
@@ -363,8 +494,9 @@ jobs:
|
|||||||
sleep $((attempt * 20))
|
sleep $((attempt * 20))
|
||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
- task: test
|
|
||||||
command: |
|
- name: Swift test
|
||||||
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
for attempt in 1 2 3; do
|
for attempt in 1 2 3; do
|
||||||
if swift test --package-path apps/macos --parallel --enable-code-coverage --show-codecov-path; then
|
if swift test --package-path apps/macos --parallel --enable-code-coverage --show-codecov-path; then
|
||||||
@@ -374,42 +506,7 @@ jobs:
|
|||||||
sleep $((attempt * 20))
|
sleep $((attempt * 20))
|
||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: false
|
|
||||||
|
|
||||||
- name: Checkout submodules (retry)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
git submodule sync --recursive
|
|
||||||
for attempt in 1 2 3 4 5; do
|
|
||||||
if git -c protocol.version=2 submodule update --init --force --depth=1 --recursive; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Submodule update failed (attempt $attempt/5). Retrying…"
|
|
||||||
sleep $((attempt * 10))
|
|
||||||
done
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Select Xcode 26.1
|
|
||||||
run: |
|
|
||||||
sudo xcode-select -s /Applications/Xcode_26.1.app
|
|
||||||
xcodebuild -version
|
|
||||||
|
|
||||||
- name: Install XcodeGen / SwiftLint / SwiftFormat
|
|
||||||
run: |
|
|
||||||
brew install xcodegen swiftlint swiftformat
|
|
||||||
|
|
||||||
- name: Show toolchain
|
|
||||||
run: |
|
|
||||||
sw_vers
|
|
||||||
xcodebuild -version
|
|
||||||
swift --version
|
|
||||||
|
|
||||||
- name: Run ${{ matrix.task }}
|
|
||||||
run: ${{ matrix.command }}
|
|
||||||
ios:
|
ios:
|
||||||
if: false # ignore iOS in CI for now
|
if: false # ignore iOS in CI for now
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
@@ -584,6 +681,8 @@ jobs:
|
|||||||
PY
|
PY
|
||||||
|
|
||||||
android:
|
android:
|
||||||
|
needs: [docs-scope]
|
||||||
|
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|||||||
4
.github/workflows/formal-conformance.yml
vendored
4
.github/workflows/formal-conformance.yml
vendored
@@ -3,6 +3,10 @@ name: Formal models (informational conformance)
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: formal-conformance-${{ github.event.pull_request.number || github.ref_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
formal_conformance:
|
formal_conformance:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
20
.github/workflows/install-smoke.yml
vendored
20
.github/workflows/install-smoke.yml
vendored
@@ -6,8 +6,28 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: install-smoke-${{ github.event.pull_request.number || github.sha }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
docs-scope:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
docs_only: ${{ steps.check.outputs.docs_only }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Detect docs-only changes
|
||||||
|
id: check
|
||||||
|
uses: ./.github/actions/detect-docs-only
|
||||||
|
|
||||||
install-smoke:
|
install-smoke:
|
||||||
|
needs: [docs-scope]
|
||||||
|
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout CLI
|
- name: Checkout CLI
|
||||||
|
|||||||
5
.github/workflows/workflow-sanity.yml
vendored
5
.github/workflows/workflow-sanity.yml
vendored
@@ -3,6 +3,11 @@ name: Workflow Sanity
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: workflow-sanity-${{ github.event.pull_request.number || github.sha }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
no-tabs:
|
no-tabs:
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -64,10 +64,15 @@ apps/ios/*.mobileprovision
|
|||||||
|
|
||||||
# Local untracked files
|
# Local untracked files
|
||||||
.local/
|
.local/
|
||||||
.vscode/
|
|
||||||
IDENTITY.md
|
IDENTITY.md
|
||||||
USER.md
|
USER.md
|
||||||
.tgz
|
.tgz
|
||||||
|
|
||||||
# local tooling
|
# local tooling
|
||||||
.serena/
|
.serena/
|
||||||
|
|
||||||
|
# Agent credentials and memory (NEVER COMMIT)
|
||||||
|
memory/
|
||||||
|
.agent/*.json
|
||||||
|
!.agent/workflows/
|
||||||
|
local/
|
||||||
|
|||||||
52
.markdownlint-cli2.jsonc
Normal file
52
.markdownlint-cli2.jsonc
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"globs": ["docs/**/*.md", "docs/**/*.mdx", "README.md"],
|
||||||
|
"ignores": ["docs/zh-CN/**", "docs/.i18n/**", "docs/reference/templates/**"],
|
||||||
|
"config": {
|
||||||
|
"default": true,
|
||||||
|
|
||||||
|
"MD013": false,
|
||||||
|
"MD025": false,
|
||||||
|
"MD029": false,
|
||||||
|
|
||||||
|
"MD033": {
|
||||||
|
"allowed_elements": [
|
||||||
|
"Note",
|
||||||
|
"Info",
|
||||||
|
"Tip",
|
||||||
|
"Warning",
|
||||||
|
"Card",
|
||||||
|
"CardGroup",
|
||||||
|
"Columns",
|
||||||
|
"Steps",
|
||||||
|
"Step",
|
||||||
|
"Tabs",
|
||||||
|
"Tab",
|
||||||
|
"Accordion",
|
||||||
|
"AccordionGroup",
|
||||||
|
"CodeGroup",
|
||||||
|
"Frame",
|
||||||
|
"Callout",
|
||||||
|
"ParamField",
|
||||||
|
"ResponseField",
|
||||||
|
"RequestExample",
|
||||||
|
"ResponseExample",
|
||||||
|
"img",
|
||||||
|
"a",
|
||||||
|
"br",
|
||||||
|
"details",
|
||||||
|
"summary",
|
||||||
|
"p",
|
||||||
|
"strong",
|
||||||
|
"picture",
|
||||||
|
"source",
|
||||||
|
"Tooltip",
|
||||||
|
"Check",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
"MD036": false,
|
||||||
|
"MD040": false,
|
||||||
|
"MD041": false,
|
||||||
|
"MD046": false,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"assets/",
|
"assets/",
|
||||||
"dist/",
|
"dist/",
|
||||||
"docs/_layouts/",
|
"docs/_layouts/",
|
||||||
|
"extensions/",
|
||||||
"node_modules/",
|
"node_modules/",
|
||||||
"patches/",
|
"patches/",
|
||||||
"pnpm-lock.yaml/",
|
"pnpm-lock.yaml/",
|
||||||
|
|||||||
@@ -8,98 +8,63 @@ Input
|
|||||||
- If missing: use the most recent PR mentioned in the conversation.
|
- If missing: use the most recent PR mentioned in the conversation.
|
||||||
- If ambiguous: ask.
|
- If ambiguous: ask.
|
||||||
|
|
||||||
Do (review-only)
|
Do (end-to-end)
|
||||||
Goal: produce a thorough review and a clear recommendation (READY for /landpr vs NEEDS WORK). Do NOT merge, do NOT push, do NOT make changes in the repo as part of this command.
|
Goal: PR must end in GitHub state = MERGED (never CLOSED). Use `gh pr merge` with `--rebase` or `--squash`.
|
||||||
|
|
||||||
1. Identify PR meta + context
|
1. Repo clean: `git status`.
|
||||||
|
2. Identify PR meta (author + head branch):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
gh pr view <PR> --json number,title,state,isDraft,author,baseRefName,headRefName,headRepository,url,body,labels,assignees,reviewRequests,files,additions,deletions --jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headRepo:.headRepository.nameWithOwner,additions,deletions,files:.files|length}'
|
gh pr view <PR> --json number,title,author,headRefName,baseRefName,headRepository --jq '{number,title,author:.author.login,head:.headRefName,base:.baseRefName,headRepo:.headRepository.nameWithOwner}'
|
||||||
|
contrib=$(gh pr view <PR> --json author --jq .author.login)
|
||||||
|
head=$(gh pr view <PR> --json headRefName --jq .headRefName)
|
||||||
|
head_repo_url=$(gh pr view <PR> --json headRepository --jq .headRepository.url)
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Read the PR description carefully
|
3. Fast-forward base:
|
||||||
- Summarize the stated goal, scope, and any “why now?” rationale.
|
- `git checkout main`
|
||||||
- Call out any missing context: motivation, alternatives considered, rollout/compat notes, risk.
|
- `git pull --ff-only`
|
||||||
|
4. Create temp base branch from main:
|
||||||
3. Read the diff thoroughly (prefer full diff)
|
- `git checkout -b temp/landpr-<ts-or-pr>`
|
||||||
|
5. Check out PR branch locally:
|
||||||
|
- `gh pr checkout <PR>`
|
||||||
|
6. Rebase PR branch onto temp base:
|
||||||
|
- `git rebase temp/landpr-<ts-or-pr>`
|
||||||
|
- Fix conflicts; keep history tidy.
|
||||||
|
7. Fix + tests + changelog:
|
||||||
|
- Implement fixes + add/adjust tests
|
||||||
|
- Update `CHANGELOG.md` and mention `#<PR>` + `@$contrib`
|
||||||
|
8. Decide merge strategy:
|
||||||
|
- Rebase if we want to preserve commit history
|
||||||
|
- Squash if we want a single clean commit
|
||||||
|
- If unclear, ask
|
||||||
|
9. Full gate (BEFORE commit):
|
||||||
|
- `pnpm lint && pnpm build && pnpm test`
|
||||||
|
10. Commit via committer (include # + contributor in commit message):
|
||||||
|
- `committer "fix: <summary> (#<PR>) (thanks @$contrib)" CHANGELOG.md <changed files>`
|
||||||
|
- `land_sha=$(git rev-parse HEAD)`
|
||||||
|
11. Push updated PR branch (rebase => usually needs force):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
gh pr diff <PR>
|
git remote add prhead "$head_repo_url.git" 2>/dev/null || git remote set-url prhead "$head_repo_url.git"
|
||||||
# If you need more surrounding context for files:
|
git push --force-with-lease prhead HEAD:$head
|
||||||
gh pr checkout <PR> # optional; still review-only
|
|
||||||
git show --stat
|
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Validate the change is needed / valuable
|
12. Merge PR (must show MERGED on GitHub):
|
||||||
- What user/customer/dev pain does this solve?
|
- Rebase: `gh pr merge <PR> --rebase`
|
||||||
- Is this change the smallest reasonable fix?
|
- Squash: `gh pr merge <PR> --squash`
|
||||||
- Are we introducing complexity for marginal benefit?
|
- Never `gh pr close` (closing is wrong)
|
||||||
- Are we changing behavior/contract in a way that needs docs or a release note?
|
13. Sync main:
|
||||||
|
- `git checkout main`
|
||||||
|
- `git pull --ff-only`
|
||||||
|
14. Comment on PR with what we did + SHAs + thanks:
|
||||||
|
|
||||||
5. Evaluate implementation quality + optimality
|
```sh
|
||||||
- Correctness: edge cases, error handling, null/undefined, concurrency, ordering.
|
merge_sha=$(gh pr view <PR> --json mergeCommit --jq '.mergeCommit.oid')
|
||||||
- Design: is the abstraction/architecture appropriate or over/under-engineered?
|
gh pr comment <PR> --body "Landed via temp rebase onto main.\n\n- Gate: pnpm lint && pnpm build && pnpm test\n- Land commit: $land_sha\n- Merge commit: $merge_sha\n\nThanks @$contrib!"
|
||||||
- Performance: hot paths, allocations, queries, network, N+1s, caching.
|
```
|
||||||
- Security/privacy: authz/authn, input validation, secrets, logging PII.
|
|
||||||
- Backwards compatibility: public APIs, config, migrations.
|
|
||||||
- Style consistency: formatting, naming, patterns used elsewhere.
|
|
||||||
|
|
||||||
6. Tests & verification
|
15. Verify PR state == MERGED:
|
||||||
- Identify what’s covered by tests (unit/integration/e2e).
|
- `gh pr view <PR> --json state --jq .state`
|
||||||
- Are there regression tests for the bug fixed / scenario added?
|
16. Delete temp branch:
|
||||||
- Missing tests? Call out exact cases that should be added.
|
- `git branch -D temp/landpr-<ts-or-pr>`
|
||||||
- If tests are present, do they actually assert the important behavior (not just snapshots / happy path)?
|
|
||||||
|
|
||||||
7. Follow-up refactors / cleanup suggestions
|
|
||||||
- Any code that should be simplified before merge?
|
|
||||||
- Any TODOs that should be tickets vs addressed now?
|
|
||||||
- Any deprecations, docs, types, or lint rules we should adjust?
|
|
||||||
|
|
||||||
8. Key questions to answer explicitly
|
|
||||||
- Can we fix everything ourselves in a follow-up, or does the contributor need to update this PR?
|
|
||||||
- Any blocking concerns (must-fix before merge)?
|
|
||||||
- Is this PR ready to land, or does it need work?
|
|
||||||
|
|
||||||
9. Output (structured)
|
|
||||||
Produce a review with these sections:
|
|
||||||
|
|
||||||
A) TL;DR recommendation
|
|
||||||
|
|
||||||
- One of: READY FOR /landpr | NEEDS WORK | NEEDS DISCUSSION
|
|
||||||
- 1–3 sentence rationale.
|
|
||||||
|
|
||||||
B) What changed
|
|
||||||
|
|
||||||
- Brief bullet summary of the diff/behavioral changes.
|
|
||||||
|
|
||||||
C) What’s good
|
|
||||||
|
|
||||||
- Bullets: correctness, simplicity, tests, docs, ergonomics, etc.
|
|
||||||
|
|
||||||
D) Concerns / questions (actionable)
|
|
||||||
|
|
||||||
- Numbered list.
|
|
||||||
- Mark each item as:
|
|
||||||
- BLOCKER (must fix before merge)
|
|
||||||
- IMPORTANT (should fix before merge)
|
|
||||||
- NIT (optional)
|
|
||||||
- For each: point to the file/area and propose a concrete fix or alternative.
|
|
||||||
|
|
||||||
E) Tests
|
|
||||||
|
|
||||||
- What exists.
|
|
||||||
- What’s missing (specific scenarios).
|
|
||||||
|
|
||||||
F) Follow-ups (optional)
|
|
||||||
|
|
||||||
- Non-blocking refactors/tickets to open later.
|
|
||||||
|
|
||||||
G) Suggested PR comment (optional)
|
|
||||||
|
|
||||||
- Offer: “Want me to draft a PR comment to the author?”
|
|
||||||
- If yes, provide a ready-to-paste comment summarizing the above, with clear asks.
|
|
||||||
|
|
||||||
Rules / Guardrails
|
|
||||||
|
|
||||||
- Review only: do not merge (`gh pr merge`), do not push branches, do not edit code.
|
|
||||||
- If you need clarification, ask questions rather than guessing.
|
|
||||||
|
|||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["oxc.oxc-vscode"]
|
||||||
|
}
|
||||||
22
.vscode/settings.json
vendored
Normal file
22
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"files.trimFinalNewlines": true,
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
|
},
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||||
|
},
|
||||||
|
"typescript.preferences.importModuleSpecifierEnding": "js",
|
||||||
|
"typescript.reportStyleChecksAsWarnings": false,
|
||||||
|
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"typescript.experimental.useTsgo": true
|
||||||
|
}
|
||||||
11
AGENTS.md
11
AGENTS.md
@@ -28,6 +28,14 @@
|
|||||||
- README (GitHub): keep absolute docs URLs (`https://docs.openclaw.ai/...`) so links work on GitHub.
|
- README (GitHub): keep absolute docs URLs (`https://docs.openclaw.ai/...`) so links work on GitHub.
|
||||||
- Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”.
|
- Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”.
|
||||||
|
|
||||||
|
## Docs i18n (zh-CN)
|
||||||
|
|
||||||
|
- `docs/zh-CN/**` is generated; do not edit unless the user explicitly asks.
|
||||||
|
- Pipeline: update English docs → adjust glossary (`docs/.i18n/glossary.zh-CN.json`) → run `scripts/docs-i18n` → apply targeted fixes only if instructed.
|
||||||
|
- Translation memory: `docs/.i18n/zh-CN.tm.jsonl` (generated).
|
||||||
|
- See `docs/.i18n/README.md`.
|
||||||
|
- The pipeline can be slow/inefficient; if it’s dragging, ping @jospalmbier on Discord instead of hacking around it.
|
||||||
|
|
||||||
## 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).
|
||||||
@@ -50,6 +58,7 @@
|
|||||||
- 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`
|
- Type-check/build: `pnpm build`
|
||||||
|
- TypeScript checks: `pnpm tsgo`
|
||||||
- Lint/format: `pnpm check`
|
- Lint/format: `pnpm check`
|
||||||
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
||||||
|
|
||||||
@@ -86,6 +95,8 @@
|
|||||||
- Group related changes; avoid bundling unrelated refactors.
|
- Group related changes; avoid bundling unrelated refactors.
|
||||||
- Changelog workflow: keep latest released version at top (no `Unreleased`); after publishing, bump version and start a new top section.
|
- Changelog workflow: keep latest released version at top (no `Unreleased`); after publishing, bump version and start a new top section.
|
||||||
- PRs should summarize scope, note testing performed, and mention any user-facing changes or new flags.
|
- PRs should summarize scope, note testing performed, and mention any user-facing changes or new flags.
|
||||||
|
- Read this when submitting a PR: `docs/help/submitting-a-pr.md` ([Submitting a PR](https://docs.openclaw.ai/help/submitting-a-pr))
|
||||||
|
- Read this when submitting an issue: `docs/help/submitting-an-issue.md` ([Submitting an Issue](https://docs.openclaw.ai/help/submitting-an-issue))
|
||||||
- PR review flow: when given a PR link, review via `gh pr view`/`gh pr diff` and do **not** change branches.
|
- PR review flow: when given a PR link, review via `gh pr view`/`gh pr diff` and do **not** change branches.
|
||||||
- PR review calls: prefer a single `gh pr view --json ...` to batch metadata/comments; run `gh pr diff` only when needed.
|
- PR review calls: prefer a single `gh pr view --json ...` to batch metadata/comments; run `gh pr diff` only when needed.
|
||||||
- Before starting a review when a GH Issue/PR is pasted: run `git pull`; if there are local changes or unpushed commits, stop and alert the user before reviewing.
|
- Before starting a review when a GH Issue/PR is pasted: run `git pull`; if there are local changes or unpushed commits, stop and alert the user before reviewing.
|
||||||
|
|||||||
140
CHANGELOG.md
140
CHANGELOG.md
@@ -2,26 +2,156 @@
|
|||||||
|
|
||||||
Docs: https://docs.openclaw.ai
|
Docs: https://docs.openclaw.ai
|
||||||
|
|
||||||
|
## 2026.2.6-4
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Gateway: add `agents.create`, `agents.update`, `agents.delete` RPC methods for web UI agent management. (#11045) Thanks @advaitpaliwal.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Web UI: make chat refresh smoothly scroll to the latest messages and suppress new-messages badge flash during manual refresh.
|
||||||
|
- Cron: route text-only isolated agent announces through the shared subagent announce flow; add exponential backoff for repeated errors; preserve future `nextRunAtMs` on restart; include current-boundary schedule matches; prevent stale threadId reuse across targets; and add per-job execution timeout. (#11641) Thanks @tyler6204.
|
||||||
|
- Subagents: stabilize announce timing, preserve compaction metrics across retries, clamp overflow-prone long timeouts, and cap impossible context usage token totals. (#11551) Thanks @tyler6204.
|
||||||
|
- Agents: recover from context overflow caused by oversized tool results (pre-emptive capping + fallback truncation). (#11579) Thanks @tyler6204.
|
||||||
|
- Gateway/CLI: when `gateway.bind=lan`, use a LAN IP for probe URLs and Control UI links. (#11448) Thanks @AnonO6.
|
||||||
|
- Memory: set Voyage embeddings `input_type` for improved retrieval. (#10818) Thanks @mcinteerj.
|
||||||
|
- Memory/QMD: run boot refresh in background by default, add configurable QMD maintenance timeouts, and retry QMD after fallback failures. (#9690, #9705)
|
||||||
|
- Media understanding: recognize `.caf` audio attachments for transcription. (#10982) Thanks @succ985.
|
||||||
|
- State dir: honor `OPENCLAW_STATE_DIR` for default device identity and canvas storage paths. (#4824) Thanks @kossoy.
|
||||||
|
- Tests: harden flaky hotspots by removing timer sleeps, consolidating onboarding provider-auth coverage, and improving memory test realism. (#11598) Thanks @gumadeiras.
|
||||||
|
|
||||||
|
## 2026.2.6
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Cron: default `wakeMode` is now `"now"` for new jobs (was `"next-heartbeat"`). (#10776) Thanks @tyler6204.
|
||||||
|
- Cron: `cron run` defaults to force execution; use `--due` to restrict to due-only. (#10776) Thanks @tyler6204.
|
||||||
|
- Models: support Anthropic Opus 4.6 and OpenAI Codex gpt-5.3-codex (forward-compat fallbacks). (#9853, #10720, #9995) Thanks @TinyTb, @calvin-hpnet, @tyler6204.
|
||||||
|
- Providers: add xAI (Grok) support. (#9885) Thanks @grp06.
|
||||||
|
- Providers: add Baidu Qianfan support. (#8868) Thanks @ide-rea.
|
||||||
|
- Web UI: add token usage dashboard. (#10072) Thanks @Takhoffman.
|
||||||
|
- Memory: native Voyage AI support. (#7078) Thanks @mcinteerj.
|
||||||
|
- Sessions: cap sessions_history payloads to reduce context overflow. (#10000) Thanks @gut-puncture.
|
||||||
|
- CLI: sort commands alphabetically in help output. (#8068) Thanks @deepsoumya617.
|
||||||
|
- CI: optimize pipeline throughput (macOS consolidation, Windows perf, workflow concurrency). (#10784) Thanks @mcaxtr.
|
||||||
|
- Agents: bump pi-mono to 0.52.7; add embedded forward-compat fallback for Opus 4.6 model ids.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Cron: run history deep-links to session chat from the dashboard. (#10776) Thanks @tyler6204.
|
||||||
|
- Cron: per-run session keys in run log entries and default labels for cron sessions. (#10776) Thanks @tyler6204.
|
||||||
|
- Cron: legacy payload field compatibility (`deliver`, `channel`, `to`, `bestEffortDeliver`) in schema. (#10776) Thanks @tyler6204.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Cron: scheduler reliability (timer drift, restart catch-up, lock contention, stale running markers). (#10776) Thanks @tyler6204.
|
||||||
|
- Cron: store migration hardening (legacy field migration, parse error handling, explicit delivery mode persistence). (#10776) Thanks @tyler6204.
|
||||||
|
- Telegram: auto-inject DM topic threadId in message tool + subagent announce. (#7235) Thanks @Lukavyi.
|
||||||
|
- Security: require auth for Gateway canvas host and A2UI assets. (#9518) Thanks @coygeek.
|
||||||
|
- Cron: fix scheduling and reminder delivery regressions; harden next-run recompute + timer re-arming + legacy schedule fields. (#9733, #9823, #9948, #9932) Thanks @tyler6204, @pycckuu, @j2h4u, @fujiwara-tofu-shop.
|
||||||
|
- Update: harden Control UI asset handling in update flow. (#10146) Thanks @gumadeiras.
|
||||||
|
- Security: add skill/plugin code safety scanner; redact credentials from config.get gateway responses. (#9806, #9858) Thanks @abdelsfane.
|
||||||
|
- Exec approvals: coerce bare string allowlist entries to objects. (#9903) Thanks @mcaxtr.
|
||||||
|
- Slack: add mention stripPatterns for /new and /reset. (#9971) Thanks @ironbyte-rgb.
|
||||||
|
- Chrome extension: fix bundled path resolution. (#8914) Thanks @kelvinCB.
|
||||||
|
- Compaction/errors: allow multiple compaction retries on context overflow; show clear billing errors. (#8928, #8391) Thanks @Glucksberg.
|
||||||
|
|
||||||
|
## 2026.2.3
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Telegram: remove last `@ts-nocheck` from `bot-handlers.ts`, use Grammy types directly, deduplicate `StickerMetadata`. Zero `@ts-nocheck` remaining in `src/telegram/`. (#9206)
|
||||||
|
- Telegram: remove `@ts-nocheck` from `bot-message.ts`, type deps via `Omit<BuildTelegramMessageContextParams>`, widen `allMedia` to `TelegramMediaRef[]`. (#9180)
|
||||||
|
- Telegram: remove `@ts-nocheck` from `bot.ts`, fix duplicate `bot.catch` error handler (Grammy overrides), remove dead reaction `message_thread_id` routing, harden sticker cache guard. (#9077)
|
||||||
|
- Onboarding: add Cloudflare AI Gateway provider setup and docs. (#7914) Thanks @roerohan.
|
||||||
|
- Onboarding: add Moonshot (.cn) auth choice and keep the China base URL when preserving defaults. (#7180) Thanks @waynelwz.
|
||||||
|
- Docs: clarify tmux send-keys for TUI by splitting text and Enter. (#7737) Thanks @Wangnov.
|
||||||
|
- Docs: mirror the landing page revamp for zh-CN (features, quickstart, docs directory, network model, credits). (#8994) Thanks @joshp123.
|
||||||
|
- Messages: add per-channel and per-account responsePrefix overrides across channels. (#9001) Thanks @mudrii.
|
||||||
|
- Cron: add announce delivery mode for isolated jobs (CLI + Control UI) and delivery mode config.
|
||||||
|
- Cron: default isolated jobs to announce delivery; accept ISO 8601 `schedule.at` in tool inputs.
|
||||||
|
- Cron: hard-migrate isolated jobs to announce/none delivery; drop legacy post-to-main/payload delivery fields and `atMs` inputs.
|
||||||
|
- Cron: delete one-shot jobs after success by default; add `--keep-after-run` for CLI.
|
||||||
|
- Cron: suppress messaging tools during announce delivery so summaries post consistently.
|
||||||
|
- Cron: avoid duplicate deliveries when isolated runs send messages directly.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Heartbeat: allow explicit accountId routing for multi-account channels. (#8702) Thanks @lsh411.
|
||||||
|
- TUI/Gateway: handle non-streaming finals, refresh history for non-local chat runs, and avoid event gap warnings for targeted tool streams. (#8432) Thanks @gumadeiras.
|
||||||
|
- Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard.
|
||||||
|
- Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo.
|
||||||
|
- Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman.
|
||||||
|
- Web UI: resolve header logo path when `gateway.controlUi.basePath` is set. (#7178) Thanks @Yeom-JinHo.
|
||||||
|
- Web UI: apply button styling to the new-messages indicator.
|
||||||
|
- Onboarding: infer auth choice from non-interactive API key flags. (#8484) Thanks @f-trycua.
|
||||||
|
- Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.
|
||||||
|
- Security: enforce sandboxed media paths for message tool attachments. (#9182) Thanks @victormier.
|
||||||
|
- Security: require explicit credentials for gateway URL overrides to prevent credential leakage. (#8113) Thanks @victormier.
|
||||||
|
- Security: gate `whatsapp_login` tool to owner senders and default-deny non-owner contexts. (#8768) Thanks @victormier.
|
||||||
|
- Voice call: harden webhook verification with host allowlists/proxy trust and keep ngrok loopback bypass.
|
||||||
|
- Voice call: add regression coverage for anonymous inbound caller IDs with allowlist policy. (#8104) Thanks @victormier.
|
||||||
|
- Cron: accept epoch timestamps and 0ms durations in CLI `--at` parsing.
|
||||||
|
- Cron: reload store data when the store file is recreated or mtime changes.
|
||||||
|
- Cron: deliver announce runs directly, honor delivery mode, and respect wakeMode for summaries. (#8540) Thanks @tyler6204.
|
||||||
|
- Telegram: include forward_from_chat metadata in forwarded messages and harden cron delivery target checks. (#8392) Thanks @Glucksberg.
|
||||||
|
- macOS: fix cron payload summary rendering and ISO 8601 formatter concurrency safety.
|
||||||
|
|
||||||
|
## 2026.2.2-3
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Update: ship legacy daemon-cli shim for pre-tsdown update imports (fixes daemon restart after npm update).
|
||||||
|
|
||||||
|
## 2026.2.2-2
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Docs: promote BlueBubbles as the recommended iMessage integration; mark imsg channel as legacy. (#8415) Thanks @tyler6204.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- CLI status: resolve build-info from bundled dist output (fixes "unknown" commit in npm builds).
|
||||||
|
|
||||||
|
## 2026.2.2-1
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- CLI status: fall back to build-info for version detection (fixes "unknown" in beta builds). Thanks @gumadeira.
|
||||||
|
|
||||||
## 2026.2.2
|
## 2026.2.2
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
- Feishu: add Feishu/Lark plugin support + docs. (#7313) Thanks @jiulingyun (openclaw-cn).
|
||||||
- Web UI: add Agents dashboard for managing agent files, tools, skills, models, channels, and cron jobs.
|
- Web UI: add Agents dashboard for managing agent files, tools, skills, models, channels, and cron jobs.
|
||||||
|
- Subagents: discourage direct messaging tool use unless a specific external recipient is requested.
|
||||||
- Memory: implement the opt-in QMD backend for workspace memory. (#3160) Thanks @vignesh07.
|
- Memory: implement the opt-in QMD backend for workspace memory. (#3160) Thanks @vignesh07.
|
||||||
- Config: allow setting a default subagent thinking level via `agents.defaults.subagents.thinking` (and per-agent `agents.list[].subagents.thinking`). (#7372) Thanks @tyler6204.
|
|
||||||
- Security: add healthcheck skill and bootstrap audit guidance. (#7641) Thanks @Takhoffman.
|
- Security: add healthcheck skill and bootstrap audit guidance. (#7641) Thanks @Takhoffman.
|
||||||
- Docs: zh-CN translation polish + pipeline guidance. (#8202, #6995) Thanks @AaronWander, @taiyi747, @Explorer1092, @rendaoyuan.
|
- Config: allow setting a default subagent thinking level via `agents.defaults.subagents.thinking` (and per-agent `agents.list[].subagents.thinking`). (#7372) Thanks @tyler6204.
|
||||||
- Docs: zh-CN translations seed + nav polish + landing notice + typo fix. (#6619, #7242, #7303, #7415) Thanks @joshp123, @lailoo.
|
- Docs: zh-CN translations seed + polish, pipeline guidance, nav/landing updates, and typo fixes. (#8202, #6995, #6619, #7242, #7303, #7415) Thanks @AaronWander, @taiyi747, @Explorer1092, @rendaoyuan, @joshp123, @lailoo.
|
||||||
|
- Docs: add zh-CN i18n guardrails to avoid editing generated translations. (#8416) Thanks @joshp123.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Security: Matrix allowlists now require full MXIDs; ambiguous name resolution no longer grants access. Thanks @MegaManSec.
|
- Docs: finish renaming the QMD memory docs to reference the OpenClaw state dir.
|
||||||
|
- Onboarding: keep TUI flow exclusive (skip completion prompt + background Web UI seed).
|
||||||
|
- Onboarding: drop completion prompt now handled by install/update.
|
||||||
|
- TUI: block onboarding output while TUI is active and restore terminal state on exit.
|
||||||
|
- CLI: cache shell completion scripts in state dir and source cached files in profiles.
|
||||||
|
- Zsh completion: escape option descriptions to avoid invalid option errors.
|
||||||
|
- Agents: repair malformed tool calls and session transcripts. (#7473) Thanks @justinhuangcode.
|
||||||
|
- fix(agents): validate AbortSignal instances before calling AbortSignal.any() (#7277) (thanks @Elarwei001)
|
||||||
|
- fix(webchat): respect user scroll position during streaming and refresh (#7226) (thanks @marcomarandiz)
|
||||||
|
- Telegram: recover from grammY long-poll timed out errors. (#7466) Thanks @macmimi23.
|
||||||
|
- Media understanding: skip binary media from file text extraction. (#7475) Thanks @AlexZhangji.
|
||||||
- Security: enforce access-group gating for Slack slash commands when channel type lookup fails.
|
- Security: enforce access-group gating for Slack slash commands when channel type lookup fails.
|
||||||
- Security: require validated shared-secret auth before skipping device identity on gateway connect.
|
- Security: require validated shared-secret auth before skipping device identity on gateway connect.
|
||||||
- Security: guard skill installer downloads with SSRF checks (block private/localhost URLs).
|
- Security: guard skill installer downloads with SSRF checks (block private/localhost URLs).
|
||||||
- Security: harden Windows exec allowlist; block cmd.exe bypass via single &. Thanks @simecek.
|
- Security: harden Windows exec allowlist; block cmd.exe bypass via single &. Thanks @simecek.
|
||||||
- Media understanding: apply SSRF guardrails to provider fetches; allow private baseUrl overrides explicitly.
|
|
||||||
- fix(voice-call): harden inbound allowlist; reject anonymous callers; require Telnyx publicKey for allowlist; token-gate Twilio media streams; cap webhook body size (thanks @simecek)
|
- fix(voice-call): harden inbound allowlist; reject anonymous callers; require Telnyx publicKey for allowlist; token-gate Twilio media streams; cap webhook body size (thanks @simecek)
|
||||||
|
- Media understanding: apply SSRF guardrails to provider fetches; allow private baseUrl overrides explicitly.
|
||||||
- fix(webchat): respect user scroll position during streaming and refresh (#7226) (thanks @marcomarandiz)
|
- fix(webchat): respect user scroll position during streaming and refresh (#7226) (thanks @marcomarandiz)
|
||||||
- Telegram: recover from grammY long-poll timed out errors. (#7466) Thanks @macmimi23.
|
- Telegram: recover from grammY long-poll timed out errors. (#7466) Thanks @macmimi23.
|
||||||
- Agents: repair malformed tool calls and session transcripts. (#7473) Thanks @justinhuangcode.
|
- Agents: repair malformed tool calls and session transcripts. (#7473) Thanks @justinhuangcode.
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ Welcome to the lobster tank! 🦞
|
|||||||
- **Christoph Nakazawa** - JS Infra
|
- **Christoph Nakazawa** - JS Infra
|
||||||
- GitHub: [@cpojer](https://github.com/cpojer) · X: [@cnakazawa](https://x.com/cnakazawa)
|
- GitHub: [@cpojer](https://github.com/cpojer) · X: [@cnakazawa](https://x.com/cnakazawa)
|
||||||
|
|
||||||
|
- **Gustavo Madeira Santana** - Multi-agents, CLI, web UI
|
||||||
|
- GitHub: [@gumadeiras](https://github.com/gumadeiras) · X: [@gumadeiras](https://x.com/gumadeiras)
|
||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
1. **Bugs & small fixes** → Open a PR!
|
1. **Bugs & small fixes** → Open a PR!
|
||||||
|
|||||||
@@ -44,5 +44,5 @@ USER node
|
|||||||
#
|
#
|
||||||
# For container platforms requiring external health checks:
|
# For container platforms requiring external health checks:
|
||||||
# 1. Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD env var
|
# 1. Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD env var
|
||||||
# 2. Override CMD: ["node","dist/index.js","gateway","--allow-unconfigured","--bind","lan"]
|
# 2. Override CMD: ["node","openclaw.mjs","gateway","--allow-unconfigured","--bind","lan"]
|
||||||
CMD ["node", "dist/index.js", "gateway", "--allow-unconfigured"]
|
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
|
||||||
|
|||||||
108
README.md
108
README.md
@@ -23,9 +23,10 @@ It answers you on the channels you already use (WhatsApp, Telegram, Slack, Disco
|
|||||||
|
|
||||||
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
||||||
|
|
||||||
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/start/faq) · [Wizard](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-clawdbot) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
|
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/start/faq) · [Wizard](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
|
||||||
|
|
||||||
Preferred setup: run the onboarding wizard (`openclaw onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
Preferred setup: run the onboarding wizard (`openclaw onboard`) in your terminal.
|
||||||
|
The wizard guides you step by step through setting up the gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
||||||
Works with npm, pnpm, or bun.
|
Works with npm, pnpm, or bun.
|
||||||
New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started)
|
New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started)
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
|
|||||||
- **[Anthropic](https://www.anthropic.com/)** (Claude Pro/Max)
|
- **[Anthropic](https://www.anthropic.com/)** (Claude Pro/Max)
|
||||||
- **[OpenAI](https://openai.com/)** (ChatGPT/Codex)
|
- **[OpenAI](https://openai.com/)** (ChatGPT/Codex)
|
||||||
|
|
||||||
Model note: while any model is supported, I strongly recommend **Anthropic Pro/Max (100/200) + Opus 4.5** for long‑context strength and better prompt‑injection resistance. See [Onboarding](https://docs.openclaw.ai/start/onboarding).
|
Model note: while any model is supported, I strongly recommend **Anthropic Pro/Max (100/200) + Opus 4.6** for long‑context strength and better prompt‑injection resistance. See [Onboarding](https://docs.openclaw.ai/start/onboarding).
|
||||||
|
|
||||||
## Models (selection + auth)
|
## Models (selection + auth)
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
|||||||
## Highlights
|
## Highlights
|
||||||
|
|
||||||
- **[Local-first Gateway](https://docs.openclaw.ai/gateway)** — single control plane for sessions, channels, tools, and events.
|
- **[Local-first Gateway](https://docs.openclaw.ai/gateway)** — single control plane for sessions, channels, tools, and events.
|
||||||
- **[Multi-channel inbox](https://docs.openclaw.ai/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, Microsoft Teams, Matrix, Zalo, Zalo Personal, WebChat, macOS, iOS/Android.
|
- **[Multi-channel inbox](https://docs.openclaw.ai/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, BlueBubbles (iMessage), iMessage (legacy), Microsoft Teams, Matrix, Zalo, Zalo Personal, WebChat, macOS, iOS/Android.
|
||||||
- **[Multi-agent routing](https://docs.openclaw.ai/gateway/configuration)** — route inbound channels/accounts/peers to isolated agents (workspaces + per-agent sessions).
|
- **[Multi-agent routing](https://docs.openclaw.ai/gateway/configuration)** — route inbound channels/accounts/peers to isolated agents (workspaces + per-agent sessions).
|
||||||
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — always-on speech for macOS/iOS/Android with ElevenLabs.
|
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — always-on speech for macOS/iOS/Android with ElevenLabs.
|
||||||
- **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
|
- **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
|
||||||
@@ -144,7 +145,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
|||||||
|
|
||||||
### Channels
|
### Channels
|
||||||
|
|
||||||
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [iMessage](https://docs.openclaw.ai/channels/imessage) (imsg), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) (extension), [Matrix](https://docs.openclaw.ai/channels/matrix) (extension), [Zalo](https://docs.openclaw.ai/channels/zalo) (extension), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser) (extension), [WebChat](https://docs.openclaw.ai/web/webchat).
|
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (iMessage, recommended), [iMessage](https://docs.openclaw.ai/channels/imessage) (legacy imsg), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) (extension), [Matrix](https://docs.openclaw.ai/channels/matrix) (extension), [Zalo](https://docs.openclaw.ai/channels/zalo) (extension), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser) (extension), [WebChat](https://docs.openclaw.ai/web/webchat).
|
||||||
- [Group routing](https://docs.openclaw.ai/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.openclaw.ai/channels).
|
- [Group routing](https://docs.openclaw.ai/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.openclaw.ai/channels).
|
||||||
|
|
||||||
### Apps + nodes
|
### Apps + nodes
|
||||||
@@ -316,7 +317,7 @@ Minimal `~/.openclaw/openclaw.json` (model + defaults):
|
|||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
agent: {
|
agent: {
|
||||||
model: "anthropic/claude-opus-4-5",
|
model: "anthropic/claude-opus-4-6",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -375,9 +376,15 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker
|
|||||||
|
|
||||||
- Requires `signal-cli` and a `channels.signal` config section.
|
- Requires `signal-cli` and a `channels.signal` config section.
|
||||||
|
|
||||||
### [iMessage](https://docs.openclaw.ai/channels/imessage)
|
### [BlueBubbles (iMessage)](https://docs.openclaw.ai/channels/bluebubbles)
|
||||||
|
|
||||||
- macOS only; Messages must be signed in.
|
- **Recommended** iMessage integration.
|
||||||
|
- Configure `channels.bluebubbles.serverUrl` + `channels.bluebubbles.password` and a webhook (`channels.bluebubbles.webhookPath`).
|
||||||
|
- The BlueBubbles server runs on macOS; the Gateway can run on macOS or elsewhere.
|
||||||
|
|
||||||
|
### [iMessage (legacy)](https://docs.openclaw.ai/channels/imessage)
|
||||||
|
|
||||||
|
- Legacy macOS-only integration via `imsg` (Messages must be signed in).
|
||||||
- If `channels.imessage.groups` is set, it becomes a group allowlist; include `"*"` to allow all.
|
- If `channels.imessage.groups` is set, it becomes a group allowlist; include `"*"` to allow all.
|
||||||
|
|
||||||
### [Microsoft Teams](https://docs.openclaw.ai/channels/msteams)
|
### [Microsoft Teams](https://docs.openclaw.ai/channels/msteams)
|
||||||
@@ -490,44 +497,49 @@ Special thanks to Adam Doppelt for lobster.bot.
|
|||||||
Thanks to all clawtributors:
|
Thanks to all clawtributors:
|
||||||
|
|
||||||
<p align="left">
|
<p align="left">
|
||||||
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/cpojer"><img src="https://avatars.githubusercontent.com/u/13352?v=4&s=48" width="48" height="48" alt="cpojer" title="cpojer"/></a> <a href="https://github.com/plum-dawg"><img src="https://avatars.githubusercontent.com/u/5909950?v=4&s=48" width="48" height="48" alt="plum-dawg" title="plum-dawg"/></a> <a href="https://github.com/bohdanpodvirnyi"><img src="https://avatars.githubusercontent.com/u/31819391?v=4&s=48" width="48" height="48" alt="bohdanpodvirnyi" title="bohdanpodvirnyi"/></a> <a href="https://github.com/iHildy"><img src="https://avatars.githubusercontent.com/u/25069719?v=4&s=48" width="48" height="48" alt="iHildy" title="iHildy"/></a> <a href="https://github.com/jaydenfyi"><img src="https://avatars.githubusercontent.com/u/213395523?v=4&s=48" width="48" height="48" alt="jaydenfyi" title="jaydenfyi"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/MatthieuBizien"><img src="https://avatars.githubusercontent.com/u/173090?v=4&s=48" width="48" height="48" alt="MatthieuBizien" title="MatthieuBizien"/></a>
|
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/cpojer"><img src="https://avatars.githubusercontent.com/u/13352?v=4&s=48" width="48" height="48" alt="cpojer" title="cpojer"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/plum-dawg"><img src="https://avatars.githubusercontent.com/u/5909950?v=4&s=48" width="48" height="48" alt="plum-dawg" title="plum-dawg"/></a> <a href="https://github.com/bohdanpodvirnyi"><img src="https://avatars.githubusercontent.com/u/31819391?v=4&s=48" width="48" height="48" alt="bohdanpodvirnyi" title="bohdanpodvirnyi"/></a> <a href="https://github.com/sebslight"><img src="https://avatars.githubusercontent.com/u/19554889?v=4&s=48" width="48" height="48" alt="sebslight" title="sebslight"/></a> <a href="https://github.com/iHildy"><img src="https://avatars.githubusercontent.com/u/25069719?v=4&s=48" width="48" height="48" alt="iHildy" title="iHildy"/></a> <a href="https://github.com/jaydenfyi"><img src="https://avatars.githubusercontent.com/u/213395523?v=4&s=48" width="48" height="48" alt="jaydenfyi" title="jaydenfyi"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a>
|
||||||
<a href="https://github.com/MaudeBot"><img src="https://avatars.githubusercontent.com/u/255777700?v=4&s=48" width="48" height="48" alt="MaudeBot" title="MaudeBot"/></a> <a href="https://github.com/Glucksberg"><img src="https://avatars.githubusercontent.com/u/80581902?v=4&s=48" width="48" height="48" alt="Glucksberg" title="Glucksberg"/></a> <a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a> <a href="https://github.com/vrknetha"><img src="https://avatars.githubusercontent.com/u/20596261?v=4&s=48" width="48" height="48" alt="vrknetha" title="vrknetha"/></a> <a href="https://github.com/radek-paclt"><img src="https://avatars.githubusercontent.com/u/50451445?v=4&s=48" width="48" height="48" alt="radek-paclt" title="radek-paclt"/></a> <a href="https://github.com/vignesh07"><img src="https://avatars.githubusercontent.com/u/1436853?v=4&s=48" width="48" height="48" alt="vignesh07" title="vignesh07"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/sebslight"><img src="https://avatars.githubusercontent.com/u/19554889?v=4&s=48" width="48" height="48" alt="sebslight" title="sebslight"/></a> <a href="https://github.com/czekaj"><img src="https://avatars.githubusercontent.com/u/1464539?v=4&s=48" width="48" height="48" alt="czekaj" title="czekaj"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a>
|
<a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/MatthieuBizien"><img src="https://avatars.githubusercontent.com/u/173090?v=4&s=48" width="48" height="48" alt="MatthieuBizien" title="MatthieuBizien"/></a> <a href="https://github.com/Glucksberg"><img src="https://avatars.githubusercontent.com/u/80581902?v=4&s=48" width="48" height="48" alt="Glucksberg" title="Glucksberg"/></a> <a href="https://github.com/MaudeBot"><img src="https://avatars.githubusercontent.com/u/255777700?v=4&s=48" width="48" height="48" alt="MaudeBot" title="MaudeBot"/></a> <a href="https://github.com/gumadeiras"><img src="https://avatars.githubusercontent.com/u/5599352?v=4&s=48" width="48" height="48" alt="gumadeiras" title="gumadeiras"/></a> <a href="https://github.com/tyler6204"><img src="https://avatars.githubusercontent.com/u/64381258?v=4&s=48" width="48" height="48" alt="tyler6204" title="tyler6204"/></a> <a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a> <a href="https://github.com/vrknetha"><img src="https://avatars.githubusercontent.com/u/20596261?v=4&s=48" width="48" height="48" alt="vrknetha" title="vrknetha"/></a> <a href="https://github.com/vignesh07"><img src="https://avatars.githubusercontent.com/u/1436853?v=4&s=48" width="48" height="48" alt="vignesh07" title="vignesh07"/></a> <a href="https://github.com/radek-paclt"><img src="https://avatars.githubusercontent.com/u/50451445?v=4&s=48" width="48" height="48" alt="radek-paclt" title="radek-paclt"/></a>
|
||||||
<a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/rodrigouroz"><img src="https://avatars.githubusercontent.com/u/384037?v=4&s=48" width="48" height="48" alt="rodrigouroz" title="rodrigouroz"/></a> <a href="https://github.com/tyler6204"><img src="https://avatars.githubusercontent.com/u/64381258?v=4&s=48" width="48" height="48" alt="tyler6204" title="tyler6204"/></a> <a href="https://github.com/juanpablodlc"><img src="https://avatars.githubusercontent.com/u/92012363?v=4&s=48" width="48" height="48" alt="juanpablodlc" title="juanpablodlc"/></a> <a href="https://github.com/conroywhitney"><img src="https://avatars.githubusercontent.com/u/249891?v=4&s=48" width="48" height="48" alt="conroywhitney" title="conroywhitney"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a>
|
<a href="https://github.com/abdelsfane"><img src="https://avatars.githubusercontent.com/u/32418586?v=4&s=48" width="48" height="48" alt="abdelsfane" title="abdelsfane"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/christianklotz"><img src="https://avatars.githubusercontent.com/u/69443?v=4&s=48" width="48" height="48" alt="christianklotz" title="christianklotz"/></a> <a href="https://github.com/czekaj"><img src="https://avatars.githubusercontent.com/u/1464539?v=4&s=48" width="48" height="48" alt="czekaj" title="czekaj"/></a> <a href="https://github.com/ethanpalm"><img src="https://avatars.githubusercontent.com/u/56270045?v=4&s=48" width="48" height="48" alt="ethanpalm" title="ethanpalm"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/rodrigouroz"><img src="https://avatars.githubusercontent.com/u/384037?v=4&s=48" width="48" height="48" alt="rodrigouroz" title="rodrigouroz"/></a>
|
||||||
<a href="https://github.com/zerone0x"><img src="https://avatars.githubusercontent.com/u/39543393?v=4&s=48" width="48" height="48" alt="zerone0x" title="zerone0x"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/patelhiren"><img src="https://avatars.githubusercontent.com/u/172098?v=4&s=48" width="48" height="48" alt="patelhiren" title="patelhiren"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a> <a href="https://github.com/jonisjongithub"><img src="https://avatars.githubusercontent.com/u/86072337?v=4&s=48" width="48" height="48" alt="jonisjongithub" title="jonisjongithub"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/JustYannicc"><img src="https://avatars.githubusercontent.com/u/52761674?v=4&s=48" width="48" height="48" alt="JustYannicc" title="JustYannicc"/></a> <a href="https://github.com/Hyaxia"><img src="https://avatars.githubusercontent.com/u/36747317?v=4&s=48" width="48" height="48" alt="Hyaxia" title="Hyaxia"/></a>
|
<a href="https://github.com/juanpablodlc"><img src="https://avatars.githubusercontent.com/u/92012363?v=4&s=48" width="48" height="48" alt="juanpablodlc" title="juanpablodlc"/></a> <a href="https://github.com/conroywhitney"><img src="https://avatars.githubusercontent.com/u/249891?v=4&s=48" width="48" height="48" alt="conroywhitney" title="conroywhitney"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a> <a href="https://github.com/zerone0x"><img src="https://avatars.githubusercontent.com/u/39543393?v=4&s=48" width="48" height="48" alt="zerone0x" title="zerone0x"/></a> <a href="https://github.com/Takhoffman"><img src="https://avatars.githubusercontent.com/u/781889?v=4&s=48" width="48" height="48" alt="Takhoffman" title="Takhoffman"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/mudrii"><img src="https://avatars.githubusercontent.com/u/220262?v=4&s=48" width="48" height="48" alt="mudrii" title="mudrii"/></a> <a href="https://github.com/patelhiren"><img src="https://avatars.githubusercontent.com/u/172098?v=4&s=48" width="48" height="48" alt="patelhiren" title="patelhiren"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a>
|
||||||
<a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/SocialNerd42069"><img src="https://avatars.githubusercontent.com/u/118244303?v=4&s=48" width="48" height="48" alt="SocialNerd42069" title="SocialNerd42069"/></a> <a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a> <a href="https://github.com/apps/google-labs-jules"><img src="https://avatars.githubusercontent.com/in/842251?v=4&s=48" width="48" height="48" alt="google-labs-jules[bot]" title="google-labs-jules[bot]"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/mousberg"><img src="https://avatars.githubusercontent.com/u/57605064?v=4&s=48" width="48" height="48" alt="mousberg" title="mousberg"/></a> <a href="https://github.com/adam91holt"><img src="https://avatars.githubusercontent.com/u/9592417?v=4&s=48" width="48" height="48" alt="adam91holt" title="adam91holt"/></a> <a href="https://github.com/hougangdev"><img src="https://avatars.githubusercontent.com/u/105773686?v=4&s=48" width="48" height="48" alt="hougangdev" title="hougangdev"/></a> <a href="https://github.com/gumadeiras"><img src="https://avatars.githubusercontent.com/u/5599352?v=4&s=48" width="48" height="48" alt="gumadeiras" title="gumadeiras"/></a> <a href="https://github.com/shakkernerd"><img src="https://avatars.githubusercontent.com/u/165377636?v=4&s=48" width="48" height="48" alt="shakkernerd" title="shakkernerd"/></a>
|
<a href="https://github.com/jonisjongithub"><img src="https://avatars.githubusercontent.com/u/86072337?v=4&s=48" width="48" height="48" alt="jonisjongithub" title="jonisjongithub"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/BunsDev"><img src="https://avatars.githubusercontent.com/u/68980965?v=4&s=48" width="48" height="48" alt="BunsDev" title="BunsDev"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/JustYannicc"><img src="https://avatars.githubusercontent.com/u/52761674?v=4&s=48" width="48" height="48" alt="JustYannicc" title="JustYannicc"/></a> <a href="https://github.com/Hyaxia"><img src="https://avatars.githubusercontent.com/u/36747317?v=4&s=48" width="48" height="48" alt="Hyaxia" title="Hyaxia"/></a> <a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/SocialNerd42069"><img src="https://avatars.githubusercontent.com/u/118244303?v=4&s=48" width="48" height="48" alt="SocialNerd42069" title="SocialNerd42069"/></a> <a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a>
|
||||||
<a href="https://github.com/mteam88"><img src="https://avatars.githubusercontent.com/u/84196639?v=4&s=48" width="48" height="48" alt="mteam88" title="mteam88"/></a> <a href="https://github.com/hirefrank"><img src="https://avatars.githubusercontent.com/u/183158?v=4&s=48" width="48" height="48" alt="hirefrank" title="hirefrank"/></a> <a href="https://github.com/joeynyc"><img src="https://avatars.githubusercontent.com/u/17919866?v=4&s=48" width="48" height="48" alt="joeynyc" title="joeynyc"/></a> <a href="https://github.com/orlyjamie"><img src="https://avatars.githubusercontent.com/u/6668807?v=4&s=48" width="48" height="48" alt="orlyjamie" title="orlyjamie"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/TSavo"><img src="https://avatars.githubusercontent.com/u/877990?v=4&s=48" width="48" height="48" alt="TSavo" title="TSavo"/></a> <a href="https://github.com/aerolalit"><img src="https://avatars.githubusercontent.com/u/17166039?v=4&s=48" width="48" height="48" alt="aerolalit" title="aerolalit"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/bradleypriest"><img src="https://avatars.githubusercontent.com/u/167215?v=4&s=48" width="48" height="48" alt="bradleypriest" title="bradleypriest"/></a>
|
<a href="https://github.com/apps/google-labs-jules"><img src="https://avatars.githubusercontent.com/in/842251?v=4&s=48" width="48" height="48" alt="google-labs-jules[bot]" title="google-labs-jules[bot]"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/adam91holt"><img src="https://avatars.githubusercontent.com/u/9592417?v=4&s=48" width="48" height="48" alt="adam91holt" title="adam91holt"/></a> <a href="https://github.com/mousberg"><img src="https://avatars.githubusercontent.com/u/57605064?v=4&s=48" width="48" height="48" alt="mousberg" title="mousberg"/></a> <a href="https://github.com/hougangdev"><img src="https://avatars.githubusercontent.com/u/105773686?v=4&s=48" width="48" height="48" alt="hougangdev" title="hougangdev"/></a> <a href="https://github.com/shakkernerd"><img src="https://avatars.githubusercontent.com/u/165377636?v=4&s=48" width="48" height="48" alt="shakkernerd" title="shakkernerd"/></a> <a href="https://github.com/coygeek"><img src="https://avatars.githubusercontent.com/u/65363919?v=4&s=48" width="48" height="48" alt="coygeek" title="coygeek"/></a> <a href="https://github.com/mteam88"><img src="https://avatars.githubusercontent.com/u/84196639?v=4&s=48" width="48" height="48" alt="mteam88" title="mteam88"/></a> <a href="https://github.com/hirefrank"><img src="https://avatars.githubusercontent.com/u/183158?v=4&s=48" width="48" height="48" alt="hirefrank" title="hirefrank"/></a> <a href="https://github.com/M00N7682"><img src="https://avatars.githubusercontent.com/u/170746674?v=4&s=48" width="48" height="48" alt="M00N7682" title="M00N7682"/></a>
|
||||||
<a href="https://github.com/benithors"><img src="https://avatars.githubusercontent.com/u/20652882?v=4&s=48" width="48" height="48" alt="benithors" title="benithors"/></a> <a href="https://github.com/rohannagpal"><img src="https://avatars.githubusercontent.com/u/4009239?v=4&s=48" width="48" height="48" alt="rohannagpal" title="rohannagpal"/></a> <a href="https://github.com/timolins"><img src="https://avatars.githubusercontent.com/u/1440854?v=4&s=48" width="48" height="48" alt="timolins" title="timolins"/></a> <a href="https://github.com/f-trycua"><img src="https://avatars.githubusercontent.com/u/195596869?v=4&s=48" width="48" height="48" alt="f-trycua" title="f-trycua"/></a> <a href="https://github.com/benostein"><img src="https://avatars.githubusercontent.com/u/31802821?v=4&s=48" width="48" height="48" alt="benostein" title="benostein"/></a> <a href="https://github.com/elliotsecops"><img src="https://avatars.githubusercontent.com/u/141947839?v=4&s=48" width="48" height="48" alt="elliotsecops" title="elliotsecops"/></a> <a href="https://github.com/christianklotz"><img src="https://avatars.githubusercontent.com/u/69443?v=4&s=48" width="48" height="48" alt="christianklotz" title="christianklotz"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/pvoo"><img src="https://avatars.githubusercontent.com/u/20116814?v=4&s=48" width="48" height="48" alt="pvoo" title="pvoo"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a>
|
<a href="https://github.com/joeynyc"><img src="https://avatars.githubusercontent.com/u/17919866?v=4&s=48" width="48" height="48" alt="joeynyc" title="joeynyc"/></a> <a href="https://github.com/orlyjamie"><img src="https://avatars.githubusercontent.com/u/6668807?v=4&s=48" width="48" height="48" alt="orlyjamie" title="orlyjamie"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/TSavo"><img src="https://avatars.githubusercontent.com/u/877990?v=4&s=48" width="48" height="48" alt="TSavo" title="TSavo"/></a> <a href="https://github.com/aerolalit"><img src="https://avatars.githubusercontent.com/u/17166039?v=4&s=48" width="48" height="48" alt="aerolalit" title="aerolalit"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/bradleypriest"><img src="https://avatars.githubusercontent.com/u/167215?v=4&s=48" width="48" height="48" alt="bradleypriest" title="bradleypriest"/></a> <a href="https://github.com/benithors"><img src="https://avatars.githubusercontent.com/u/20652882?v=4&s=48" width="48" height="48" alt="benithors" title="benithors"/></a> <a href="https://github.com/lsh411"><img src="https://avatars.githubusercontent.com/u/6801488?v=4&s=48" width="48" height="48" alt="lsh411" title="lsh411"/></a>
|
||||||
<a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a> <a href="https://github.com/cristip73"><img src="https://avatars.githubusercontent.com/u/24499421?v=4&s=48" width="48" height="48" alt="cristip73" title="cristip73"/></a> <a href="https://github.com/stefangalescu"><img src="https://avatars.githubusercontent.com/u/52995748?v=4&s=48" width="48" height="48" alt="stefangalescu" title="stefangalescu"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a> <a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/leszekszpunar"><img src="https://avatars.githubusercontent.com/u/13106764?v=4&s=48" width="48" height="48" alt="leszekszpunar" title="leszekszpunar"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a>
|
<a href="https://github.com/gut-puncture"><img src="https://avatars.githubusercontent.com/u/75851986?v=4&s=48" width="48" height="48" alt="gut-puncture" title="gut-puncture"/></a> <a href="https://github.com/rohannagpal"><img src="https://avatars.githubusercontent.com/u/4009239?v=4&s=48" width="48" height="48" alt="rohannagpal" title="rohannagpal"/></a> <a href="https://github.com/timolins"><img src="https://avatars.githubusercontent.com/u/1440854?v=4&s=48" width="48" height="48" alt="timolins" title="timolins"/></a> <a href="https://github.com/f-trycua"><img src="https://avatars.githubusercontent.com/u/195596869?v=4&s=48" width="48" height="48" alt="f-trycua" title="f-trycua"/></a> <a href="https://github.com/benostein"><img src="https://avatars.githubusercontent.com/u/31802821?v=4&s=48" width="48" height="48" alt="benostein" title="benostein"/></a> <a href="https://github.com/elliotsecops"><img src="https://avatars.githubusercontent.com/u/141947839?v=4&s=48" width="48" height="48" alt="elliotsecops" title="elliotsecops"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/pvoo"><img src="https://avatars.githubusercontent.com/u/20116814?v=4&s=48" width="48" height="48" alt="pvoo" title="pvoo"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a> <a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a>
|
||||||
<a href="https://github.com/davidguttman"><img src="https://avatars.githubusercontent.com/u/431696?v=4&s=48" width="48" height="48" alt="davidguttman" title="davidguttman"/></a> <a href="https://github.com/sleontenko"><img src="https://avatars.githubusercontent.com/u/7135949?v=4&s=48" width="48" height="48" alt="sleontenko" title="sleontenko"/></a> <a href="https://github.com/denysvitali"><img src="https://avatars.githubusercontent.com/u/4939519?v=4&s=48" width="48" height="48" alt="denysvitali" title="denysvitali"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a> <a href="https://github.com/peschee"><img src="https://avatars.githubusercontent.com/u/63866?v=4&s=48" width="48" height="48" alt="peschee" title="peschee"/></a> <a href="https://github.com/nonggialiang"><img src="https://avatars.githubusercontent.com/u/14367839?v=4&s=48" width="48" height="48" alt="nonggialiang" title="nonggialiang"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/dominicnunez"><img src="https://avatars.githubusercontent.com/u/43616264?v=4&s=48" width="48" height="48" alt="dominicnunez" title="dominicnunez"/></a> <a href="https://github.com/lploc94"><img src="https://avatars.githubusercontent.com/u/28453843?v=4&s=48" width="48" height="48" alt="lploc94" title="lploc94"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a>
|
<a href="https://github.com/cristip73"><img src="https://avatars.githubusercontent.com/u/24499421?v=4&s=48" width="48" height="48" alt="cristip73" title="cristip73"/></a> <a href="https://github.com/stefangalescu"><img src="https://avatars.githubusercontent.com/u/52995748?v=4&s=48" width="48" height="48" alt="stefangalescu" title="stefangalescu"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a> <a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/leszekszpunar"><img src="https://avatars.githubusercontent.com/u/13106764?v=4&s=48" width="48" height="48" alt="leszekszpunar" title="leszekszpunar"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/pycckuu"><img src="https://avatars.githubusercontent.com/u/1489583?v=4&s=48" width="48" height="48" alt="pycckuu" title="pycckuu"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a>
|
||||||
<a href="https://github.com/sfo2001"><img src="https://avatars.githubusercontent.com/u/103369858?v=4&s=48" width="48" height="48" alt="sfo2001" title="sfo2001"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/danielz1z"><img src="https://avatars.githubusercontent.com/u/235270390?v=4&s=48" width="48" height="48" alt="danielz1z" title="danielz1z"/></a> <a href="https://github.com/AdeboyeDN"><img src="https://avatars.githubusercontent.com/u/65312338?v=4&s=48" width="48" height="48" alt="AdeboyeDN" title="AdeboyeDN"/></a> <a href="https://github.com/Alg0rix"><img src="https://avatars.githubusercontent.com/u/53804949?v=4&s=48" width="48" height="48" alt="Alg0rix" title="Alg0rix"/></a> <a href="https://github.com/Takhoffman"><img src="https://avatars.githubusercontent.com/u/781889?v=4&s=48" width="48" height="48" alt="Takhoffman" title="Takhoffman"/></a> <a href="https://github.com/papago2355"><img src="https://avatars.githubusercontent.com/u/68721273?v=4&s=48" width="48" height="48" alt="papago2355" title="papago2355"/></a> <a href="https://github.com/apps/clawdinator"><img src="https://avatars.githubusercontent.com/in/2607181?v=4&s=48" width="48" height="48" alt="clawdinator[bot]" title="clawdinator[bot]"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a>
|
<a href="https://github.com/davidguttman"><img src="https://avatars.githubusercontent.com/u/431696?v=4&s=48" width="48" height="48" alt="davidguttman" title="davidguttman"/></a> <a href="https://github.com/sleontenko"><img src="https://avatars.githubusercontent.com/u/7135949?v=4&s=48" width="48" height="48" alt="sleontenko" title="sleontenko"/></a> <a href="https://github.com/denysvitali"><img src="https://avatars.githubusercontent.com/u/4939519?v=4&s=48" width="48" height="48" alt="denysvitali" title="denysvitali"/></a> <a href="https://github.com/apps/clawdinator"><img src="https://avatars.githubusercontent.com/in/2607181?v=4&s=48" width="48" height="48" alt="clawdinator[bot]" title="clawdinator[bot]"/></a> <a href="https://github.com/TinyTb"><img src="https://avatars.githubusercontent.com/u/5957298?v=4&s=48" width="48" height="48" alt="TinyTb" title="TinyTb"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a> <a href="https://github.com/peschee"><img src="https://avatars.githubusercontent.com/u/63866?v=4&s=48" width="48" height="48" alt="peschee" title="peschee"/></a> <a href="https://github.com/nicolasstanley"><img src="https://avatars.githubusercontent.com/u/60584925?v=4&s=48" width="48" height="48" alt="nicolasstanley" title="nicolasstanley"/></a> <a href="https://github.com/davidiach"><img src="https://avatars.githubusercontent.com/u/28102235?v=4&s=48" width="48" height="48" alt="davidiach" title="davidiach"/></a> <a href="https://github.com/nonggialiang"><img src="https://avatars.githubusercontent.com/u/14367839?v=4&s=48" width="48" height="48" alt="nonggialiang" title="nonggialiang"/></a>
|
||||||
<a href="https://github.com/evanotero"><img src="https://avatars.githubusercontent.com/u/13204105?v=4&s=48" width="48" height="48" alt="evanotero" title="evanotero"/></a> <a href="https://github.com/KristijanJovanovski"><img src="https://avatars.githubusercontent.com/u/8942284?v=4&s=48" width="48" height="48" alt="KristijanJovanovski" title="KristijanJovanovski"/></a> <a href="https://github.com/jlowin"><img src="https://avatars.githubusercontent.com/u/153965?v=4&s=48" width="48" height="48" alt="jlowin" title="jlowin"/></a> <a href="https://github.com/rdev"><img src="https://avatars.githubusercontent.com/u/8418866?v=4&s=48" width="48" height="48" alt="rdev" title="rdev"/></a> <a href="https://github.com/rhuanssauro"><img src="https://avatars.githubusercontent.com/u/164682191?v=4&s=48" width="48" height="48" alt="rhuanssauro" title="rhuanssauro"/></a> <a href="https://github.com/joshrad-dev"><img src="https://avatars.githubusercontent.com/u/62785552?v=4&s=48" width="48" height="48" alt="joshrad-dev" title="joshrad-dev"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/adityashaw2"><img src="https://avatars.githubusercontent.com/u/41204444?v=4&s=48" width="48" height="48" alt="adityashaw2" title="adityashaw2"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a>
|
<a href="https://github.com/ironbyte-rgb"><img src="https://avatars.githubusercontent.com/u/230665944?v=4&s=48" width="48" height="48" alt="ironbyte-rgb" title="ironbyte-rgb"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/dominicnunez"><img src="https://avatars.githubusercontent.com/u/43616264?v=4&s=48" width="48" height="48" alt="dominicnunez" title="dominicnunez"/></a> <a href="https://github.com/lploc94"><img src="https://avatars.githubusercontent.com/u/28453843?v=4&s=48" width="48" height="48" alt="lploc94" title="lploc94"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/sfo2001"><img src="https://avatars.githubusercontent.com/u/103369858?v=4&s=48" width="48" height="48" alt="sfo2001" title="sfo2001"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/danielz1z"><img src="https://avatars.githubusercontent.com/u/235270390?v=4&s=48" width="48" height="48" alt="danielz1z" title="danielz1z"/></a> <a href="https://github.com/Iranb"><img src="https://avatars.githubusercontent.com/u/49674669?v=4&s=48" width="48" height="48" alt="Iranb" title="Iranb"/></a>
|
||||||
<a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/ryancontent"><img src="https://avatars.githubusercontent.com/u/39743613?v=4&s=48" width="48" height="48" alt="ryancontent" title="ryancontent"/></a> <a href="https://github.com/jasonsschin"><img src="https://avatars.githubusercontent.com/u/1456889?v=4&s=48" width="48" height="48" alt="jasonsschin" title="jasonsschin"/></a> <a href="https://github.com/artuskg"><img src="https://avatars.githubusercontent.com/u/11966157?v=4&s=48" width="48" height="48" alt="artuskg" title="artuskg"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/pauloportella"><img src="https://avatars.githubusercontent.com/u/22947229?v=4&s=48" width="48" height="48" alt="pauloportella" title="pauloportella"/></a> <a href="https://github.com/HirokiKobayashi-R"><img src="https://avatars.githubusercontent.com/u/37167840?v=4&s=48" width="48" height="48" alt="HirokiKobayashi-R" title="HirokiKobayashi-R"/></a> <a href="https://github.com/ThanhNguyxn"><img src="https://avatars.githubusercontent.com/u/74597207?v=4&s=48" width="48" height="48" alt="ThanhNguyxn" title="ThanhNguyxn"/></a> <a href="https://github.com/kimitaka"><img src="https://avatars.githubusercontent.com/u/167225?v=4&s=48" width="48" height="48" alt="kimitaka" title="kimitaka"/></a> <a href="https://github.com/yuting0624"><img src="https://avatars.githubusercontent.com/u/32728916?v=4&s=48" width="48" height="48" alt="yuting0624" title="yuting0624"/></a>
|
<a href="https://github.com/AdeboyeDN"><img src="https://avatars.githubusercontent.com/u/65312338?v=4&s=48" width="48" height="48" alt="AdeboyeDN" title="AdeboyeDN"/></a> <a href="https://github.com/Alg0rix"><img src="https://avatars.githubusercontent.com/u/53804949?v=4&s=48" width="48" height="48" alt="Alg0rix" title="Alg0rix"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/papago2355"><img src="https://avatars.githubusercontent.com/u/68721273?v=4&s=48" width="48" height="48" alt="papago2355" title="papago2355"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a> <a href="https://github.com/evanotero"><img src="https://avatars.githubusercontent.com/u/13204105?v=4&s=48" width="48" height="48" alt="evanotero" title="evanotero"/></a> <a href="https://github.com/KristijanJovanovski"><img src="https://avatars.githubusercontent.com/u/8942284?v=4&s=48" width="48" height="48" alt="KristijanJovanovski" title="KristijanJovanovski"/></a> <a href="https://github.com/jlowin"><img src="https://avatars.githubusercontent.com/u/153965?v=4&s=48" width="48" height="48" alt="jlowin" title="jlowin"/></a> <a href="https://github.com/rdev"><img src="https://avatars.githubusercontent.com/u/8418866?v=4&s=48" width="48" height="48" alt="rdev" title="rdev"/></a> <a href="https://github.com/rhuanssauro"><img src="https://avatars.githubusercontent.com/u/164682191?v=4&s=48" width="48" height="48" alt="rhuanssauro" title="rhuanssauro"/></a>
|
||||||
<a href="https://github.com/neooriginal"><img src="https://avatars.githubusercontent.com/u/54811660?v=4&s=48" width="48" height="48" alt="neooriginal" title="neooriginal"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/baccula"><img src="https://avatars.githubusercontent.com/u/22080883?v=4&s=48" width="48" height="48" alt="baccula" title="baccula"/></a> <a href="https://github.com/manikv12"><img src="https://avatars.githubusercontent.com/u/49544491?v=4&s=48" width="48" height="48" alt="manikv12" title="manikv12"/></a> <a href="https://github.com/myfunc"><img src="https://avatars.githubusercontent.com/u/19294627?v=4&s=48" width="48" height="48" alt="myfunc" title="myfunc"/></a> <a href="https://github.com/travisirby"><img src="https://avatars.githubusercontent.com/u/5958376?v=4&s=48" width="48" height="48" alt="travisirby" title="travisirby"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/connorshea"><img src="https://avatars.githubusercontent.com/u/2977353?v=4&s=48" width="48" height="48" alt="connorshea" title="connorshea"/></a> <a href="https://github.com/kyleok"><img src="https://avatars.githubusercontent.com/u/58307870?v=4&s=48" width="48" height="48" alt="kyleok" title="kyleok"/></a>
|
<a href="https://github.com/joshrad-dev"><img src="https://avatars.githubusercontent.com/u/62785552?v=4&s=48" width="48" height="48" alt="joshrad-dev" title="joshrad-dev"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/adityashaw2"><img src="https://avatars.githubusercontent.com/u/41204444?v=4&s=48" width="48" height="48" alt="adityashaw2" title="adityashaw2"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a> <a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/ryancontent"><img src="https://avatars.githubusercontent.com/u/39743613?v=4&s=48" width="48" height="48" alt="ryancontent" title="ryancontent"/></a> <a href="https://github.com/jasonsschin"><img src="https://avatars.githubusercontent.com/u/1456889?v=4&s=48" width="48" height="48" alt="jasonsschin" title="jasonsschin"/></a> <a href="https://github.com/artuskg"><img src="https://avatars.githubusercontent.com/u/11966157?v=4&s=48" width="48" height="48" alt="artuskg" title="artuskg"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/pauloportella"><img src="https://avatars.githubusercontent.com/u/22947229?v=4&s=48" width="48" height="48" alt="pauloportella" title="pauloportella"/></a>
|
||||||
<a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4&s=48" width="48" height="48" alt="dependabot[bot]" title="dependabot[bot]"/></a> <a href="https://github.com/amitbiswal007"><img src="https://avatars.githubusercontent.com/u/108086198?v=4&s=48" width="48" height="48" alt="amitbiswal007" title="amitbiswal007"/></a> <a href="https://github.com/John-Rood"><img src="https://avatars.githubusercontent.com/u/62669593?v=4&s=48" width="48" height="48" alt="John-Rood" title="John-Rood"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a> <a href="https://github.com/uos-status"><img src="https://avatars.githubusercontent.com/u/255712580?v=4&s=48" width="48" height="48" alt="uos-status" title="uos-status"/></a> <a href="https://github.com/gerardward2007"><img src="https://avatars.githubusercontent.com/u/3002155?v=4&s=48" width="48" height="48" alt="gerardward2007" title="gerardward2007"/></a> <a href="https://github.com/roshanasingh4"><img src="https://avatars.githubusercontent.com/u/88576930?v=4&s=48" width="48" height="48" alt="roshanasingh4" title="roshanasingh4"/></a> <a href="https://github.com/tosh-hamburg"><img src="https://avatars.githubusercontent.com/u/58424326?v=4&s=48" width="48" height="48" alt="tosh-hamburg" title="tosh-hamburg"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a>
|
<a href="https://github.com/HirokiKobayashi-R"><img src="https://avatars.githubusercontent.com/u/37167840?v=4&s=48" width="48" height="48" alt="HirokiKobayashi-R" title="HirokiKobayashi-R"/></a> <a href="https://github.com/ThanhNguyxn"><img src="https://avatars.githubusercontent.com/u/74597207?v=4&s=48" width="48" height="48" alt="ThanhNguyxn" title="ThanhNguyxn"/></a> <a href="https://github.com/18-RAJAT"><img src="https://avatars.githubusercontent.com/u/78920780?v=4&s=48" width="48" height="48" alt="18-RAJAT" title="18-RAJAT"/></a> <a href="https://github.com/kimitaka"><img src="https://avatars.githubusercontent.com/u/167225?v=4&s=48" width="48" height="48" alt="kimitaka" title="kimitaka"/></a> <a href="https://github.com/yuting0624"><img src="https://avatars.githubusercontent.com/u/32728916?v=4&s=48" width="48" height="48" alt="yuting0624" title="yuting0624"/></a> <a href="https://github.com/neooriginal"><img src="https://avatars.githubusercontent.com/u/54811660?v=4&s=48" width="48" height="48" alt="neooriginal" title="neooriginal"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/unisone"><img src="https://avatars.githubusercontent.com/u/32521398?v=4&s=48" width="48" height="48" alt="unisone" title="unisone"/></a> <a href="https://github.com/baccula"><img src="https://avatars.githubusercontent.com/u/22080883?v=4&s=48" width="48" height="48" alt="baccula" title="baccula"/></a>
|
||||||
<a href="https://github.com/badlogic"><img src="https://avatars.githubusercontent.com/u/514052?v=4&s=48" width="48" height="48" alt="badlogic" title="badlogic"/></a> <a href="https://github.com/dlauer"><img src="https://avatars.githubusercontent.com/u/757041?v=4&s=48" width="48" height="48" alt="dlauer" title="dlauer"/></a> <a href="https://github.com/JonUleis"><img src="https://avatars.githubusercontent.com/u/7644941?v=4&s=48" width="48" height="48" alt="JonUleis" title="JonUleis"/></a> <a href="https://github.com/shivamraut101"><img src="https://avatars.githubusercontent.com/u/110457469?v=4&s=48" width="48" height="48" alt="shivamraut101" title="shivamraut101"/></a> <a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/cheeeee"><img src="https://avatars.githubusercontent.com/u/21245729?v=4&s=48" width="48" height="48" alt="cheeeee" title="cheeeee"/></a> <a href="https://github.com/robbyczgw-cla"><img src="https://avatars.githubusercontent.com/u/239660374?v=4&s=48" width="48" height="48" alt="robbyczgw-cla" title="robbyczgw-cla"/></a> <a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/j1philli"><img src="https://avatars.githubusercontent.com/u/3744255?v=4&s=48" width="48" height="48" alt="Josh Phillips" title="Josh Phillips"/></a> <a href="https://github.com/pookNast"><img src="https://avatars.githubusercontent.com/u/14242552?v=4&s=48" width="48" height="48" alt="pookNast" title="pookNast"/></a>
|
<a href="https://github.com/manikv12"><img src="https://avatars.githubusercontent.com/u/49544491?v=4&s=48" width="48" height="48" alt="manikv12" title="manikv12"/></a> <a href="https://github.com/myfunc"><img src="https://avatars.githubusercontent.com/u/19294627?v=4&s=48" width="48" height="48" alt="myfunc" title="myfunc"/></a> <a href="https://github.com/travisirby"><img src="https://avatars.githubusercontent.com/u/5958376?v=4&s=48" width="48" height="48" alt="travisirby" title="travisirby"/></a> <a href="https://github.com/fujiwara-tofu-shop"><img src="https://avatars.githubusercontent.com/u/259415332?v=4&s=48" width="48" height="48" alt="fujiwara-tofu-shop" title="fujiwara-tofu-shop"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/connorshea"><img src="https://avatars.githubusercontent.com/u/2977353?v=4&s=48" width="48" height="48" alt="connorshea" title="connorshea"/></a> <a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/kyleok"><img src="https://avatars.githubusercontent.com/u/58307870?v=4&s=48" width="48" height="48" alt="kyleok" title="kyleok"/></a> <a href="https://github.com/slonce70"><img src="https://avatars.githubusercontent.com/u/130596182?v=4&s=48" width="48" height="48" alt="slonce70" title="slonce70"/></a> <a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a>
|
||||||
<a href="https://github.com/Whoaa512"><img src="https://avatars.githubusercontent.com/u/1581943?v=4&s=48" width="48" height="48" alt="Whoaa512" title="Whoaa512"/></a> <a href="https://github.com/chriseidhof"><img src="https://avatars.githubusercontent.com/u/5382?v=4&s=48" width="48" height="48" alt="chriseidhof" title="chriseidhof"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/ysqander"><img src="https://avatars.githubusercontent.com/u/80843820?v=4&s=48" width="48" height="48" alt="ysqander" title="ysqander"/></a> <a href="https://github.com/search?q=Yurii%20Chukhlib"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Yurii Chukhlib" title="Yurii Chukhlib"/></a> <a href="https://github.com/aj47"><img src="https://avatars.githubusercontent.com/u/8023513?v=4&s=48" width="48" height="48" alt="aj47" title="aj47"/></a> <a href="https://github.com/kennyklee"><img src="https://avatars.githubusercontent.com/u/1432489?v=4&s=48" width="48" height="48" alt="kennyklee" title="kennyklee"/></a> <a href="https://github.com/superman32432432"><img src="https://avatars.githubusercontent.com/u/7228420?v=4&s=48" width="48" height="48" alt="superman32432432" title="superman32432432"/></a> <a href="https://github.com/grp06"><img src="https://avatars.githubusercontent.com/u/1573959?v=4&s=48" width="48" height="48" alt="grp06" title="grp06"/></a> <a href="https://github.com/Hisleren"><img src="https://avatars.githubusercontent.com/u/83217244?v=4&s=48" width="48" height="48" alt="Hisleren" title="Hisleren"/></a>
|
<a href="https://github.com/badlogic"><img src="https://avatars.githubusercontent.com/u/514052?v=4&s=48" width="48" height="48" alt="badlogic" title="badlogic"/></a> <a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4&s=48" width="48" height="48" alt="dependabot[bot]" title="dependabot[bot]"/></a> <a href="https://github.com/amitbiswal007"><img src="https://avatars.githubusercontent.com/u/108086198?v=4&s=48" width="48" height="48" alt="amitbiswal007" title="amitbiswal007"/></a> <a href="https://github.com/John-Rood"><img src="https://avatars.githubusercontent.com/u/62669593?v=4&s=48" width="48" height="48" alt="John-Rood" title="John-Rood"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a> <a href="https://github.com/uos-status"><img src="https://avatars.githubusercontent.com/u/255712580?v=4&s=48" width="48" height="48" alt="uos-status" title="uos-status"/></a> <a href="https://github.com/gerardward2007"><img src="https://avatars.githubusercontent.com/u/3002155?v=4&s=48" width="48" height="48" alt="gerardward2007" title="gerardward2007"/></a> <a href="https://github.com/roshanasingh4"><img src="https://avatars.githubusercontent.com/u/88576930?v=4&s=48" width="48" height="48" alt="roshanasingh4" title="roshanasingh4"/></a> <a href="https://github.com/tosh-hamburg"><img src="https://avatars.githubusercontent.com/u/58424326?v=4&s=48" width="48" height="48" alt="tosh-hamburg" title="tosh-hamburg"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a>
|
||||||
<a href="https://github.com/shatner"><img src="https://avatars.githubusercontent.com/u/17735435?v=4&s=48" width="48" height="48" alt="shatner" title="shatner"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/austinm911"><img src="https://avatars.githubusercontent.com/u/31991302?v=4&s=48" width="48" height="48" alt="austinm911" title="austinm911"/></a> <a href="https://github.com/apps/blacksmith-sh"><img src="https://avatars.githubusercontent.com/in/807020?v=4&s=48" width="48" height="48" alt="blacksmith-sh[bot]" title="blacksmith-sh[bot]"/></a> <a href="https://github.com/damoahdominic"><img src="https://avatars.githubusercontent.com/u/4623434?v=4&s=48" width="48" height="48" alt="damoahdominic" title="damoahdominic"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/GHesericsu"><img src="https://avatars.githubusercontent.com/u/60202455?v=4&s=48" width="48" height="48" alt="GHesericsu" title="GHesericsu"/></a> <a href="https://github.com/HeimdallStrategy"><img src="https://avatars.githubusercontent.com/u/223014405?v=4&s=48" width="48" height="48" alt="HeimdallStrategy" title="HeimdallStrategy"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a>
|
<a href="https://github.com/dlauer"><img src="https://avatars.githubusercontent.com/u/757041?v=4&s=48" width="48" height="48" alt="dlauer" title="dlauer"/></a> <a href="https://github.com/grp06"><img src="https://avatars.githubusercontent.com/u/1573959?v=4&s=48" width="48" height="48" alt="grp06" title="grp06"/></a> <a href="https://github.com/JonUleis"><img src="https://avatars.githubusercontent.com/u/7644941?v=4&s=48" width="48" height="48" alt="JonUleis" title="JonUleis"/></a> <a href="https://github.com/shivamraut101"><img src="https://avatars.githubusercontent.com/u/110457469?v=4&s=48" width="48" height="48" alt="shivamraut101" title="shivamraut101"/></a> <a href="https://github.com/cheeeee"><img src="https://avatars.githubusercontent.com/u/21245729?v=4&s=48" width="48" height="48" alt="cheeeee" title="cheeeee"/></a> <a href="https://github.com/robbyczgw-cla"><img src="https://avatars.githubusercontent.com/u/239660374?v=4&s=48" width="48" height="48" alt="robbyczgw-cla" title="robbyczgw-cla"/></a> <a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/j1philli"><img src="https://avatars.githubusercontent.com/u/3744255?v=4&s=48" width="48" height="48" alt="Josh Phillips" title="Josh Phillips"/></a> <a href="https://github.com/Wangnov"><img src="https://avatars.githubusercontent.com/u/48670012?v=4&s=48" width="48" height="48" alt="Wangnov" title="Wangnov"/></a> <a href="https://github.com/kaizen403"><img src="https://avatars.githubusercontent.com/u/134706404?v=4&s=48" width="48" height="48" alt="kaizen403" title="kaizen403"/></a>
|
||||||
<a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a> <a href="https://github.com/mahmoudashraf93"><img src="https://avatars.githubusercontent.com/u/9130129?v=4&s=48" width="48" height="48" alt="mahmoudashraf93" title="mahmoudashraf93"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a> <a href="https://github.com/robhparker"><img src="https://avatars.githubusercontent.com/u/7404740?v=4&s=48" width="48" height="48" alt="robhparker" title="robhparker"/></a> <a href="https://github.com/search?q=Ryan%20Lisse"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ryan Lisse" title="Ryan Lisse"/></a> <a href="https://github.com/dougvk"><img src="https://avatars.githubusercontent.com/u/401660?v=4&s=48" width="48" height="48" alt="dougvk" title="dougvk"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/fal3"><img src="https://avatars.githubusercontent.com/u/6484295?v=4&s=48" width="48" height="48" alt="fal3" title="fal3"/></a>
|
<a href="https://github.com/pookNast"><img src="https://avatars.githubusercontent.com/u/14242552?v=4&s=48" width="48" height="48" alt="pookNast" title="pookNast"/></a> <a href="https://github.com/Whoaa512"><img src="https://avatars.githubusercontent.com/u/1581943?v=4&s=48" width="48" height="48" alt="Whoaa512" title="Whoaa512"/></a> <a href="https://github.com/chriseidhof"><img src="https://avatars.githubusercontent.com/u/5382?v=4&s=48" width="48" height="48" alt="chriseidhof" title="chriseidhof"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/therealZpoint-bot"><img src="https://avatars.githubusercontent.com/u/258706705?v=4&s=48" width="48" height="48" alt="therealZpoint-bot" title="therealZpoint-bot"/></a> <a href="https://github.com/wangai-studio"><img src="https://avatars.githubusercontent.com/u/256938352?v=4&s=48" width="48" height="48" alt="wangai-studio" title="wangai-studio"/></a> <a href="https://github.com/ysqander"><img src="https://avatars.githubusercontent.com/u/80843820?v=4&s=48" width="48" height="48" alt="ysqander" title="ysqander"/></a> <a href="https://github.com/search?q=Yurii%20Chukhlib"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Yurii Chukhlib" title="Yurii Chukhlib"/></a> <a href="https://github.com/aj47"><img src="https://avatars.githubusercontent.com/u/8023513?v=4&s=48" width="48" height="48" alt="aj47" title="aj47"/></a> <a href="https://github.com/kennyklee"><img src="https://avatars.githubusercontent.com/u/1432489?v=4&s=48" width="48" height="48" alt="kennyklee" title="kennyklee"/></a>
|
||||||
<a href="https://github.com/search?q=Ghost"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ghost" title="Ghost"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/search?q=Keith%20the%20Silly%20Goose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Keith the Silly Goose" title="Keith the Silly Goose"/></a> <a href="https://github.com/search?q=L36%20Server"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="L36 Server" title="L36 Server"/></a> <a href="https://github.com/search?q=Marc"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Marc" title="Marc"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/mkbehr"><img src="https://avatars.githubusercontent.com/u/1285?v=4&s=48" width="48" height="48" alt="mkbehr" title="mkbehr"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a> <a href="https://github.com/sibbl"><img src="https://avatars.githubusercontent.com/u/866535?v=4&s=48" width="48" height="48" alt="sibbl" title="sibbl"/></a> <a href="https://github.com/abhijeet117"><img src="https://avatars.githubusercontent.com/u/192859219?v=4&s=48" width="48" height="48" alt="abhijeet117" title="abhijeet117"/></a>
|
<a href="https://github.com/superman32432432"><img src="https://avatars.githubusercontent.com/u/7228420?v=4&s=48" width="48" height="48" alt="superman32432432" title="superman32432432"/></a> <a href="https://github.com/Hisleren"><img src="https://avatars.githubusercontent.com/u/83217244?v=4&s=48" width="48" height="48" alt="Hisleren" title="Hisleren"/></a> <a href="https://github.com/shatner"><img src="https://avatars.githubusercontent.com/u/17735435?v=4&s=48" width="48" height="48" alt="shatner" title="shatner"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/austinm911"><img src="https://avatars.githubusercontent.com/u/31991302?v=4&s=48" width="48" height="48" alt="austinm911" title="austinm911"/></a> <a href="https://github.com/apps/blacksmith-sh"><img src="https://avatars.githubusercontent.com/in/807020?v=4&s=48" width="48" height="48" alt="blacksmith-sh[bot]" title="blacksmith-sh[bot]"/></a> <a href="https://github.com/damoahdominic"><img src="https://avatars.githubusercontent.com/u/4623434?v=4&s=48" width="48" height="48" alt="damoahdominic" title="damoahdominic"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/GHesericsu"><img src="https://avatars.githubusercontent.com/u/60202455?v=4&s=48" width="48" height="48" alt="GHesericsu" title="GHesericsu"/></a> <a href="https://github.com/HeimdallStrategy"><img src="https://avatars.githubusercontent.com/u/223014405?v=4&s=48" width="48" height="48" alt="HeimdallStrategy" title="HeimdallStrategy"/></a>
|
||||||
<a href="https://github.com/chrisrodz"><img src="https://avatars.githubusercontent.com/u/2967620?v=4&s=48" width="48" height="48" alt="chrisrodz" title="chrisrodz"/></a> <a href="https://github.com/search?q=Friederike%20Seiler"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Friederike Seiler" title="Friederike Seiler"/></a> <a href="https://github.com/gabriel-trigo"><img src="https://avatars.githubusercontent.com/u/38991125?v=4&s=48" width="48" height="48" alt="gabriel-trigo" title="gabriel-trigo"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/itsjling"><img src="https://avatars.githubusercontent.com/u/2521993?v=4&s=48" width="48" height="48" alt="itsjling" title="itsjling"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/search?q=Joshua%20Mitchell"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Joshua Mitchell" title="Joshua Mitchell"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></a> <a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a>
|
<a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a> <a href="https://github.com/Lukavyi"><img src="https://avatars.githubusercontent.com/u/1013690?v=4&s=48" width="48" height="48" alt="Lukavyi" title="Lukavyi"/></a> <a href="https://github.com/mahmoudashraf93"><img src="https://avatars.githubusercontent.com/u/9130129?v=4&s=48" width="48" height="48" alt="mahmoudashraf93" title="mahmoudashraf93"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a> <a href="https://github.com/robhparker"><img src="https://avatars.githubusercontent.com/u/7404740?v=4&s=48" width="48" height="48" alt="robhparker" title="robhparker"/></a> <a href="https://github.com/search?q=Ryan%20Lisse"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ryan Lisse" title="Ryan Lisse"/></a>
|
||||||
<a href="https://github.com/ogulcancelik"><img src="https://avatars.githubusercontent.com/u/7064011?v=4&s=48" width="48" height="48" alt="ogulcancelik" title="ogulcancelik"/></a> <a href="https://github.com/pasogott"><img src="https://avatars.githubusercontent.com/u/23458152?v=4&s=48" width="48" height="48" alt="pasogott" title="pasogott"/></a> <a href="https://github.com/petradonka"><img src="https://avatars.githubusercontent.com/u/7353770?v=4&s=48" width="48" height="48" alt="petradonka" title="petradonka"/></a> <a href="https://github.com/rubyrunsstuff"><img src="https://avatars.githubusercontent.com/u/246602379?v=4&s=48" width="48" height="48" alt="rubyrunsstuff" title="rubyrunsstuff"/></a> <a href="https://github.com/siddhantjain"><img src="https://avatars.githubusercontent.com/u/4835232?v=4&s=48" width="48" height="48" alt="siddhantjain" title="siddhantjain"/></a> <a href="https://github.com/spiceoogway"><img src="https://avatars.githubusercontent.com/u/105812383?v=4&s=48" width="48" height="48" alt="spiceoogway" title="spiceoogway"/></a> <a href="https://github.com/suminhthanh"><img src="https://avatars.githubusercontent.com/u/2907636?v=4&s=48" width="48" height="48" alt="suminhthanh" title="suminhthanh"/></a> <a href="https://github.com/svkozak"><img src="https://avatars.githubusercontent.com/u/31941359?v=4&s=48" width="48" height="48" alt="svkozak" title="svkozak"/></a> <a href="https://github.com/wes-davis"><img src="https://avatars.githubusercontent.com/u/16506720?v=4&s=48" width="48" height="48" alt="wes-davis" title="wes-davis"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a>
|
<a href="https://github.com/Yeom-JinHo"><img src="https://avatars.githubusercontent.com/u/81306489?v=4&s=48" width="48" height="48" alt="Yeom-JinHo" title="Yeom-JinHo"/></a> <a href="https://github.com/doodlewind"><img src="https://avatars.githubusercontent.com/u/7312949?v=4&s=48" width="48" height="48" alt="doodlewind" title="doodlewind"/></a> <a href="https://github.com/dougvk"><img src="https://avatars.githubusercontent.com/u/401660?v=4&s=48" width="48" height="48" alt="dougvk" title="dougvk"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/fal3"><img src="https://avatars.githubusercontent.com/u/6484295?v=4&s=48" width="48" height="48" alt="fal3" title="fal3"/></a> <a href="https://github.com/search?q=Ghost"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ghost" title="Ghost"/></a> <a href="https://github.com/hyf0-agent"><img src="https://avatars.githubusercontent.com/u/258783736?v=4&s=48" width="48" height="48" alt="hyf0-agent" title="hyf0-agent"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/search?q=Keith%20the%20Silly%20Goose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Keith the Silly Goose" title="Keith the Silly Goose"/></a> <a href="https://github.com/search?q=L36%20Server"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="L36 Server" title="L36 Server"/></a>
|
||||||
<a href="https://github.com/24601"><img src="https://avatars.githubusercontent.com/u/1157207?v=4&s=48" width="48" height="48" alt="24601" title="24601"/></a> <a href="https://github.com/ameno-"><img src="https://avatars.githubusercontent.com/u/2416135?v=4&s=48" width="48" height="48" alt="ameno-" title="ameno-"/></a> <a href="https://github.com/bonald"><img src="https://avatars.githubusercontent.com/u/12394874?v=4&s=48" width="48" height="48" alt="bonald" title="bonald"/></a> <a href="https://github.com/bravostation"><img src="https://avatars.githubusercontent.com/u/257991910?v=4&s=48" width="48" height="48" alt="bravostation" title="bravostation"/></a> <a href="https://github.com/search?q=Chris%20Taylor"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Chris Taylor" title="Chris Taylor"/></a> <a href="https://github.com/dguido"><img src="https://avatars.githubusercontent.com/u/294844?v=4&s=48" width="48" height="48" alt="dguido" title="dguido"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/evalexpr"><img src="https://avatars.githubusercontent.com/u/23485511?v=4&s=48" width="48" height="48" alt="evalexpr" title="evalexpr"/></a> <a href="https://github.com/henrino3"><img src="https://avatars.githubusercontent.com/u/4260288?v=4&s=48" width="48" height="48" alt="henrino3" title="henrino3"/></a> <a href="https://github.com/humanwritten"><img src="https://avatars.githubusercontent.com/u/206531610?v=4&s=48" width="48" height="48" alt="humanwritten" title="humanwritten"/></a>
|
<a href="https://github.com/search?q=Marc"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Marc" title="Marc"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/mkbehr"><img src="https://avatars.githubusercontent.com/u/1285?v=4&s=48" width="48" height="48" alt="mkbehr" title="mkbehr"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a> <a href="https://github.com/sibbl"><img src="https://avatars.githubusercontent.com/u/866535?v=4&s=48" width="48" height="48" alt="sibbl" title="sibbl"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a> <a href="https://github.com/abhijeet117"><img src="https://avatars.githubusercontent.com/u/192859219?v=4&s=48" width="48" height="48" alt="abhijeet117" title="abhijeet117"/></a> <a href="https://github.com/chrisrodz"><img src="https://avatars.githubusercontent.com/u/2967620?v=4&s=48" width="48" height="48" alt="chrisrodz" title="chrisrodz"/></a> <a href="https://github.com/search?q=Friederike%20Seiler"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Friederike Seiler" title="Friederike Seiler"/></a> <a href="https://github.com/gabriel-trigo"><img src="https://avatars.githubusercontent.com/u/38991125?v=4&s=48" width="48" height="48" alt="gabriel-trigo" title="gabriel-trigo"/></a>
|
||||||
<a href="https://github.com/larlyssa"><img src="https://avatars.githubusercontent.com/u/13128869?v=4&s=48" width="48" height="48" alt="larlyssa" title="larlyssa"/></a> <a href="https://github.com/Lukavyi"><img src="https://avatars.githubusercontent.com/u/1013690?v=4&s=48" width="48" height="48" alt="Lukavyi" title="Lukavyi"/></a> <a href="https://github.com/mitsuhiko"><img src="https://avatars.githubusercontent.com/u/7396?v=4&s=48" width="48" height="48" alt="mitsuhiko" title="mitsuhiko"/></a> <a href="https://github.com/odysseus0"><img src="https://avatars.githubusercontent.com/u/8635094?v=4&s=48" width="48" height="48" alt="odysseus0" title="odysseus0"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/pi0"><img src="https://avatars.githubusercontent.com/u/5158436?v=4&s=48" width="48" height="48" alt="pi0" title="pi0"/></a> <a href="https://github.com/rmorse"><img src="https://avatars.githubusercontent.com/u/853547?v=4&s=48" width="48" height="48" alt="rmorse" title="rmorse"/></a> <a href="https://github.com/search?q=Roopak%20Nijhara"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Roopak Nijhara" title="Roopak Nijhara"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a>
|
<a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/itsjling"><img src="https://avatars.githubusercontent.com/u/2521993?v=4&s=48" width="48" height="48" alt="itsjling" title="itsjling"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/search?q=Joshua%20Mitchell"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Joshua Mitchell" title="Joshua Mitchell"/></a> <a href="https://github.com/kelvinCB"><img src="https://avatars.githubusercontent.com/u/50544379?v=4&s=48" width="48" height="48" alt="kelvinCB" title="kelvinCB"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></a> <a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a> <a href="https://github.com/mattqdev"><img src="https://avatars.githubusercontent.com/u/115874885?v=4&s=48" width="48" height="48" alt="mattqdev" title="mattqdev"/></a> <a href="https://github.com/mitsuhiko"><img src="https://avatars.githubusercontent.com/u/7396?v=4&s=48" width="48" height="48" alt="mitsuhiko" title="mitsuhiko"/></a>
|
||||||
<a href="https://github.com/search?q=Ubuntu"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ubuntu" title="Ubuntu"/></a> <a href="https://github.com/search?q=xiaose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="xiaose" title="xiaose"/></a> <a href="https://github.com/search?q=Aaron%20Konyer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Aaron Konyer" title="Aaron Konyer"/></a> <a href="https://github.com/aaronveklabs"><img src="https://avatars.githubusercontent.com/u/225997828?v=4&s=48" width="48" height="48" alt="aaronveklabs" title="aaronveklabs"/></a> <a href="https://github.com/andreabadesso"><img src="https://avatars.githubusercontent.com/u/3586068?v=4&s=48" width="48" height="48" alt="andreabadesso" title="andreabadesso"/></a> <a href="https://github.com/search?q=Andrii"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Andrii" title="Andrii"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></a> <a href="https://github.com/search?q=ClawdFx"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ClawdFx" title="ClawdFx"/></a> <a href="https://github.com/danballance"><img src="https://avatars.githubusercontent.com/u/13839912?v=4&s=48" width="48" height="48" alt="danballance" title="danballance"/></a>
|
<a href="https://github.com/ogulcancelik"><img src="https://avatars.githubusercontent.com/u/7064011?v=4&s=48" width="48" height="48" alt="ogulcancelik" title="ogulcancelik"/></a> <a href="https://github.com/pasogott"><img src="https://avatars.githubusercontent.com/u/23458152?v=4&s=48" width="48" height="48" alt="pasogott" title="pasogott"/></a> <a href="https://github.com/petradonka"><img src="https://avatars.githubusercontent.com/u/7353770?v=4&s=48" width="48" height="48" alt="petradonka" title="petradonka"/></a> <a href="https://github.com/rubyrunsstuff"><img src="https://avatars.githubusercontent.com/u/246602379?v=4&s=48" width="48" height="48" alt="rubyrunsstuff" title="rubyrunsstuff"/></a> <a href="https://github.com/siddhantjain"><img src="https://avatars.githubusercontent.com/u/4835232?v=4&s=48" width="48" height="48" alt="siddhantjain" title="siddhantjain"/></a> <a href="https://github.com/spiceoogway"><img src="https://avatars.githubusercontent.com/u/105812383?v=4&s=48" width="48" height="48" alt="spiceoogway" title="spiceoogway"/></a> <a href="https://github.com/suminhthanh"><img src="https://avatars.githubusercontent.com/u/2907636?v=4&s=48" width="48" height="48" alt="suminhthanh" title="suminhthanh"/></a> <a href="https://github.com/svkozak"><img src="https://avatars.githubusercontent.com/u/31941359?v=4&s=48" width="48" height="48" alt="svkozak" title="svkozak"/></a> <a href="https://github.com/wes-davis"><img src="https://avatars.githubusercontent.com/u/16506720?v=4&s=48" width="48" height="48" alt="wes-davis" title="wes-davis"/></a> <a href="https://github.com/24601"><img src="https://avatars.githubusercontent.com/u/1157207?v=4&s=48" width="48" height="48" alt="24601" title="24601"/></a>
|
||||||
<a href="https://github.com/EnzeD"><img src="https://avatars.githubusercontent.com/u/9866900?v=4&s=48" width="48" height="48" alt="EnzeD" title="EnzeD"/></a> <a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/Evizero"><img src="https://avatars.githubusercontent.com/u/10854026?v=4&s=48" width="48" height="48" alt="Evizero" title="Evizero"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/itsjaydesu"><img src="https://avatars.githubusercontent.com/u/220390?v=4&s=48" width="48" height="48" alt="itsjaydesu" title="itsjaydesu"/></a> <a href="https://github.com/ivancasco"><img src="https://avatars.githubusercontent.com/u/2452858?v=4&s=48" width="48" height="48" alt="ivancasco" title="ivancasco"/></a> <a href="https://github.com/ivanrvpereira"><img src="https://avatars.githubusercontent.com/u/183991?v=4&s=48" width="48" height="48" alt="ivanrvpereira" title="ivanrvpereira"/></a> <a href="https://github.com/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a>
|
<a href="https://github.com/ameno-"><img src="https://avatars.githubusercontent.com/u/2416135?v=4&s=48" width="48" height="48" alt="ameno-" title="ameno-"/></a> <a href="https://github.com/bonald"><img src="https://avatars.githubusercontent.com/u/12394874?v=4&s=48" width="48" height="48" alt="bonald" title="bonald"/></a> <a href="https://github.com/bravostation"><img src="https://avatars.githubusercontent.com/u/257991910?v=4&s=48" width="48" height="48" alt="bravostation" title="bravostation"/></a> <a href="https://github.com/search?q=Chris%20Taylor"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Chris Taylor" title="Chris Taylor"/></a> <a href="https://github.com/dguido"><img src="https://avatars.githubusercontent.com/u/294844?v=4&s=48" width="48" height="48" alt="dguido" title="dguido"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/evalexpr"><img src="https://avatars.githubusercontent.com/u/23485511?v=4&s=48" width="48" height="48" alt="evalexpr" title="evalexpr"/></a> <a href="https://github.com/henrino3"><img src="https://avatars.githubusercontent.com/u/4260288?v=4&s=48" width="48" height="48" alt="henrino3" title="henrino3"/></a> <a href="https://github.com/humanwritten"><img src="https://avatars.githubusercontent.com/u/206531610?v=4&s=48" width="48" height="48" alt="humanwritten" title="humanwritten"/></a> <a href="https://github.com/j2h4u"><img src="https://avatars.githubusercontent.com/u/39818683?v=4&s=48" width="48" height="48" alt="j2h4u" title="j2h4u"/></a>
|
||||||
<a href="https://github.com/search?q=jeffersonwarrior"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/longmaba"><img src="https://avatars.githubusercontent.com/u/9361500?v=4&s=48" width="48" height="48" alt="longmaba" title="longmaba"/></a> <a href="https://github.com/MarvinCui"><img src="https://avatars.githubusercontent.com/u/130876763?v=4&s=48" width="48" height="48" alt="MarvinCui" title="MarvinCui"/></a> <a href="https://github.com/mjrussell"><img src="https://avatars.githubusercontent.com/u/1641895?v=4&s=48" width="48" height="48" alt="mjrussell" title="mjrussell"/></a> <a href="https://github.com/odnxe"><img src="https://avatars.githubusercontent.com/u/403141?v=4&s=48" width="48" height="48" alt="odnxe" title="odnxe"/></a> <a href="https://github.com/optimikelabs"><img src="https://avatars.githubusercontent.com/u/31423109?v=4&s=48" width="48" height="48" alt="optimikelabs" title="optimikelabs"/></a> <a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a> <a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/search?q=Pocket%20Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Pocket Clawd" title="Pocket Clawd"/></a>
|
<a href="https://github.com/larlyssa"><img src="https://avatars.githubusercontent.com/u/13128869?v=4&s=48" width="48" height="48" alt="larlyssa" title="larlyssa"/></a> <a href="https://github.com/odysseus0"><img src="https://avatars.githubusercontent.com/u/8635094?v=4&s=48" width="48" height="48" alt="odysseus0" title="odysseus0"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/pi0"><img src="https://avatars.githubusercontent.com/u/5158436?v=4&s=48" width="48" height="48" alt="pi0" title="pi0"/></a> <a href="https://github.com/rmorse"><img src="https://avatars.githubusercontent.com/u/853547?v=4&s=48" width="48" height="48" alt="rmorse" title="rmorse"/></a> <a href="https://github.com/search?q=Roopak%20Nijhara"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Roopak Nijhara" title="Roopak Nijhara"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a> <a href="https://github.com/search?q=Ubuntu"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ubuntu" title="Ubuntu"/></a> <a href="https://github.com/search?q=xiaose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="xiaose" title="xiaose"/></a>
|
||||||
<a href="https://github.com/robaxelsen"><img src="https://avatars.githubusercontent.com/u/13132899?v=4&s=48" width="48" height="48" alt="robaxelsen" title="robaxelsen"/></a> <a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a> <a href="https://github.com/Suksham-sharma"><img src="https://avatars.githubusercontent.com/u/94667656?v=4&s=48" width="48" height="48" alt="Suksham-sharma" title="Suksham-sharma"/></a> <a href="https://github.com/T5-AndyML"><img src="https://avatars.githubusercontent.com/u/22801233?v=4&s=48" width="48" height="48" alt="T5-AndyML" title="T5-AndyML"/></a> <a href="https://github.com/tewatia"><img src="https://avatars.githubusercontent.com/u/22875334?v=4&s=48" width="48" height="48" alt="tewatia" title="tewatia"/></a> <a href="https://github.com/thejhinvirtuoso"><img src="https://avatars.githubusercontent.com/u/258521837?v=4&s=48" width="48" height="48" alt="thejhinvirtuoso" title="thejhinvirtuoso"/></a> <a href="https://github.com/travisp"><img src="https://avatars.githubusercontent.com/u/165698?v=4&s=48" width="48" height="48" alt="travisp" title="travisp"/></a> <a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=william%20arzt"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="william arzt" title="william arzt"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a>
|
<a href="https://github.com/search?q=Aaron%20Konyer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Aaron Konyer" title="Aaron Konyer"/></a> <a href="https://github.com/aaronveklabs"><img src="https://avatars.githubusercontent.com/u/225997828?v=4&s=48" width="48" height="48" alt="aaronveklabs" title="aaronveklabs"/></a> <a href="https://github.com/aldoeliacim"><img src="https://avatars.githubusercontent.com/u/17973757?v=4&s=48" width="48" height="48" alt="aldoeliacim" title="aldoeliacim"/></a> <a href="https://github.com/andreabadesso"><img src="https://avatars.githubusercontent.com/u/3586068?v=4&s=48" width="48" height="48" alt="andreabadesso" title="andreabadesso"/></a> <a href="https://github.com/search?q=Andrii"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Andrii" title="Andrii"/></a> <a href="https://github.com/BinaryMuse"><img src="https://avatars.githubusercontent.com/u/189606?v=4&s=48" width="48" height="48" alt="BinaryMuse" title="BinaryMuse"/></a> <a href="https://github.com/bqcfjwhz85-arch"><img src="https://avatars.githubusercontent.com/u/239267175?v=4&s=48" width="48" height="48" alt="bqcfjwhz85-arch" title="bqcfjwhz85-arch"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></a> <a href="https://github.com/search?q=ClawdFx"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ClawdFx" title="ClawdFx"/></a>
|
||||||
<a href="https://github.com/0oAstro"><img src="https://avatars.githubusercontent.com/u/79555780?v=4&s=48" width="48" height="48" alt="0oAstro" title="0oAstro"/></a> <a href="https://github.com/abhaymundhara"><img src="https://avatars.githubusercontent.com/u/62872231?v=4&s=48" width="48" height="48" alt="abhaymundhara" title="abhaymundhara"/></a> <a href="https://github.com/aduk059"><img src="https://avatars.githubusercontent.com/u/257603478?v=4&s=48" width="48" height="48" alt="aduk059" title="aduk059"/></a> <a href="https://github.com/aldoeliacim"><img src="https://avatars.githubusercontent.com/u/17973757?v=4&s=48" width="48" height="48" alt="aldoeliacim" title="aldoeliacim"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/Alex-Alaniz"><img src="https://avatars.githubusercontent.com/u/88956822?v=4&s=48" width="48" height="48" alt="Alex-Alaniz" title="Alex-Alaniz"/></a> <a href="https://github.com/alexanderatallah"><img src="https://avatars.githubusercontent.com/u/1011391?v=4&s=48" width="48" height="48" alt="alexanderatallah" title="alexanderatallah"/></a> <a href="https://github.com/alexstyl"><img src="https://avatars.githubusercontent.com/u/1665273?v=4&s=48" width="48" height="48" alt="alexstyl" title="alexstyl"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a> <a href="https://github.com/anpoirier"><img src="https://avatars.githubusercontent.com/u/1245729?v=4&s=48" width="48" height="48" alt="anpoirier" title="anpoirier"/></a>
|
<a href="https://github.com/search?q=damaozi"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="damaozi" title="damaozi"/></a> <a href="https://github.com/danballance"><img src="https://avatars.githubusercontent.com/u/13839912?v=4&s=48" width="48" height="48" alt="danballance" title="danballance"/></a> <a href="https://github.com/Elarwei001"><img src="https://avatars.githubusercontent.com/u/168552401?v=4&s=48" width="48" height="48" alt="Elarwei001" title="Elarwei001"/></a> <a href="https://github.com/EnzeD"><img src="https://avatars.githubusercontent.com/u/9866900?v=4&s=48" width="48" height="48" alt="EnzeD" title="EnzeD"/></a> <a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/Evizero"><img src="https://avatars.githubusercontent.com/u/10854026?v=4&s=48" width="48" height="48" alt="Evizero" title="Evizero"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/gildo"><img src="https://avatars.githubusercontent.com/u/133645?v=4&s=48" width="48" height="48" alt="gildo" title="gildo"/></a> <a href="https://github.com/hclsys"><img src="https://avatars.githubusercontent.com/u/7755017?v=4&s=48" width="48" height="48" alt="hclsys" title="hclsys"/></a> <a href="https://github.com/itsjaydesu"><img src="https://avatars.githubusercontent.com/u/220390?v=4&s=48" width="48" height="48" alt="itsjaydesu" title="itsjaydesu"/></a>
|
||||||
<a href="https://github.com/araa47"><img src="https://avatars.githubusercontent.com/u/22760261?v=4&s=48" width="48" height="48" alt="araa47" title="araa47"/></a> <a href="https://github.com/arthyn"><img src="https://avatars.githubusercontent.com/u/5466421?v=4&s=48" width="48" height="48" alt="arthyn" title="arthyn"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/search?q=Ayush%20Ojha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ayush Ojha" title="Ayush Ojha"/></a> <a href="https://github.com/Ayush10"><img src="https://avatars.githubusercontent.com/u/7945279?v=4&s=48" width="48" height="48" alt="Ayush10" title="Ayush10"/></a> <a href="https://github.com/bguidolim"><img src="https://avatars.githubusercontent.com/u/987360?v=4&s=48" width="48" height="48" alt="bguidolim" title="bguidolim"/></a> <a href="https://github.com/bolismauro"><img src="https://avatars.githubusercontent.com/u/771999?v=4&s=48" width="48" height="48" alt="bolismauro" title="bolismauro"/></a> <a href="https://github.com/championswimmer"><img src="https://avatars.githubusercontent.com/u/1327050?v=4&s=48" width="48" height="48" alt="championswimmer" title="championswimmer"/></a> <a href="https://github.com/chenyuan99"><img src="https://avatars.githubusercontent.com/u/25518100?v=4&s=48" width="48" height="48" alt="chenyuan99" title="chenyuan99"/></a> <a href="https://github.com/Chloe-VP"><img src="https://avatars.githubusercontent.com/u/257371598?v=4&s=48" width="48" height="48" alt="Chloe-VP" title="Chloe-VP"/></a>
|
<a href="https://github.com/ivancasco"><img src="https://avatars.githubusercontent.com/u/2452858?v=4&s=48" width="48" height="48" alt="ivancasco" title="ivancasco"/></a> <a href="https://github.com/ivanrvpereira"><img src="https://avatars.githubusercontent.com/u/183991?v=4&s=48" width="48" height="48" alt="ivanrvpereira" title="ivanrvpereira"/></a> <a href="https://github.com/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/search?q=jeffersonwarrior"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/lailoo"><img src="https://avatars.githubusercontent.com/u/20536249?v=4&s=48" width="48" height="48" alt="lailoo" title="lailoo"/></a> <a href="https://github.com/longmaba"><img src="https://avatars.githubusercontent.com/u/9361500?v=4&s=48" width="48" height="48" alt="longmaba" title="longmaba"/></a> <a href="https://github.com/search?q=Marco%20Marandiz"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Marco Marandiz" title="Marco Marandiz"/></a>
|
||||||
<a href="https://github.com/search?q=Clawdbot%20Maintainers"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawdbot Maintainers" title="Clawdbot Maintainers"/></a> <a href="https://github.com/conhecendoia"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendoia" title="conhecendoia"/></a> <a href="https://github.com/dasilva333"><img src="https://avatars.githubusercontent.com/u/947827?v=4&s=48" width="48" height="48" alt="dasilva333" title="dasilva333"/></a> <a href="https://github.com/David-Marsh-Photo"><img src="https://avatars.githubusercontent.com/u/228404527?v=4&s=48" width="48" height="48" alt="David-Marsh-Photo" title="David-Marsh-Photo"/></a> <a href="https://github.com/search?q=Developer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Developer" title="Developer"/></a> <a href="https://github.com/search?q=Dimitrios%20Ploutarchos"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Dimitrios Ploutarchos" title="Dimitrios Ploutarchos"/></a> <a href="https://github.com/search?q=Drake%20Thomsen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Drake Thomsen" title="Drake Thomsen"/></a> <a href="https://github.com/dylanneve1"><img src="https://avatars.githubusercontent.com/u/31746704?v=4&s=48" width="48" height="48" alt="dylanneve1" title="dylanneve1"/></a> <a href="https://github.com/search?q=Felix%20Krause"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Felix Krause" title="Felix Krause"/></a> <a href="https://github.com/foeken"><img src="https://avatars.githubusercontent.com/u/13864?v=4&s=48" width="48" height="48" alt="foeken" title="foeken"/></a>
|
<a href="https://github.com/MarvinCui"><img src="https://avatars.githubusercontent.com/u/130876763?v=4&s=48" width="48" height="48" alt="MarvinCui" title="MarvinCui"/></a> <a href="https://github.com/mattezell"><img src="https://avatars.githubusercontent.com/u/361409?v=4&s=48" width="48" height="48" alt="mattezell" title="mattezell"/></a> <a href="https://github.com/mjrussell"><img src="https://avatars.githubusercontent.com/u/1641895?v=4&s=48" width="48" height="48" alt="mjrussell" title="mjrussell"/></a> <a href="https://github.com/odnxe"><img src="https://avatars.githubusercontent.com/u/403141?v=4&s=48" width="48" height="48" alt="odnxe" title="odnxe"/></a> <a href="https://github.com/optimikelabs"><img src="https://avatars.githubusercontent.com/u/31423109?v=4&s=48" width="48" height="48" alt="optimikelabs" title="optimikelabs"/></a> <a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a> <a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/search?q=Pocket%20Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Pocket Clawd" title="Pocket Clawd"/></a> <a href="https://github.com/robaxelsen"><img src="https://avatars.githubusercontent.com/u/13132899?v=4&s=48" width="48" height="48" alt="robaxelsen" title="robaxelsen"/></a> <a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a>
|
||||||
<a href="https://github.com/frankekn"><img src="https://avatars.githubusercontent.com/u/4488090?v=4&s=48" width="48" height="48" alt="frankekn" title="frankekn"/></a> <a href="https://github.com/fredheir"><img src="https://avatars.githubusercontent.com/u/3304869?v=4&s=48" width="48" height="48" alt="fredheir" title="fredheir"/></a> <a href="https://github.com/search?q=ganghyun%20kim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ganghyun kim" title="ganghyun kim"/></a> <a href="https://github.com/grrowl"><img src="https://avatars.githubusercontent.com/u/907140?v=4&s=48" width="48" height="48" alt="grrowl" title="grrowl"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/HassanFleyah"><img src="https://avatars.githubusercontent.com/u/228002017?v=4&s=48" width="48" height="48" alt="HassanFleyah" title="HassanFleyah"/></a> <a href="https://github.com/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></a> <a href="https://github.com/hclsys"><img src="https://avatars.githubusercontent.com/u/7755017?v=4&s=48" width="48" height="48" alt="hclsys" title="hclsys"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a>
|
<a href="https://github.com/Suksham-sharma"><img src="https://avatars.githubusercontent.com/u/94667656?v=4&s=48" width="48" height="48" alt="Suksham-sharma" title="Suksham-sharma"/></a> <a href="https://github.com/T5-AndyML"><img src="https://avatars.githubusercontent.com/u/22801233?v=4&s=48" width="48" height="48" alt="T5-AndyML" title="T5-AndyML"/></a> <a href="https://github.com/tewatia"><img src="https://avatars.githubusercontent.com/u/22875334?v=4&s=48" width="48" height="48" alt="tewatia" title="tewatia"/></a> <a href="https://github.com/thejhinvirtuoso"><img src="https://avatars.githubusercontent.com/u/258521837?v=4&s=48" width="48" height="48" alt="thejhinvirtuoso" title="thejhinvirtuoso"/></a> <a href="https://github.com/travisp"><img src="https://avatars.githubusercontent.com/u/165698?v=4&s=48" width="48" height="48" alt="travisp" title="travisp"/></a> <a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=william%20arzt"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="william arzt" title="william arzt"/></a> <a href="https://github.com/yudshj"><img src="https://avatars.githubusercontent.com/u/16971372?v=4&s=48" width="48" height="48" alt="yudshj" title="yudshj"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a> <a href="https://github.com/0oAstro"><img src="https://avatars.githubusercontent.com/u/79555780?v=4&s=48" width="48" height="48" alt="0oAstro" title="0oAstro"/></a>
|
||||||
<a href="https://github.com/iamEvanYT"><img src="https://avatars.githubusercontent.com/u/47493765?v=4&s=48" width="48" height="48" alt="iamEvanYT" title="iamEvanYT"/></a> <a href="https://github.com/search?q=Jamie%20Openshaw"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jamie Openshaw" title="Jamie Openshaw"/></a> <a href="https://github.com/search?q=Jane"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jane" title="Jane"/></a> <a href="https://github.com/search?q=Jarvis%20Deploy"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis Deploy" title="Jarvis Deploy"/></a> <a href="https://github.com/search?q=Jefferson%20Nunn"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jefferson Nunn" title="Jefferson Nunn"/></a> <a href="https://github.com/jogi47"><img src="https://avatars.githubusercontent.com/u/1710139?v=4&s=48" width="48" height="48" alt="jogi47" title="jogi47"/></a> <a href="https://github.com/kentaro"><img src="https://avatars.githubusercontent.com/u/3458?v=4&s=48" width="48" height="48" alt="kentaro" title="kentaro"/></a> <a href="https://github.com/search?q=Kevin%20Lin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kevin Lin" title="Kevin Lin"/></a> <a href="https://github.com/kira-ariaki"><img src="https://avatars.githubusercontent.com/u/257352493?v=4&s=48" width="48" height="48" alt="kira-ariaki" title="kira-ariaki"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a>
|
<a href="https://github.com/abhaymundhara"><img src="https://avatars.githubusercontent.com/u/62872231?v=4&s=48" width="48" height="48" alt="abhaymundhara" title="abhaymundhara"/></a> <a href="https://github.com/aduk059"><img src="https://avatars.githubusercontent.com/u/257603478?v=4&s=48" width="48" height="48" alt="aduk059" title="aduk059"/></a> <a href="https://github.com/aisling404"><img src="https://avatars.githubusercontent.com/u/211950534?v=4&s=48" width="48" height="48" alt="aisling404" title="aisling404"/></a> <a href="https://github.com/akramcodez"><img src="https://avatars.githubusercontent.com/u/179671552?v=4&s=48" width="48" height="48" alt="akramcodez" title="akramcodez"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/Alex-Alaniz"><img src="https://avatars.githubusercontent.com/u/88956822?v=4&s=48" width="48" height="48" alt="Alex-Alaniz" title="Alex-Alaniz"/></a> <a href="https://github.com/alexanderatallah"><img src="https://avatars.githubusercontent.com/u/1011391?v=4&s=48" width="48" height="48" alt="alexanderatallah" title="alexanderatallah"/></a> <a href="https://github.com/alexstyl"><img src="https://avatars.githubusercontent.com/u/1665273?v=4&s=48" width="48" height="48" alt="alexstyl" title="alexstyl"/></a> <a href="https://github.com/AlexZhangji"><img src="https://avatars.githubusercontent.com/u/3280924?v=4&s=48" width="48" height="48" alt="AlexZhangji" title="AlexZhangji"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a>
|
||||||
<a href="https://github.com/Kiwitwitter"><img src="https://avatars.githubusercontent.com/u/25277769?v=4&s=48" width="48" height="48" alt="Kiwitwitter" title="Kiwitwitter"/></a> <a href="https://github.com/levifig"><img src="https://avatars.githubusercontent.com/u/1605?v=4&s=48" width="48" height="48" alt="levifig" title="levifig"/></a> <a href="https://github.com/search?q=Lloyd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Lloyd" title="Lloyd"/></a> <a href="https://github.com/loganaden"><img src="https://avatars.githubusercontent.com/u/1688420?v=4&s=48" width="48" height="48" alt="loganaden" title="loganaden"/></a> <a href="https://github.com/longjos"><img src="https://avatars.githubusercontent.com/u/740160?v=4&s=48" width="48" height="48" alt="longjos" title="longjos"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/louzhixian"><img src="https://avatars.githubusercontent.com/u/7994361?v=4&s=48" width="48" height="48" alt="louzhixian" title="louzhixian"/></a> <a href="https://github.com/martinpucik"><img src="https://avatars.githubusercontent.com/u/5503097?v=4&s=48" width="48" height="48" alt="martinpucik" title="martinpucik"/></a> <a href="https://github.com/search?q=Matt%20mini"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Matt mini" title="Matt mini"/></a> <a href="https://github.com/mertcicekci0"><img src="https://avatars.githubusercontent.com/u/179321902?v=4&s=48" width="48" height="48" alt="mertcicekci0" title="mertcicekci0"/></a>
|
<a href="https://github.com/anpoirier"><img src="https://avatars.githubusercontent.com/u/1245729?v=4&s=48" width="48" height="48" alt="anpoirier" title="anpoirier"/></a> <a href="https://github.com/araa47"><img src="https://avatars.githubusercontent.com/u/22760261?v=4&s=48" width="48" height="48" alt="araa47" title="araa47"/></a> <a href="https://github.com/arthyn"><img src="https://avatars.githubusercontent.com/u/5466421?v=4&s=48" width="48" height="48" alt="arthyn" title="arthyn"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/search?q=Ayush%20Ojha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ayush Ojha" title="Ayush Ojha"/></a> <a href="https://github.com/Ayush10"><img src="https://avatars.githubusercontent.com/u/7945279?v=4&s=48" width="48" height="48" alt="Ayush10" title="Ayush10"/></a> <a href="https://github.com/bguidolim"><img src="https://avatars.githubusercontent.com/u/987360?v=4&s=48" width="48" height="48" alt="bguidolim" title="bguidolim"/></a> <a href="https://github.com/bolismauro"><img src="https://avatars.githubusercontent.com/u/771999?v=4&s=48" width="48" height="48" alt="bolismauro" title="bolismauro"/></a> <a href="https://github.com/caelum0x"><img src="https://avatars.githubusercontent.com/u/130079063?v=4&s=48" width="48" height="48" alt="caelum0x" title="caelum0x"/></a> <a href="https://github.com/championswimmer"><img src="https://avatars.githubusercontent.com/u/1327050?v=4&s=48" width="48" height="48" alt="championswimmer" title="championswimmer"/></a>
|
||||||
<a href="https://github.com/search?q=Miles"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Miles" title="Miles"/></a> <a href="https://github.com/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/search?q=Mustafa%20Tag%20Eldeen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mustafa Tag Eldeen" title="Mustafa Tag Eldeen"/></a> <a href="https://github.com/mylukin"><img src="https://avatars.githubusercontent.com/u/1021019?v=4&s=48" width="48" height="48" alt="mylukin" title="mylukin"/></a> <a href="https://github.com/nathanbosse"><img src="https://avatars.githubusercontent.com/u/4040669?v=4&s=48" width="48" height="48" alt="nathanbosse" title="nathanbosse"/></a> <a href="https://github.com/ndraiman"><img src="https://avatars.githubusercontent.com/u/12609607?v=4&s=48" width="48" height="48" alt="ndraiman" title="ndraiman"/></a> <a href="https://github.com/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></a> <a href="https://github.com/Noctivoro"><img src="https://avatars.githubusercontent.com/u/183974570?v=4&s=48" width="48" height="48" alt="Noctivoro" title="Noctivoro"/></a> <a href="https://github.com/ozgur-polat"><img src="https://avatars.githubusercontent.com/u/26483942?v=4&s=48" width="48" height="48" alt="ozgur-polat" title="ozgur-polat"/></a>
|
<a href="https://github.com/chenyuan99"><img src="https://avatars.githubusercontent.com/u/25518100?v=4&s=48" width="48" height="48" alt="chenyuan99" title="chenyuan99"/></a> <a href="https://github.com/Chloe-VP"><img src="https://avatars.githubusercontent.com/u/257371598?v=4&s=48" width="48" height="48" alt="Chloe-VP" title="Chloe-VP"/></a> <a href="https://github.com/search?q=Clawdbot%20Maintainers"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawdbot Maintainers" title="Clawdbot Maintainers"/></a> <a href="https://github.com/conhecendoia"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendoia" title="conhecendoia"/></a> <a href="https://github.com/dasilva333"><img src="https://avatars.githubusercontent.com/u/947827?v=4&s=48" width="48" height="48" alt="dasilva333" title="dasilva333"/></a> <a href="https://github.com/David-Marsh-Photo"><img src="https://avatars.githubusercontent.com/u/228404527?v=4&s=48" width="48" height="48" alt="David-Marsh-Photo" title="David-Marsh-Photo"/></a> <a href="https://github.com/deepsoumya617"><img src="https://avatars.githubusercontent.com/u/80877391?v=4&s=48" width="48" height="48" alt="deepsoumya617" title="deepsoumya617"/></a> <a href="https://github.com/search?q=Developer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Developer" title="Developer"/></a> <a href="https://github.com/search?q=Dimitrios%20Ploutarchos"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Dimitrios Ploutarchos" title="Dimitrios Ploutarchos"/></a> <a href="https://github.com/search?q=Drake%20Thomsen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Drake Thomsen" title="Drake Thomsen"/></a>
|
||||||
<a href="https://github.com/ppamment"><img src="https://avatars.githubusercontent.com/u/2122919?v=4&s=48" width="48" height="48" alt="ppamment" title="ppamment"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="prathamdby" title="prathamdby"/></a> <a href="https://github.com/ptn1411"><img src="https://avatars.githubusercontent.com/u/57529765?v=4&s=48" width="48" height="48" alt="ptn1411" title="ptn1411"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rony%20Kelner"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rony Kelner" title="Rony Kelner"/></a> <a href="https://github.com/ryancnelson"><img src="https://avatars.githubusercontent.com/u/347171?v=4&s=48" width="48" height="48" alt="ryancnelson" title="ryancnelson"/></a> <a href="https://github.com/search?q=Samrat%20Jha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Samrat Jha" title="Samrat Jha"/></a> <a href="https://github.com/senoldogann"><img src="https://avatars.githubusercontent.com/u/45736551?v=4&s=48" width="48" height="48" alt="senoldogann" title="senoldogann"/></a> <a href="https://github.com/Seredeep"><img src="https://avatars.githubusercontent.com/u/22802816?v=4&s=48" width="48" height="48" alt="Seredeep" title="Seredeep"/></a>
|
<a href="https://github.com/dvrshil"><img src="https://avatars.githubusercontent.com/u/81693876?v=4&s=48" width="48" height="48" alt="dvrshil" title="dvrshil"/></a> <a href="https://github.com/dxd5001"><img src="https://avatars.githubusercontent.com/u/1886046?v=4&s=48" width="48" height="48" alt="dxd5001" title="dxd5001"/></a> <a href="https://github.com/dylanneve1"><img src="https://avatars.githubusercontent.com/u/31746704?v=4&s=48" width="48" height="48" alt="dylanneve1" title="dylanneve1"/></a> <a href="https://github.com/search?q=Felix%20Krause"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Felix Krause" title="Felix Krause"/></a> <a href="https://github.com/foeken"><img src="https://avatars.githubusercontent.com/u/13864?v=4&s=48" width="48" height="48" alt="foeken" title="foeken"/></a> <a href="https://github.com/frankekn"><img src="https://avatars.githubusercontent.com/u/4488090?v=4&s=48" width="48" height="48" alt="frankekn" title="frankekn"/></a> <a href="https://github.com/fredheir"><img src="https://avatars.githubusercontent.com/u/3304869?v=4&s=48" width="48" height="48" alt="fredheir" title="fredheir"/></a> <a href="https://github.com/search?q=ganghyun%20kim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ganghyun kim" title="ganghyun kim"/></a> <a href="https://github.com/grrowl"><img src="https://avatars.githubusercontent.com/u/907140?v=4&s=48" width="48" height="48" alt="grrowl" title="grrowl"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a>
|
||||||
<a href="https://github.com/sergical"><img src="https://avatars.githubusercontent.com/u/3760543?v=4&s=48" width="48" height="48" alt="sergical" title="sergical"/></a> <a href="https://github.com/shiv19"><img src="https://avatars.githubusercontent.com/u/9407019?v=4&s=48" width="48" height="48" alt="shiv19" title="shiv19"/></a> <a href="https://github.com/shiyuanhai"><img src="https://avatars.githubusercontent.com/u/1187370?v=4&s=48" width="48" height="48" alt="shiyuanhai" title="shiyuanhai"/></a> <a href="https://github.com/siraht"><img src="https://avatars.githubusercontent.com/u/73152895?v=4&s=48" width="48" height="48" alt="siraht" title="siraht"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/search?q=techboss"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="techboss" title="techboss"/></a> <a href="https://github.com/testingabc321"><img src="https://avatars.githubusercontent.com/u/8577388?v=4&s=48" width="48" height="48" alt="testingabc321" title="testingabc321"/></a> <a href="https://github.com/search?q=The%20Admiral"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="The Admiral" title="The Admiral"/></a> <a href="https://github.com/thesash"><img src="https://avatars.githubusercontent.com/u/1166151?v=4&s=48" width="48" height="48" alt="thesash" title="thesash"/></a> <a href="https://github.com/search?q=Vibe%20Kanban"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vibe Kanban" title="Vibe Kanban"/></a>
|
<a href="https://github.com/HassanFleyah"><img src="https://avatars.githubusercontent.com/u/228002017?v=4&s=48" width="48" height="48" alt="HassanFleyah" title="HassanFleyah"/></a> <a href="https://github.com/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/iamEvanYT"><img src="https://avatars.githubusercontent.com/u/47493765?v=4&s=48" width="48" height="48" alt="iamEvanYT" title="iamEvanYT"/></a> <a href="https://github.com/ichbinlucaskim"><img src="https://avatars.githubusercontent.com/u/125564751?v=4&s=48" width="48" height="48" alt="ichbinlucaskim" title="ichbinlucaskim"/></a> <a href="https://github.com/search?q=Jamie%20Openshaw"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jamie Openshaw" title="Jamie Openshaw"/></a> <a href="https://github.com/search?q=Jane"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jane" title="Jane"/></a> <a href="https://github.com/search?q=Jarvis%20Deploy"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis Deploy" title="Jarvis Deploy"/></a> <a href="https://github.com/search?q=Jefferson%20Nunn"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jefferson Nunn" title="Jefferson Nunn"/></a>
|
||||||
<a href="https://github.com/voidserf"><img src="https://avatars.githubusercontent.com/u/477673?v=4&s=48" width="48" height="48" alt="voidserf" title="voidserf"/></a> <a href="https://github.com/search?q=Vultr-Clawd%20Admin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vultr-Clawd Admin" title="Vultr-Clawd Admin"/></a> <a href="https://github.com/search?q=Wimmie"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Wimmie" title="Wimmie"/></a> <a href="https://github.com/search?q=wolfred"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="wolfred" title="wolfred"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/YangHuang2280"><img src="https://avatars.githubusercontent.com/u/201681634?v=4&s=48" width="48" height="48" alt="YangHuang2280" title="YangHuang2280"/></a> <a href="https://github.com/yazinsai"><img src="https://avatars.githubusercontent.com/u/1846034?v=4&s=48" width="48" height="48" alt="yazinsai" title="yazinsai"/></a> <a href="https://github.com/yevhen"><img src="https://avatars.githubusercontent.com/u/107726?v=4&s=48" width="48" height="48" alt="yevhen" title="yevhen"/></a> <a href="https://github.com/YiWang24"><img src="https://avatars.githubusercontent.com/u/176262341?v=4&s=48" width="48" height="48" alt="YiWang24" title="YiWang24"/></a> <a href="https://github.com/search?q=ymat19"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ymat19" title="ymat19"/></a>
|
<a href="https://github.com/jogi47"><img src="https://avatars.githubusercontent.com/u/1710139?v=4&s=48" width="48" height="48" alt="jogi47" title="jogi47"/></a> <a href="https://github.com/kentaro"><img src="https://avatars.githubusercontent.com/u/3458?v=4&s=48" width="48" height="48" alt="kentaro" title="kentaro"/></a> <a href="https://github.com/search?q=Kevin%20Lin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kevin Lin" title="Kevin Lin"/></a> <a href="https://github.com/kira-ariaki"><img src="https://avatars.githubusercontent.com/u/257352493?v=4&s=48" width="48" height="48" alt="kira-ariaki" title="kira-ariaki"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/Kiwitwitter"><img src="https://avatars.githubusercontent.com/u/25277769?v=4&s=48" width="48" height="48" alt="Kiwitwitter" title="Kiwitwitter"/></a> <a href="https://github.com/levifig"><img src="https://avatars.githubusercontent.com/u/1605?v=4&s=48" width="48" height="48" alt="levifig" title="levifig"/></a> <a href="https://github.com/search?q=Lloyd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Lloyd" title="Lloyd"/></a> <a href="https://github.com/loganaden"><img src="https://avatars.githubusercontent.com/u/1688420?v=4&s=48" width="48" height="48" alt="loganaden" title="loganaden"/></a> <a href="https://github.com/longjos"><img src="https://avatars.githubusercontent.com/u/740160?v=4&s=48" width="48" height="48" alt="longjos" title="longjos"/></a>
|
||||||
<a href="https://github.com/search?q=Zach%20Knickerbocker"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Zach Knickerbocker" title="Zach Knickerbocker"/></a> <a href="https://github.com/zackerthescar"><img src="https://avatars.githubusercontent.com/u/38077284?v=4&s=48" width="48" height="48" alt="zackerthescar" title="zackerthescar"/></a> <a href="https://github.com/0xJonHoldsCrypto"><img src="https://avatars.githubusercontent.com/u/81202085?v=4&s=48" width="48" height="48" alt="0xJonHoldsCrypto" title="0xJonHoldsCrypto"/></a> <a href="https://github.com/aaronn"><img src="https://avatars.githubusercontent.com/u/1653630?v=4&s=48" width="48" height="48" alt="aaronn" title="aaronn"/></a> <a href="https://github.com/Alphonse-arianee"><img src="https://avatars.githubusercontent.com/u/254457365?v=4&s=48" width="48" height="48" alt="Alphonse-arianee" title="Alphonse-arianee"/></a> <a href="https://github.com/atalovesyou"><img src="https://avatars.githubusercontent.com/u/3534502?v=4&s=48" width="48" height="48" alt="atalovesyou" title="atalovesyou"/></a> <a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a> <a href="https://github.com/carlulsoe"><img src="https://avatars.githubusercontent.com/u/34673973?v=4&s=48" width="48" height="48" alt="carlulsoe" title="carlulsoe"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a> <a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a>
|
<a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/louzhixian"><img src="https://avatars.githubusercontent.com/u/7994361?v=4&s=48" width="48" height="48" alt="louzhixian" title="louzhixian"/></a> <a href="https://github.com/search?q=mac%20mimi"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="mac mimi" title="mac mimi"/></a> <a href="https://github.com/martinpucik"><img src="https://avatars.githubusercontent.com/u/5503097?v=4&s=48" width="48" height="48" alt="martinpucik" title="martinpucik"/></a> <a href="https://github.com/search?q=Matt%20mini"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Matt mini" title="Matt mini"/></a> <a href="https://github.com/mcaxtr"><img src="https://avatars.githubusercontent.com/u/7562095?v=4&s=48" width="48" height="48" alt="mcaxtr" title="mcaxtr"/></a> <a href="https://github.com/mertcicekci0"><img src="https://avatars.githubusercontent.com/u/179321902?v=4&s=48" width="48" height="48" alt="mertcicekci0" title="mertcicekci0"/></a> <a href="https://github.com/search?q=Miles"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Miles" title="Miles"/></a> <a href="https://github.com/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a>
|
||||||
<a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/odrobnik"><img src="https://avatars.githubusercontent.com/u/333270?v=4&s=48" width="48" height="48" alt="odrobnik" title="odrobnik"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/rhjoh"><img src="https://avatars.githubusercontent.com/u/105699450?v=4&s=48" width="48" height="48" alt="rhjoh" title="rhjoh"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></a> <a href="https://github.com/ronak-guliani"><img src="https://avatars.githubusercontent.com/u/23518228?v=4&s=48" width="48" height="48" alt="ronak-guliani" title="ronak-guliani"/></a>
|
<a href="https://github.com/search?q=Mustafa%20Tag%20Eldeen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mustafa Tag Eldeen" title="Mustafa Tag Eldeen"/></a> <a href="https://github.com/mylukin"><img src="https://avatars.githubusercontent.com/u/1021019?v=4&s=48" width="48" height="48" alt="mylukin" title="mylukin"/></a> <a href="https://github.com/nathanbosse"><img src="https://avatars.githubusercontent.com/u/4040669?v=4&s=48" width="48" height="48" alt="nathanbosse" title="nathanbosse"/></a> <a href="https://github.com/ndraiman"><img src="https://avatars.githubusercontent.com/u/12609607?v=4&s=48" width="48" height="48" alt="ndraiman" title="ndraiman"/></a> <a href="https://github.com/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></a> <a href="https://github.com/Noctivoro"><img src="https://avatars.githubusercontent.com/u/183974570?v=4&s=48" width="48" height="48" alt="Noctivoro" title="Noctivoro"/></a> <a href="https://github.com/Omar-Khaleel"><img src="https://avatars.githubusercontent.com/u/240748662?v=4&s=48" width="48" height="48" alt="Omar-Khaleel" title="Omar-Khaleel"/></a> <a href="https://github.com/ozgur-polat"><img src="https://avatars.githubusercontent.com/u/26483942?v=4&s=48" width="48" height="48" alt="ozgur-polat" title="ozgur-polat"/></a> <a href="https://github.com/ppamment"><img src="https://avatars.githubusercontent.com/u/2122919?v=4&s=48" width="48" height="48" alt="ppamment" title="ppamment"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="prathamdby" title="prathamdby"/></a>
|
||||||
<a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a>
|
<a href="https://github.com/ptn1411"><img src="https://avatars.githubusercontent.com/u/57529765?v=4&s=48" width="48" height="48" alt="ptn1411" title="ptn1411"/></a> <a href="https://github.com/rafelbev"><img src="https://avatars.githubusercontent.com/u/467120?v=4&s=48" width="48" height="48" alt="rafelbev" title="rafelbev"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rony%20Kelner"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rony Kelner" title="Rony Kelner"/></a> <a href="https://github.com/ryancnelson"><img src="https://avatars.githubusercontent.com/u/347171?v=4&s=48" width="48" height="48" alt="ryancnelson" title="ryancnelson"/></a> <a href="https://github.com/search?q=Samrat%20Jha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Samrat Jha" title="Samrat Jha"/></a> <a href="https://github.com/senoldogann"><img src="https://avatars.githubusercontent.com/u/45736551?v=4&s=48" width="48" height="48" alt="senoldogann" title="senoldogann"/></a> <a href="https://github.com/Seredeep"><img src="https://avatars.githubusercontent.com/u/22802816?v=4&s=48" width="48" height="48" alt="Seredeep" title="Seredeep"/></a> <a href="https://github.com/sergical"><img src="https://avatars.githubusercontent.com/u/3760543?v=4&s=48" width="48" height="48" alt="sergical" title="sergical"/></a>
|
||||||
|
<a href="https://github.com/shiv19"><img src="https://avatars.githubusercontent.com/u/9407019?v=4&s=48" width="48" height="48" alt="shiv19" title="shiv19"/></a> <a href="https://github.com/shiyuanhai"><img src="https://avatars.githubusercontent.com/u/1187370?v=4&s=48" width="48" height="48" alt="shiyuanhai" title="shiyuanhai"/></a> <a href="https://github.com/Shrinija17"><img src="https://avatars.githubusercontent.com/u/199155426?v=4&s=48" width="48" height="48" alt="Shrinija17" title="Shrinija17"/></a> <a href="https://github.com/siraht"><img src="https://avatars.githubusercontent.com/u/73152895?v=4&s=48" width="48" height="48" alt="siraht" title="siraht"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/stephenchen2025"><img src="https://avatars.githubusercontent.com/u/218387130?v=4&s=48" width="48" height="48" alt="stephenchen2025" title="stephenchen2025"/></a> <a href="https://github.com/search?q=techboss"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="techboss" title="techboss"/></a> <a href="https://github.com/testingabc321"><img src="https://avatars.githubusercontent.com/u/8577388?v=4&s=48" width="48" height="48" alt="testingabc321" title="testingabc321"/></a> <a href="https://github.com/search?q=The%20Admiral"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="The Admiral" title="The Admiral"/></a> <a href="https://github.com/thesash"><img src="https://avatars.githubusercontent.com/u/1166151?v=4&s=48" width="48" height="48" alt="thesash" title="thesash"/></a>
|
||||||
|
<a href="https://github.com/search?q=Vibe%20Kanban"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vibe Kanban" title="Vibe Kanban"/></a> <a href="https://github.com/vincentkoc"><img src="https://avatars.githubusercontent.com/u/25068?v=4&s=48" width="48" height="48" alt="vincentkoc" title="vincentkoc"/></a> <a href="https://github.com/voidserf"><img src="https://avatars.githubusercontent.com/u/477673?v=4&s=48" width="48" height="48" alt="voidserf" title="voidserf"/></a> <a href="https://github.com/search?q=Vultr-Clawd%20Admin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vultr-Clawd Admin" title="Vultr-Clawd Admin"/></a> <a href="https://github.com/search?q=Wimmie"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Wimmie" title="Wimmie"/></a> <a href="https://github.com/search?q=wolfred"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="wolfred" title="wolfred"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/wytheme"><img src="https://avatars.githubusercontent.com/u/5009358?v=4&s=48" width="48" height="48" alt="wytheme" title="wytheme"/></a> <a href="https://github.com/YangHuang2280"><img src="https://avatars.githubusercontent.com/u/201681634?v=4&s=48" width="48" height="48" alt="YangHuang2280" title="YangHuang2280"/></a> <a href="https://github.com/yazinsai"><img src="https://avatars.githubusercontent.com/u/1846034?v=4&s=48" width="48" height="48" alt="yazinsai" title="yazinsai"/></a>
|
||||||
|
<a href="https://github.com/yevhen"><img src="https://avatars.githubusercontent.com/u/107726?v=4&s=48" width="48" height="48" alt="yevhen" title="yevhen"/></a> <a href="https://github.com/YiWang24"><img src="https://avatars.githubusercontent.com/u/176262341?v=4&s=48" width="48" height="48" alt="YiWang24" title="YiWang24"/></a> <a href="https://github.com/search?q=ymat19"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ymat19" title="ymat19"/></a> <a href="https://github.com/search?q=Zach%20Knickerbocker"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Zach Knickerbocker" title="Zach Knickerbocker"/></a> <a href="https://github.com/zackerthescar"><img src="https://avatars.githubusercontent.com/u/38077284?v=4&s=48" width="48" height="48" alt="zackerthescar" title="zackerthescar"/></a> <a href="https://github.com/0xJonHoldsCrypto"><img src="https://avatars.githubusercontent.com/u/81202085?v=4&s=48" width="48" height="48" alt="0xJonHoldsCrypto" title="0xJonHoldsCrypto"/></a> <a href="https://github.com/aaronn"><img src="https://avatars.githubusercontent.com/u/1653630?v=4&s=48" width="48" height="48" alt="aaronn" title="aaronn"/></a> <a href="https://github.com/Alphonse-arianee"><img src="https://avatars.githubusercontent.com/u/254457365?v=4&s=48" width="48" height="48" alt="Alphonse-arianee" title="Alphonse-arianee"/></a> <a href="https://github.com/atalovesyou"><img src="https://avatars.githubusercontent.com/u/3534502?v=4&s=48" width="48" height="48" alt="atalovesyou" title="atalovesyou"/></a> <a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a>
|
||||||
|
<a href="https://github.com/carlulsoe"><img src="https://avatars.githubusercontent.com/u/34673973?v=4&s=48" width="48" height="48" alt="carlulsoe" title="carlulsoe"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a> <a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a> <a href="https://github.com/jiulingyun"><img src="https://avatars.githubusercontent.com/u/126459548?v=4&s=48" width="48" height="48" alt="jiulingyun" title="jiulingyun"/></a> <a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/odrobnik"><img src="https://avatars.githubusercontent.com/u/333270?v=4&s=48" width="48" height="48" alt="odrobnik" title="odrobnik"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a>
|
||||||
|
<a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/rhjoh"><img src="https://avatars.githubusercontent.com/u/105699450?v=4&s=48" width="48" height="48" alt="rhjoh" title="rhjoh"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></a> <a href="https://github.com/ronak-guliani"><img src="https://avatars.githubusercontent.com/u/23518228?v=4&s=48" width="48" height="48" alt="ronak-guliani" title="ronak-guliani"/></a> <a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
271
appcast.xml
271
appcast.xml
@@ -2,6 +2,100 @@
|
|||||||
<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>OpenClaw</title>
|
<title>OpenClaw</title>
|
||||||
|
<item>
|
||||||
|
<title>2026.2.3</title>
|
||||||
|
<pubDate>Wed, 04 Feb 2026 17:47:10 -0800</pubDate>
|
||||||
|
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||||
|
<sparkle:version>8900</sparkle:version>
|
||||||
|
<sparkle:shortVersionString>2026.2.3</sparkle:shortVersionString>
|
||||||
|
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||||
|
<description><![CDATA[<h2>OpenClaw 2026.2.3</h2>
|
||||||
|
<h3>Changes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Telegram: remove last <code>@ts-nocheck</code> from <code>bot-handlers.ts</code>, use Grammy types directly, deduplicate <code>StickerMetadata</code>. Zero <code>@ts-nocheck</code> remaining in <code>src/telegram/</code>. (#9206)</li>
|
||||||
|
<li>Telegram: remove <code>@ts-nocheck</code> from <code>bot-message.ts</code>, type deps via <code>Omit<BuildTelegramMessageContextParams></code>, widen <code>allMedia</code> to <code>TelegramMediaRef[]</code>. (#9180)</li>
|
||||||
|
<li>Telegram: remove <code>@ts-nocheck</code> from <code>bot.ts</code>, fix duplicate <code>bot.catch</code> error handler (Grammy overrides), remove dead reaction <code>message_thread_id</code> routing, harden sticker cache guard. (#9077)</li>
|
||||||
|
<li>Onboarding: add Cloudflare AI Gateway provider setup and docs. (#7914) Thanks @roerohan.</li>
|
||||||
|
<li>Onboarding: add Moonshot (.cn) auth choice and keep the China base URL when preserving defaults. (#7180) Thanks @waynelwz.</li>
|
||||||
|
<li>Docs: clarify tmux send-keys for TUI by splitting text and Enter. (#7737) Thanks @Wangnov.</li>
|
||||||
|
<li>Docs: mirror the landing page revamp for zh-CN (features, quickstart, docs directory, network model, credits). (#8994) Thanks @joshp123.</li>
|
||||||
|
<li>Messages: add per-channel and per-account responsePrefix overrides across channels. (#9001) Thanks @mudrii.</li>
|
||||||
|
<li>Cron: add announce delivery mode for isolated jobs (CLI + Control UI) and delivery mode config.</li>
|
||||||
|
<li>Cron: default isolated jobs to announce delivery; accept ISO 8601 <code>schedule.at</code> in tool inputs.</li>
|
||||||
|
<li>Cron: hard-migrate isolated jobs to announce/none delivery; drop legacy post-to-main/payload delivery fields and <code>atMs</code> inputs.</li>
|
||||||
|
<li>Cron: delete one-shot jobs after success by default; add <code>--keep-after-run</code> for CLI.</li>
|
||||||
|
<li>Cron: suppress messaging tools during announce delivery so summaries post consistently.</li>
|
||||||
|
<li>Cron: avoid duplicate deliveries when isolated runs send messages directly.</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Fixes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Heartbeat: allow explicit accountId routing for multi-account channels. (#8702) Thanks @lsh411.</li>
|
||||||
|
<li>TUI/Gateway: handle non-streaming finals, refresh history for non-local chat runs, and avoid event gap warnings for targeted tool streams. (#8432) Thanks @gumadeiras.</li>
|
||||||
|
<li>Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard.</li>
|
||||||
|
<li>Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo.</li>
|
||||||
|
<li>Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman.</li>
|
||||||
|
<li>Web UI: resolve header logo path when <code>gateway.controlUi.basePath</code> is set. (#7178) Thanks @Yeom-JinHo.</li>
|
||||||
|
<li>Web UI: apply button styling to the new-messages indicator.</li>
|
||||||
|
<li>Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.</li>
|
||||||
|
<li>Security: enforce sandboxed media paths for message tool attachments. (#9182) Thanks @victormier.</li>
|
||||||
|
<li>Security: require explicit credentials for gateway URL overrides to prevent credential leakage. (#8113) Thanks @victormier.</li>
|
||||||
|
<li>Security: gate <code>whatsapp_login</code> tool to owner senders and default-deny non-owner contexts. (#8768) Thanks @victormier.</li>
|
||||||
|
<li>Voice call: harden webhook verification with host allowlists/proxy trust and keep ngrok loopback bypass.</li>
|
||||||
|
<li>Voice call: add regression coverage for anonymous inbound caller IDs with allowlist policy. (#8104) Thanks @victormier.</li>
|
||||||
|
<li>Cron: accept epoch timestamps and 0ms durations in CLI <code>--at</code> parsing.</li>
|
||||||
|
<li>Cron: reload store data when the store file is recreated or mtime changes.</li>
|
||||||
|
<li>Cron: deliver announce runs directly, honor delivery mode, and respect wakeMode for summaries. (#8540) Thanks @tyler6204.</li>
|
||||||
|
<li>Telegram: include forward_from_chat metadata in forwarded messages and harden cron delivery target checks. (#8392) Thanks @Glucksberg.</li>
|
||||||
|
<li>macOS: fix cron payload summary rendering and ISO 8601 formatter concurrency safety.</li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||||
|
]]></description>
|
||||||
|
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.2.3/OpenClaw-2026.2.3.zip" length="22530161" type="application/octet-stream" sparkle:edSignature="7eHUaQC6cx87HWbcaPh9T437+LqfE9VtQBf4p9JBjIyBrqGYxxp9KPvI5unEjg55j9j2djCXhseSMeyyRmvYBg=="/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>2026.2.2</title>
|
||||||
|
<pubDate>Tue, 03 Feb 2026 17:04:17 -0800</pubDate>
|
||||||
|
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||||
|
<sparkle:version>8809</sparkle:version>
|
||||||
|
<sparkle:shortVersionString>2026.2.2</sparkle:shortVersionString>
|
||||||
|
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||||
|
<description><![CDATA[<h2>OpenClaw 2026.2.2</h2>
|
||||||
|
<h3>Changes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Feishu: add Feishu/Lark plugin support + docs. (#7313) Thanks @jiulingyun (openclaw-cn).</li>
|
||||||
|
<li>Web UI: add Agents dashboard for managing agent files, tools, skills, models, channels, and cron jobs.</li>
|
||||||
|
<li>Memory: implement the opt-in QMD backend for workspace memory. (#3160) Thanks @vignesh07.</li>
|
||||||
|
<li>Security: add healthcheck skill and bootstrap audit guidance. (#7641) Thanks @Takhoffman.</li>
|
||||||
|
<li>Config: allow setting a default subagent thinking level via <code>agents.defaults.subagents.thinking</code> (and per-agent <code>agents.list[].subagents.thinking</code>). (#7372) Thanks @tyler6204.</li>
|
||||||
|
<li>Docs: zh-CN translations seed + polish, pipeline guidance, nav/landing updates, and typo fixes. (#8202, #6995, #6619, #7242, #7303, #7415) Thanks @AaronWander, @taiyi747, @Explorer1092, @rendaoyuan, @joshp123, @lailoo.</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Fixes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Security: require operator.approvals for gateway /approve commands. (#1) Thanks @mitsuhiko, @yueyueL.</li>
|
||||||
|
<li>Security: Matrix allowlists now require full MXIDs; ambiguous name resolution no longer grants access. Thanks @MegaManSec.</li>
|
||||||
|
<li>Security: enforce access-group gating for Slack slash commands when channel type lookup fails.</li>
|
||||||
|
<li>Security: require validated shared-secret auth before skipping device identity on gateway connect.</li>
|
||||||
|
<li>Security: guard skill installer downloads with SSRF checks (block private/localhost URLs).</li>
|
||||||
|
<li>Security: harden Windows exec allowlist; block cmd.exe bypass via single &. Thanks @simecek.</li>
|
||||||
|
<li>fix(voice-call): harden inbound allowlist; reject anonymous callers; require Telnyx publicKey for allowlist; token-gate Twilio media streams; cap webhook body size (thanks @simecek)</li>
|
||||||
|
<li>Media understanding: apply SSRF guardrails to provider fetches; allow private baseUrl overrides explicitly.</li>
|
||||||
|
<li>fix(webchat): respect user scroll position during streaming and refresh (#7226) (thanks @marcomarandiz)</li>
|
||||||
|
<li>Telegram: recover from grammY long-poll timed out errors. (#7466) Thanks @macmimi23.</li>
|
||||||
|
<li>Agents: repair malformed tool calls and session transcripts. (#7473) Thanks @justinhuangcode.</li>
|
||||||
|
<li>fix(agents): validate AbortSignal instances before calling AbortSignal.any() (#7277) (thanks @Elarwei001)</li>
|
||||||
|
<li>Media understanding: skip binary media from file text extraction. (#7475) Thanks @AlexZhangji.</li>
|
||||||
|
<li>Onboarding: keep TUI flow exclusive (skip completion prompt + background Web UI seed); completion prompt now handled by install/update.</li>
|
||||||
|
<li>TUI: block onboarding output while TUI is active and restore terminal state on exit.</li>
|
||||||
|
<li>CLI/Zsh completion: cache scripts in state dir and escape option descriptions to avoid invalid option errors.</li>
|
||||||
|
<li>fix(ui): resolve Control UI asset path correctly.</li>
|
||||||
|
<li>fix(ui): refresh agent files after external edits.</li>
|
||||||
|
<li>Docs: finish renaming the QMD memory docs to reference the OpenClaw state dir.</li>
|
||||||
|
<li>Tests: stub SSRF DNS pinning in web auto-reply + Gemini video coverage. (#6619) Thanks @joshp123.</li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||||
|
]]></description>
|
||||||
|
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.2.2/OpenClaw-2026.2.2.zip" length="22519052" type="application/octet-stream" sparkle:edSignature="a6viD+aS5EfY/RkPIPMfoQQNkJCk6QTdV5WobXFxyYwURskUm8/nXTHVXsCh1c5+0WKUnmlDIyf0i+6IWiavAA=="/>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>2026.2.1</title>
|
<title>2026.2.1</title>
|
||||||
<pubDate>Mon, 02 Feb 2026 03:53:03 -0800</pubDate>
|
<pubDate>Mon, 02 Feb 2026 03:53:03 -0800</pubDate>
|
||||||
@@ -68,182 +162,5 @@
|
|||||||
]]></description>
|
]]></description>
|
||||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.2.1/OpenClaw-2026.2.1.zip" length="22458919" type="application/octet-stream" sparkle:edSignature="kA/8VQlVdtYphcB1iuFrhWczwWKgkVZMfDfQ7T9WD405D8JKTv5CZ1n8lstIVkpk4xog3UhrfaaoTG8Bf8DMAQ=="/>
|
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.2.1/OpenClaw-2026.2.1.zip" length="22458919" type="application/octet-stream" sparkle:edSignature="kA/8VQlVdtYphcB1iuFrhWczwWKgkVZMfDfQ7T9WD405D8JKTv5CZ1n8lstIVkpk4xog3UhrfaaoTG8Bf8DMAQ=="/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<title>2026.1.30</title>
|
|
||||||
<pubDate>Sat, 31 Jan 2026 14:29:57 +0100</pubDate>
|
|
||||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
|
||||||
<sparkle:version>8469</sparkle:version>
|
|
||||||
<sparkle:shortVersionString>2026.1.30</sparkle:shortVersionString>
|
|
||||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
|
||||||
<description><![CDATA[<h2>OpenClaw 2026.1.30</h2>
|
|
||||||
<h3>Changes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>CLI: add <code>completion</code> command (Zsh/Bash/PowerShell/Fish) and auto-setup during postinstall/onboarding.</li>
|
|
||||||
<li>CLI: add per-agent <code>models status</code> (<code>--agent</code> filter). (#4780) Thanks @jlowin.</li>
|
|
||||||
<li>Agents: add Kimi K2.5 to the synthetic model catalog. (#4407) Thanks @manikv12.</li>
|
|
||||||
<li>Auth: switch Kimi Coding to built-in provider; normalize OAuth profile email.</li>
|
|
||||||
<li>Auth: add MiniMax OAuth plugin + onboarding option. (#4521) Thanks @Maosghoul.</li>
|
|
||||||
<li>Agents: update pi SDK/API usage and dependencies.</li>
|
|
||||||
<li>Web UI: refresh sessions after chat commands and improve session display names.</li>
|
|
||||||
<li>Build: move TypeScript builds to <code>tsdown</code> + <code>tsgo</code> (faster builds, CI typechecks), update tsconfig target, and clean up lint rules.</li>
|
|
||||||
<li>Build: align npm tar override and bin metadata so the <code>openclaw</code> CLI entrypoint is preserved in npm publishes.</li>
|
|
||||||
<li>Docs: add pi/pi-dev docs and update OpenClaw branding + install links.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Fixes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Security: restrict local path extraction in media parser to prevent LFI. (#4880)</li>
|
|
||||||
<li>Gateway: prevent token defaults from becoming the literal "undefined". (#4873) Thanks @Hisleren.</li>
|
|
||||||
<li>Control UI: fix assets resolution for npm global installs. (#4909) Thanks @YuriNachos.</li>
|
|
||||||
<li>macOS: avoid stderr pipe backpressure in gateway discovery. (#3304) Thanks @abhijeet117.</li>
|
|
||||||
<li>Telegram: normalize account token lookup for non-normalized IDs. (#5055) Thanks @jasonsschin.</li>
|
|
||||||
<li>Telegram: preserve delivery thread fallback and fix threadId handling in delivery context.</li>
|
|
||||||
<li>Telegram: fix HTML nesting for overlapping styles/links. (#4578) Thanks @ThanhNguyxn.</li>
|
|
||||||
<li>Telegram: accept numeric messageId/chatId in react actions. (#4533) Thanks @Ayush10.</li>
|
|
||||||
<li>Telegram: honor per-account proxy dispatcher via undici fetch. (#4456) Thanks @spiceoogway.</li>
|
|
||||||
<li>Telegram: scope skill commands to bound agent per bot. (#4360) Thanks @robhparker.</li>
|
|
||||||
<li>BlueBubbles: debounce by messageId to preserve attachments in text+image messages. (#4984)</li>
|
|
||||||
<li>Routing: prefer requesterOrigin over stale session entries for sub-agent announce delivery. (#4957)</li>
|
|
||||||
<li>Extensions: restore embedded extension discovery typings.</li>
|
|
||||||
<li>CLI: fix <code>tui:dev</code> port resolution.</li>
|
|
||||||
<li>LINE: fix status command TypeError. (#4651)</li>
|
|
||||||
<li>OAuth: skip expired-token warnings when refresh tokens are still valid. (#4593)</li>
|
|
||||||
<li>Build: skip redundant UI install step in Dockerfile. (#4584) Thanks @obviyus.</li>
|
|
||||||
</ul>
|
|
||||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
|
||||||
]]></description>
|
|
||||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.1.30/OpenClaw-2026.1.30.zip" length="22458594" type="application/octet-stream" sparkle:edSignature="77/GuEcruKGgu2CJyMq+OVwzaJ2v1VzRQC9NmOirKO3uH5Nn5HaoouwrOHnOanrzlD4OvPW0FS5GH2E4Ntu4CQ=="/>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>2026.1.29</title>
|
|
||||||
<pubDate>Fri, 30 Jan 2026 06:24:15 +0100</pubDate>
|
|
||||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
|
||||||
<sparkle:version>8345</sparkle:version>
|
|
||||||
<sparkle:shortVersionString>2026.1.29</sparkle:shortVersionString>
|
|
||||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
|
||||||
<description><![CDATA[<h2>OpenClaw 2026.1.29</h2>
|
|
||||||
Status: stable.
|
|
||||||
<h3>Changes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Rebrand: rename the npm package/CLI to <code>openclaw</code>, add a <code>openclaw</code> compatibility shim, and move extensions to the <code>@openclaw/*</code> scope.</li>
|
|
||||||
<li>Onboarding: strengthen security warning copy for beta + access control expectations.</li>
|
|
||||||
<li>Onboarding: add Venice API key to non-interactive flow. (#1893) Thanks @jonisjongithub.</li>
|
|
||||||
<li>Config: auto-migrate legacy state/config paths and keep config resolution consistent across legacy filenames.</li>
|
|
||||||
<li>Gateway: warn on hook tokens via query params; document header auth preference. (#2200) Thanks @YuriNachos.</li>
|
|
||||||
<li>Gateway: add dangerous Control UI device auth bypass flag + audit warnings. (#2248)</li>
|
|
||||||
<li>Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz.</li>
|
|
||||||
<li>Web UI: keep sub-agent announce replies visible in WebChat. (#1977) Thanks @andrescardonas7.</li>
|
|
||||||
<li>Browser: route browser control via gateway/node; remove standalone browser control command and control URL config.</li>
|
|
||||||
<li>Browser: route <code>browser.request</code> via node proxies when available; honor proxy timeouts; derive browser ports from <code>gateway.port</code>.</li>
|
|
||||||
<li>Browser: fall back to URL matching for extension relay target resolution. (#1999) Thanks @jonit-dev.</li>
|
|
||||||
<li>Telegram: allow caption param for media sends. (#1888) Thanks @mguellsegarra.</li>
|
|
||||||
<li>Telegram: support plugin sendPayload channelData (media/buttons) and validate plugin commands. (#1917) Thanks @JoshuaLelon.</li>
|
|
||||||
<li>Telegram: avoid block replies when streaming is disabled. (#1885) Thanks @ivancasco.</li>
|
|
||||||
<li>Telegram: add optional silent send flag (disable notifications). (#2382) Thanks @Suksham-sharma.</li>
|
|
||||||
<li>Telegram: support editing sent messages via message(action="edit"). (#2394) Thanks @marcelomar21.</li>
|
|
||||||
<li>Telegram: support quote replies for message tool and inbound context. (#2900) Thanks @aduk059.</li>
|
|
||||||
<li>Telegram: add sticker receive/send with vision caching. (#2629) Thanks @longjos.</li>
|
|
||||||
<li>Telegram: send sticker pixels to vision models. (#2650)</li>
|
|
||||||
<li>Telegram: keep topic IDs in restart sentinel notifications. (#1807) Thanks @hsrvc.</li>
|
|
||||||
<li>Discord: add configurable privileged gateway intents for presences/members. (#2266) Thanks @kentaro.</li>
|
|
||||||
<li>Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999.</li>
|
|
||||||
<li>Matrix: switch plugin SDK to @vector-im/matrix-bot-sdk.</li>
|
|
||||||
<li>Tlon: format thread reply IDs as @ud. (#1837) Thanks @wca4a.</li>
|
|
||||||
<li>Tools: add per-sender group tool policies and fix precedence. (#1757) Thanks @adam91holt.</li>
|
|
||||||
<li>Agents: summarize dropped messages during compaction safeguard pruning. (#2509) Thanks @jogi47.</li>
|
|
||||||
<li>Agents: expand cron tool description with full schema docs. (#1988) Thanks @tomascupr.</li>
|
|
||||||
<li>Agents: honor tools.exec.safeBins in exec allowlist checks. (#2281)</li>
|
|
||||||
<li>Memory Search: allow extra paths for memory indexing (ignores symlinks). (#3600) Thanks @kira-ariaki.</li>
|
|
||||||
<li>Skills: add multi-image input support to Nano Banana Pro skill. (#1958) Thanks @tyler6204.</li>
|
|
||||||
<li>Skills: add missing dependency metadata for GitHub, Notion, Slack, Discord. (#1995) Thanks @jackheuberger.</li>
|
|
||||||
<li>Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev.</li>
|
|
||||||
<li>Routing: add per-account DM session scope and document multi-account isolation. (#3095) Thanks @jarvis-sam.</li>
|
|
||||||
<li>Routing: precompile session key regexes. (#1697) Thanks @Ray0907.</li>
|
|
||||||
<li>CLI: use Node's module compile cache for faster startup. (#2808) Thanks @pi0.</li>
|
|
||||||
<li>Auth: show copyable Google auth URL after ASCII prompt. (#1787) Thanks @robbyczgw-cla.</li>
|
|
||||||
<li>TUI: avoid width overflow when rendering selection lists. (#1686) Thanks @mossein.</li>
|
|
||||||
<li>macOS: finish OpenClaw app rename for macOS sources, bundle identifiers, and shared kit paths. (#2844) Thanks @fal3.</li>
|
|
||||||
<li>Branding: update launchd labels, mobile bundle IDs, and logging subsystems to bot.molt (legacy bundle ID migrations). Thanks @thewilloftheshadow.</li>
|
|
||||||
<li>macOS: limit project-local <code>node_modules/.bin</code> PATH preference to debug builds (reduce PATH hijacking risk).</li>
|
|
||||||
<li>macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal.</li>
|
|
||||||
<li>macOS: avoid crash when rendering code blocks by bumping Textual to 0.3.1. (#2033) Thanks @garricn.</li>
|
|
||||||
<li>Update: ignore dist/control-ui for dirty checks and restore after ui builds. (#1976) Thanks @Glucksberg.</li>
|
|
||||||
<li>Build: bundle A2UI assets during build and stop tracking generated bundles. (#2455) Thanks @0oAstro.</li>
|
|
||||||
<li>CI: increase Node heap size for macOS checks. (#1890) Thanks @realZachi.</li>
|
|
||||||
<li>Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918.</li>
|
|
||||||
<li>Gateway: prefer newest session metadata when combining stores. (#1823) Thanks @emanuelst.</li>
|
|
||||||
<li>Docs: tighten Fly private deployment steps. (#2289) Thanks @dguido.</li>
|
|
||||||
<li>Docs: add migration guide for moving to a new machine. (#2381)</li>
|
|
||||||
<li>Docs: add Northflank one-click deployment guide. (#2167) Thanks @AdeboyeDN.</li>
|
|
||||||
<li>Docs: add Vercel AI Gateway to providers sidebar. (#1901) Thanks @jerilynzheng.</li>
|
|
||||||
<li>Docs: add Render deployment guide. (#1975) Thanks @anurag.</li>
|
|
||||||
<li>Docs: add Claude Max API Proxy guide. (#1875) Thanks @atalovesyou.</li>
|
|
||||||
<li>Docs: add DigitalOcean deployment guide. (#1870) Thanks @0xJonHoldsCrypto.</li>
|
|
||||||
<li>Docs: add Oracle Cloud (OCI) platform guide + cross-links. (#2333) Thanks @hirefrank.</li>
|
|
||||||
<li>Docs: add Raspberry Pi install guide. (#1871) Thanks @0xJonHoldsCrypto.</li>
|
|
||||||
<li>Docs: add GCP Compute Engine deployment guide. (#1848) Thanks @hougangdev.</li>
|
|
||||||
<li>Docs: add LINE channel guide. Thanks @thewilloftheshadow.</li>
|
|
||||||
<li>Docs: credit both contributors for Control UI refresh. (#1852) Thanks @EnzeD.</li>
|
|
||||||
<li>Docs: keep docs header sticky so navbar stays visible while scrolling. (#2445) Thanks @chenyuan99.</li>
|
|
||||||
<li>Docs: update exe.dev install instructions. (#https://github.com/openclaw/openclaw/pull/3047) Thanks @zackerthescar.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Breaking</h3>
|
|
||||||
<ul>
|
|
||||||
<li><strong>BREAKING:</strong> Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Fixes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796)</li>
|
|
||||||
<li>Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R.</li>
|
|
||||||
<li>Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald.</li>
|
|
||||||
<li>Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald.</li>
|
|
||||||
<li>Agents: prevent retries on oversized image errors and surface size limits. (#2871) Thanks @Suksham-sharma.</li>
|
|
||||||
<li>Agents: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94.</li>
|
|
||||||
<li>Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355.</li>
|
|
||||||
<li>Telegram: include AccountId in native command context for multi-agent routing. (#2942) Thanks @Chloe-VP.</li>
|
|
||||||
<li>Telegram: handle video note attachments in media extraction. (#2905) Thanks @mylukin.</li>
|
|
||||||
<li>TTS: read OPENAI_TTS_BASE_URL at runtime instead of module load to honor config.env. (#3341) Thanks @hclsys.</li>
|
|
||||||
<li>macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.</li>
|
|
||||||
<li>Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.</li>
|
|
||||||
<li>Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.</li>
|
|
||||||
<li>Agents: guard channel tool listActions to avoid plugin crashes. (#2859) Thanks @mbelinky.</li>
|
|
||||||
<li>Discord: stop resolveDiscordTarget from passing directory params into messaging target parsers. Fixes #3167. Thanks @thewilloftheshadow.</li>
|
|
||||||
<li>Discord: avoid resolving bare channel names to user DMs when a username matches. Thanks @thewilloftheshadow.</li>
|
|
||||||
<li>Discord: fix directory config type import for target resolution. Thanks @thewilloftheshadow.</li>
|
|
||||||
<li>Providers: update MiniMax API endpoint and compatibility mode. (#3064) Thanks @hlbbbbbbb.</li>
|
|
||||||
<li>Telegram: treat more network errors as recoverable in polling. (#3013) Thanks @ryancontent.</li>
|
|
||||||
<li>Discord: resolve usernames to user IDs for outbound messages. (#2649) Thanks @nonggialiang.</li>
|
|
||||||
<li>Providers: update Moonshot Kimi model references to kimi-k2.5. (#2762) Thanks @MarvinCui.</li>
|
|
||||||
<li>Gateway: suppress AbortError and transient network errors in unhandled rejections. (#2451) Thanks @Glucksberg.</li>
|
|
||||||
<li>TTS: keep /tts status replies on text-only commands and avoid duplicate block-stream audio. (#2451) Thanks @Glucksberg.</li>
|
|
||||||
<li>Security: pin npm overrides to keep tar@7.5.4 for install toolchains.</li>
|
|
||||||
<li>Security: properly test Windows ACL audit for config includes. (#2403) Thanks @dominicnunez.</li>
|
|
||||||
<li>CLI: recognize versioned Node executables when parsing argv. (#2490) Thanks @David-Marsh-Photo.</li>
|
|
||||||
<li>CLI: avoid prompting for gateway runtime under the spinner. (#2874)</li>
|
|
||||||
<li>BlueBubbles: coalesce inbound URL link preview messages. (#1981) Thanks @tyler6204.</li>
|
|
||||||
<li>Cron: allow payloads containing "heartbeat" in event filter. (#2219) Thanks @dwfinkelstein.</li>
|
|
||||||
<li>CLI: avoid loading config for global help/version while registering plugin commands. (#2212) Thanks @dial481.</li>
|
|
||||||
<li>Agents: include memory.md when bootstrapping memory context. (#2318) Thanks @czekaj.</li>
|
|
||||||
<li>Agents: release session locks on process termination and cover more signals. (#2483) Thanks @janeexai.</li>
|
|
||||||
<li>Agents: skip cooldowned providers during model failover. (#2143) Thanks @YiWang24.</li>
|
|
||||||
<li>Telegram: harden polling + retry behavior for transient network errors and Node 22 transport issues. (#2420) Thanks @techboss.</li>
|
|
||||||
<li>Telegram: ignore non-forum group message_thread_id while preserving DM thread sessions. (#2731) Thanks @dylanneve1.</li>
|
|
||||||
<li>Telegram: wrap reasoning italics per line to avoid raw underscores. (#2181) Thanks @YuriNachos.</li>
|
|
||||||
<li>Telegram: centralize API error logging for delivery and bot calls. (#2492) Thanks @altryne.</li>
|
|
||||||
<li>Voice Call: enforce Twilio webhook signature verification for ngrok URLs; disable ngrok free tier bypass by default.</li>
|
|
||||||
<li>Security: harden Tailscale Serve auth by validating identity via local tailscaled before trusting headers.</li>
|
|
||||||
<li>Media: fix text attachment MIME misclassification with CSV/TSV inference and UTF-16 detection; add XML attribute escaping for file output. (#3628) Thanks @frankekn.</li>
|
|
||||||
<li>Build: align memory-core peer dependency with lockfile.</li>
|
|
||||||
<li>Security: add mDNS discovery mode with minimal default to reduce information disclosure. (#1882) Thanks @orlyjamie.</li>
|
|
||||||
<li>Security: harden URL fetches with DNS pinning to reduce rebinding risk. Thanks Chris Zheng.</li>
|
|
||||||
<li>Web UI: improve WebChat image paste previews and allow image-only sends. (#1925) Thanks @smartprogrammer93.</li>
|
|
||||||
<li>Security: wrap external hook content by default with a per-hook opt-out. (#1827) Thanks @mertcicekci0.</li>
|
|
||||||
<li>Gateway: default auth now fail-closed (token/password required; Tailscale Serve identity remains allowed).</li>
|
|
||||||
<li>Gateway: treat loopback + non-local Host connections as remote unless trusted proxy headers are present.</li>
|
|
||||||
<li>Onboarding: remove unsupported gateway auth "off" choice from onboarding/configure flows and CLI flags.</li>
|
|
||||||
</ul>
|
|
||||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
|
||||||
]]></description>
|
|
||||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.1.29/OpenClaw-2026.1.29.zip" length="22458204" type="application/octet-stream" sparkle:edSignature="HqHwZHQyG/CEfBuQnQ/RffJQPKpSbCVrho9C6rgt93S5ek4AH6hUhB3BBKY8sbX1IVFATKK5QZZNE0YPAf7eBw=="/>
|
|
||||||
</item>
|
|
||||||
</channel>
|
</channel>
|
||||||
</rss>
|
</rss>
|
||||||
@@ -21,8 +21,8 @@ android {
|
|||||||
applicationId = "ai.openclaw.android"
|
applicationId = "ai.openclaw.android"
|
||||||
minSdk = 31
|
minSdk = 31
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 202602020
|
versionCode = 202602030
|
||||||
versionName = "2026.2.2"
|
versionName = "2026.2.6"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.2.2</string>
|
<string>2026.2.6</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260202</string>
|
<string>20260202</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>BNDL</string>
|
<string>BNDL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.2.2</string>
|
<string>2026.2.6</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260202</string>
|
<string>20260202</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ targets:
|
|||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClaw
|
CFBundleDisplayName: OpenClaw
|
||||||
CFBundleIconName: AppIcon
|
CFBundleIconName: AppIcon
|
||||||
CFBundleShortVersionString: "2026.2.2"
|
CFBundleShortVersionString: "2026.2.6"
|
||||||
CFBundleVersion: "20260202"
|
CFBundleVersion: "20260202"
|
||||||
UILaunchScreen: {}
|
UILaunchScreen: {}
|
||||||
UIApplicationSceneManifest:
|
UIApplicationSceneManifest:
|
||||||
@@ -130,5 +130,5 @@ targets:
|
|||||||
path: Tests/Info.plist
|
path: Tests/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClawTests
|
CFBundleDisplayName: OpenClawTests
|
||||||
CFBundleShortVersionString: "2026.2.2"
|
CFBundleShortVersionString: "2026.2.6"
|
||||||
CFBundleVersion: "20260202"
|
CFBundleVersion: "20260202"
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ extension CronJobEditor {
|
|||||||
self.wakeMode = job.wakeMode
|
self.wakeMode = job.wakeMode
|
||||||
|
|
||||||
switch job.schedule {
|
switch job.schedule {
|
||||||
case let .at(atMs):
|
case let .at(at):
|
||||||
self.scheduleKind = .at
|
self.scheduleKind = .at
|
||||||
self.atDate = Date(timeIntervalSince1970: TimeInterval(atMs) / 1000)
|
if let date = CronSchedule.parseAtDate(at) {
|
||||||
|
self.atDate = date
|
||||||
|
}
|
||||||
case let .every(everyMs, _):
|
case let .every(everyMs, _):
|
||||||
self.scheduleKind = .every
|
self.scheduleKind = .every
|
||||||
self.everyText = self.formatDuration(ms: everyMs)
|
self.everyText = self.formatDuration(ms: everyMs)
|
||||||
@@ -36,19 +38,22 @@ extension CronJobEditor {
|
|||||||
case let .systemEvent(text):
|
case let .systemEvent(text):
|
||||||
self.payloadKind = .systemEvent
|
self.payloadKind = .systemEvent
|
||||||
self.systemEventText = text
|
self.systemEventText = text
|
||||||
case let .agentTurn(message, thinking, timeoutSeconds, deliver, channel, to, bestEffortDeliver):
|
case let .agentTurn(message, thinking, timeoutSeconds, _, _, _, _):
|
||||||
self.payloadKind = .agentTurn
|
self.payloadKind = .agentTurn
|
||||||
self.agentMessage = message
|
self.agentMessage = message
|
||||||
self.thinking = thinking ?? ""
|
self.thinking = thinking ?? ""
|
||||||
self.timeoutSeconds = timeoutSeconds.map(String.init) ?? ""
|
self.timeoutSeconds = timeoutSeconds.map(String.init) ?? ""
|
||||||
self.deliver = deliver ?? false
|
|
||||||
let trimmed = (channel ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
self.channel = trimmed.isEmpty ? "last" : trimmed
|
|
||||||
self.to = to ?? ""
|
|
||||||
self.bestEffortDeliver = bestEffortDeliver ?? false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.postPrefix = job.isolation?.postToMainPrefix ?? "Cron"
|
if let delivery = job.delivery {
|
||||||
|
self.deliveryMode = delivery.mode == .announce ? .announce : .none
|
||||||
|
let trimmed = (delivery.channel ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
self.channel = trimmed.isEmpty ? "last" : trimmed
|
||||||
|
self.to = delivery.to ?? ""
|
||||||
|
self.bestEffortDeliver = delivery.bestEffort ?? false
|
||||||
|
} else if self.sessionTarget == .isolated {
|
||||||
|
self.deliveryMode = .announce
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func save() {
|
func save() {
|
||||||
@@ -88,15 +93,29 @@ extension CronJobEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.sessionTarget == .isolated {
|
if self.sessionTarget == .isolated {
|
||||||
let trimmed = self.postPrefix.trimmingCharacters(in: .whitespacesAndNewlines)
|
root["delivery"] = self.buildDelivery()
|
||||||
root["isolation"] = [
|
|
||||||
"postToMainPrefix": trimmed.isEmpty ? "Cron" : trimmed,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return root.mapValues { AnyCodable($0) }
|
return root.mapValues { AnyCodable($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildDelivery() -> [String: Any] {
|
||||||
|
let mode = self.deliveryMode == .announce ? "announce" : "none"
|
||||||
|
var delivery: [String: Any] = ["mode": mode]
|
||||||
|
if self.deliveryMode == .announce {
|
||||||
|
let trimmed = self.channel.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
delivery["channel"] = trimmed.isEmpty ? "last" : trimmed
|
||||||
|
let to = self.to.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if !to.isEmpty { delivery["to"] = to }
|
||||||
|
if self.bestEffortDeliver {
|
||||||
|
delivery["bestEffort"] = true
|
||||||
|
} else if self.job?.delivery?.bestEffort == true {
|
||||||
|
delivery["bestEffort"] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return delivery
|
||||||
|
}
|
||||||
|
|
||||||
func trimmed(_ value: String) -> String {
|
func trimmed(_ value: String) -> String {
|
||||||
value.trimmingCharacters(in: .whitespacesAndNewlines)
|
value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
}
|
}
|
||||||
@@ -115,7 +134,7 @@ extension CronJobEditor {
|
|||||||
func buildSchedule() throws -> [String: Any] {
|
func buildSchedule() throws -> [String: Any] {
|
||||||
switch self.scheduleKind {
|
switch self.scheduleKind {
|
||||||
case .at:
|
case .at:
|
||||||
return ["kind": "at", "atMs": Int(self.atDate.timeIntervalSince1970 * 1000)]
|
return ["kind": "at", "at": CronSchedule.formatIsoDate(self.atDate)]
|
||||||
case .every:
|
case .every:
|
||||||
guard let ms = Self.parseDurationMs(self.everyText) else {
|
guard let ms = Self.parseDurationMs(self.everyText) else {
|
||||||
throw NSError(
|
throw NSError(
|
||||||
@@ -209,14 +228,6 @@ extension CronJobEditor {
|
|||||||
let thinking = self.thinking.trimmingCharacters(in: .whitespacesAndNewlines)
|
let thinking = self.thinking.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if !thinking.isEmpty { payload["thinking"] = thinking }
|
if !thinking.isEmpty { payload["thinking"] = thinking }
|
||||||
if let n = Int(self.timeoutSeconds), n > 0 { payload["timeoutSeconds"] = n }
|
if let n = Int(self.timeoutSeconds), n > 0 { payload["timeoutSeconds"] = n }
|
||||||
payload["deliver"] = self.deliver
|
|
||||||
if self.deliver {
|
|
||||||
let trimmed = self.channel.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
payload["channel"] = trimmed.isEmpty ? "last" : trimmed
|
|
||||||
let to = self.to.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
||||||
if !to.isEmpty { payload["to"] = to }
|
|
||||||
payload["bestEffortDeliver"] = self.bestEffortDeliver
|
|
||||||
}
|
|
||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ extension CronJobEditor {
|
|||||||
|
|
||||||
self.payloadKind = .agentTurn
|
self.payloadKind = .agentTurn
|
||||||
self.agentMessage = "Run diagnostic"
|
self.agentMessage = "Run diagnostic"
|
||||||
self.deliver = true
|
self.deliveryMode = .announce
|
||||||
self.channel = "last"
|
self.channel = "last"
|
||||||
self.to = "+15551230000"
|
self.to = "+15551230000"
|
||||||
self.thinking = "low"
|
self.thinking = "low"
|
||||||
self.timeoutSeconds = "90"
|
self.timeoutSeconds = "90"
|
||||||
self.bestEffortDeliver = true
|
self.bestEffortDeliver = true
|
||||||
self.postPrefix = "Cron"
|
|
||||||
|
|
||||||
_ = self.buildAgentTurnPayload()
|
_ = self.buildAgentTurnPayload()
|
||||||
_ = try? self.buildPayload()
|
_ = try? self.buildPayload()
|
||||||
|
|||||||
@@ -16,23 +16,20 @@ struct CronJobEditor: View {
|
|||||||
+ "Use an isolated session for agent turns so your main chat stays clean."
|
+ "Use an isolated session for agent turns so your main chat stays clean."
|
||||||
static let sessionTargetNote =
|
static let sessionTargetNote =
|
||||||
"Main jobs post a system event into the current main session. "
|
"Main jobs post a system event into the current main session. "
|
||||||
+ "Isolated jobs run OpenClaw in a dedicated session and can deliver results (WhatsApp/Telegram/Discord/etc)."
|
+ "Isolated jobs run OpenClaw in a dedicated session and can announce results to a channel."
|
||||||
static let scheduleKindNote =
|
static let scheduleKindNote =
|
||||||
"“At” runs once, “Every” repeats with a duration, “Cron” uses a 5-field Unix expression."
|
"“At” runs once, “Every” repeats with a duration, “Cron” uses a 5-field Unix expression."
|
||||||
static let isolatedPayloadNote =
|
static let isolatedPayloadNote =
|
||||||
"Isolated jobs always run an agent turn. The result can be delivered to a channel, "
|
"Isolated jobs always run an agent turn. Announce sends a short summary to a channel."
|
||||||
+ "and a short summary is posted back to your main chat."
|
|
||||||
static let mainPayloadNote =
|
static let mainPayloadNote =
|
||||||
"System events are injected into the current main session. Agent turns require an isolated session target."
|
"System events are injected into the current main session. Agent turns require an isolated session target."
|
||||||
static let mainSummaryNote =
|
|
||||||
"Controls the label used when posting the completion summary back to the main session."
|
|
||||||
|
|
||||||
@State var name: String = ""
|
@State var name: String = ""
|
||||||
@State var description: String = ""
|
@State var description: String = ""
|
||||||
@State var agentId: String = ""
|
@State var agentId: String = ""
|
||||||
@State var enabled: Bool = true
|
@State var enabled: Bool = true
|
||||||
@State var sessionTarget: CronSessionTarget = .main
|
@State var sessionTarget: CronSessionTarget = .main
|
||||||
@State var wakeMode: CronWakeMode = .nextHeartbeat
|
@State var wakeMode: CronWakeMode = .now
|
||||||
@State var deleteAfterRun: Bool = false
|
@State var deleteAfterRun: Bool = false
|
||||||
|
|
||||||
enum ScheduleKind: String, CaseIterable, Identifiable { case at, every, cron; var id: String { rawValue } }
|
enum ScheduleKind: String, CaseIterable, Identifiable { case at, every, cron; var id: String { rawValue } }
|
||||||
@@ -46,13 +43,13 @@ struct CronJobEditor: View {
|
|||||||
@State var payloadKind: PayloadKind = .systemEvent
|
@State var payloadKind: PayloadKind = .systemEvent
|
||||||
@State var systemEventText: String = ""
|
@State var systemEventText: String = ""
|
||||||
@State var agentMessage: String = ""
|
@State var agentMessage: String = ""
|
||||||
@State var deliver: Bool = false
|
enum DeliveryChoice: String, CaseIterable, Identifiable { case announce, none; var id: String { rawValue } }
|
||||||
|
@State var deliveryMode: DeliveryChoice = .announce
|
||||||
@State var channel: String = "last"
|
@State var channel: String = "last"
|
||||||
@State var to: String = ""
|
@State var to: String = ""
|
||||||
@State var thinking: String = ""
|
@State var thinking: String = ""
|
||||||
@State var timeoutSeconds: String = ""
|
@State var timeoutSeconds: String = ""
|
||||||
@State var bestEffortDeliver: Bool = false
|
@State var bestEffortDeliver: Bool = false
|
||||||
@State var postPrefix: String = "Cron"
|
|
||||||
|
|
||||||
var channelOptions: [String] {
|
var channelOptions: [String] {
|
||||||
let ordered = self.channelsStore.orderedChannelIds()
|
let ordered = self.channelsStore.orderedChannelIds()
|
||||||
@@ -122,8 +119,8 @@ struct CronJobEditor: View {
|
|||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("Wake mode")
|
self.gridLabel("Wake mode")
|
||||||
Picker("", selection: self.$wakeMode) {
|
Picker("", selection: self.$wakeMode) {
|
||||||
Text("next-heartbeat").tag(CronWakeMode.nextHeartbeat)
|
|
||||||
Text("now").tag(CronWakeMode.now)
|
Text("now").tag(CronWakeMode.now)
|
||||||
|
Text("next-heartbeat").tag(CronWakeMode.nextHeartbeat)
|
||||||
}
|
}
|
||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
.pickerStyle(.segmented)
|
.pickerStyle(.segmented)
|
||||||
@@ -248,27 +245,6 @@ struct CronJobEditor: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.sessionTarget == .isolated {
|
|
||||||
GroupBox("Main session summary") {
|
|
||||||
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
|
|
||||||
GridRow {
|
|
||||||
self.gridLabel("Prefix")
|
|
||||||
TextField("Cron", text: self.$postPrefix)
|
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
|
||||||
GridRow {
|
|
||||||
Color.clear
|
|
||||||
.frame(width: self.labelColumnWidth, height: 1)
|
|
||||||
Text(
|
|
||||||
Self.mainSummaryNote)
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, 2)
|
||||||
@@ -340,13 +316,17 @@ struct CronJobEditor: View {
|
|||||||
.frame(width: 180, alignment: .leading)
|
.frame(width: 180, alignment: .leading)
|
||||||
}
|
}
|
||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("Deliver")
|
self.gridLabel("Delivery")
|
||||||
Toggle("Deliver result to a channel", isOn: self.$deliver)
|
Picker("", selection: self.$deliveryMode) {
|
||||||
.toggleStyle(.switch)
|
Text("Announce summary").tag(DeliveryChoice.announce)
|
||||||
|
Text("None").tag(DeliveryChoice.none)
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
.pickerStyle(.segmented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.deliver {
|
if self.deliveryMode == .announce {
|
||||||
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
|
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 14, verticalSpacing: 10) {
|
||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("Channel")
|
self.gridLabel("Channel")
|
||||||
@@ -367,7 +347,7 @@ struct CronJobEditor: View {
|
|||||||
}
|
}
|
||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("Best-effort")
|
self.gridLabel("Best-effort")
|
||||||
Toggle("Do not fail the job if delivery fails", isOn: self.$bestEffortDeliver)
|
Toggle("Do not fail the job if announce fails", isOn: self.$bestEffortDeliver)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,26 @@ enum CronWakeMode: String, CaseIterable, Identifiable, Codable {
|
|||||||
var id: String { self.rawValue }
|
var id: String { self.rawValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CronDeliveryMode: String, CaseIterable, Identifiable, Codable {
|
||||||
|
case none
|
||||||
|
case announce
|
||||||
|
|
||||||
|
var id: String { self.rawValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CronDelivery: Codable, Equatable {
|
||||||
|
var mode: CronDeliveryMode
|
||||||
|
var channel: String?
|
||||||
|
var to: String?
|
||||||
|
var bestEffort: Bool?
|
||||||
|
}
|
||||||
|
|
||||||
enum CronSchedule: Codable, Equatable {
|
enum CronSchedule: Codable, Equatable {
|
||||||
case at(atMs: Int)
|
case at(at: String)
|
||||||
case every(everyMs: Int, anchorMs: Int?)
|
case every(everyMs: Int, anchorMs: Int?)
|
||||||
case cron(expr: String, tz: String?)
|
case cron(expr: String, tz: String?)
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey { case kind, atMs, everyMs, anchorMs, expr, tz }
|
enum CodingKeys: String, CodingKey { case kind, at, atMs, everyMs, anchorMs, expr, tz }
|
||||||
|
|
||||||
var kind: String {
|
var kind: String {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -34,7 +48,21 @@ enum CronSchedule: Codable, Equatable {
|
|||||||
let kind = try container.decode(String.self, forKey: .kind)
|
let kind = try container.decode(String.self, forKey: .kind)
|
||||||
switch kind {
|
switch kind {
|
||||||
case "at":
|
case "at":
|
||||||
self = try .at(atMs: container.decode(Int.self, forKey: .atMs))
|
if let at = try container.decodeIfPresent(String.self, forKey: .at),
|
||||||
|
!at.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||||
|
{
|
||||||
|
self = .at(at: at)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let atMs = try container.decodeIfPresent(Int.self, forKey: .atMs) {
|
||||||
|
let date = Date(timeIntervalSince1970: TimeInterval(atMs) / 1000)
|
||||||
|
self = .at(at: Self.formatIsoDate(date))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw DecodingError.dataCorruptedError(
|
||||||
|
forKey: .at,
|
||||||
|
in: container,
|
||||||
|
debugDescription: "Missing schedule.at")
|
||||||
case "every":
|
case "every":
|
||||||
self = try .every(
|
self = try .every(
|
||||||
everyMs: container.decode(Int.self, forKey: .everyMs),
|
everyMs: container.decode(Int.self, forKey: .everyMs),
|
||||||
@@ -55,8 +83,8 @@ enum CronSchedule: Codable, Equatable {
|
|||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
try container.encode(self.kind, forKey: .kind)
|
try container.encode(self.kind, forKey: .kind)
|
||||||
switch self {
|
switch self {
|
||||||
case let .at(atMs):
|
case let .at(at):
|
||||||
try container.encode(atMs, forKey: .atMs)
|
try container.encode(at, forKey: .at)
|
||||||
case let .every(everyMs, anchorMs):
|
case let .every(everyMs, anchorMs):
|
||||||
try container.encode(everyMs, forKey: .everyMs)
|
try container.encode(everyMs, forKey: .everyMs)
|
||||||
try container.encodeIfPresent(anchorMs, forKey: .anchorMs)
|
try container.encodeIfPresent(anchorMs, forKey: .anchorMs)
|
||||||
@@ -65,6 +93,25 @@ enum CronSchedule: Codable, Equatable {
|
|||||||
try container.encodeIfPresent(tz, forKey: .tz)
|
try container.encodeIfPresent(tz, forKey: .tz)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func parseAtDate(_ value: String) -> Date? {
|
||||||
|
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
if trimmed.isEmpty { return nil }
|
||||||
|
if let date = makeIsoFormatter(withFractional: true).date(from: trimmed) { return date }
|
||||||
|
return makeIsoFormatter(withFractional: false).date(from: trimmed)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func formatIsoDate(_ date: Date) -> String {
|
||||||
|
makeIsoFormatter(withFractional: false).string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func makeIsoFormatter(withFractional: Bool) -> ISO8601DateFormatter {
|
||||||
|
let formatter = ISO8601DateFormatter()
|
||||||
|
formatter.formatOptions = withFractional
|
||||||
|
? [.withInternetDateTime, .withFractionalSeconds]
|
||||||
|
: [.withInternetDateTime]
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CronPayload: Codable, Equatable {
|
enum CronPayload: Codable, Equatable {
|
||||||
@@ -131,10 +178,6 @@ enum CronPayload: Codable, Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CronIsolation: Codable, Equatable {
|
|
||||||
var postToMainPrefix: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CronJobState: Codable, Equatable {
|
struct CronJobState: Codable, Equatable {
|
||||||
var nextRunAtMs: Int?
|
var nextRunAtMs: Int?
|
||||||
var runningAtMs: Int?
|
var runningAtMs: Int?
|
||||||
@@ -157,7 +200,7 @@ struct CronJob: Identifiable, Codable, Equatable {
|
|||||||
let sessionTarget: CronSessionTarget
|
let sessionTarget: CronSessionTarget
|
||||||
let wakeMode: CronWakeMode
|
let wakeMode: CronWakeMode
|
||||||
let payload: CronPayload
|
let payload: CronPayload
|
||||||
let isolation: CronIsolation?
|
let delivery: CronDelivery?
|
||||||
let state: CronJobState
|
let state: CronJobState
|
||||||
|
|
||||||
var displayName: String {
|
var displayName: String {
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ extension CronSettings {
|
|||||||
|
|
||||||
func scheduleSummary(_ schedule: CronSchedule) -> String {
|
func scheduleSummary(_ schedule: CronSchedule) -> String {
|
||||||
switch schedule {
|
switch schedule {
|
||||||
case let .at(atMs):
|
case let .at(at):
|
||||||
let date = Date(timeIntervalSince1970: TimeInterval(atMs) / 1000)
|
if let date = CronSchedule.parseAtDate(at) {
|
||||||
return "at \(date.formatted(date: .abbreviated, time: .standard))"
|
return "at \(date.formatted(date: .abbreviated, time: .standard))"
|
||||||
|
}
|
||||||
|
return "at \(at)"
|
||||||
case let .every(everyMs, _):
|
case let .every(everyMs, _):
|
||||||
return "every \(self.formatDuration(ms: everyMs))"
|
return "every \(self.formatDuration(ms: everyMs))"
|
||||||
case let .cron(expr, tz):
|
case let .cron(expr, tz):
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ extension CronSettings {
|
|||||||
.foregroundStyle(.orange)
|
.foregroundStyle(.orange)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
}
|
}
|
||||||
self.payloadSummary(job.payload)
|
self.payloadSummary(job)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
@@ -205,8 +205,9 @@ extension CronSettings {
|
|||||||
.padding(.vertical, 4)
|
.padding(.vertical, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func payloadSummary(_ payload: CronPayload) -> some View {
|
func payloadSummary(_ job: CronJob) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
let payload = job.payload
|
||||||
|
return VStack(alignment: .leading, spacing: 6) {
|
||||||
Text("Payload")
|
Text("Payload")
|
||||||
.font(.caption.weight(.semibold))
|
.font(.caption.weight(.semibold))
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -215,7 +216,7 @@ extension CronSettings {
|
|||||||
Text(text)
|
Text(text)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
case let .agentTurn(message, thinking, timeoutSeconds, deliver, provider, to, _):
|
case let .agentTurn(message, thinking, timeoutSeconds, _, _, _, _):
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(message)
|
Text(message)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
@@ -223,10 +224,19 @@ extension CronSettings {
|
|||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if let thinking, !thinking.isEmpty { StatusPill(text: "think \(thinking)", tint: .secondary) }
|
if let thinking, !thinking.isEmpty { StatusPill(text: "think \(thinking)", tint: .secondary) }
|
||||||
if let timeoutSeconds { StatusPill(text: "\(timeoutSeconds)s", tint: .secondary) }
|
if let timeoutSeconds { StatusPill(text: "\(timeoutSeconds)s", tint: .secondary) }
|
||||||
if deliver ?? false {
|
if job.sessionTarget == .isolated {
|
||||||
StatusPill(text: "deliver", tint: .secondary)
|
let delivery = job.delivery
|
||||||
if let provider, !provider.isEmpty { StatusPill(text: provider, tint: .secondary) }
|
if let delivery {
|
||||||
if let to, !to.isEmpty { StatusPill(text: to, tint: .secondary) }
|
if delivery.mode == .announce {
|
||||||
|
StatusPill(text: "announce", tint: .secondary)
|
||||||
|
if let channel = delivery.channel, !channel.isEmpty {
|
||||||
|
StatusPill(text: channel, tint: .secondary)
|
||||||
|
}
|
||||||
|
if let to = delivery.to, !to.isEmpty { StatusPill(text: to, tint: .secondary) }
|
||||||
|
} else {
|
||||||
|
StatusPill(text: "no delivery", tint: .secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ struct CronSettings_Previews: PreviewProvider {
|
|||||||
message: "Summarize inbox",
|
message: "Summarize inbox",
|
||||||
thinking: "low",
|
thinking: "low",
|
||||||
timeoutSeconds: 600,
|
timeoutSeconds: 600,
|
||||||
deliver: true,
|
deliver: nil,
|
||||||
channel: "last",
|
channel: nil,
|
||||||
to: nil,
|
to: nil,
|
||||||
bestEffortDeliver: true),
|
bestEffortDeliver: nil),
|
||||||
isolation: CronIsolation(postToMainPrefix: "Cron"),
|
delivery: CronDelivery(mode: .announce, channel: "last", to: nil, bestEffort: true),
|
||||||
state: CronJobState(
|
state: CronJobState(
|
||||||
nextRunAtMs: Int(Date().addingTimeInterval(3600).timeIntervalSince1970 * 1000),
|
nextRunAtMs: Int(Date().addingTimeInterval(3600).timeIntervalSince1970 * 1000),
|
||||||
runningAtMs: nil,
|
runningAtMs: nil,
|
||||||
@@ -75,11 +75,11 @@ extension CronSettings {
|
|||||||
message: "Summarize",
|
message: "Summarize",
|
||||||
thinking: "low",
|
thinking: "low",
|
||||||
timeoutSeconds: 120,
|
timeoutSeconds: 120,
|
||||||
deliver: true,
|
deliver: nil,
|
||||||
channel: "whatsapp",
|
channel: nil,
|
||||||
to: "+15551234567",
|
to: nil,
|
||||||
bestEffortDeliver: true),
|
bestEffortDeliver: nil),
|
||||||
isolation: CronIsolation(postToMainPrefix: "[cron] "),
|
delivery: CronDelivery(mode: .announce, channel: "whatsapp", to: "+15551234567", bestEffort: true),
|
||||||
state: CronJobState(
|
state: CronJobState(
|
||||||
nextRunAtMs: 1_700_000_200_000,
|
nextRunAtMs: 1_700_000_200_000,
|
||||||
runningAtMs: nil,
|
runningAtMs: nil,
|
||||||
@@ -111,7 +111,7 @@ extension CronSettings {
|
|||||||
_ = view.detailCard(job)
|
_ = view.detailCard(job)
|
||||||
_ = view.runHistoryCard(job)
|
_ = view.runHistoryCard(job)
|
||||||
_ = view.runRow(run)
|
_ = view.runRow(run)
|
||||||
_ = view.payloadSummary(job.payload)
|
_ = view.payloadSummary(job)
|
||||||
_ = view.scheduleSummary(job.schedule)
|
_ = view.scheduleSummary(job.schedule)
|
||||||
_ = view.statusTint(job.state.lastStatus)
|
_ = view.statusTint(job.state.lastStatus)
|
||||||
_ = view.nextRunLabel(Date())
|
_ = view.nextRunLabel(Date())
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ extension OnboardingView {
|
|||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.frame(maxWidth: 540)
|
.frame(maxWidth: 540)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
Text("OpenClaw supports any model — we strongly recommend Opus 4.5 for the best experience.")
|
Text("OpenClaw supports any model — we strongly recommend Opus 4.6 for the best experience.")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.2.2</string>
|
<string>2026.2.6</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>202602020</string>
|
<string>202602020</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ extension SessionRow {
|
|||||||
systemSent: true,
|
systemSent: true,
|
||||||
abortedLastRun: true,
|
abortedLastRun: true,
|
||||||
tokens: SessionTokenStats(input: 5000, output: 1200, total: 6200, contextTokens: 200_000),
|
tokens: SessionTokenStats(input: 5000, output: 1200, total: 6200, contextTokens: 200_000),
|
||||||
model: "claude-opus-4-5"),
|
model: "claude-opus-4-6"),
|
||||||
SessionRow(
|
SessionRow(
|
||||||
id: "global",
|
id: "global",
|
||||||
key: "global",
|
key: "global",
|
||||||
@@ -242,7 +242,7 @@ struct SessionStoreSnapshot {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
enum SessionLoader {
|
enum SessionLoader {
|
||||||
static let fallbackModel = "claude-opus-4-5"
|
static let fallbackModel = "claude-opus-4-6"
|
||||||
static let fallbackContextTokens = 200_000
|
static let fallbackContextTokens = 200_000
|
||||||
|
|
||||||
static let defaultStorePath = standardize(
|
static let defaultStorePath = standardize(
|
||||||
|
|||||||
@@ -1119,6 +1119,35 @@ public struct SessionsCompactParams: Codable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct SessionsUsageParams: Codable, Sendable {
|
||||||
|
public let key: String?
|
||||||
|
public let startdate: String?
|
||||||
|
public let enddate: String?
|
||||||
|
public let limit: Int?
|
||||||
|
public let includecontextweight: Bool?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
key: String?,
|
||||||
|
startdate: String?,
|
||||||
|
enddate: String?,
|
||||||
|
limit: Int?,
|
||||||
|
includecontextweight: Bool?
|
||||||
|
) {
|
||||||
|
self.key = key
|
||||||
|
self.startdate = startdate
|
||||||
|
self.enddate = enddate
|
||||||
|
self.limit = limit
|
||||||
|
self.includecontextweight = includecontextweight
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case key
|
||||||
|
case startdate = "startDate"
|
||||||
|
case enddate = "endDate"
|
||||||
|
case limit
|
||||||
|
case includecontextweight = "includeContextWeight"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct ConfigGetParams: Codable, Sendable {
|
public struct ConfigGetParams: Codable, Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1560,6 +1589,140 @@ public struct AgentSummary: Codable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct AgentsCreateParams: Codable, Sendable {
|
||||||
|
public let name: String
|
||||||
|
public let workspace: String
|
||||||
|
public let emoji: String?
|
||||||
|
public let avatar: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
name: String,
|
||||||
|
workspace: String,
|
||||||
|
emoji: String?,
|
||||||
|
avatar: String?
|
||||||
|
) {
|
||||||
|
self.name = name
|
||||||
|
self.workspace = workspace
|
||||||
|
self.emoji = emoji
|
||||||
|
self.avatar = avatar
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case name
|
||||||
|
case workspace
|
||||||
|
case emoji
|
||||||
|
case avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsCreateResult: Codable, Sendable {
|
||||||
|
public let ok: Bool
|
||||||
|
public let agentid: String
|
||||||
|
public let name: String
|
||||||
|
public let workspace: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ok: Bool,
|
||||||
|
agentid: String,
|
||||||
|
name: String,
|
||||||
|
workspace: String
|
||||||
|
) {
|
||||||
|
self.ok = ok
|
||||||
|
self.agentid = agentid
|
||||||
|
self.name = name
|
||||||
|
self.workspace = workspace
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ok
|
||||||
|
case agentid = "agentId"
|
||||||
|
case name
|
||||||
|
case workspace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsUpdateParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let name: String?
|
||||||
|
public let workspace: String?
|
||||||
|
public let model: String?
|
||||||
|
public let avatar: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
name: String?,
|
||||||
|
workspace: String?,
|
||||||
|
model: String?,
|
||||||
|
avatar: String?
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.name = name
|
||||||
|
self.workspace = workspace
|
||||||
|
self.model = model
|
||||||
|
self.avatar = avatar
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case name
|
||||||
|
case workspace
|
||||||
|
case model
|
||||||
|
case avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsUpdateResult: Codable, Sendable {
|
||||||
|
public let ok: Bool
|
||||||
|
public let agentid: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ok: Bool,
|
||||||
|
agentid: String
|
||||||
|
) {
|
||||||
|
self.ok = ok
|
||||||
|
self.agentid = agentid
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ok
|
||||||
|
case agentid = "agentId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsDeleteParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let deletefiles: Bool?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
deletefiles: Bool?
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.deletefiles = deletefiles
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case deletefiles = "deleteFiles"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsDeleteResult: Codable, Sendable {
|
||||||
|
public let ok: Bool
|
||||||
|
public let agentid: String
|
||||||
|
public let removedbindings: Int
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ok: Bool,
|
||||||
|
agentid: String,
|
||||||
|
removedbindings: Int
|
||||||
|
) {
|
||||||
|
self.ok = ok
|
||||||
|
self.agentid = agentid
|
||||||
|
self.removedbindings = removedbindings
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ok
|
||||||
|
case agentid = "agentId"
|
||||||
|
case removedbindings = "removedBindings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct AgentsFileEntry: Codable, Sendable {
|
public struct AgentsFileEntry: Codable, Sendable {
|
||||||
public let name: String
|
public let name: String
|
||||||
public let path: String
|
public let path: String
|
||||||
@@ -1872,7 +2035,7 @@ public struct CronJob: Codable, Sendable {
|
|||||||
public let sessiontarget: AnyCodable
|
public let sessiontarget: AnyCodable
|
||||||
public let wakemode: AnyCodable
|
public let wakemode: AnyCodable
|
||||||
public let payload: AnyCodable
|
public let payload: AnyCodable
|
||||||
public let isolation: [String: AnyCodable]?
|
public let delivery: [String: AnyCodable]?
|
||||||
public let state: [String: AnyCodable]
|
public let state: [String: AnyCodable]
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -1888,7 +2051,7 @@ public struct CronJob: Codable, Sendable {
|
|||||||
sessiontarget: AnyCodable,
|
sessiontarget: AnyCodable,
|
||||||
wakemode: AnyCodable,
|
wakemode: AnyCodable,
|
||||||
payload: AnyCodable,
|
payload: AnyCodable,
|
||||||
isolation: [String: AnyCodable]?,
|
delivery: [String: AnyCodable]?,
|
||||||
state: [String: AnyCodable]
|
state: [String: AnyCodable]
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
@@ -1903,7 +2066,7 @@ public struct CronJob: Codable, Sendable {
|
|||||||
self.sessiontarget = sessiontarget
|
self.sessiontarget = sessiontarget
|
||||||
self.wakemode = wakemode
|
self.wakemode = wakemode
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
self.isolation = isolation
|
self.delivery = delivery
|
||||||
self.state = state
|
self.state = state
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
@@ -1919,7 +2082,7 @@ public struct CronJob: Codable, Sendable {
|
|||||||
case sessiontarget = "sessionTarget"
|
case sessiontarget = "sessionTarget"
|
||||||
case wakemode = "wakeMode"
|
case wakemode = "wakeMode"
|
||||||
case payload
|
case payload
|
||||||
case isolation
|
case delivery
|
||||||
case state
|
case state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1950,7 +2113,7 @@ public struct CronAddParams: Codable, Sendable {
|
|||||||
public let sessiontarget: AnyCodable
|
public let sessiontarget: AnyCodable
|
||||||
public let wakemode: AnyCodable
|
public let wakemode: AnyCodable
|
||||||
public let payload: AnyCodable
|
public let payload: AnyCodable
|
||||||
public let isolation: [String: AnyCodable]?
|
public let delivery: [String: AnyCodable]?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
name: String,
|
name: String,
|
||||||
@@ -1962,7 +2125,7 @@ public struct CronAddParams: Codable, Sendable {
|
|||||||
sessiontarget: AnyCodable,
|
sessiontarget: AnyCodable,
|
||||||
wakemode: AnyCodable,
|
wakemode: AnyCodable,
|
||||||
payload: AnyCodable,
|
payload: AnyCodable,
|
||||||
isolation: [String: AnyCodable]?
|
delivery: [String: AnyCodable]?
|
||||||
) {
|
) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.agentid = agentid
|
self.agentid = agentid
|
||||||
@@ -1973,7 +2136,7 @@ public struct CronAddParams: Codable, Sendable {
|
|||||||
self.sessiontarget = sessiontarget
|
self.sessiontarget = sessiontarget
|
||||||
self.wakemode = wakemode
|
self.wakemode = wakemode
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
self.isolation = isolation
|
self.delivery = delivery
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case name
|
case name
|
||||||
@@ -1985,7 +2148,7 @@ public struct CronAddParams: Codable, Sendable {
|
|||||||
case sessiontarget = "sessionTarget"
|
case sessiontarget = "sessionTarget"
|
||||||
case wakemode = "wakeMode"
|
case wakemode = "wakeMode"
|
||||||
case payload
|
case payload
|
||||||
case isolation
|
case delivery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1996,6 +2159,8 @@ public struct CronRunLogEntry: Codable, Sendable {
|
|||||||
public let status: AnyCodable?
|
public let status: AnyCodable?
|
||||||
public let error: String?
|
public let error: String?
|
||||||
public let summary: String?
|
public let summary: String?
|
||||||
|
public let sessionid: String?
|
||||||
|
public let sessionkey: String?
|
||||||
public let runatms: Int?
|
public let runatms: Int?
|
||||||
public let durationms: Int?
|
public let durationms: Int?
|
||||||
public let nextrunatms: Int?
|
public let nextrunatms: Int?
|
||||||
@@ -2007,6 +2172,8 @@ public struct CronRunLogEntry: Codable, Sendable {
|
|||||||
status: AnyCodable?,
|
status: AnyCodable?,
|
||||||
error: String?,
|
error: String?,
|
||||||
summary: String?,
|
summary: String?,
|
||||||
|
sessionid: String?,
|
||||||
|
sessionkey: String?,
|
||||||
runatms: Int?,
|
runatms: Int?,
|
||||||
durationms: Int?,
|
durationms: Int?,
|
||||||
nextrunatms: Int?
|
nextrunatms: Int?
|
||||||
@@ -2017,6 +2184,8 @@ public struct CronRunLogEntry: Codable, Sendable {
|
|||||||
self.status = status
|
self.status = status
|
||||||
self.error = error
|
self.error = error
|
||||||
self.summary = summary
|
self.summary = summary
|
||||||
|
self.sessionid = sessionid
|
||||||
|
self.sessionkey = sessionkey
|
||||||
self.runatms = runatms
|
self.runatms = runatms
|
||||||
self.durationms = durationms
|
self.durationms = durationms
|
||||||
self.nextrunatms = nextrunatms
|
self.nextrunatms = nextrunatms
|
||||||
@@ -2028,6 +2197,8 @@ public struct CronRunLogEntry: Codable, Sendable {
|
|||||||
case status
|
case status
|
||||||
case error
|
case error
|
||||||
case summary
|
case summary
|
||||||
|
case sessionid = "sessionId"
|
||||||
|
case sessionkey = "sessionKey"
|
||||||
case runatms = "runAtMs"
|
case runatms = "runAtMs"
|
||||||
case durationms = "durationMs"
|
case durationms = "durationMs"
|
||||||
case nextrunatms = "nextRunAtMs"
|
case nextrunatms = "nextRunAtMs"
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ struct CronJobEditorSmokeTests {
|
|||||||
message: "Summarize the last day",
|
message: "Summarize the last day",
|
||||||
thinking: "low",
|
thinking: "low",
|
||||||
timeoutSeconds: 120,
|
timeoutSeconds: 120,
|
||||||
deliver: true,
|
deliver: nil,
|
||||||
channel: "whatsapp",
|
channel: nil,
|
||||||
to: "+15551234567",
|
to: nil,
|
||||||
bestEffortDeliver: true),
|
bestEffortDeliver: nil),
|
||||||
isolation: CronIsolation(postToMainPrefix: "Cron"),
|
delivery: CronDelivery(mode: .announce, channel: "whatsapp", to: "+15551234567", bestEffort: true),
|
||||||
state: CronJobState(
|
state: CronJobState(
|
||||||
nextRunAtMs: 1_700_000_100_000,
|
nextRunAtMs: 1_700_000_100_000,
|
||||||
runningAtMs: nil,
|
runningAtMs: nil,
|
||||||
|
|||||||
@@ -5,12 +5,24 @@ import Testing
|
|||||||
@Suite
|
@Suite
|
||||||
struct CronModelsTests {
|
struct CronModelsTests {
|
||||||
@Test func scheduleAtEncodesAndDecodes() throws {
|
@Test func scheduleAtEncodesAndDecodes() throws {
|
||||||
let schedule = CronSchedule.at(atMs: 123)
|
let schedule = CronSchedule.at(at: "2026-02-03T18:00:00Z")
|
||||||
let data = try JSONEncoder().encode(schedule)
|
let data = try JSONEncoder().encode(schedule)
|
||||||
let decoded = try JSONDecoder().decode(CronSchedule.self, from: data)
|
let decoded = try JSONDecoder().decode(CronSchedule.self, from: data)
|
||||||
#expect(decoded == schedule)
|
#expect(decoded == schedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test func scheduleAtDecodesLegacyAtMs() throws {
|
||||||
|
let json = """
|
||||||
|
{"kind":"at","atMs":1700000000000}
|
||||||
|
"""
|
||||||
|
let decoded = try JSONDecoder().decode(CronSchedule.self, from: Data(json.utf8))
|
||||||
|
if case let .at(at) = decoded {
|
||||||
|
#expect(at.hasPrefix("2023-"))
|
||||||
|
} else {
|
||||||
|
#expect(Bool(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test func scheduleEveryEncodesAndDecodesWithAnchor() throws {
|
@Test func scheduleEveryEncodesAndDecodesWithAnchor() throws {
|
||||||
let schedule = CronSchedule.every(everyMs: 5000, anchorMs: 10000)
|
let schedule = CronSchedule.every(everyMs: 5000, anchorMs: 10000)
|
||||||
let data = try JSONEncoder().encode(schedule)
|
let data = try JSONEncoder().encode(schedule)
|
||||||
@@ -49,11 +61,11 @@ struct CronModelsTests {
|
|||||||
deleteAfterRun: true,
|
deleteAfterRun: true,
|
||||||
createdAtMs: 0,
|
createdAtMs: 0,
|
||||||
updatedAtMs: 0,
|
updatedAtMs: 0,
|
||||||
schedule: .at(atMs: 1_700_000_000_000),
|
schedule: .at(at: "2026-02-03T18:00:00Z"),
|
||||||
sessionTarget: .main,
|
sessionTarget: .main,
|
||||||
wakeMode: .now,
|
wakeMode: .now,
|
||||||
payload: .systemEvent(text: "ping"),
|
payload: .systemEvent(text: "ping"),
|
||||||
isolation: nil,
|
delivery: nil,
|
||||||
state: CronJobState())
|
state: CronJobState())
|
||||||
let data = try JSONEncoder().encode(job)
|
let data = try JSONEncoder().encode(job)
|
||||||
let decoded = try JSONDecoder().decode(CronJob.self, from: data)
|
let decoded = try JSONDecoder().decode(CronJob.self, from: data)
|
||||||
@@ -62,7 +74,7 @@ struct CronModelsTests {
|
|||||||
|
|
||||||
@Test func scheduleDecodeRejectsUnknownKind() {
|
@Test func scheduleDecodeRejectsUnknownKind() {
|
||||||
let json = """
|
let json = """
|
||||||
{"kind":"wat","atMs":1}
|
{"kind":"wat","at":"2026-02-03T18:00:00Z"}
|
||||||
"""
|
"""
|
||||||
#expect(throws: DecodingError.self) {
|
#expect(throws: DecodingError.self) {
|
||||||
_ = try JSONDecoder().decode(CronSchedule.self, from: Data(json.utf8))
|
_ = try JSONDecoder().decode(CronSchedule.self, from: Data(json.utf8))
|
||||||
@@ -88,11 +100,11 @@ struct CronModelsTests {
|
|||||||
deleteAfterRun: nil,
|
deleteAfterRun: nil,
|
||||||
createdAtMs: 0,
|
createdAtMs: 0,
|
||||||
updatedAtMs: 0,
|
updatedAtMs: 0,
|
||||||
schedule: .at(atMs: 0),
|
schedule: .at(at: "2026-02-03T18:00:00Z"),
|
||||||
sessionTarget: .main,
|
sessionTarget: .main,
|
||||||
wakeMode: .now,
|
wakeMode: .now,
|
||||||
payload: .systemEvent(text: "hi"),
|
payload: .systemEvent(text: "hi"),
|
||||||
isolation: nil,
|
delivery: nil,
|
||||||
state: CronJobState())
|
state: CronJobState())
|
||||||
#expect(base.displayName == "hello")
|
#expect(base.displayName == "hello")
|
||||||
|
|
||||||
@@ -111,11 +123,11 @@ struct CronModelsTests {
|
|||||||
deleteAfterRun: nil,
|
deleteAfterRun: nil,
|
||||||
createdAtMs: 0,
|
createdAtMs: 0,
|
||||||
updatedAtMs: 0,
|
updatedAtMs: 0,
|
||||||
schedule: .at(atMs: 0),
|
schedule: .at(at: "2026-02-03T18:00:00Z"),
|
||||||
sessionTarget: .main,
|
sessionTarget: .main,
|
||||||
wakeMode: .now,
|
wakeMode: .now,
|
||||||
payload: .systemEvent(text: "hi"),
|
payload: .systemEvent(text: "hi"),
|
||||||
isolation: nil,
|
delivery: nil,
|
||||||
state: CronJobState(
|
state: CronJobState(
|
||||||
nextRunAtMs: 1_700_000_000_000,
|
nextRunAtMs: 1_700_000_000_000,
|
||||||
runningAtMs: nil,
|
runningAtMs: nil,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ struct MenuSessionsInjectorTests {
|
|||||||
let injector = MenuSessionsInjector()
|
let injector = MenuSessionsInjector()
|
||||||
injector.setTestingControlChannelConnected(true)
|
injector.setTestingControlChannelConnected(true)
|
||||||
|
|
||||||
let defaults = SessionDefaults(model: "anthropic/claude-opus-4-5", contextTokens: 200_000)
|
let defaults = SessionDefaults(model: "anthropic/claude-opus-4-6", contextTokens: 200_000)
|
||||||
let rows = [
|
let rows = [
|
||||||
SessionRow(
|
SessionRow(
|
||||||
id: "main",
|
id: "main",
|
||||||
@@ -41,7 +41,7 @@ struct MenuSessionsInjectorTests {
|
|||||||
systemSent: false,
|
systemSent: false,
|
||||||
abortedLastRun: false,
|
abortedLastRun: false,
|
||||||
tokens: SessionTokenStats(input: 10, output: 20, total: 30, contextTokens: 200_000),
|
tokens: SessionTokenStats(input: 10, output: 20, total: 30, contextTokens: 200_000),
|
||||||
model: "claude-opus-4-5"),
|
model: "claude-opus-4-6"),
|
||||||
SessionRow(
|
SessionRow(
|
||||||
id: "discord:group:alpha",
|
id: "discord:group:alpha",
|
||||||
key: "discord:group:alpha",
|
key: "discord:group:alpha",
|
||||||
@@ -58,7 +58,7 @@ struct MenuSessionsInjectorTests {
|
|||||||
systemSent: true,
|
systemSent: true,
|
||||||
abortedLastRun: true,
|
abortedLastRun: true,
|
||||||
tokens: SessionTokenStats(input: 50, output: 50, total: 100, contextTokens: 200_000),
|
tokens: SessionTokenStats(input: 50, output: 50, total: 100, contextTokens: 200_000),
|
||||||
model: "claude-opus-4-5"),
|
model: "claude-opus-4-6"),
|
||||||
]
|
]
|
||||||
let snapshot = SessionStoreSnapshot(
|
let snapshot = SessionStoreSnapshot(
|
||||||
storePath: "/tmp/sessions.json",
|
storePath: "/tmp/sessions.json",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ struct SettingsViewSmokeTests {
|
|||||||
sessionTarget: .main,
|
sessionTarget: .main,
|
||||||
wakeMode: .now,
|
wakeMode: .now,
|
||||||
payload: .systemEvent(text: "ping"),
|
payload: .systemEvent(text: "ping"),
|
||||||
isolation: nil,
|
delivery: nil,
|
||||||
state: CronJobState(
|
state: CronJobState(
|
||||||
nextRunAtMs: 1_700_000_200_000,
|
nextRunAtMs: 1_700_000_200_000,
|
||||||
runningAtMs: nil,
|
runningAtMs: nil,
|
||||||
@@ -48,11 +48,11 @@ struct SettingsViewSmokeTests {
|
|||||||
message: "hello",
|
message: "hello",
|
||||||
thinking: "low",
|
thinking: "low",
|
||||||
timeoutSeconds: 30,
|
timeoutSeconds: 30,
|
||||||
deliver: true,
|
deliver: nil,
|
||||||
channel: "sms",
|
channel: nil,
|
||||||
to: "+15551234567",
|
to: nil,
|
||||||
bestEffortDeliver: true),
|
bestEffortDeliver: nil),
|
||||||
isolation: CronIsolation(postToMainPrefix: "[cron] "),
|
delivery: CronDelivery(mode: .announce, channel: "sms", to: "+15551234567", bestEffort: true),
|
||||||
state: CronJobState(
|
state: CronJobState(
|
||||||
nextRunAtMs: nil,
|
nextRunAtMs: nil,
|
||||||
runningAtMs: nil,
|
runningAtMs: nil,
|
||||||
|
|||||||
@@ -1119,6 +1119,35 @@ public struct SessionsCompactParams: Codable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct SessionsUsageParams: Codable, Sendable {
|
||||||
|
public let key: String?
|
||||||
|
public let startdate: String?
|
||||||
|
public let enddate: String?
|
||||||
|
public let limit: Int?
|
||||||
|
public let includecontextweight: Bool?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
key: String?,
|
||||||
|
startdate: String?,
|
||||||
|
enddate: String?,
|
||||||
|
limit: Int?,
|
||||||
|
includecontextweight: Bool?
|
||||||
|
) {
|
||||||
|
self.key = key
|
||||||
|
self.startdate = startdate
|
||||||
|
self.enddate = enddate
|
||||||
|
self.limit = limit
|
||||||
|
self.includecontextweight = includecontextweight
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case key
|
||||||
|
case startdate = "startDate"
|
||||||
|
case enddate = "endDate"
|
||||||
|
case limit
|
||||||
|
case includecontextweight = "includeContextWeight"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct ConfigGetParams: Codable, Sendable {
|
public struct ConfigGetParams: Codable, Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1560,6 +1589,140 @@ public struct AgentSummary: Codable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct AgentsCreateParams: Codable, Sendable {
|
||||||
|
public let name: String
|
||||||
|
public let workspace: String
|
||||||
|
public let emoji: String?
|
||||||
|
public let avatar: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
name: String,
|
||||||
|
workspace: String,
|
||||||
|
emoji: String?,
|
||||||
|
avatar: String?
|
||||||
|
) {
|
||||||
|
self.name = name
|
||||||
|
self.workspace = workspace
|
||||||
|
self.emoji = emoji
|
||||||
|
self.avatar = avatar
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case name
|
||||||
|
case workspace
|
||||||
|
case emoji
|
||||||
|
case avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsCreateResult: Codable, Sendable {
|
||||||
|
public let ok: Bool
|
||||||
|
public let agentid: String
|
||||||
|
public let name: String
|
||||||
|
public let workspace: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ok: Bool,
|
||||||
|
agentid: String,
|
||||||
|
name: String,
|
||||||
|
workspace: String
|
||||||
|
) {
|
||||||
|
self.ok = ok
|
||||||
|
self.agentid = agentid
|
||||||
|
self.name = name
|
||||||
|
self.workspace = workspace
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ok
|
||||||
|
case agentid = "agentId"
|
||||||
|
case name
|
||||||
|
case workspace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsUpdateParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let name: String?
|
||||||
|
public let workspace: String?
|
||||||
|
public let model: String?
|
||||||
|
public let avatar: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
name: String?,
|
||||||
|
workspace: String?,
|
||||||
|
model: String?,
|
||||||
|
avatar: String?
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.name = name
|
||||||
|
self.workspace = workspace
|
||||||
|
self.model = model
|
||||||
|
self.avatar = avatar
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case name
|
||||||
|
case workspace
|
||||||
|
case model
|
||||||
|
case avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsUpdateResult: Codable, Sendable {
|
||||||
|
public let ok: Bool
|
||||||
|
public let agentid: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ok: Bool,
|
||||||
|
agentid: String
|
||||||
|
) {
|
||||||
|
self.ok = ok
|
||||||
|
self.agentid = agentid
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ok
|
||||||
|
case agentid = "agentId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsDeleteParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let deletefiles: Bool?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
deletefiles: Bool?
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.deletefiles = deletefiles
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case deletefiles = "deleteFiles"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsDeleteResult: Codable, Sendable {
|
||||||
|
public let ok: Bool
|
||||||
|
public let agentid: String
|
||||||
|
public let removedbindings: Int
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ok: Bool,
|
||||||
|
agentid: String,
|
||||||
|
removedbindings: Int
|
||||||
|
) {
|
||||||
|
self.ok = ok
|
||||||
|
self.agentid = agentid
|
||||||
|
self.removedbindings = removedbindings
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ok
|
||||||
|
case agentid = "agentId"
|
||||||
|
case removedbindings = "removedBindings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct AgentsFileEntry: Codable, Sendable {
|
public struct AgentsFileEntry: Codable, Sendable {
|
||||||
public let name: String
|
public let name: String
|
||||||
public let path: String
|
public let path: String
|
||||||
@@ -1872,7 +2035,7 @@ public struct CronJob: Codable, Sendable {
|
|||||||
public let sessiontarget: AnyCodable
|
public let sessiontarget: AnyCodable
|
||||||
public let wakemode: AnyCodable
|
public let wakemode: AnyCodable
|
||||||
public let payload: AnyCodable
|
public let payload: AnyCodable
|
||||||
public let isolation: [String: AnyCodable]?
|
public let delivery: [String: AnyCodable]?
|
||||||
public let state: [String: AnyCodable]
|
public let state: [String: AnyCodable]
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -1888,7 +2051,7 @@ public struct CronJob: Codable, Sendable {
|
|||||||
sessiontarget: AnyCodable,
|
sessiontarget: AnyCodable,
|
||||||
wakemode: AnyCodable,
|
wakemode: AnyCodable,
|
||||||
payload: AnyCodable,
|
payload: AnyCodable,
|
||||||
isolation: [String: AnyCodable]?,
|
delivery: [String: AnyCodable]?,
|
||||||
state: [String: AnyCodable]
|
state: [String: AnyCodable]
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
@@ -1903,7 +2066,7 @@ public struct CronJob: Codable, Sendable {
|
|||||||
self.sessiontarget = sessiontarget
|
self.sessiontarget = sessiontarget
|
||||||
self.wakemode = wakemode
|
self.wakemode = wakemode
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
self.isolation = isolation
|
self.delivery = delivery
|
||||||
self.state = state
|
self.state = state
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
@@ -1919,7 +2082,7 @@ public struct CronJob: Codable, Sendable {
|
|||||||
case sessiontarget = "sessionTarget"
|
case sessiontarget = "sessionTarget"
|
||||||
case wakemode = "wakeMode"
|
case wakemode = "wakeMode"
|
||||||
case payload
|
case payload
|
||||||
case isolation
|
case delivery
|
||||||
case state
|
case state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1950,7 +2113,7 @@ public struct CronAddParams: Codable, Sendable {
|
|||||||
public let sessiontarget: AnyCodable
|
public let sessiontarget: AnyCodable
|
||||||
public let wakemode: AnyCodable
|
public let wakemode: AnyCodable
|
||||||
public let payload: AnyCodable
|
public let payload: AnyCodable
|
||||||
public let isolation: [String: AnyCodable]?
|
public let delivery: [String: AnyCodable]?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
name: String,
|
name: String,
|
||||||
@@ -1962,7 +2125,7 @@ public struct CronAddParams: Codable, Sendable {
|
|||||||
sessiontarget: AnyCodable,
|
sessiontarget: AnyCodable,
|
||||||
wakemode: AnyCodable,
|
wakemode: AnyCodable,
|
||||||
payload: AnyCodable,
|
payload: AnyCodable,
|
||||||
isolation: [String: AnyCodable]?
|
delivery: [String: AnyCodable]?
|
||||||
) {
|
) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.agentid = agentid
|
self.agentid = agentid
|
||||||
@@ -1973,7 +2136,7 @@ public struct CronAddParams: Codable, Sendable {
|
|||||||
self.sessiontarget = sessiontarget
|
self.sessiontarget = sessiontarget
|
||||||
self.wakemode = wakemode
|
self.wakemode = wakemode
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
self.isolation = isolation
|
self.delivery = delivery
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case name
|
case name
|
||||||
@@ -1985,7 +2148,7 @@ public struct CronAddParams: Codable, Sendable {
|
|||||||
case sessiontarget = "sessionTarget"
|
case sessiontarget = "sessionTarget"
|
||||||
case wakemode = "wakeMode"
|
case wakemode = "wakeMode"
|
||||||
case payload
|
case payload
|
||||||
case isolation
|
case delivery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1996,6 +2159,8 @@ public struct CronRunLogEntry: Codable, Sendable {
|
|||||||
public let status: AnyCodable?
|
public let status: AnyCodable?
|
||||||
public let error: String?
|
public let error: String?
|
||||||
public let summary: String?
|
public let summary: String?
|
||||||
|
public let sessionid: String?
|
||||||
|
public let sessionkey: String?
|
||||||
public let runatms: Int?
|
public let runatms: Int?
|
||||||
public let durationms: Int?
|
public let durationms: Int?
|
||||||
public let nextrunatms: Int?
|
public let nextrunatms: Int?
|
||||||
@@ -2007,6 +2172,8 @@ public struct CronRunLogEntry: Codable, Sendable {
|
|||||||
status: AnyCodable?,
|
status: AnyCodable?,
|
||||||
error: String?,
|
error: String?,
|
||||||
summary: String?,
|
summary: String?,
|
||||||
|
sessionid: String?,
|
||||||
|
sessionkey: String?,
|
||||||
runatms: Int?,
|
runatms: Int?,
|
||||||
durationms: Int?,
|
durationms: Int?,
|
||||||
nextrunatms: Int?
|
nextrunatms: Int?
|
||||||
@@ -2017,6 +2184,8 @@ public struct CronRunLogEntry: Codable, Sendable {
|
|||||||
self.status = status
|
self.status = status
|
||||||
self.error = error
|
self.error = error
|
||||||
self.summary = summary
|
self.summary = summary
|
||||||
|
self.sessionid = sessionid
|
||||||
|
self.sessionkey = sessionkey
|
||||||
self.runatms = runatms
|
self.runatms = runatms
|
||||||
self.durationms = durationms
|
self.durationms = durationms
|
||||||
self.nextrunatms = nextrunatms
|
self.nextrunatms = nextrunatms
|
||||||
@@ -2028,6 +2197,8 @@ public struct CronRunLogEntry: Codable, Sendable {
|
|||||||
case status
|
case status
|
||||||
case error
|
case error
|
||||||
case summary
|
case summary
|
||||||
|
case sessionid = "sessionId"
|
||||||
|
case sessionkey = "sessionKey"
|
||||||
case runatms = "runAtMs"
|
case runatms = "runAtMs"
|
||||||
case durationms = "durationMs"
|
case durationms = "durationMs"
|
||||||
case nextrunatms = "nextRunAtMs"
|
case nextrunatms = "nextRunAtMs"
|
||||||
|
|||||||
@@ -39,6 +39,26 @@
|
|||||||
"source": "Getting started",
|
"source": "Getting started",
|
||||||
"target": "入门指南"
|
"target": "入门指南"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"source": "Quick start",
|
||||||
|
"target": "快速开始"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Quick Start",
|
||||||
|
"target": "快速开始"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Docs directory",
|
||||||
|
"target": "文档目录"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Credits",
|
||||||
|
"target": "致谢"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Features",
|
||||||
|
"target": "功能"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"source": "DMs",
|
"source": "DMs",
|
||||||
"target": "私信"
|
"target": "私信"
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
title: "OpenClaw Docs"
|
|
||||||
description: "A TypeScript/Node gateway + macOS/iOS/Android companions for WhatsApp (web) and Telegram (bot)."
|
|
||||||
markdown: kramdown
|
|
||||||
highlighter: rouge
|
|
||||||
|
|
||||||
# Keep GitHub Pages' default page URLs (e.g. /gateway.html). Many docs links
|
|
||||||
# are written as relative *.md links and are rewritten during the build.
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- jekyll-relative-links
|
|
||||||
|
|
||||||
relative_links:
|
|
||||||
enabled: true
|
|
||||||
collections: true
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
- scope:
|
|
||||||
path: ""
|
|
||||||
values:
|
|
||||||
layout: default
|
|
||||||
|
|
||||||
nav:
|
|
||||||
- title: "Home"
|
|
||||||
url: "/"
|
|
||||||
- title: "OpenClaw Assistant"
|
|
||||||
url: "/start/openclaw/"
|
|
||||||
- title: "Gateway"
|
|
||||||
url: "/gateway/"
|
|
||||||
- title: "Remote"
|
|
||||||
url: "/gateway/remote/"
|
|
||||||
- title: "Discovery"
|
|
||||||
url: "/gateway/discovery/"
|
|
||||||
- title: "Configuration"
|
|
||||||
url: "/gateway/configuration/"
|
|
||||||
- title: "WebChat"
|
|
||||||
url: "/web/webchat/"
|
|
||||||
- title: "macOS App"
|
|
||||||
url: "/platforms/macos/"
|
|
||||||
- title: "iOS App"
|
|
||||||
url: "/platforms/ios/"
|
|
||||||
- title: "Android App"
|
|
||||||
url: "/platforms/android/"
|
|
||||||
- title: "Telegram"
|
|
||||||
url: "/channels/telegram/"
|
|
||||||
- title: "Security"
|
|
||||||
url: "/gateway/security/"
|
|
||||||
- title: "Troubleshooting"
|
|
||||||
url: "/gateway/troubleshooting/"
|
|
||||||
|
|
||||||
kramdown:
|
|
||||||
input: GFM
|
|
||||||
hard_wrap: false
|
|
||||||
syntax_highlighter: rouge
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en" data-theme="auto">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta name="color-scheme" content="light dark" />
|
|
||||||
<title>
|
|
||||||
{% if page.url == "/" %}{{ site.title }}{% else %}{{ page.title | default: page.path | split: "/" | last | replace: ".md", "" }} · {{ site.title }}{% endif %}
|
|
||||||
</title>
|
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Pixelify+Sans:wght@400..700&family=Fragment+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<script>
|
|
||||||
(() => {
|
|
||||||
try {
|
|
||||||
const stored = localStorage.getItem("openclaw:theme");
|
|
||||||
if (stored === "light" || stored === "dark") document.documentElement.dataset.theme = stored;
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<link rel="stylesheet" href="{{ "/assets/terminal.css" | relative_url }}" />
|
|
||||||
<link rel="stylesheet" href="{{ "/assets/markdown.css" | relative_url }}" />
|
|
||||||
<script defer src="{{ "/assets/theme.js" | relative_url }}"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<a class="skip-link" href="#content">Skip to content</a>
|
|
||||||
|
|
||||||
<header class="shell">
|
|
||||||
<div class="shell__frame" role="banner">
|
|
||||||
<div class="shell__titlebar">
|
|
||||||
<div class="brand" aria-label="OpenClaw docs terminal">
|
|
||||||
<img
|
|
||||||
class="brand__logo"
|
|
||||||
src="{{ "/assets/pixel-lobster.svg" | relative_url }}"
|
|
||||||
alt=""
|
|
||||||
width="40"
|
|
||||||
height="40"
|
|
||||||
decoding="async"
|
|
||||||
/>
|
|
||||||
<div class="brand__text">
|
|
||||||
<div class="brand__name">OpenClaw</div>
|
|
||||||
<div class="brand__hint">docs // lobster terminal</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="titlebar__actions">
|
|
||||||
<a class="titlebar__cta" href="https://github.com/openclaw/openclaw">
|
|
||||||
<span class="titlebar__cta-label">GitHub</span>
|
|
||||||
<span class="titlebar__cta-meta">repo</span>
|
|
||||||
</a>
|
|
||||||
<a class="titlebar__cta titlebar__cta--accent" href="https://github.com/openclaw/openclaw/releases/latest">
|
|
||||||
<span class="titlebar__cta-label">Download</span>
|
|
||||||
<span class="titlebar__cta-meta">latest</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="shell__nav" aria-label="Primary">
|
|
||||||
<nav class="nav">
|
|
||||||
{% assign nav = site.nav | default: empty %}
|
|
||||||
{% for item in nav %}
|
|
||||||
{% assign item_url = item.url | relative_url %}
|
|
||||||
<a class="nav__link" href="{{ item_url }}">
|
|
||||||
<span class="nav__chev">›</span>{{ item.title }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="shell__status" aria-hidden="true">
|
|
||||||
<span class="status__dot"></span>
|
|
||||||
<span class="status__text">
|
|
||||||
{% if page.url == "/" %}
|
|
||||||
ready
|
|
||||||
{% else %}
|
|
||||||
viewing: {{ page.path }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main id="content" class="content" role="main">
|
|
||||||
<div class="terminal">
|
|
||||||
<div class="terminal__prompt" aria-hidden="true">
|
|
||||||
<span class="prompt__user">openclaw</span>@<span class="prompt__host">openclaw</span>:<span class="prompt__path">~/docs</span>$<span class="prompt__cmd">
|
|
||||||
{% if page.url == "/" %}cat index.md{% else %}less {{ page.path }}{% endif %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if page.summary %}
|
|
||||||
<p class="terminal__summary">{{ page.summary }}</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if page.read_when %}
|
|
||||||
<details class="terminal__meta">
|
|
||||||
<summary>Read when…</summary>
|
|
||||||
<ul>
|
|
||||||
{% for hint in page.read_when %}
|
|
||||||
<li>{{ hint }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<article class="markdown">
|
|
||||||
{{ content }}
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<footer class="terminal__footer" role="contentinfo">
|
|
||||||
<div class="footer__line">
|
|
||||||
<span class="footer__sig">openclaw.ai</span>
|
|
||||||
<span class="footer__sep">·</span>
|
|
||||||
<a href="https://github.com/openclaw/openclaw">source</a>
|
|
||||||
<span class="footer__sep">·</span>
|
|
||||||
<a href="https://github.com/openclaw/openclaw/releases">releases</a>
|
|
||||||
</div>
|
|
||||||
<div class="footer__hint" aria-hidden="true">
|
|
||||||
tip: press <kbd>F2</kbd> (Mac: <kbd>fn</kbd>+<kbd>F2</kbd>) to flip
|
|
||||||
the universe
|
|
||||||
</div>
|
|
||||||
<div class="footer__actions">
|
|
||||||
<button
|
|
||||||
class="theme-toggle"
|
|
||||||
type="button"
|
|
||||||
data-theme-toggle
|
|
||||||
aria-label="Toggle theme (F2; on Mac: fn+F2)"
|
|
||||||
aria-pressed="false"
|
|
||||||
>
|
|
||||||
<span class="theme-toggle__key">F2</span>
|
|
||||||
<span class="theme-toggle__label" data-theme-label>theme</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
BIN
docs/assets/macos-onboarding/01-macos-warning.jpeg
Normal file
BIN
docs/assets/macos-onboarding/01-macos-warning.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
BIN
docs/assets/macos-onboarding/02-local-networks.jpeg
Normal file
BIN
docs/assets/macos-onboarding/02-local-networks.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
BIN
docs/assets/macos-onboarding/03-security-notice.png
Normal file
BIN
docs/assets/macos-onboarding/03-security-notice.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 KiB |
BIN
docs/assets/macos-onboarding/04-choose-gateway.png
Normal file
BIN
docs/assets/macos-onboarding/04-choose-gateway.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 312 KiB |
BIN
docs/assets/macos-onboarding/05-permissions.png
Normal file
BIN
docs/assets/macos-onboarding/05-permissions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 362 KiB |
@@ -1,179 +0,0 @@
|
|||||||
.markdown {
|
|
||||||
margin-top: 18px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mdx-content > h1:first-of-type {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown h1,
|
|
||||||
.markdown h2,
|
|
||||||
.markdown h3,
|
|
||||||
.markdown h4 {
|
|
||||||
font-family: var(--font-pixel);
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
line-height: 1.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown h1 {
|
|
||||||
font-size: clamp(28px, 4vw, 44px);
|
|
||||||
margin: 26px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown h2 {
|
|
||||||
font-size: 22px;
|
|
||||||
margin: 26px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown h3 {
|
|
||||||
font-size: 18px;
|
|
||||||
margin: 22px 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown p {
|
|
||||||
margin: 0 0 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown a {
|
|
||||||
color: var(--link);
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px dotted color-mix(in oklab, var(--link) 65%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown a:hover {
|
|
||||||
color: var(--link2);
|
|
||||||
border-bottom-color: color-mix(in oklab, var(--link2) 75%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown hr {
|
|
||||||
border: 0;
|
|
||||||
height: 1px;
|
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
transparent,
|
|
||||||
color-mix(in oklab, var(--frame-border) 30%, transparent),
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
margin: 26px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown blockquote {
|
|
||||||
margin: 18px 0;
|
|
||||||
padding: 14px 14px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: color-mix(in oklab, var(--panel) 70%, transparent);
|
|
||||||
border-left: 6px solid color-mix(in oklab, var(--accent) 60%, transparent);
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown ul,
|
|
||||||
.markdown ol {
|
|
||||||
margin: 0 0 14px 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown li {
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 20%, transparent);
|
|
||||||
box-shadow: 0 12px 0 -8px rgba(0, 0, 0, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.showcase-link {
|
|
||||||
position: relative;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.showcase-preview {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 100%;
|
|
||||||
width: min(420px, 80vw);
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: color-mix(in oklab, var(--panel) 92%, transparent);
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 30%, transparent);
|
|
||||||
box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.55);
|
|
||||||
transform: translate(-50%, 10px) scale(0.98);
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 20;
|
|
||||||
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.showcase-preview img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 25%, transparent);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.showcase-link:hover .showcase-preview,
|
|
||||||
.showcase-link:focus-within .showcase-preview {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
transform: translate(-50%, 6px) scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: none) {
|
|
||||||
.showcase-preview {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown code {
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-size: 0.95em;
|
|
||||||
padding: 0.15em 0.35em;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: color-mix(in oklab, var(--panel) 70%, var(--code-bg));
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 18%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown pre {
|
|
||||||
background: var(--code-bg);
|
|
||||||
color: var(--code-fg);
|
|
||||||
padding: 14px 14px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 18%, transparent);
|
|
||||||
overflow-x: auto;
|
|
||||||
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--code-accent) 12%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown pre code {
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 16px 0 22px;
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 20%, transparent);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown th,
|
|
||||||
.markdown td {
|
|
||||||
padding: 10px 10px;
|
|
||||||
border-bottom: 1px solid color-mix(in oklab, var(--frame-border) 15%, transparent);
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown th {
|
|
||||||
background: color-mix(in oklab, var(--panel2) 85%, transparent);
|
|
||||||
font-family: var(--font-pixel);
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
}
|
|
||||||
@@ -1,473 +0,0 @@
|
|||||||
:root {
|
|
||||||
--font-body: "Fragment Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
--font-pixel: "Pixelify Sans", system-ui, sans-serif;
|
|
||||||
--radius: 14px;
|
|
||||||
--radius-sm: 10px;
|
|
||||||
--border: 2px;
|
|
||||||
--shadow-px: 0 0 0 var(--border) var(--frame-border), 0 12px 0 -4px rgba(0, 0, 0, 0.25);
|
|
||||||
--scanline-size: 6px;
|
|
||||||
--scanline-opacity: 0.08;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"],
|
|
||||||
html[data-theme="auto"] {
|
|
||||||
--bg0: #fbf4e7;
|
|
||||||
--bg1: #fffaf0;
|
|
||||||
--panel: #fffdf8;
|
|
||||||
--panel2: #fff6e5;
|
|
||||||
--text: #10221c;
|
|
||||||
--muted: #3e5a50;
|
|
||||||
--faint: #6b7f77;
|
|
||||||
--link: #0f6b4c;
|
|
||||||
--link2: #ff4f40;
|
|
||||||
--accent: #ff4f40;
|
|
||||||
--accent2: #00b88a;
|
|
||||||
--frame-border: #1b2e27;
|
|
||||||
--code-bg: #0b1713;
|
|
||||||
--code-fg: #eafff6;
|
|
||||||
--code-accent: #67ff9b;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"] {
|
|
||||||
--bg0: #0b1a22;
|
|
||||||
--bg1: #0a1720;
|
|
||||||
--panel: #0e231f;
|
|
||||||
--panel2: #102a24;
|
|
||||||
--text: #c9eadc;
|
|
||||||
--muted: #8ab8aa;
|
|
||||||
--faint: #699b8d;
|
|
||||||
--link: #6fe8c7;
|
|
||||||
--link2: #ff7b63;
|
|
||||||
--accent: #ff4f40;
|
|
||||||
--accent2: #5fdfa2;
|
|
||||||
--frame-border: #6fbfa8;
|
|
||||||
--code-bg: #091814;
|
|
||||||
--code-fg: #d7f5e8;
|
|
||||||
--code-accent: #5fdfa2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
html[data-theme="auto"] {
|
|
||||||
--bg0: #0b1a22;
|
|
||||||
--bg1: #0a1720;
|
|
||||||
--panel: #0e231f;
|
|
||||||
--panel2: #102a24;
|
|
||||||
--text: #c9eadc;
|
|
||||||
--muted: #8ab8aa;
|
|
||||||
--faint: #699b8d;
|
|
||||||
--link: #6fe8c7;
|
|
||||||
--link2: #ff7b63;
|
|
||||||
--accent: #ff4f40;
|
|
||||||
--accent2: #5fdfa2;
|
|
||||||
--frame-border: #6fbfa8;
|
|
||||||
--code-bg: #091814;
|
|
||||||
--code-fg: #d7f5e8;
|
|
||||||
--code-accent: #5fdfa2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: var(--font-body);
|
|
||||||
color: var(--text);
|
|
||||||
background:
|
|
||||||
radial-gradient(1100px 700px at 20% -10%, color-mix(in oklab, var(--accent) 18%, transparent), transparent 55%),
|
|
||||||
radial-gradient(900px 600px at 95% 10%, color-mix(in oklab, var(--accent2) 14%, transparent), transparent 60%),
|
|
||||||
radial-gradient(900px 600px at 50% 120%, color-mix(in oklab, var(--link) 10%, transparent), transparent 55%),
|
|
||||||
linear-gradient(180deg, var(--bg0), var(--bg1));
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before,
|
|
||||||
body::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skip-link {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
left: 10px;
|
|
||||||
z-index: 10;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--panel);
|
|
||||||
color: var(--text);
|
|
||||||
text-decoration: none;
|
|
||||||
transform: translateY(-130%);
|
|
||||||
box-shadow: var(--shadow-px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.skip-link:focus {
|
|
||||||
transform: translateY(0);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 100;
|
|
||||||
padding: 22px 16px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell__frame {
|
|
||||||
max-width: 1120px;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background:
|
|
||||||
linear-gradient(180deg, color-mix(in oklab, var(--panel2) 88%, transparent), color-mix(in oklab, var(--panel) 92%, transparent));
|
|
||||||
box-shadow: var(--shadow-px);
|
|
||||||
border: var(--border) solid var(--frame-border);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell__titlebar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 14px 14px 12px;
|
|
||||||
background:
|
|
||||||
linear-gradient(90deg, color-mix(in oklab, var(--accent) 26%, transparent), transparent 42%),
|
|
||||||
linear-gradient(180deg, color-mix(in oklab, var(--panel) 90%, #000 10%), color-mix(in oklab, var(--panel2) 90%, #000 10%));
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand__logo {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
filter: drop-shadow(0 2px 0 color-mix(in oklab, var(--frame-border) 80%, transparent));
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand__text {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand__name {
|
|
||||||
font-family: var(--font-pixel);
|
|
||||||
letter-spacing: 0.14em;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 1.1;
|
|
||||||
text-shadow: 0 1px 0 color-mix(in oklab, var(--frame-border) 55%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand__hint {
|
|
||||||
margin-top: 2px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--muted);
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar__actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar__cta {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 12px 8px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background:
|
|
||||||
linear-gradient(140deg, color-mix(in oklab, var(--accent) 10%, transparent), transparent 60%),
|
|
||||||
color-mix(in oklab, var(--panel) 92%, transparent);
|
|
||||||
border: var(--border) solid color-mix(in oklab, var(--frame-border) 80%, transparent);
|
|
||||||
color: var(--text);
|
|
||||||
text-decoration: none;
|
|
||||||
box-shadow:
|
|
||||||
0 6px 0 -3px rgba(0, 0, 0, 0.25),
|
|
||||||
inset 0 0 0 1px color-mix(in oklab, var(--panel2) 55%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar__cta:hover {
|
|
||||||
border-color: color-mix(in oklab, var(--accent2) 45%, transparent);
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 2px color-mix(in oklab, var(--accent2) 30%, transparent),
|
|
||||||
0 6px 0 -3px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar__cta:active {
|
|
||||||
transform: translateY(1px);
|
|
||||||
box-shadow: 0 4px 0 -3px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar__cta:focus-visible {
|
|
||||||
outline: 3px solid color-mix(in oklab, var(--accent2) 60%, transparent);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar__cta--accent {
|
|
||||||
background:
|
|
||||||
linear-gradient(120deg, color-mix(in oklab, var(--accent) 22%, transparent), transparent 70%),
|
|
||||||
color-mix(in oklab, var(--panel) 88%, transparent);
|
|
||||||
border-color: color-mix(in oklab, var(--accent) 60%, var(--frame-border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar__cta-label {
|
|
||||||
font-family: var(--font-pixel);
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
font-size: 12px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar__cta-meta {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 20px;
|
|
||||||
padding: 0 8px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
background: var(--code-bg);
|
|
||||||
color: var(--code-accent);
|
|
||||||
border: 1px solid color-mix(in oklab, var(--code-accent) 30%, transparent);
|
|
||||||
box-shadow: inset 0 0 12px color-mix(in oklab, var(--code-accent) 25%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 9px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: color-mix(in oklab, var(--panel) 92%, transparent);
|
|
||||||
border: var(--border) solid var(--frame-border);
|
|
||||||
color: var(--text);
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 6px 0 -3px rgba(0, 0, 0, 0.25);
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle:active {
|
|
||||||
transform: translateY(1px);
|
|
||||||
box-shadow: 0 4px 0 -3px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle__key {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 36px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 9px;
|
|
||||||
background: var(--code-bg);
|
|
||||||
color: var(--code-accent);
|
|
||||||
border: 1px solid color-mix(in oklab, var(--code-accent) 30%, transparent);
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 12px;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
text-shadow: 0 0 14px color-mix(in oklab, var(--code-accent) 55%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle__label {
|
|
||||||
font-family: var(--font-pixel);
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
font-size: 12px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle:focus-visible {
|
|
||||||
outline: 3px solid color-mix(in oklab, var(--accent2) 60%, transparent);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell__nav {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 14px 12px;
|
|
||||||
border-top: 1px solid color-mix(in oklab, var(--frame-border) 25%, transparent);
|
|
||||||
background: linear-gradient(180deg, transparent, color-mix(in oklab, var(--panel2) 78%, transparent));
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav__link {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 6px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--text);
|
|
||||||
background: color-mix(in oklab, var(--panel) 85%, transparent);
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 20%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav__link:hover {
|
|
||||||
border-color: color-mix(in oklab, var(--accent2) 45%, transparent);
|
|
||||||
box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent2) 30%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav__chev {
|
|
||||||
color: var(--accent);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shell__status {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status__dot {
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: radial-gradient(circle at 30% 30%, var(--accent2), color-mix(in oklab, var(--accent2) 30%, #000));
|
|
||||||
box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent2) 18%, transparent), 0 0 18px color-mix(in oklab, var(--accent2) 50%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 18px 16px 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal {
|
|
||||||
position: relative;
|
|
||||||
max-width: 1120px;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
border: var(--border) solid var(--frame-border);
|
|
||||||
background: linear-gradient(180deg, color-mix(in oklab, var(--panel) 92%, transparent), color-mix(in oklab, var(--panel2) 86%, transparent));
|
|
||||||
box-shadow: var(--shadow-px);
|
|
||||||
padding: 18px 16px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal__prompt {
|
|
||||||
display: block;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: var(--code-bg);
|
|
||||||
color: var(--code-fg);
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 18%, transparent);
|
|
||||||
overflow-x: auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt__user {
|
|
||||||
color: var(--code-accent);
|
|
||||||
text-shadow: 0 0 16px color-mix(in oklab, var(--code-accent) 52%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt__host {
|
|
||||||
color: color-mix(in oklab, var(--code-fg) 92%, var(--accent2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt__path {
|
|
||||||
color: color-mix(in oklab, var(--code-fg) 78%, var(--link));
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt__cmd {
|
|
||||||
margin-left: 8px;
|
|
||||||
color: color-mix(in oklab, var(--code-fg) 90%, var(--accent));
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal__summary {
|
|
||||||
margin: 12px 2px 0;
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal__meta {
|
|
||||||
margin: 12px 2px 0;
|
|
||||||
padding: 12px 12px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px dashed color-mix(in oklab, var(--frame-border) 25%, transparent);
|
|
||||||
background: color-mix(in oklab, var(--panel) 76%, transparent);
|
|
||||||
color: var(--faint);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal__meta summary {
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: var(--font-pixel);
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal__meta ul {
|
|
||||||
margin: 10px 0 0 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal__footer {
|
|
||||||
margin-top: 22px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid color-mix(in oklab, var(--frame-border) 20%, transparent);
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__actions {
|
|
||||||
margin-top: 14px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal__footer a {
|
|
||||||
color: var(--link);
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px dotted color-mix(in oklab, var(--link) 55%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal__footer a:hover {
|
|
||||||
color: var(--link2);
|
|
||||||
border-bottom-color: color-mix(in oklab, var(--link2) 75%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer__hint {
|
|
||||||
margin-top: 8px;
|
|
||||||
color: var(--faint);
|
|
||||||
}
|
|
||||||
|
|
||||||
kbd {
|
|
||||||
font-family: var(--font-body);
|
|
||||||
font-size: 0.9em;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid color-mix(in oklab, var(--frame-border) 18%, transparent);
|
|
||||||
background: color-mix(in oklab, var(--panel) 65%, transparent);
|
|
||||||
box-shadow: 0 6px 0 -4px rgba(0, 0, 0, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
@supports not (color: color-mix(in oklab, black, white)) {
|
|
||||||
body::before,
|
|
||||||
body::after {
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
const THEME_STORAGE_KEY = "openclaw:theme";
|
|
||||||
|
|
||||||
function safeGet(key) {
|
|
||||||
try {
|
|
||||||
return localStorage.getItem(key);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function safeSet(key, value) {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(key, value);
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function preferredTheme() {
|
|
||||||
const stored = safeGet(THEME_STORAGE_KEY);
|
|
||||||
if (stored === "light" || stored === "dark") return stored;
|
|
||||||
return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyTheme(theme) {
|
|
||||||
document.documentElement.dataset.theme = theme;
|
|
||||||
|
|
||||||
const toggle = document.querySelector("[data-theme-toggle]");
|
|
||||||
const label = document.querySelector("[data-theme-label]");
|
|
||||||
|
|
||||||
if (toggle instanceof HTMLButtonElement) toggle.setAttribute("aria-pressed", theme === "dark" ? "true" : "false");
|
|
||||||
if (label) label.textContent = theme === "dark" ? "dark" : "light";
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleTheme() {
|
|
||||||
const current = document.documentElement.dataset.theme === "dark" ? "dark" : "light";
|
|
||||||
const next = current === "dark" ? "light" : "dark";
|
|
||||||
safeSet(THEME_STORAGE_KEY, next);
|
|
||||||
applyTheme(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTheme(preferredTheme());
|
|
||||||
|
|
||||||
document.addEventListener("click", (event) => {
|
|
||||||
const target = event.target;
|
|
||||||
const button = target instanceof Element ? target.closest("[data-theme-toggle]") : null;
|
|
||||||
if (button) toggleTheme();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
|
||||||
if (event.key === "F2") {
|
|
||||||
event.preventDefault();
|
|
||||||
toggleTheme();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -17,13 +17,15 @@ the right time, and can optionally deliver output back to a chat.
|
|||||||
If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_,
|
If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_,
|
||||||
cron is the mechanism.
|
cron is the mechanism.
|
||||||
|
|
||||||
|
Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting)
|
||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
|
|
||||||
- Cron runs **inside the Gateway** (not inside the model).
|
- Cron runs **inside the Gateway** (not inside the model).
|
||||||
- Jobs persist under `~/.openclaw/cron/` so restarts don’t lose schedules.
|
- Jobs persist under `~/.openclaw/cron/` so restarts don’t lose schedules.
|
||||||
- Two execution styles:
|
- Two execution styles:
|
||||||
- **Main session**: enqueue a system event, then run on the next heartbeat.
|
- **Main session**: enqueue a system event, then run on the next heartbeat.
|
||||||
- **Isolated**: run a dedicated agent turn in `cron:<jobId>`, optionally deliver output.
|
- **Isolated**: run a dedicated agent turn in `cron:<jobId>`, with delivery (announce by default or none).
|
||||||
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
|
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
|
||||||
|
|
||||||
## Quick start (actionable)
|
## Quick start (actionable)
|
||||||
@@ -40,7 +42,7 @@ openclaw cron add \
|
|||||||
--delete-after-run
|
--delete-after-run
|
||||||
|
|
||||||
openclaw cron list
|
openclaw cron list
|
||||||
openclaw cron run <job-id> --force
|
openclaw cron run <job-id>
|
||||||
openclaw cron runs --id <job-id>
|
openclaw cron runs --id <job-id>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ openclaw cron add \
|
|||||||
--tz "America/Los_Angeles" \
|
--tz "America/Los_Angeles" \
|
||||||
--session isolated \
|
--session isolated \
|
||||||
--message "Summarize overnight updates." \
|
--message "Summarize overnight updates." \
|
||||||
--deliver \
|
--announce \
|
||||||
--channel slack \
|
--channel slack \
|
||||||
--to "channel:C1234567890"
|
--to "channel:C1234567890"
|
||||||
```
|
```
|
||||||
@@ -86,7 +88,8 @@ Think of a cron job as: **when** to run + **what** to do.
|
|||||||
- Main session → `payload.kind = "systemEvent"`
|
- Main session → `payload.kind = "systemEvent"`
|
||||||
- Isolated session → `payload.kind = "agentTurn"`
|
- Isolated session → `payload.kind = "agentTurn"`
|
||||||
|
|
||||||
Optional: `deleteAfterRun: true` removes successful one-shot jobs from the store.
|
Optional: one-shot jobs (`schedule.kind = "at"`) delete after success by default. Set
|
||||||
|
`deleteAfterRun: false` to keep them (they will disable after success).
|
||||||
|
|
||||||
## Concepts
|
## Concepts
|
||||||
|
|
||||||
@@ -96,19 +99,19 @@ A cron job is a stored record with:
|
|||||||
|
|
||||||
- a **schedule** (when it should run),
|
- a **schedule** (when it should run),
|
||||||
- a **payload** (what it should do),
|
- a **payload** (what it should do),
|
||||||
- optional **delivery** (where output should be sent).
|
- optional **delivery mode** (announce or none).
|
||||||
- optional **agent binding** (`agentId`): run the job under a specific agent; if
|
- optional **agent binding** (`agentId`): run the job under a specific agent; if
|
||||||
missing or unknown, the gateway falls back to the default agent.
|
missing or unknown, the gateway falls back to the default agent.
|
||||||
|
|
||||||
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
|
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
|
||||||
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
|
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
|
||||||
Jobs can optionally auto-delete after a successful one-shot run via `deleteAfterRun: true`.
|
One-shot jobs auto-delete after success by default; set `deleteAfterRun: false` to keep them.
|
||||||
|
|
||||||
### Schedules
|
### Schedules
|
||||||
|
|
||||||
Cron supports three schedule kinds:
|
Cron supports three schedule kinds:
|
||||||
|
|
||||||
- `at`: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC.
|
- `at`: one-shot timestamp via `schedule.at` (ISO 8601).
|
||||||
- `every`: fixed interval (ms).
|
- `every`: fixed interval (ms).
|
||||||
- `cron`: 5-field cron expression with optional IANA timezone.
|
- `cron`: 5-field cron expression with optional IANA timezone.
|
||||||
|
|
||||||
@@ -122,8 +125,8 @@ local timezone is used.
|
|||||||
Main jobs enqueue a system event and optionally wake the heartbeat runner.
|
Main jobs enqueue a system event and optionally wake the heartbeat runner.
|
||||||
They must use `payload.kind = "systemEvent"`.
|
They must use `payload.kind = "systemEvent"`.
|
||||||
|
|
||||||
- `wakeMode: "next-heartbeat"` (default): event waits for the next scheduled heartbeat.
|
- `wakeMode: "now"` (default): event triggers an immediate heartbeat run.
|
||||||
- `wakeMode: "now"`: event triggers an immediate heartbeat run.
|
- `wakeMode: "next-heartbeat"`: event waits for the next scheduled heartbeat.
|
||||||
|
|
||||||
This is the best fit when you want the normal heartbeat prompt + main-session context.
|
This is the best fit when you want the normal heartbeat prompt + main-session context.
|
||||||
See [Heartbeat](/gateway/heartbeat).
|
See [Heartbeat](/gateway/heartbeat).
|
||||||
@@ -136,9 +139,13 @@ Key behaviors:
|
|||||||
|
|
||||||
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
|
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
|
||||||
- Each run starts a **fresh session id** (no prior conversation carry-over).
|
- Each run starts a **fresh session id** (no prior conversation carry-over).
|
||||||
- A summary is posted to the main session (prefix `Cron`, configurable).
|
- Default behavior: if `delivery` is omitted, isolated jobs announce a summary (`delivery.mode = "announce"`).
|
||||||
- `wakeMode: "now"` triggers an immediate heartbeat after posting the summary.
|
- `delivery.mode` (isolated-only) chooses what happens:
|
||||||
- If `payload.deliver: true`, output is delivered to a channel; otherwise it stays internal.
|
- `announce`: deliver a summary to the target channel and post a brief summary to the main session.
|
||||||
|
- `none`: internal only (no delivery, no main-session summary).
|
||||||
|
- `wakeMode` controls when the main-session summary posts:
|
||||||
|
- `now`: immediate heartbeat.
|
||||||
|
- `next-heartbeat`: waits for the next scheduled heartbeat.
|
||||||
|
|
||||||
Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spam
|
Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spam
|
||||||
your main chat history.
|
your main chat history.
|
||||||
@@ -155,16 +162,35 @@ Common `agentTurn` fields:
|
|||||||
- `message`: required text prompt.
|
- `message`: required text prompt.
|
||||||
- `model` / `thinking`: optional overrides (see below).
|
- `model` / `thinking`: optional overrides (see below).
|
||||||
- `timeoutSeconds`: optional timeout override.
|
- `timeoutSeconds`: optional timeout override.
|
||||||
- `deliver`: `true` to send output to a channel target.
|
|
||||||
- `channel`: `last` or a specific channel.
|
|
||||||
- `to`: channel-specific target (phone/chat/channel id).
|
|
||||||
- `bestEffortDeliver`: avoid failing the job if delivery fails.
|
|
||||||
|
|
||||||
Isolation options (only for `session=isolated`):
|
Delivery config (isolated jobs only):
|
||||||
|
|
||||||
- `postToMainPrefix` (CLI: `--post-prefix`): prefix for the system event in main.
|
- `delivery.mode`: `none` | `announce`.
|
||||||
- `postToMainMode`: `summary` (default) or `full`.
|
- `delivery.channel`: `last` or a specific channel.
|
||||||
- `postToMainMaxChars`: max chars when `postToMainMode=full` (default 8000).
|
- `delivery.to`: channel-specific target (phone/chat/channel id).
|
||||||
|
- `delivery.bestEffort`: avoid failing the job if announce delivery fails.
|
||||||
|
|
||||||
|
Announce delivery suppresses messaging tool sends for the run; use `delivery.channel`/`delivery.to`
|
||||||
|
to target the chat instead. When `delivery.mode = "none"`, no summary is posted to the main session.
|
||||||
|
|
||||||
|
If `delivery` is omitted for isolated jobs, OpenClaw defaults to `announce`.
|
||||||
|
|
||||||
|
#### Announce delivery flow
|
||||||
|
|
||||||
|
When `delivery.mode = "announce"`, cron delivers directly via the outbound channel adapters.
|
||||||
|
The main agent is not spun up to craft or forward the message.
|
||||||
|
|
||||||
|
Behavior details:
|
||||||
|
|
||||||
|
- Content: delivery uses the isolated run's outbound payloads (text/media) with normal chunking and
|
||||||
|
channel formatting.
|
||||||
|
- Heartbeat-only responses (`HEARTBEAT_OK` with no real content) are not delivered.
|
||||||
|
- If the isolated run already sent a message to the same target via the message tool, delivery is
|
||||||
|
skipped to avoid duplicates.
|
||||||
|
- Missing or invalid delivery targets fail the job unless `delivery.bestEffort = true`.
|
||||||
|
- A short summary is posted to the main session only when `delivery.mode = "announce"`.
|
||||||
|
- The main-session summary respects `wakeMode`: `now` triggers an immediate heartbeat and
|
||||||
|
`next-heartbeat` waits for the next scheduled heartbeat.
|
||||||
|
|
||||||
### Model and thinking overrides
|
### Model and thinking overrides
|
||||||
|
|
||||||
@@ -185,19 +211,16 @@ Resolution priority:
|
|||||||
|
|
||||||
### Delivery (channel + target)
|
### Delivery (channel + target)
|
||||||
|
|
||||||
Isolated jobs can deliver output to a channel. The job payload can specify:
|
Isolated jobs can deliver output to a channel via the top-level `delivery` config:
|
||||||
|
|
||||||
- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`
|
- `delivery.mode`: `announce` (deliver a summary) or `none`.
|
||||||
- `to`: channel-specific recipient target
|
- `delivery.channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`.
|
||||||
|
- `delivery.to`: channel-specific recipient target.
|
||||||
|
|
||||||
If `channel` or `to` is omitted, cron can fall back to the main session’s “last route”
|
Delivery config is only valid for isolated jobs (`sessionTarget: "isolated"`).
|
||||||
(the last place the agent replied).
|
|
||||||
|
|
||||||
Delivery notes:
|
If `delivery.channel` or `delivery.to` is omitted, cron can fall back to the main session’s
|
||||||
|
“last route” (the last place the agent replied).
|
||||||
- If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted.
|
|
||||||
- Use `deliver: true` when you want last-route delivery without an explicit `to`.
|
|
||||||
- Use `deliver: false` to keep output internal even if a `to` is present.
|
|
||||||
|
|
||||||
Target format reminders:
|
Target format reminders:
|
||||||
|
|
||||||
@@ -220,8 +243,8 @@ Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
|||||||
## JSON schema for tool calls
|
## JSON schema for tool calls
|
||||||
|
|
||||||
Use these shapes when calling Gateway `cron.*` tools directly (agent tool calls or RPC).
|
Use these shapes when calling Gateway `cron.*` tools directly (agent tool calls or RPC).
|
||||||
CLI flags accept human durations like `20m`, but tool calls use epoch milliseconds for
|
CLI flags accept human durations like `20m`, but tool calls should use an ISO 8601 string
|
||||||
`atMs` and `everyMs` (ISO timestamps are accepted for `at` times).
|
for `schedule.at` and milliseconds for `schedule.everyMs`.
|
||||||
|
|
||||||
### cron.add params
|
### cron.add params
|
||||||
|
|
||||||
@@ -230,7 +253,7 @@ One-shot, main session job (system event):
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "Reminder",
|
"name": "Reminder",
|
||||||
"schedule": { "kind": "at", "atMs": 1738262400000 },
|
"schedule": { "kind": "at", "at": "2026-02-01T16:00:00Z" },
|
||||||
"sessionTarget": "main",
|
"sessionTarget": "main",
|
||||||
"wakeMode": "now",
|
"wakeMode": "now",
|
||||||
"payload": { "kind": "systemEvent", "text": "Reminder text" },
|
"payload": { "kind": "systemEvent", "text": "Reminder text" },
|
||||||
@@ -248,23 +271,26 @@ Recurring, isolated job with delivery:
|
|||||||
"wakeMode": "next-heartbeat",
|
"wakeMode": "next-heartbeat",
|
||||||
"payload": {
|
"payload": {
|
||||||
"kind": "agentTurn",
|
"kind": "agentTurn",
|
||||||
"message": "Summarize overnight updates.",
|
"message": "Summarize overnight updates."
|
||||||
"deliver": true,
|
},
|
||||||
|
"delivery": {
|
||||||
|
"mode": "announce",
|
||||||
"channel": "slack",
|
"channel": "slack",
|
||||||
"to": "channel:C1234567890",
|
"to": "channel:C1234567890",
|
||||||
"bestEffortDeliver": true
|
"bestEffort": true
|
||||||
},
|
}
|
||||||
"isolation": { "postToMainPrefix": "Cron", "postToMainMode": "summary" }
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `schedule.kind`: `at` (`atMs`), `every` (`everyMs`), or `cron` (`expr`, optional `tz`).
|
- `schedule.kind`: `at` (`at`), `every` (`everyMs`), or `cron` (`expr`, optional `tz`).
|
||||||
- `atMs` and `everyMs` are epoch milliseconds.
|
- `schedule.at` accepts ISO 8601 (timezone optional; treated as UTC when omitted).
|
||||||
|
- `everyMs` is milliseconds.
|
||||||
- `sessionTarget` must be `"main"` or `"isolated"` and must match `payload.kind`.
|
- `sessionTarget` must be `"main"` or `"isolated"` and must match `payload.kind`.
|
||||||
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun`, `isolation`.
|
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`),
|
||||||
- `wakeMode` defaults to `"next-heartbeat"` when omitted.
|
`delivery`.
|
||||||
|
- `wakeMode` defaults to `"now"` when omitted.
|
||||||
|
|
||||||
### cron.update params
|
### cron.update params
|
||||||
|
|
||||||
@@ -341,7 +367,7 @@ openclaw cron add \
|
|||||||
--wake now
|
--wake now
|
||||||
```
|
```
|
||||||
|
|
||||||
Recurring isolated job (deliver to WhatsApp):
|
Recurring isolated job (announce to WhatsApp):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron add \
|
openclaw cron add \
|
||||||
@@ -350,7 +376,7 @@ openclaw cron add \
|
|||||||
--tz "America/Los_Angeles" \
|
--tz "America/Los_Angeles" \
|
||||||
--session isolated \
|
--session isolated \
|
||||||
--message "Summarize inbox + calendar for today." \
|
--message "Summarize inbox + calendar for today." \
|
||||||
--deliver \
|
--announce \
|
||||||
--channel whatsapp \
|
--channel whatsapp \
|
||||||
--to "+15551234567"
|
--to "+15551234567"
|
||||||
```
|
```
|
||||||
@@ -364,7 +390,7 @@ openclaw cron add \
|
|||||||
--tz "America/Los_Angeles" \
|
--tz "America/Los_Angeles" \
|
||||||
--session isolated \
|
--session isolated \
|
||||||
--message "Summarize today; send to the nightly topic." \
|
--message "Summarize today; send to the nightly topic." \
|
||||||
--deliver \
|
--announce \
|
||||||
--channel telegram \
|
--channel telegram \
|
||||||
--to "-1001234567890:topic:123"
|
--to "-1001234567890:topic:123"
|
||||||
```
|
```
|
||||||
@@ -380,7 +406,7 @@ openclaw cron add \
|
|||||||
--message "Weekly deep analysis of project progress." \
|
--message "Weekly deep analysis of project progress." \
|
||||||
--model "opus" \
|
--model "opus" \
|
||||||
--thinking high \
|
--thinking high \
|
||||||
--deliver \
|
--announce \
|
||||||
--channel whatsapp \
|
--channel whatsapp \
|
||||||
--to "+15551234567"
|
--to "+15551234567"
|
||||||
```
|
```
|
||||||
@@ -396,10 +422,11 @@ openclaw cron edit <jobId> --agent ops
|
|||||||
openclaw cron edit <jobId> --clear-agent
|
openclaw cron edit <jobId> --clear-agent
|
||||||
```
|
```
|
||||||
|
|
||||||
Manual run (debug):
|
Manual run (force is the default, use `--due` to only run when due):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron run <jobId> --force
|
openclaw cron run <jobId>
|
||||||
|
openclaw cron run <jobId> --due
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit an existing job (patch fields):
|
Edit an existing job (patch fields):
|
||||||
@@ -437,6 +464,13 @@ openclaw system event --mode now --text "Next heartbeat: check battery."
|
|||||||
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
||||||
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
|
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
|
||||||
|
|
||||||
|
### A recurring job keeps delaying after failures
|
||||||
|
|
||||||
|
- OpenClaw applies exponential retry backoff for recurring jobs after consecutive errors:
|
||||||
|
30s, 1m, 5m, 15m, then 60m between retries.
|
||||||
|
- Backoff resets automatically after the next successful run.
|
||||||
|
- One-shot (`at`) jobs disable after a terminal run (`ok`, `error`, or `skipped`) and do not retry.
|
||||||
|
|
||||||
### Telegram delivers to the wrong place
|
### Telegram delivers to the wrong place
|
||||||
|
|
||||||
- For forum topics, use `-100…:topic:<id>` so it’s explicit and unambiguous.
|
- For forum topics, use `-100…:topic:<id>` so it’s explicit and unambiguous.
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ Cron jobs run at **exact times** and can run in isolated sessions without affect
|
|||||||
- **Exact timing**: 5-field cron expressions with timezone support.
|
- **Exact timing**: 5-field cron expressions with timezone support.
|
||||||
- **Session isolation**: Runs in `cron:<jobId>` without polluting main history.
|
- **Session isolation**: Runs in `cron:<jobId>` without polluting main history.
|
||||||
- **Model overrides**: Use a cheaper or more powerful model per job.
|
- **Model overrides**: Use a cheaper or more powerful model per job.
|
||||||
- **Delivery control**: Can deliver directly to a channel; still posts a summary to main by default (configurable).
|
- **Delivery control**: Isolated jobs default to `announce` (summary); choose `none` as needed.
|
||||||
|
- **Immediate delivery**: Announce mode posts directly without waiting for heartbeat.
|
||||||
- **No agent context needed**: Runs even if main session is idle or compacted.
|
- **No agent context needed**: Runs even if main session is idle or compacted.
|
||||||
- **One-shot support**: `--at` for precise future timestamps.
|
- **One-shot support**: `--at` for precise future timestamps.
|
||||||
|
|
||||||
@@ -104,12 +105,12 @@ openclaw cron add \
|
|||||||
--session isolated \
|
--session isolated \
|
||||||
--message "Generate today's briefing: weather, calendar, top emails, news summary." \
|
--message "Generate today's briefing: weather, calendar, top emails, news summary." \
|
||||||
--model opus \
|
--model opus \
|
||||||
--deliver \
|
--announce \
|
||||||
--channel whatsapp \
|
--channel whatsapp \
|
||||||
--to "+15551234567"
|
--to "+15551234567"
|
||||||
```
|
```
|
||||||
|
|
||||||
This runs at exactly 7:00 AM New York time, uses Opus for quality, and delivers directly to WhatsApp.
|
This runs at exactly 7:00 AM New York time, uses Opus for quality, and announces a summary directly to WhatsApp.
|
||||||
|
|
||||||
### Cron example: One-shot reminder
|
### Cron example: One-shot reminder
|
||||||
|
|
||||||
@@ -173,7 +174,7 @@ The most efficient setup uses **both**:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Daily morning briefing at 7am
|
# Daily morning briefing at 7am
|
||||||
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --deliver
|
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --announce
|
||||||
|
|
||||||
# Weekly project review on Mondays at 9am
|
# Weekly project review on Mondays at 9am
|
||||||
openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated --message "..." --model opus
|
openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated --message "..." --model opus
|
||||||
@@ -215,12 +216,12 @@ See [Lobster](/tools/lobster) for full usage and examples.
|
|||||||
Both heartbeat and cron can interact with the main session, but differently:
|
Both heartbeat and cron can interact with the main session, but differently:
|
||||||
|
|
||||||
| | Heartbeat | Cron (main) | Cron (isolated) |
|
| | Heartbeat | Cron (main) | Cron (isolated) |
|
||||||
| ------- | ------------------------------- | ------------------------ | ---------------------- |
|
| ------- | ------------------------------- | ------------------------ | -------------------------- |
|
||||||
| Session | Main | Main (via system event) | `cron:<jobId>` |
|
| Session | Main | Main (via system event) | `cron:<jobId>` |
|
||||||
| History | Shared | Shared | Fresh each run |
|
| History | Shared | Shared | Fresh each run |
|
||||||
| Context | Full | Full | None (starts clean) |
|
| Context | Full | Full | None (starts clean) |
|
||||||
| Model | Main session model | Main session model | Can override |
|
| Model | Main session model | Main session model | Can override |
|
||||||
| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Summary posted to main |
|
| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Announce summary (default) |
|
||||||
|
|
||||||
### When to use main session cron
|
### When to use main session cron
|
||||||
|
|
||||||
@@ -245,7 +246,7 @@ Use `--session isolated` when you want:
|
|||||||
|
|
||||||
- A clean slate without prior context
|
- A clean slate without prior context
|
||||||
- Different model or thinking settings
|
- Different model or thinking settings
|
||||||
- Output delivered directly to a channel (summary still posts to main by default)
|
- Announce summaries directly to a channel
|
||||||
- History that doesn't clutter main session
|
- History that doesn't clutter main session
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -256,7 +257,7 @@ openclaw cron add \
|
|||||||
--message "Weekly codebase analysis..." \
|
--message "Weekly codebase analysis..." \
|
||||||
--model opus \
|
--model opus \
|
||||||
--thinking high \
|
--thinking high \
|
||||||
--deliver
|
--announce
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cost Considerations
|
## Cost Considerations
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Hooks are small scripts that run when something happens. There are two kinds:
|
|||||||
- **Hooks** (this page): run inside the Gateway when agent events fire, like `/new`, `/reset`, `/stop`, or lifecycle events.
|
- **Hooks** (this page): run inside the Gateway when agent events fire, like `/new`, `/reset`, `/stop`, or lifecycle events.
|
||||||
- **Webhooks**: external HTTP webhooks that let other systems trigger work in OpenClaw. See [Webhook Hooks](/automation/webhook) or use `openclaw webhooks` for Gmail helper commands.
|
- **Webhooks**: external HTTP webhooks that let other systems trigger work in OpenClaw. See [Webhook Hooks](/automation/webhook) or use `openclaw webhooks` for Gmail helper commands.
|
||||||
|
|
||||||
Hooks can also be bundled inside plugins; see [Plugins](/plugin#plugin-hooks).
|
Hooks can also be bundled inside plugins; see [Plugins](/tools/plugin#plugin-hooks).
|
||||||
|
|
||||||
Common uses:
|
Common uses:
|
||||||
|
|
||||||
@@ -444,7 +444,7 @@ openclaw hooks enable session-memory
|
|||||||
openclaw hooks disable command-logger
|
openclaw hooks disable command-logger
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bundled Hooks
|
## Bundled hook reference
|
||||||
|
|
||||||
### session-memory
|
### session-memory
|
||||||
|
|
||||||
@@ -787,6 +787,7 @@ Session reset
|
|||||||
```
|
```
|
||||||
|
|
||||||
3. List all discovered hooks:
|
3. List all discovered hooks:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw hooks list
|
openclaw hooks list
|
||||||
```
|
```
|
||||||
@@ -818,6 +819,7 @@ Look for missing:
|
|||||||
2. Restart your gateway process so hooks reload.
|
2. Restart your gateway process so hooks reload.
|
||||||
|
|
||||||
3. Check gateway logs for errors:
|
3. Check gateway logs for errors:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/clawlog.sh | grep hook
|
./scripts/clawlog.sh | grep hook
|
||||||
```
|
```
|
||||||
@@ -892,6 +894,7 @@ node -e "import('./path/to/handler.ts').then(console.log)"
|
|||||||
```
|
```
|
||||||
|
|
||||||
4. Verify and restart your gateway process:
|
4. Verify and restart your gateway process:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw hooks list
|
openclaw hooks list
|
||||||
# Should show: 🎯 my-hook ✓
|
# Should show: 🎯 my-hook ✓
|
||||||
122
docs/automation/troubleshooting.md
Normal file
122
docs/automation/troubleshooting.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
---
|
||||||
|
summary: "Troubleshoot cron and heartbeat scheduling and delivery"
|
||||||
|
read_when:
|
||||||
|
- Cron did not run
|
||||||
|
- Cron ran but no message was delivered
|
||||||
|
- Heartbeat seems silent or skipped
|
||||||
|
title: "Automation Troubleshooting"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Automation troubleshooting
|
||||||
|
|
||||||
|
Use this page for scheduler and delivery issues (`cron` + `heartbeat`).
|
||||||
|
|
||||||
|
## Command ladder
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw status
|
||||||
|
openclaw gateway status
|
||||||
|
openclaw logs --follow
|
||||||
|
openclaw doctor
|
||||||
|
openclaw channels status --probe
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run automation checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw cron status
|
||||||
|
openclaw cron list
|
||||||
|
openclaw system heartbeat last
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cron not firing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw cron status
|
||||||
|
openclaw cron list
|
||||||
|
openclaw cron runs --id <jobId> --limit 20
|
||||||
|
openclaw logs --follow
|
||||||
|
```
|
||||||
|
|
||||||
|
Good output looks like:
|
||||||
|
|
||||||
|
- `cron status` reports enabled and a future `nextWakeAtMs`.
|
||||||
|
- Job is enabled and has a valid schedule/timezone.
|
||||||
|
- `cron runs` shows `ok` or explicit skip reason.
|
||||||
|
|
||||||
|
Common signatures:
|
||||||
|
|
||||||
|
- `cron: scheduler disabled; jobs will not run automatically` → cron disabled in config/env.
|
||||||
|
- `cron: timer tick failed` → scheduler tick crashed; inspect surrounding stack/log context.
|
||||||
|
- `reason: not-due` in run output → manual run called without `--force` and job not due yet.
|
||||||
|
|
||||||
|
## Cron fired but no delivery
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw cron runs --id <jobId> --limit 20
|
||||||
|
openclaw cron list
|
||||||
|
openclaw channels status --probe
|
||||||
|
openclaw logs --follow
|
||||||
|
```
|
||||||
|
|
||||||
|
Good output looks like:
|
||||||
|
|
||||||
|
- Run status is `ok`.
|
||||||
|
- Delivery mode/target are set for isolated jobs.
|
||||||
|
- Channel probe reports target channel connected.
|
||||||
|
|
||||||
|
Common signatures:
|
||||||
|
|
||||||
|
- Run succeeded but delivery mode is `none` → no external message is expected.
|
||||||
|
- Delivery target missing/invalid (`channel`/`to`) → run may succeed internally but skip outbound.
|
||||||
|
- Channel auth errors (`unauthorized`, `missing_scope`, `Forbidden`) → delivery blocked by channel credentials/permissions.
|
||||||
|
|
||||||
|
## Heartbeat suppressed or skipped
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw system heartbeat last
|
||||||
|
openclaw logs --follow
|
||||||
|
openclaw config get agents.defaults.heartbeat
|
||||||
|
openclaw channels status --probe
|
||||||
|
```
|
||||||
|
|
||||||
|
Good output looks like:
|
||||||
|
|
||||||
|
- Heartbeat enabled with non-zero interval.
|
||||||
|
- Last heartbeat result is `ran` (or skip reason is understood).
|
||||||
|
|
||||||
|
Common signatures:
|
||||||
|
|
||||||
|
- `heartbeat skipped` with `reason=quiet-hours` → outside `activeHours`.
|
||||||
|
- `requests-in-flight` → main lane busy; heartbeat deferred.
|
||||||
|
- `empty-heartbeat-file` → `HEARTBEAT.md` exists but has no actionable content.
|
||||||
|
- `alerts-disabled` → visibility settings suppress outbound heartbeat messages.
|
||||||
|
|
||||||
|
## Timezone and activeHours gotchas
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw config get agents.defaults.heartbeat.activeHours
|
||||||
|
openclaw config get agents.defaults.heartbeat.activeHours.timezone
|
||||||
|
openclaw config get agents.defaults.userTimezone || echo "agents.defaults.userTimezone not set"
|
||||||
|
openclaw cron list
|
||||||
|
openclaw logs --follow
|
||||||
|
```
|
||||||
|
|
||||||
|
Quick rules:
|
||||||
|
|
||||||
|
- `Config path not found: agents.defaults.userTimezone` means the key is unset; heartbeat falls back to host timezone (or `activeHours.timezone` if set).
|
||||||
|
- Cron without `--tz` uses gateway host timezone.
|
||||||
|
- Heartbeat `activeHours` uses configured timezone resolution (`user`, `local`, or explicit IANA tz).
|
||||||
|
- ISO timestamps without timezone are treated as UTC for cron `at` schedules.
|
||||||
|
|
||||||
|
Common signatures:
|
||||||
|
|
||||||
|
- Jobs run at the wrong wall-clock time after host timezone changes.
|
||||||
|
- Heartbeat always skipped during your daytime because `activeHours.timezone` is wrong.
|
||||||
|
|
||||||
|
Related:
|
||||||
|
|
||||||
|
- [/automation/cron-jobs](/automation/cron-jobs)
|
||||||
|
- [/gateway/heartbeat](/gateway/heartbeat)
|
||||||
|
- [/automation/cron-vs-heartbeat](/automation/cron-vs-heartbeat)
|
||||||
|
- [/concepts/timezone](/concepts/timezone)
|
||||||
@@ -12,7 +12,7 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
|
|||||||
|
|
||||||
## Get an API key
|
## Get an API key
|
||||||
|
|
||||||
1. Create a Brave Search API account at https://brave.com/search/api/
|
1. Create a Brave Search API account at [https://brave.com/search/api/](https://brave.com/search/api/)
|
||||||
2. In the dashboard, choose the **Data for Search** plan and generate an API key.
|
2. In the dashboard, choose the **Data for Search** plan and generate an API key.
|
||||||
3. Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
|
3. Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
|
|||||||
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
||||||
- Incoming messages arrive via webhooks; outgoing replies, typing indicators, read receipts, and tapbacks are REST calls.
|
- Incoming messages arrive via webhooks; outgoing replies, typing indicators, read receipts, and tapbacks are REST calls.
|
||||||
- Attachments and stickers are ingested as inbound media (and surfaced to the agent when possible).
|
- Attachments and stickers are ingested as inbound media (and surfaced to the agent when possible).
|
||||||
- Pairing/allowlist works the same way as other channels (`/start/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
|
- Pairing/allowlist works the same way as other channels (`/channels/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
|
||||||
- Reactions are surfaced as system events just like Slack/Telegram so agents can "mention" them before replying.
|
- Reactions are surfaced as system events just like Slack/Telegram so agents can "mention" them before replying.
|
||||||
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
|
|||||||
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
||||||
2. In the BlueBubbles config, enable the web API and set a password.
|
2. In the BlueBubbles config, enable the web API and set a password.
|
||||||
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
|
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -39,9 +40,84 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
||||||
5. Start the gateway; it will register the webhook handler and start pairing.
|
5. Start the gateway; it will register the webhook handler and start pairing.
|
||||||
|
|
||||||
|
## Keeping Messages.app alive (VM / headless setups)
|
||||||
|
|
||||||
|
Some macOS VM / always-on setups can end up with Messages.app going “idle” (incoming events stop until the app is opened/foregrounded). A simple workaround is to **poke Messages every 5 minutes** using an AppleScript + LaunchAgent.
|
||||||
|
|
||||||
|
### 1) Save the AppleScript
|
||||||
|
|
||||||
|
Save this as:
|
||||||
|
|
||||||
|
- `~/Scripts/poke-messages.scpt`
|
||||||
|
|
||||||
|
Example script (non-interactive; does not steal focus):
|
||||||
|
|
||||||
|
```applescript
|
||||||
|
try
|
||||||
|
tell application "Messages"
|
||||||
|
if not running then
|
||||||
|
launch
|
||||||
|
end if
|
||||||
|
|
||||||
|
-- Touch the scripting interface to keep the process responsive.
|
||||||
|
set _chatCount to (count of chats)
|
||||||
|
end tell
|
||||||
|
on error
|
||||||
|
-- Ignore transient failures (first-run prompts, locked session, etc).
|
||||||
|
end try
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) Install a LaunchAgent
|
||||||
|
|
||||||
|
Save this as:
|
||||||
|
|
||||||
|
- `~/Library/LaunchAgents/com.user.poke-messages.plist`
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>com.user.poke-messages</string>
|
||||||
|
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/bin/bash</string>
|
||||||
|
<string>-lc</string>
|
||||||
|
<string>/usr/bin/osascript "$HOME/Scripts/poke-messages.scpt"</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
|
<key>StartInterval</key>
|
||||||
|
<integer>300</integer>
|
||||||
|
|
||||||
|
<key>StandardOutPath</key>
|
||||||
|
<string>/tmp/poke-messages.log</string>
|
||||||
|
<key>StandardErrorPath</key>
|
||||||
|
<string>/tmp/poke-messages.err</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- This runs **every 300 seconds** and **on login**.
|
||||||
|
- The first run may trigger macOS **Automation** prompts (`osascript` → Messages). Approve them in the same user session that runs the LaunchAgent.
|
||||||
|
|
||||||
|
Load it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
launchctl unload ~/Library/LaunchAgents/com.user.poke-messages.plist 2>/dev/null || true
|
||||||
|
launchctl load ~/Library/LaunchAgents/com.user.poke-messages.plist
|
||||||
|
```
|
||||||
|
|
||||||
## Onboarding
|
## Onboarding
|
||||||
|
|
||||||
BlueBubbles is available in the interactive setup wizard:
|
BlueBubbles is available in the interactive setup wizard:
|
||||||
@@ -73,7 +149,7 @@ DMs:
|
|||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list bluebubbles`
|
- `openclaw pairing list bluebubbles`
|
||||||
- `openclaw pairing approve bluebubbles <CODE>`
|
- `openclaw pairing approve bluebubbles <CODE>`
|
||||||
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||||
|
|
||||||
Groups:
|
Groups:
|
||||||
|
|
||||||
@@ -261,4 +337,4 @@ Prefer `chat_guid` for stable routing:
|
|||||||
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
|
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
|
||||||
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
|
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
|
||||||
|
|
||||||
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/plugins) guide.
|
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide.
|
||||||
|
|||||||
@@ -437,6 +437,6 @@ Planned features:
|
|||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [Multi-Agent Configuration](/multi-agent-sandbox-tools)
|
- [Multi-Agent Configuration](/tools/multi-agent-sandbox-tools)
|
||||||
- [Routing Configuration](/concepts/channel-routing)
|
- [Routing Configuration](/channels/channel-routing)
|
||||||
- [Session Management](/concepts/sessions)
|
- [Session Management](/concepts/sessions)
|
||||||
@@ -68,7 +68,7 @@ Config:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
See: [Broadcast Groups](/broadcast-groups).
|
See: [Broadcast Groups](/channels/broadcast-groups).
|
||||||
|
|
||||||
## Config overview
|
## Config overview
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ In **Bot** → **Privileged Gateway Intents**, enable:
|
|||||||
- **Message Content Intent** (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages)
|
- **Message Content Intent** (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages)
|
||||||
- **Server Members Intent** (recommended; required for some member/user lookups and allowlist matching in guilds)
|
- **Server Members Intent** (recommended; required for some member/user lookups and allowlist matching in guilds)
|
||||||
|
|
||||||
You usually do **not** need **Presence Intent**.
|
You usually do **not** need **Presence Intent**. Setting the bot's own presence (`setPresence` action) uses gateway OP3 and does not require this intent; it is only needed if you want to receive presence updates about other guild members.
|
||||||
|
|
||||||
### 3) Generate an invite URL (OAuth2 URL Generator)
|
### 3) Generate an invite URL (OAuth2 URL Generator)
|
||||||
|
|
||||||
@@ -196,6 +196,7 @@ Notes:
|
|||||||
- If `channels` is present, any channel not listed is denied by default.
|
- If `channels` is present, any channel not listed is denied by default.
|
||||||
- Use a `"*"` channel entry to apply defaults across all channels; explicit channel entries override the wildcard.
|
- Use a `"*"` channel entry to apply defaults across all channels; explicit channel entries override the wildcard.
|
||||||
- Threads inherit parent channel config (allowlist, `requireMention`, skills, prompts, etc.) unless you add the thread channel id explicitly.
|
- Threads inherit parent channel config (allowlist, `requireMention`, skills, prompts, etc.) unless you add the thread channel id explicitly.
|
||||||
|
- Owner hint: when a per-guild or per-channel `users` allowlist matches the sender, OpenClaw treats that sender as the owner in the system prompt. For a global owner across channels, set `commands.ownerAllowFrom`.
|
||||||
- Bot-authored messages are ignored by default; set `channels.discord.allowBots=true` to allow them (own messages remain filtered).
|
- Bot-authored messages are ignored by default; set `channels.discord.allowBots=true` to allow them (own messages remain filtered).
|
||||||
- Warning: If you allow replies to other bots (`channels.discord.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.discord.guilds.*.channels.<id>.users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`.
|
- Warning: If you allow replies to other bots (`channels.discord.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.discord.guilds.*.channels.<id>.users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`.
|
||||||
|
|
||||||
@@ -278,6 +279,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
|||||||
voiceStatus: true,
|
voiceStatus: true,
|
||||||
events: true,
|
events: true,
|
||||||
moderation: false,
|
moderation: false,
|
||||||
|
presence: false,
|
||||||
},
|
},
|
||||||
replyToMode: "off",
|
replyToMode: "off",
|
||||||
dm: {
|
dm: {
|
||||||
@@ -333,7 +335,7 @@ ack reaction after the bot replies.
|
|||||||
- `guilds.<id>.channels.<channel>.toolsBySender`: optional per-sender tool policy overrides within the channel (`"*"` wildcard supported).
|
- `guilds.<id>.channels.<channel>.toolsBySender`: optional per-sender tool policy overrides within the channel (`"*"` wildcard supported).
|
||||||
- `guilds.<id>.channels.<channel>.users`: optional per-channel user allowlist.
|
- `guilds.<id>.channels.<channel>.users`: optional per-channel user allowlist.
|
||||||
- `guilds.<id>.channels.<channel>.skills`: skill filter (omit = all skills, empty = none).
|
- `guilds.<id>.channels.<channel>.skills`: skill filter (omit = all skills, empty = none).
|
||||||
- `guilds.<id>.channels.<channel>.systemPrompt`: extra system prompt for the channel (combined with channel topic).
|
- `guilds.<id>.channels.<channel>.systemPrompt`: extra system prompt for the channel. Discord channel topics are injected as **untrusted** context (not system prompt).
|
||||||
- `guilds.<id>.channels.<channel>.enabled`: set `false` to disable the channel.
|
- `guilds.<id>.channels.<channel>.enabled`: set `false` to disable the channel.
|
||||||
- `guilds.<id>.channels`: channel rules (keys are channel slugs or ids).
|
- `guilds.<id>.channels`: channel rules (keys are channel slugs or ids).
|
||||||
- `guilds.<id>.requireMention`: per-guild mention requirement (overridable per channel).
|
- `guilds.<id>.requireMention`: per-guild mention requirement (overridable per channel).
|
||||||
@@ -353,6 +355,7 @@ ack reaction after the bot replies.
|
|||||||
- `channels` (create/edit/delete channels + categories + permissions)
|
- `channels` (create/edit/delete channels + categories + permissions)
|
||||||
- `roles` (role add/remove, default `false`)
|
- `roles` (role add/remove, default `false`)
|
||||||
- `moderation` (timeout/kick/ban, default `false`)
|
- `moderation` (timeout/kick/ban, default `false`)
|
||||||
|
- `presence` (bot status/activity, default `false`)
|
||||||
- `execApprovals`: Discord-only exec approval DMs (button UI). Supports `enabled`, `approvers`, `agentFilter`, `sessionFilter`.
|
- `execApprovals`: Discord-only exec approval DMs (button UI). Supports `enabled`, `approvers`, `agentFilter`, `sessionFilter`.
|
||||||
|
|
||||||
Reaction notifications use `guilds.<id>.reactionNotifications`:
|
Reaction notifications use `guilds.<id>.reactionNotifications`:
|
||||||
@@ -412,6 +415,7 @@ Allowlist notes (PK-enabled):
|
|||||||
| events | enabled | List/create scheduled events |
|
| events | enabled | List/create scheduled events |
|
||||||
| roles | disabled | Role add/remove |
|
| roles | disabled | Role add/remove |
|
||||||
| moderation | disabled | Timeout/kick/ban |
|
| moderation | disabled | Timeout/kick/ban |
|
||||||
|
| presence | disabled | Bot status/activity (setPresence) |
|
||||||
|
|
||||||
- `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag.
|
- `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag.
|
||||||
|
|
||||||
@@ -460,6 +464,7 @@ The agent can call `discord` with actions like:
|
|||||||
- `searchMessages`, `memberInfo`, `roleInfo`, `roleAdd`, `roleRemove`, `emojiList`
|
- `searchMessages`, `memberInfo`, `roleInfo`, `roleAdd`, `roleRemove`, `emojiList`
|
||||||
- `channelInfo`, `channelList`, `voiceStatus`, `eventList`, `eventCreate`
|
- `channelInfo`, `channelList`, `voiceStatus`, `eventList`, `eventCreate`
|
||||||
- `timeout`, `kick`, `ban`
|
- `timeout`, `kick`, `ban`
|
||||||
|
- `setPresence` (bot activity and online status)
|
||||||
|
|
||||||
Discord message ids are surfaced in the injected context (`[discord message id: …]` and history lines) so the agent can target them.
|
Discord message ids are surfaced in the injected context (`[discord message id: …]` and history lines) so the agent can target them.
|
||||||
Emoji can be unicode (e.g., `✅`) or custom emoji syntax like `<:party_blob:1234567890>`.
|
Emoji can be unicode (e.g., `✅`) or custom emoji syntax like `<:party_blob:1234567890>`.
|
||||||
|
|||||||
579
docs/channels/feishu.md
Normal file
579
docs/channels/feishu.md
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
---
|
||||||
|
summary: "Feishu bot overview, features, and configuration"
|
||||||
|
read_when:
|
||||||
|
- You want to connect a Feishu/Lark bot
|
||||||
|
- You are configuring the Feishu channel
|
||||||
|
title: Feishu
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feishu bot
|
||||||
|
|
||||||
|
Feishu (Lark) is a team chat platform used by companies for messaging and collaboration. This plugin connects OpenClaw to a Feishu/Lark bot using the platform’s WebSocket event subscription so messages can be received without exposing a public webhook URL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugin required
|
||||||
|
|
||||||
|
Install the Feishu plugin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw plugins install @openclaw/feishu
|
||||||
|
```
|
||||||
|
|
||||||
|
Local checkout (when running from a git repo):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw plugins install ./extensions/feishu
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
There are two ways to add the Feishu channel:
|
||||||
|
|
||||||
|
### Method 1: onboarding wizard (recommended)
|
||||||
|
|
||||||
|
If you just installed OpenClaw, run the wizard:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw onboard
|
||||||
|
```
|
||||||
|
|
||||||
|
The wizard guides you through:
|
||||||
|
|
||||||
|
1. Creating a Feishu app and collecting credentials
|
||||||
|
2. Configuring app credentials in OpenClaw
|
||||||
|
3. Starting the gateway
|
||||||
|
|
||||||
|
✅ **After configuration**, check gateway status:
|
||||||
|
|
||||||
|
- `openclaw gateway status`
|
||||||
|
- `openclaw logs --follow`
|
||||||
|
|
||||||
|
### Method 2: CLI setup
|
||||||
|
|
||||||
|
If you already completed initial install, add the channel via CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw channels add
|
||||||
|
```
|
||||||
|
|
||||||
|
Choose **Feishu**, then enter the App ID and App Secret.
|
||||||
|
|
||||||
|
✅ **After configuration**, manage the gateway:
|
||||||
|
|
||||||
|
- `openclaw gateway status`
|
||||||
|
- `openclaw gateway restart`
|
||||||
|
- `openclaw logs --follow`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Create a Feishu app
|
||||||
|
|
||||||
|
### 1. Open Feishu Open Platform
|
||||||
|
|
||||||
|
Visit [Feishu Open Platform](https://open.feishu.cn/app) and sign in.
|
||||||
|
|
||||||
|
Lark (global) tenants should use [https://open.larksuite.com/app](https://open.larksuite.com/app) and set `domain: "lark"` in the Feishu config.
|
||||||
|
|
||||||
|
### 2. Create an app
|
||||||
|
|
||||||
|
1. Click **Create enterprise app**
|
||||||
|
2. Fill in the app name + description
|
||||||
|
3. Choose an app icon
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 3. Copy credentials
|
||||||
|
|
||||||
|
From **Credentials & Basic Info**, copy:
|
||||||
|
|
||||||
|
- **App ID** (format: `cli_xxx`)
|
||||||
|
- **App Secret**
|
||||||
|
|
||||||
|
❗ **Important:** keep the App Secret private.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 4. Configure permissions
|
||||||
|
|
||||||
|
On **Permissions**, click **Batch import** and paste:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scopes": {
|
||||||
|
"tenant": [
|
||||||
|
"aily:file:read",
|
||||||
|
"aily:file:write",
|
||||||
|
"application:application.app_message_stats.overview:readonly",
|
||||||
|
"application:application:self_manage",
|
||||||
|
"application:bot.menu:write",
|
||||||
|
"contact:user.employee_id:readonly",
|
||||||
|
"corehr:file:download",
|
||||||
|
"event:ip_list",
|
||||||
|
"im:chat.access_event.bot_p2p_chat:read",
|
||||||
|
"im:chat.members:bot_access",
|
||||||
|
"im:message",
|
||||||
|
"im:message.group_at_msg:readonly",
|
||||||
|
"im:message.p2p_msg:readonly",
|
||||||
|
"im:message:readonly",
|
||||||
|
"im:message:send_as_bot",
|
||||||
|
"im:resource"
|
||||||
|
],
|
||||||
|
"user": ["aily:file:read", "aily:file:write", "im:chat.access_event.bot_p2p_chat:read"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5. Enable bot capability
|
||||||
|
|
||||||
|
In **App Capability** > **Bot**:
|
||||||
|
|
||||||
|
1. Enable bot capability
|
||||||
|
2. Set the bot name
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 6. Configure event subscription
|
||||||
|
|
||||||
|
⚠️ **Important:** before setting event subscription, make sure:
|
||||||
|
|
||||||
|
1. You already ran `openclaw channels add` for Feishu
|
||||||
|
2. The gateway is running (`openclaw gateway status`)
|
||||||
|
|
||||||
|
In **Event Subscription**:
|
||||||
|
|
||||||
|
1. Choose **Use long connection to receive events** (WebSocket)
|
||||||
|
2. Add the event: `im.message.receive_v1`
|
||||||
|
|
||||||
|
⚠️ If the gateway is not running, the long-connection setup may fail to save.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 7. Publish the app
|
||||||
|
|
||||||
|
1. Create a version in **Version Management & Release**
|
||||||
|
2. Submit for review and publish
|
||||||
|
3. Wait for admin approval (enterprise apps usually auto-approve)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Configure OpenClaw
|
||||||
|
|
||||||
|
### Configure with the wizard (recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw channels add
|
||||||
|
```
|
||||||
|
|
||||||
|
Choose **Feishu** and paste your App ID + App Secret.
|
||||||
|
|
||||||
|
### Configure via config file
|
||||||
|
|
||||||
|
Edit `~/.openclaw/openclaw.json`:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
feishu: {
|
||||||
|
enabled: true,
|
||||||
|
dmPolicy: "pairing",
|
||||||
|
accounts: {
|
||||||
|
main: {
|
||||||
|
appId: "cli_xxx",
|
||||||
|
appSecret: "xxx",
|
||||||
|
botName: "My AI assistant",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure via environment variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export FEISHU_APP_ID="cli_xxx"
|
||||||
|
export FEISHU_APP_SECRET="xxx"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lark (global) domain
|
||||||
|
|
||||||
|
If your tenant is on Lark (international), set the domain to `lark` (or a full domain string). You can set it at `channels.feishu.domain` or per account (`channels.feishu.accounts.<id>.domain`).
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
feishu: {
|
||||||
|
domain: "lark",
|
||||||
|
accounts: {
|
||||||
|
main: {
|
||||||
|
appId: "cli_xxx",
|
||||||
|
appSecret: "xxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Start + test
|
||||||
|
|
||||||
|
### 1. Start the gateway
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Send a test message
|
||||||
|
|
||||||
|
In Feishu, find your bot and send a message.
|
||||||
|
|
||||||
|
### 3. Approve pairing
|
||||||
|
|
||||||
|
By default, the bot replies with a pairing code. Approve it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw pairing approve feishu <CODE>
|
||||||
|
```
|
||||||
|
|
||||||
|
After approval, you can chat normally.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- **Feishu bot channel**: Feishu bot managed by the gateway
|
||||||
|
- **Deterministic routing**: replies always return to Feishu
|
||||||
|
- **Session isolation**: DMs share a main session; groups are isolated
|
||||||
|
- **WebSocket connection**: long connection via Feishu SDK, no public URL needed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Access control
|
||||||
|
|
||||||
|
### Direct messages
|
||||||
|
|
||||||
|
- **Default**: `dmPolicy: "pairing"` (unknown users get a pairing code)
|
||||||
|
- **Approve pairing**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw pairing list feishu
|
||||||
|
openclaw pairing approve feishu <CODE>
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Allowlist mode**: set `channels.feishu.allowFrom` with allowed Open IDs
|
||||||
|
|
||||||
|
### Group chats
|
||||||
|
|
||||||
|
**1. Group policy** (`channels.feishu.groupPolicy`):
|
||||||
|
|
||||||
|
- `"open"` = allow everyone in groups (default)
|
||||||
|
- `"allowlist"` = only allow `groupAllowFrom`
|
||||||
|
- `"disabled"` = disable group messages
|
||||||
|
|
||||||
|
**2. Mention requirement** (`channels.feishu.groups.<chat_id>.requireMention`):
|
||||||
|
|
||||||
|
- `true` = require @mention (default)
|
||||||
|
- `false` = respond without mentions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Group configuration examples
|
||||||
|
|
||||||
|
### Allow all groups, require @mention (default)
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
feishu: {
|
||||||
|
groupPolicy: "open",
|
||||||
|
// Default requireMention: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Allow all groups, no @mention required
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
feishu: {
|
||||||
|
groups: {
|
||||||
|
oc_xxx: { requireMention: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Allow specific users in groups only
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
feishu: {
|
||||||
|
groupPolicy: "allowlist",
|
||||||
|
groupAllowFrom: ["ou_xxx", "ou_yyy"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Get group/user IDs
|
||||||
|
|
||||||
|
### Group IDs (chat_id)
|
||||||
|
|
||||||
|
Group IDs look like `oc_xxx`.
|
||||||
|
|
||||||
|
**Method 1 (recommended)**
|
||||||
|
|
||||||
|
1. Start the gateway and @mention the bot in the group
|
||||||
|
2. Run `openclaw logs --follow` and look for `chat_id`
|
||||||
|
|
||||||
|
**Method 2**
|
||||||
|
|
||||||
|
Use the Feishu API debugger to list group chats.
|
||||||
|
|
||||||
|
### User IDs (open_id)
|
||||||
|
|
||||||
|
User IDs look like `ou_xxx`.
|
||||||
|
|
||||||
|
**Method 1 (recommended)**
|
||||||
|
|
||||||
|
1. Start the gateway and DM the bot
|
||||||
|
2. Run `openclaw logs --follow` and look for `open_id`
|
||||||
|
|
||||||
|
**Method 2**
|
||||||
|
|
||||||
|
Check pairing requests for user Open IDs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw pairing list feishu
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| --------- | ----------------- |
|
||||||
|
| `/status` | Show bot status |
|
||||||
|
| `/reset` | Reset the session |
|
||||||
|
| `/model` | Show/switch model |
|
||||||
|
|
||||||
|
> Note: Feishu does not support native command menus yet, so commands must be sent as text.
|
||||||
|
|
||||||
|
## Gateway management commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| -------------------------- | ----------------------------- |
|
||||||
|
| `openclaw gateway status` | Show gateway status |
|
||||||
|
| `openclaw gateway install` | Install/start gateway service |
|
||||||
|
| `openclaw gateway stop` | Stop gateway service |
|
||||||
|
| `openclaw gateway restart` | Restart gateway service |
|
||||||
|
| `openclaw logs --follow` | Tail gateway logs |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Bot does not respond in group chats
|
||||||
|
|
||||||
|
1. Ensure the bot is added to the group
|
||||||
|
2. Ensure you @mention the bot (default behavior)
|
||||||
|
3. Check `groupPolicy` is not set to `"disabled"`
|
||||||
|
4. Check logs: `openclaw logs --follow`
|
||||||
|
|
||||||
|
### Bot does not receive messages
|
||||||
|
|
||||||
|
1. Ensure the app is published and approved
|
||||||
|
2. Ensure event subscription includes `im.message.receive_v1`
|
||||||
|
3. Ensure **long connection** is enabled
|
||||||
|
4. Ensure app permissions are complete
|
||||||
|
5. Ensure the gateway is running: `openclaw gateway status`
|
||||||
|
6. Check logs: `openclaw logs --follow`
|
||||||
|
|
||||||
|
### App Secret leak
|
||||||
|
|
||||||
|
1. Reset the App Secret in Feishu Open Platform
|
||||||
|
2. Update the App Secret in your config
|
||||||
|
3. Restart the gateway
|
||||||
|
|
||||||
|
### Message send failures
|
||||||
|
|
||||||
|
1. Ensure the app has `im:message:send_as_bot` permission
|
||||||
|
2. Ensure the app is published
|
||||||
|
3. Check logs for detailed errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced configuration
|
||||||
|
|
||||||
|
### Multiple accounts
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
feishu: {
|
||||||
|
accounts: {
|
||||||
|
main: {
|
||||||
|
appId: "cli_xxx",
|
||||||
|
appSecret: "xxx",
|
||||||
|
botName: "Primary bot",
|
||||||
|
},
|
||||||
|
backup: {
|
||||||
|
appId: "cli_yyy",
|
||||||
|
appSecret: "yyy",
|
||||||
|
botName: "Backup bot",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Message limits
|
||||||
|
|
||||||
|
- `textChunkLimit`: outbound text chunk size (default: 2000 chars)
|
||||||
|
- `mediaMaxMb`: media upload/download limit (default: 30MB)
|
||||||
|
|
||||||
|
### Streaming
|
||||||
|
|
||||||
|
Feishu supports streaming replies via interactive cards. When enabled, the bot updates a card as it generates text.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
feishu: {
|
||||||
|
streaming: true, // enable streaming card output (default true)
|
||||||
|
blockStreaming: true, // enable block-level streaming (default true)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `streaming: false` to wait for the full reply before sending.
|
||||||
|
|
||||||
|
### Multi-agent routing
|
||||||
|
|
||||||
|
Use `bindings` to route Feishu DMs or groups to different agents.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
list: [
|
||||||
|
{ id: "main" },
|
||||||
|
{
|
||||||
|
id: "clawd-fan",
|
||||||
|
workspace: "/home/user/clawd-fan",
|
||||||
|
agentDir: "/home/user/.openclaw/agents/clawd-fan/agent",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "clawd-xi",
|
||||||
|
workspace: "/home/user/clawd-xi",
|
||||||
|
agentDir: "/home/user/.openclaw/agents/clawd-xi/agent",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
agentId: "main",
|
||||||
|
match: {
|
||||||
|
channel: "feishu",
|
||||||
|
peer: { kind: "dm", id: "ou_xxx" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
agentId: "clawd-fan",
|
||||||
|
match: {
|
||||||
|
channel: "feishu",
|
||||||
|
peer: { kind: "dm", id: "ou_yyy" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
agentId: "clawd-xi",
|
||||||
|
match: {
|
||||||
|
channel: "feishu",
|
||||||
|
peer: { kind: "group", id: "oc_zzz" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Routing fields:
|
||||||
|
|
||||||
|
- `match.channel`: `"feishu"`
|
||||||
|
- `match.peer.kind`: `"dm"` or `"group"`
|
||||||
|
- `match.peer.id`: user Open ID (`ou_xxx`) or group ID (`oc_xxx`)
|
||||||
|
|
||||||
|
See [Get group/user IDs](#get-groupuser-ids) for lookup tips.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration reference
|
||||||
|
|
||||||
|
Full configuration: [Gateway configuration](/gateway/configuration)
|
||||||
|
|
||||||
|
Key options:
|
||||||
|
|
||||||
|
| Setting | Description | Default |
|
||||||
|
| ------------------------------------------------- | ------------------------------- | --------- |
|
||||||
|
| `channels.feishu.enabled` | Enable/disable channel | `true` |
|
||||||
|
| `channels.feishu.domain` | API domain (`feishu` or `lark`) | `feishu` |
|
||||||
|
| `channels.feishu.accounts.<id>.appId` | App ID | - |
|
||||||
|
| `channels.feishu.accounts.<id>.appSecret` | App Secret | - |
|
||||||
|
| `channels.feishu.accounts.<id>.domain` | Per-account API domain override | `feishu` |
|
||||||
|
| `channels.feishu.dmPolicy` | DM policy | `pairing` |
|
||||||
|
| `channels.feishu.allowFrom` | DM allowlist (open_id list) | - |
|
||||||
|
| `channels.feishu.groupPolicy` | Group policy | `open` |
|
||||||
|
| `channels.feishu.groupAllowFrom` | Group allowlist | - |
|
||||||
|
| `channels.feishu.groups.<chat_id>.requireMention` | Require @mention | `true` |
|
||||||
|
| `channels.feishu.groups.<chat_id>.enabled` | Enable group | `true` |
|
||||||
|
| `channels.feishu.textChunkLimit` | Message chunk size | `2000` |
|
||||||
|
| `channels.feishu.mediaMaxMb` | Media size limit | `30` |
|
||||||
|
| `channels.feishu.streaming` | Enable streaming card output | `true` |
|
||||||
|
| `channels.feishu.blockStreaming` | Enable block streaming | `true` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## dmPolicy reference
|
||||||
|
|
||||||
|
| Value | Behavior |
|
||||||
|
| ------------- | --------------------------------------------------------------- |
|
||||||
|
| `"pairing"` | **Default.** Unknown users get a pairing code; must be approved |
|
||||||
|
| `"allowlist"` | Only users in `allowFrom` can chat |
|
||||||
|
| `"open"` | Allow all users (requires `"*"` in allowFrom) |
|
||||||
|
| `"disabled"` | Disable DMs |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Supported message types
|
||||||
|
|
||||||
|
### Receive
|
||||||
|
|
||||||
|
- ✅ Text
|
||||||
|
- ✅ Rich text (post)
|
||||||
|
- ✅ Images
|
||||||
|
- ✅ Files
|
||||||
|
- ✅ Audio
|
||||||
|
- ✅ Video
|
||||||
|
- ✅ Stickers
|
||||||
|
|
||||||
|
### Send
|
||||||
|
|
||||||
|
- ✅ Text
|
||||||
|
- ✅ Images
|
||||||
|
- ✅ Files
|
||||||
|
- ✅ Audio
|
||||||
|
- ⚠️ Rich text (partial support)
|
||||||
@@ -101,6 +101,7 @@ Use Tailscale Serve for the private dashboard and Funnel for the public webhook
|
|||||||
If prompted, visit the authorization URL shown in the output to enable Funnel for this node in your tailnet policy.
|
If prompted, visit the authorization URL shown in the output to enable Funnel for this node in your tailnet policy.
|
||||||
|
|
||||||
5. **Verify the configuration:**
|
5. **Verify the configuration:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tailscale serve status
|
tailscale serve status
|
||||||
tailscale funnel status
|
tailscale funnel status
|
||||||
@@ -225,6 +226,7 @@ This means the webhook handler isn't registered. Common causes:
|
|||||||
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
|
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
|
||||||
|
|
||||||
3. **Gateway not restarted**: After adding config, restart the gateway:
|
3. **Gateway not restarted**: After adding config, restart the gateway:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw gateway restart
|
openclaw gateway restart
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ otherwise -> reply
|
|||||||

|

|
||||||
|
|
||||||
If you want...
|
If you want...
|
||||||
|
|
||||||
| Goal | What to set |
|
| Goal | What to set |
|
||||||
|------|-------------|
|
| -------------------------------------------- | ---------------------------------------------------------- |
|
||||||
| Allow all groups but only reply on @mentions | `groups: { "*": { requireMention: true } }` |
|
| Allow all groups but only reply on @mentions | `groups: { "*": { requireMention: true } }` |
|
||||||
| Disable all group replies | `groupPolicy: "disabled"` |
|
| Disable all group replies | `groupPolicy: "disabled"` |
|
||||||
| Only specific groups | `groups: { "<group-id>": { ... } }` (no `"*"` key) |
|
| Only specific groups | `groups: { "<group-id>": { ... } }` (no `"*"` key) |
|
||||||
@@ -370,4 +371,4 @@ The agent system prompt includes a group intro on the first turn of a new group
|
|||||||
|
|
||||||
## WhatsApp specifics
|
## WhatsApp specifics
|
||||||
|
|
||||||
See [Group messages](/concepts/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
See [Group messages](/channels/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
---
|
---
|
||||||
summary: "iMessage support via imsg (JSON-RPC over stdio), setup, and chat_id routing"
|
summary: "Legacy iMessage support via imsg (JSON-RPC over stdio). New setups should use BlueBubbles."
|
||||||
read_when:
|
read_when:
|
||||||
- Setting up iMessage support
|
- Setting up iMessage support
|
||||||
- Debugging iMessage send/receive
|
- Debugging iMessage send/receive
|
||||||
title: iMessage
|
title: iMessage
|
||||||
---
|
---
|
||||||
|
|
||||||
# iMessage (imsg)
|
# iMessage (legacy: imsg)
|
||||||
|
|
||||||
Status: external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio).
|
> **Recommended:** Use [BlueBubbles](/channels/bluebubbles) for new iMessage setups.
|
||||||
|
>
|
||||||
|
> The `imsg` channel is a legacy external-CLI integration and may be removed in a future release.
|
||||||
|
|
||||||
|
Status: legacy external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio).
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
|
|
||||||
@@ -58,6 +62,28 @@ Disable with:
|
|||||||
- Automation permission when sending.
|
- Automation permission when sending.
|
||||||
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
|
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
|
||||||
|
|
||||||
|
## Troubleshooting macOS Privacy and Security TCC
|
||||||
|
|
||||||
|
If sending/receiving fails (for example, `imsg rpc` exits non-zero, times out, or the gateway appears to hang), a common cause is a macOS permission prompt that was never approved.
|
||||||
|
|
||||||
|
macOS grants TCC permissions per app/process context. Approve prompts in the same context that runs `imsg` (for example, Terminal/iTerm, a LaunchAgent session, or an SSH-launched process).
|
||||||
|
|
||||||
|
Checklist:
|
||||||
|
|
||||||
|
- **Full Disk Access**: allow access for the process running OpenClaw (and any shell/SSH wrapper that executes `imsg`). This is required to read the Messages database (`chat.db`).
|
||||||
|
- **Automation → Messages**: allow the process running OpenClaw (and/or your terminal) to control **Messages.app** for outbound sends.
|
||||||
|
- **`imsg` CLI health**: verify `imsg` is installed and supports RPC (`imsg rpc --help`).
|
||||||
|
|
||||||
|
Tip: If OpenClaw is running headless (LaunchAgent/systemd/SSH) the macOS prompt can be easy to miss. Run a one-time interactive command in a GUI terminal to force the prompt, then retry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
imsg chats --limit 1
|
||||||
|
# or
|
||||||
|
imsg send <handle> "test"
|
||||||
|
```
|
||||||
|
|
||||||
|
Related macOS folder permissions (Desktop/Documents/Downloads): [/platforms/mac/permissions](/platforms/mac/permissions).
|
||||||
|
|
||||||
## Setup (fast path)
|
## Setup (fast path)
|
||||||
|
|
||||||
1. Ensure Messages is signed in on this Mac.
|
1. Ensure Messages is signed in on this Mac.
|
||||||
@@ -77,7 +103,7 @@ If you want the bot to send from a **separate iMessage identity** (and keep your
|
|||||||
6. Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
|
6. Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
|
||||||
7. Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
|
7. Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
|
||||||
|
|
||||||
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
|
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry. See [Troubleshooting macOS Privacy and Security TCC](#troubleshooting-macos-privacy-and-security-tcc).
|
||||||
|
|
||||||
Example wrapper (`chmod +x`). Replace `<bot-macos-user>` with your actual macOS username:
|
Example wrapper (`chmod +x`). Replace `<bot-macos-user>` with your actual macOS username:
|
||||||
|
|
||||||
@@ -198,7 +224,7 @@ DMs:
|
|||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list imessage`
|
- `openclaw pairing list imessage`
|
||||||
- `openclaw pairing approve imessage <CODE>`
|
- `openclaw pairing approve imessage <CODE>`
|
||||||
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/channels/pairing)
|
||||||
|
|
||||||
Groups:
|
Groups:
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ Text is supported everywhere; media and reactions vary by channel.
|
|||||||
- [Telegram](/channels/telegram) — Bot API via grammY; supports groups.
|
- [Telegram](/channels/telegram) — Bot API via grammY; supports groups.
|
||||||
- [Discord](/channels/discord) — Discord Bot API + Gateway; supports servers, channels, and DMs.
|
- [Discord](/channels/discord) — Discord Bot API + Gateway; supports servers, channels, and DMs.
|
||||||
- [Slack](/channels/slack) — Bolt SDK; workspace apps.
|
- [Slack](/channels/slack) — Bolt SDK; workspace apps.
|
||||||
|
- [Feishu](/channels/feishu) — Feishu/Lark bot via WebSocket (plugin, installed separately).
|
||||||
- [Google Chat](/channels/googlechat) — Google Chat API app via HTTP webhook.
|
- [Google Chat](/channels/googlechat) — Google Chat API app via HTTP webhook.
|
||||||
- [Mattermost](/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (plugin, installed separately).
|
- [Mattermost](/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (plugin, installed separately).
|
||||||
- [Signal](/channels/signal) — signal-cli; privacy-focused.
|
- [Signal](/channels/signal) — signal-cli; privacy-focused.
|
||||||
- [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
|
- [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
|
||||||
- [iMessage](/channels/imessage) — macOS only; native integration via imsg (legacy, consider BlueBubbles for new setups).
|
- [iMessage (legacy)](/channels/imessage) — Legacy macOS integration via imsg CLI (deprecated, use BlueBubbles for new setups).
|
||||||
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
|
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
|
||||||
- [LINE](/channels/line) — LINE Messaging API bot (plugin, installed separately).
|
- [LINE](/channels/line) — LINE Messaging API bot (plugin, installed separately).
|
||||||
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
|
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
|
||||||
@@ -38,7 +39,7 @@ Text is supported everywhere; media and reactions vary by channel.
|
|||||||
- Channels can run simultaneously; configure multiple and OpenClaw will route per chat.
|
- Channels can run simultaneously; configure multiple and OpenClaw will route per chat.
|
||||||
- Fastest setup is usually **Telegram** (simple bot token). WhatsApp requires QR pairing and
|
- Fastest setup is usually **Telegram** (simple bot token). WhatsApp requires QR pairing and
|
||||||
stores more state on disk.
|
stores more state on disk.
|
||||||
- Group behavior varies by channel; see [Groups](/concepts/groups).
|
- Group behavior varies by channel; see [Groups](/channels/groups).
|
||||||
- DM pairing and allowlists are enforced for safety; see [Security](/gateway/security).
|
- DM pairing and allowlists are enforced for safety; see [Security](/gateway/security).
|
||||||
- Telegram internals: [grammY notes](/channels/grammy).
|
- Telegram internals: [grammY notes](/channels/grammy).
|
||||||
- Troubleshooting: [Channel troubleshooting](/channels/troubleshooting).
|
- Troubleshooting: [Channel troubleshooting](/channels/troubleshooting).
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ openclaw plugins install ./extensions/line
|
|||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Create a LINE Developers account and open the Console:
|
1. Create a LINE Developers account and open the Console:
|
||||||
https://developers.line.biz/console/
|
[https://developers.line.biz/console/](https://developers.line.biz/console/)
|
||||||
2. Create (or pick) a Provider and add a **Messaging API** channel.
|
2. Create (or pick) a Provider and add a **Messaging API** channel.
|
||||||
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
|
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
|
||||||
4. Enable **Use webhook** in the Messaging API settings.
|
4. Enable **Use webhook** in the Messaging API settings.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ openclaw plugins install ./extensions/matrix
|
|||||||
If you choose Matrix during configure/onboarding and a git checkout is detected,
|
If you choose Matrix during configure/onboarding and a git checkout is detected,
|
||||||
OpenClaw will offer the local install path automatically.
|
OpenClaw will offer the local install path automatically.
|
||||||
|
|
||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ Details: [Plugins](/plugin)
|
|||||||
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
|
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
|
||||||
5. Restart the gateway (or finish onboarding).
|
5. Restart the gateway (or finish onboarding).
|
||||||
6. Start a DM with the bot or invite it to a room from any Matrix client
|
6. Start a DM with the bot or invite it to a room from any Matrix client
|
||||||
(Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
|
(Element, Beeper, etc.; see [https://matrix.org/ecosystem/clients/](https://matrix.org/ecosystem/clients/)). Beeper requires E2EE,
|
||||||
so set `channels.matrix.encryption: true` and verify the device.
|
so set `channels.matrix.encryption: true` and verify the device.
|
||||||
|
|
||||||
Minimal config (access token, user ID auto-fetched):
|
Minimal config (access token, user ID auto-fetched):
|
||||||
@@ -202,6 +202,32 @@ Once verified, the bot can decrypt messages in encrypted rooms.
|
|||||||
| Location | ✅ Supported (geo URI; altitude ignored) |
|
| Location | ✅ Supported (geo URI; altitude ignored) |
|
||||||
| Native commands | ✅ Supported |
|
| Native commands | ✅ Supported |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Run this ladder first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw status
|
||||||
|
openclaw gateway status
|
||||||
|
openclaw logs --follow
|
||||||
|
openclaw doctor
|
||||||
|
openclaw channels status --probe
|
||||||
|
```
|
||||||
|
|
||||||
|
Then confirm DM pairing state if needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw pairing list matrix
|
||||||
|
```
|
||||||
|
|
||||||
|
Common failures:
|
||||||
|
|
||||||
|
- Logged in but room messages ignored: room blocked by `groupPolicy` or room allowlist.
|
||||||
|
- DMs ignored: sender pending approval when `channels.matrix.dm.policy="pairing"`.
|
||||||
|
- Encrypted rooms fail: crypto support or encryption settings mismatch.
|
||||||
|
|
||||||
|
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
|
||||||
|
|
||||||
## Configuration reference (Matrix)
|
## Configuration reference (Matrix)
|
||||||
|
|
||||||
Full configuration: [Configuration](/gateway/configuration)
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ openclaw plugins install ./extensions/mattermost
|
|||||||
If you choose Mattermost during configure/onboarding and a git checkout is detected,
|
If you choose Mattermost during configure/onboarding and a git checkout is detected,
|
||||||
OpenClaw will offer the local install path automatically.
|
OpenClaw will offer the local install path automatically.
|
||||||
|
|
||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Quick setup
|
## Quick setup
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ openclaw plugins install ./extensions/msteams
|
|||||||
If you choose Teams during configure/onboarding and a git checkout is detected,
|
If you choose Teams during configure/onboarding and a git checkout is detected,
|
||||||
OpenClaw will offer the local install path automatically.
|
OpenClaw will offer the local install path automatically.
|
||||||
|
|
||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
|
|
||||||
@@ -558,6 +558,7 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
|
|||||||
```
|
```
|
||||||
|
|
||||||
4. **Configure OpenClaw:**
|
4. **Configure OpenClaw:**
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -747,7 +748,7 @@ Bots have limited support in private channels:
|
|||||||
|
|
||||||
- **"Icon file cannot be empty":** The manifest references icon files that are 0 bytes. Create valid PNG icons (32x32 for `outline.png`, 192x192 for `color.png`).
|
- **"Icon file cannot be empty":** The manifest references icon files that are 0 bytes. Create valid PNG icons (32x32 for `outline.png`, 192x192 for `color.png`).
|
||||||
- **"webApplicationInfo.Id already in use":** The app is still installed in another team/chat. Find and uninstall it first, or wait 5-10 minutes for propagation.
|
- **"webApplicationInfo.Id already in use":** The app is still installed in another team/chat. Find and uninstall it first, or wait 5-10 minutes for propagation.
|
||||||
- **"Something went wrong" on upload:** Upload via https://admin.teams.microsoft.com instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
|
- **"Something went wrong" on upload:** Upload via [https://admin.teams.microsoft.com](https://admin.teams.microsoft.com) instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
|
||||||
- **Sideload failing:** Try "Upload an app to your org's app catalog" instead of "Upload a custom app" - this often bypasses sideload restrictions.
|
- **Sideload failing:** Try "Upload an app to your org's app catalog" instead of "Upload a custom app" - this often bypasses sideload restrictions.
|
||||||
|
|
||||||
### RSC permissions not working
|
### RSC permissions not working
|
||||||
|
|||||||
@@ -28,15 +28,17 @@ openclaw plugins install ./extensions/nextcloud-talk
|
|||||||
If you choose Nextcloud Talk during configure/onboarding and a git checkout is detected,
|
If you choose Nextcloud Talk during configure/onboarding and a git checkout is detected,
|
||||||
OpenClaw will offer the local install path automatically.
|
OpenClaw will offer the local install path automatically.
|
||||||
|
|
||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
|
|
||||||
1. Install the Nextcloud Talk plugin.
|
1. Install the Nextcloud Talk plugin.
|
||||||
2. On your Nextcloud server, create a bot:
|
2. On your Nextcloud server, create a bot:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature reaction
|
./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature reaction
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Enable the bot in the target room settings.
|
3. Enable the bot in the target room settings.
|
||||||
4. Configure OpenClaw:
|
4. Configure OpenClaw:
|
||||||
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
||||||
@@ -72,6 +74,7 @@ Minimal config:
|
|||||||
- `openclaw pairing list nextcloud-talk`
|
- `openclaw pairing list nextcloud-talk`
|
||||||
- `openclaw pairing approve nextcloud-talk <CODE>`
|
- `openclaw pairing approve nextcloud-talk <CODE>`
|
||||||
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
|
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
|
||||||
|
- `allowFrom` matches Nextcloud user IDs only; display names are ignored.
|
||||||
|
|
||||||
## Rooms (groups)
|
## Rooms (groups)
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ openclaw devices approve <requestId>
|
|||||||
openclaw devices reject <requestId>
|
openclaw devices reject <requestId>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Where the state lives
|
### Node pairing state storage
|
||||||
|
|
||||||
Stored under `~/.openclaw/devices/`:
|
Stored under `~/.openclaw/devices/`:
|
||||||
|
|
||||||
@@ -80,6 +80,7 @@ Stored under `~/.openclaw/devices/`:
|
|||||||
- Telegram: [Telegram](/channels/telegram)
|
- Telegram: [Telegram](/channels/telegram)
|
||||||
- WhatsApp: [WhatsApp](/channels/whatsapp)
|
- WhatsApp: [WhatsApp](/channels/whatsapp)
|
||||||
- Signal: [Signal](/channels/signal)
|
- Signal: [Signal](/channels/signal)
|
||||||
- iMessage: [iMessage](/channels/imessage)
|
- BlueBubbles (iMessage): [BlueBubbles](/channels/bluebubbles)
|
||||||
|
- iMessage (legacy): [iMessage](/channels/imessage)
|
||||||
- Discord: [Discord](/channels/discord)
|
- Discord: [Discord](/channels/discord)
|
||||||
- Slack: [Slack](/channels/slack)
|
- Slack: [Slack](/channels/slack)
|
||||||
@@ -109,7 +109,7 @@ DMs:
|
|||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list signal`
|
- `openclaw pairing list signal`
|
||||||
- `openclaw pairing approve signal <CODE>`
|
- `openclaw pairing approve signal <CODE>`
|
||||||
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/channels/pairing)
|
||||||
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
|
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
|
||||||
|
|
||||||
Groups:
|
Groups:
|
||||||
@@ -168,6 +168,32 @@ Config:
|
|||||||
- Groups: `signal:group:<groupId>`.
|
- Groups: `signal:group:<groupId>`.
|
||||||
- Usernames: `username:<name>` (if supported by your Signal account).
|
- Usernames: `username:<name>` (if supported by your Signal account).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Run this ladder first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw status
|
||||||
|
openclaw gateway status
|
||||||
|
openclaw logs --follow
|
||||||
|
openclaw doctor
|
||||||
|
openclaw channels status --probe
|
||||||
|
```
|
||||||
|
|
||||||
|
Then confirm DM pairing state if needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw pairing list signal
|
||||||
|
```
|
||||||
|
|
||||||
|
Common failures:
|
||||||
|
|
||||||
|
- Daemon reachable but no replies: verify account/daemon settings (`httpUrl`, `account`) and receive mode.
|
||||||
|
- DMs ignored: sender is pending pairing approval.
|
||||||
|
- Group messages ignored: group sender/mention gating blocks delivery.
|
||||||
|
|
||||||
|
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
|
||||||
|
|
||||||
## Configuration reference (Signal)
|
## Configuration reference (Signal)
|
||||||
|
|
||||||
Full configuration: [Configuration](/gateway/configuration)
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Minimal config:
|
|||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
1. Create a Slack app (From scratch) in https://api.slack.com/apps.
|
1. Create a Slack app (From scratch) in [https://api.slack.com/apps](https://api.slack.com/apps).
|
||||||
2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
||||||
3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||||
4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
||||||
@@ -49,7 +49,7 @@ Use the manifest below so scopes and events stay in sync.
|
|||||||
|
|
||||||
Multi-account support: use `channels.slack.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
Multi-account support: use `channels.slack.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||||
|
|
||||||
### OpenClaw config (minimal)
|
### OpenClaw config (Socket mode)
|
||||||
|
|
||||||
Set tokens via env vars (recommended):
|
Set tokens via env vars (recommended):
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ Example with userTokenReadOnly explicitly set (allow user token writes):
|
|||||||
Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments).
|
Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments).
|
||||||
HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL.
|
HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL.
|
||||||
|
|
||||||
### Setup
|
### Setup (HTTP mode)
|
||||||
|
|
||||||
1. Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
|
1. Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
|
||||||
2. **Basic Information** → copy the **Signing Secret**.
|
2. **Basic Information** → copy the **Signing Secret**.
|
||||||
@@ -260,30 +260,30 @@ If you enable native commands, add one `slash_commands` entry per command you wa
|
|||||||
|
|
||||||
Slack's Conversations API is type-scoped: you only need the scopes for the
|
Slack's Conversations API is type-scoped: you only need the scopes for the
|
||||||
conversation types you actually touch (channels, groups, im, mpim). See
|
conversation types you actually touch (channels, groups, im, mpim). See
|
||||||
https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
|
[https://docs.slack.dev/apis/web-api/using-the-conversations-api/](https://docs.slack.dev/apis/web-api/using-the-conversations-api/) for the overview.
|
||||||
|
|
||||||
### Bot token scopes (required)
|
### Bot token scopes (required)
|
||||||
|
|
||||||
- `chat:write` (send/update/delete messages via `chat.postMessage`)
|
- `chat:write` (send/update/delete messages via `chat.postMessage`)
|
||||||
https://docs.slack.dev/reference/methods/chat.postMessage
|
[https://docs.slack.dev/reference/methods/chat.postMessage](https://docs.slack.dev/reference/methods/chat.postMessage)
|
||||||
- `im:write` (open DMs via `conversations.open` for user DMs)
|
- `im:write` (open DMs via `conversations.open` for user DMs)
|
||||||
https://docs.slack.dev/reference/methods/conversations.open
|
[https://docs.slack.dev/reference/methods/conversations.open](https://docs.slack.dev/reference/methods/conversations.open)
|
||||||
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
||||||
https://docs.slack.dev/reference/methods/conversations.history
|
[https://docs.slack.dev/reference/methods/conversations.history](https://docs.slack.dev/reference/methods/conversations.history)
|
||||||
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
|
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
|
||||||
https://docs.slack.dev/reference/methods/conversations.info
|
[https://docs.slack.dev/reference/methods/conversations.info](https://docs.slack.dev/reference/methods/conversations.info)
|
||||||
- `users:read` (user lookup)
|
- `users:read` (user lookup)
|
||||||
https://docs.slack.dev/reference/methods/users.info
|
[https://docs.slack.dev/reference/methods/users.info](https://docs.slack.dev/reference/methods/users.info)
|
||||||
- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`)
|
- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`)
|
||||||
https://docs.slack.dev/reference/methods/reactions.get
|
[https://docs.slack.dev/reference/methods/reactions.get](https://docs.slack.dev/reference/methods/reactions.get)
|
||||||
https://docs.slack.dev/reference/methods/reactions.add
|
[https://docs.slack.dev/reference/methods/reactions.add](https://docs.slack.dev/reference/methods/reactions.add)
|
||||||
- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`)
|
- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`)
|
||||||
https://docs.slack.dev/reference/scopes/pins.read
|
[https://docs.slack.dev/reference/scopes/pins.read](https://docs.slack.dev/reference/scopes/pins.read)
|
||||||
https://docs.slack.dev/reference/scopes/pins.write
|
[https://docs.slack.dev/reference/scopes/pins.write](https://docs.slack.dev/reference/scopes/pins.write)
|
||||||
- `emoji:read` (`emoji.list`)
|
- `emoji:read` (`emoji.list`)
|
||||||
https://docs.slack.dev/reference/scopes/emoji.read
|
[https://docs.slack.dev/reference/scopes/emoji.read](https://docs.slack.dev/reference/scopes/emoji.read)
|
||||||
- `files:write` (uploads via `files.uploadV2`)
|
- `files:write` (uploads via `files.uploadV2`)
|
||||||
https://docs.slack.dev/messaging/working-with-files/#upload
|
[https://docs.slack.dev/messaging/working-with-files/#upload](https://docs.slack.dev/messaging/working-with-files/#upload)
|
||||||
|
|
||||||
### User token scopes (optional, read-only by default)
|
### User token scopes (optional, read-only by default)
|
||||||
|
|
||||||
@@ -302,9 +302,9 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
|
|||||||
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
|
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
|
||||||
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
|
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
|
||||||
- `chat:write.public` (only if we want to post to channels the bot isn't in)
|
- `chat:write.public` (only if we want to post to channels the bot isn't in)
|
||||||
https://docs.slack.dev/reference/scopes/chat.write.public
|
[https://docs.slack.dev/reference/scopes/chat.write.public](https://docs.slack.dev/reference/scopes/chat.write.public)
|
||||||
- `users:read.email` (only if we need email fields from `users.info`)
|
- `users:read.email` (only if we need email fields from `users.info`)
|
||||||
https://docs.slack.dev/changelog/2017-04-narrowing-email-access
|
[https://docs.slack.dev/changelog/2017-04-narrowing-email-access](https://docs.slack.dev/changelog/2017-04-narrowing-email-access)
|
||||||
- `files:read` (only if we start listing/reading file metadata)
|
- `files:read` (only if we start listing/reading file metadata)
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
@@ -537,6 +537,32 @@ Slack tool actions can be gated with `channels.slack.actions.*`:
|
|||||||
scopes you expect (`chat:write`, `reactions:write`, `pins:write`,
|
scopes you expect (`chat:write`, `reactions:write`, `pins:write`,
|
||||||
`files:write`) or those operations will fail.
|
`files:write`) or those operations will fail.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Run this ladder first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw status
|
||||||
|
openclaw gateway status
|
||||||
|
openclaw logs --follow
|
||||||
|
openclaw doctor
|
||||||
|
openclaw channels status --probe
|
||||||
|
```
|
||||||
|
|
||||||
|
Then confirm DM pairing state if needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw pairing list slack
|
||||||
|
```
|
||||||
|
|
||||||
|
Common failures:
|
||||||
|
|
||||||
|
- Connected but no channel replies: channel blocked by `groupPolicy` or not in `channels.slack.channels` allowlist.
|
||||||
|
- DMs ignored: sender not approved when `channels.slack.dm.policy="pairing"`.
|
||||||
|
- API errors (`missing_scope`, `not_in_channel`, auth failures): bot/app tokens or Slack scopes are incomplete.
|
||||||
|
|
||||||
|
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.
|
- Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ You can add custom commands to the menu via config:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Setup troubleshooting (commands)
|
||||||
|
|
||||||
- `setMyCommands failed` in logs usually means outbound HTTPS/DNS is blocked to `api.telegram.org`.
|
- `setMyCommands failed` in logs usually means outbound HTTPS/DNS is blocked to `api.telegram.org`.
|
||||||
- If you see `sendMessage` or `sendChatAction` failures, check IPv6 routing and DNS.
|
- If you see `sendMessage` or `sendChatAction` failures, check IPv6 routing and DNS.
|
||||||
@@ -351,7 +351,7 @@ Use the global setting when all Telegram bots/accounts should behave the same. U
|
|||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list telegram`
|
- `openclaw pairing list telegram`
|
||||||
- `openclaw pairing approve telegram <CODE>`
|
- `openclaw pairing approve telegram <CODE>`
|
||||||
- Pairing is the default token exchange used for Telegram DMs. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange used for Telegram DMs. Details: [Pairing](/channels/pairing)
|
||||||
- `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible.
|
- `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible.
|
||||||
|
|
||||||
#### Finding your Telegram user ID
|
#### Finding your Telegram user ID
|
||||||
@@ -365,6 +365,7 @@ Alternate (official Bot API):
|
|||||||
|
|
||||||
1. DM your bot.
|
1. DM your bot.
|
||||||
2. Fetch updates with your bot token and read `message.from.id`:
|
2. Fetch updates with your bot token and read `message.from.id`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||||
```
|
```
|
||||||
@@ -392,6 +393,23 @@ Two independent controls:
|
|||||||
|
|
||||||
Most users want: `groupPolicy: "allowlist"` + `groupAllowFrom` + specific groups listed in `channels.telegram.groups`
|
Most users want: `groupPolicy: "allowlist"` + `groupAllowFrom` + specific groups listed in `channels.telegram.groups`
|
||||||
|
|
||||||
|
To allow **any group member** to talk in a specific group (while still keeping control commands restricted to authorized senders), set a per-group override:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
groups: {
|
||||||
|
"-1001234567890": {
|
||||||
|
groupPolicy: "open",
|
||||||
|
requireMention: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Long-polling vs webhook
|
## Long-polling vs webhook
|
||||||
|
|
||||||
- Default: long-polling (no public URL required).
|
- Default: long-polling (no public URL required).
|
||||||
@@ -714,12 +732,14 @@ Provider options:
|
|||||||
- `channels.telegram.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
- `channels.telegram.groupPolicy`: `open | allowlist | disabled` (default: allowlist).
|
||||||
- `channels.telegram.groupAllowFrom`: group sender allowlist (ids/usernames).
|
- `channels.telegram.groupAllowFrom`: group sender allowlist (ids/usernames).
|
||||||
- `channels.telegram.groups`: per-group defaults + allowlist (use `"*"` for global defaults).
|
- `channels.telegram.groups`: per-group defaults + allowlist (use `"*"` for global defaults).
|
||||||
|
- `channels.telegram.groups.<id>.groupPolicy`: per-group override for groupPolicy (`open | allowlist | disabled`).
|
||||||
- `channels.telegram.groups.<id>.requireMention`: mention gating default.
|
- `channels.telegram.groups.<id>.requireMention`: mention gating default.
|
||||||
- `channels.telegram.groups.<id>.skills`: skill filter (omit = all skills, empty = none).
|
- `channels.telegram.groups.<id>.skills`: skill filter (omit = all skills, empty = none).
|
||||||
- `channels.telegram.groups.<id>.allowFrom`: per-group sender allowlist override.
|
- `channels.telegram.groups.<id>.allowFrom`: per-group sender allowlist override.
|
||||||
- `channels.telegram.groups.<id>.systemPrompt`: extra system prompt for the group.
|
- `channels.telegram.groups.<id>.systemPrompt`: extra system prompt for the group.
|
||||||
- `channels.telegram.groups.<id>.enabled`: disable the group when `false`.
|
- `channels.telegram.groups.<id>.enabled`: disable the group when `false`.
|
||||||
- `channels.telegram.groups.<id>.topics.<threadId>.*`: per-topic overrides (same fields as group).
|
- `channels.telegram.groups.<id>.topics.<threadId>.*`: per-topic overrides (same fields as group).
|
||||||
|
- `channels.telegram.groups.<id>.topics.<threadId>.groupPolicy`: per-topic override for groupPolicy (`open | allowlist | disabled`).
|
||||||
- `channels.telegram.groups.<id>.topics.<threadId>.requireMention`: per-topic mention gating override.
|
- `channels.telegram.groups.<id>.topics.<threadId>.requireMention`: per-topic mention gating override.
|
||||||
- `channels.telegram.capabilities.inlineButtons`: `off | dm | group | all | allowlist` (default: allowlist).
|
- `channels.telegram.capabilities.inlineButtons`: `off | dm | group | all | allowlist` (default: allowlist).
|
||||||
- `channels.telegram.accounts.<account>.capabilities.inlineButtons`: per-account override.
|
- `channels.telegram.accounts.<account>.capabilities.inlineButtons`: per-account override.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Local checkout (when running from a git repo):
|
|||||||
openclaw plugins install ./extensions/tlon
|
openclaw plugins install ./extensions/tlon
|
||||||
```
|
```
|
||||||
|
|
||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,116 @@
|
|||||||
---
|
---
|
||||||
summary: "Channel-specific troubleshooting shortcuts (Discord/Telegram/WhatsApp)"
|
summary: "Fast channel level troubleshooting with per channel failure signatures and fixes"
|
||||||
read_when:
|
read_when:
|
||||||
- A channel connects but messages don’t flow
|
- Channel transport says connected but replies fail
|
||||||
- Investigating channel misconfiguration (intents, permissions, privacy mode)
|
- You need channel specific checks before deep provider docs
|
||||||
title: "Channel Troubleshooting"
|
title: "Channel Troubleshooting"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Channel troubleshooting
|
# Channel troubleshooting
|
||||||
|
|
||||||
Start with:
|
Use this page when a channel connects but behavior is wrong.
|
||||||
|
|
||||||
|
## Command ladder
|
||||||
|
|
||||||
|
Run these in order first:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
openclaw status
|
||||||
|
openclaw gateway status
|
||||||
|
openclaw logs --follow
|
||||||
openclaw doctor
|
openclaw doctor
|
||||||
openclaw channels status --probe
|
openclaw channels status --probe
|
||||||
```
|
```
|
||||||
|
|
||||||
`channels status --probe` prints warnings when it can detect common channel misconfigurations, and includes small live checks (credentials, some permissions/membership).
|
Healthy baseline:
|
||||||
|
|
||||||
## Channels
|
- `Runtime: running`
|
||||||
|
- `RPC probe: ok`
|
||||||
|
- Channel probe shows connected/ready
|
||||||
|
|
||||||
- Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
|
## WhatsApp
|
||||||
- Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
|
||||||
- WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
|
|
||||||
|
|
||||||
## Telegram quick fixes
|
### WhatsApp failure signatures
|
||||||
|
|
||||||
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
|
| Symptom | Fastest check | Fix |
|
||||||
- Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies).
|
| ------------------------------- | --------------------------------------------------- | ------------------------------------------------------- |
|
||||||
|
| Connected but no DM replies | `openclaw pairing list whatsapp` | Approve sender or switch DM policy/allowlist. |
|
||||||
|
| Group messages ignored | Check `requireMention` + mention patterns in config | Mention the bot or relax mention policy for that group. |
|
||||||
|
| Random disconnect/relogin loops | `openclaw channels status --probe` + logs | Re-login and verify credentials directory is healthy. |
|
||||||
|
|
||||||
|
Full troubleshooting: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
|
||||||
|
|
||||||
|
## Telegram
|
||||||
|
|
||||||
|
### Telegram failure signatures
|
||||||
|
|
||||||
|
| Symptom | Fastest check | Fix |
|
||||||
|
| --------------------------------- | ----------------------------------------------- | --------------------------------------------------------- |
|
||||||
|
| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. |
|
||||||
|
| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. |
|
||||||
|
| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. |
|
||||||
|
|
||||||
|
Full troubleshooting: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
||||||
|
|
||||||
|
## Discord
|
||||||
|
|
||||||
|
### Discord failure signatures
|
||||||
|
|
||||||
|
| Symptom | Fastest check | Fix |
|
||||||
|
| ------------------------------- | ----------------------------------- | --------------------------------------------------------- |
|
||||||
|
| Bot online but no guild replies | `openclaw channels status --probe` | Allow guild/channel and verify message content intent. |
|
||||||
|
| Group messages ignored | Check logs for mention gating drops | Mention bot or set guild/channel `requireMention: false`. |
|
||||||
|
| DM replies missing | `openclaw pairing list discord` | Approve DM pairing or adjust DM policy. |
|
||||||
|
|
||||||
|
Full troubleshooting: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
|
||||||
|
|
||||||
|
## Slack
|
||||||
|
|
||||||
|
### Slack failure signatures
|
||||||
|
|
||||||
|
| Symptom | Fastest check | Fix |
|
||||||
|
| -------------------------------------- | ----------------------------------------- | ------------------------------------------------- |
|
||||||
|
| Socket mode connected but no responses | `openclaw channels status --probe` | Verify app token + bot token and required scopes. |
|
||||||
|
| DMs blocked | `openclaw pairing list slack` | Approve pairing or relax DM policy. |
|
||||||
|
| Channel message ignored | Check `groupPolicy` and channel allowlist | Allow the channel or switch policy to `open`. |
|
||||||
|
|
||||||
|
Full troubleshooting: [/channels/slack#troubleshooting](/channels/slack#troubleshooting)
|
||||||
|
|
||||||
|
## iMessage and BlueBubbles
|
||||||
|
|
||||||
|
### iMessage and BlueBubbles failure signatures
|
||||||
|
|
||||||
|
| Symptom | Fastest check | Fix |
|
||||||
|
| -------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
|
||||||
|
| No inbound events | Verify webhook/server reachability and app permissions | Fix webhook URL or BlueBubbles server state. |
|
||||||
|
| Can send but no receive on macOS | Check macOS privacy permissions for Messages automation | Re-grant TCC permissions and restart channel process. |
|
||||||
|
| DM sender blocked | `openclaw pairing list imessage` or `openclaw pairing list bluebubbles` | Approve pairing or update allowlist. |
|
||||||
|
|
||||||
|
Full troubleshooting:
|
||||||
|
|
||||||
|
- [/channels/imessage#troubleshooting-macos-privacy-and-security-tcc](/channels/imessage#troubleshooting-macos-privacy-and-security-tcc)
|
||||||
|
- [/channels/bluebubbles#troubleshooting](/channels/bluebubbles#troubleshooting)
|
||||||
|
|
||||||
|
## Signal
|
||||||
|
|
||||||
|
### Signal failure signatures
|
||||||
|
|
||||||
|
| Symptom | Fastest check | Fix |
|
||||||
|
| ------------------------------- | ------------------------------------------ | -------------------------------------------------------- |
|
||||||
|
| Daemon reachable but bot silent | `openclaw channels status --probe` | Verify `signal-cli` daemon URL/account and receive mode. |
|
||||||
|
| DM blocked | `openclaw pairing list signal` | Approve sender or adjust DM policy. |
|
||||||
|
| Group replies do not trigger | Check group allowlist and mention patterns | Add sender/group or loosen gating. |
|
||||||
|
|
||||||
|
Full troubleshooting: [/channels/signal#troubleshooting](/channels/signal#troubleshooting)
|
||||||
|
|
||||||
|
## Matrix
|
||||||
|
|
||||||
|
### Matrix failure signatures
|
||||||
|
|
||||||
|
| Symptom | Fastest check | Fix |
|
||||||
|
| ----------------------------------- | -------------------------------------------- | ----------------------------------------------- |
|
||||||
|
| Logged in but ignores room messages | `openclaw channels status --probe` | Check `groupPolicy` and room allowlist. |
|
||||||
|
| DMs do not process | `openclaw pairing list matrix` | Approve sender or adjust DM policy. |
|
||||||
|
| Encrypted rooms fail | Verify crypto module and encryption settings | Enable encryption support and rejoin/sync room. |
|
||||||
|
|
||||||
|
Full troubleshooting: [/channels/matrix#troubleshooting](/channels/matrix#troubleshooting)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Local checkout (when running from a git repo):
|
|||||||
openclaw plugins install ./extensions/twitch
|
openclaw plugins install ./extensions/twitch
|
||||||
```
|
```
|
||||||
|
|
||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ Details: [Plugins](/plugin)
|
|||||||
- Select **Bot Token**
|
- Select **Bot Token**
|
||||||
- Verify scopes `chat:read` and `chat:write` are selected
|
- Verify scopes `chat:read` and `chat:write` are selected
|
||||||
- Copy the **Client ID** and **Access Token**
|
- Copy the **Client ID** and **Access Token**
|
||||||
3. Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
3. Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/)
|
||||||
4. Configure the token:
|
4. Configure the token:
|
||||||
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
|
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
|
||||||
- Or config: `channels.twitch.accessToken`
|
- Or config: `channels.twitch.accessToken`
|
||||||
@@ -123,7 +123,7 @@ Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want
|
|||||||
|
|
||||||
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
|
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
|
||||||
|
|
||||||
Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/ (Convert your Twitch username to ID)
|
Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/) (Convert your Twitch username to ID)
|
||||||
|
|
||||||
## Token refresh (optional)
|
## Token refresh (optional)
|
||||||
|
|
||||||
|
|||||||
@@ -205,11 +205,13 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
|||||||
|
|
||||||
- `Body` is the current message body with envelope.
|
- `Body` is the current message body with envelope.
|
||||||
- Quoted reply context is **always appended**:
|
- Quoted reply context is **always appended**:
|
||||||
|
|
||||||
```
|
```
|
||||||
[Replying to +1555 id:ABC123]
|
[Replying to +1555 id:ABC123]
|
||||||
<quoted text or <media:...>>
|
<quoted text or <media:...>>
|
||||||
[/Replying]
|
[/Replying]
|
||||||
```
|
```
|
||||||
|
|
||||||
- Reply metadata also set:
|
- Reply metadata also set:
|
||||||
- `ReplyToId` = stanzaId
|
- `ReplyToId` = stanzaId
|
||||||
- `ReplyToBody` = quoted body or media placeholder
|
- `ReplyToBody` = quoted body or media placeholder
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Zalo ships as a plugin and is not bundled with the core install.
|
|||||||
|
|
||||||
- Install via CLI: `openclaw plugins install @openclaw/zalo`
|
- Install via CLI: `openclaw plugins install @openclaw/zalo`
|
||||||
- Or select **Zalo** during onboarding and confirm the install prompt
|
- Or select **Zalo** during onboarding and confirm the install prompt
|
||||||
- Details: [Plugins](/plugin)
|
- Details: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ It is a good fit for support or notifications where you want deterministic routi
|
|||||||
|
|
||||||
### 1) Create a bot token (Zalo Bot Platform)
|
### 1) Create a bot token (Zalo Bot Platform)
|
||||||
|
|
||||||
1. Go to **https://bot.zaloplatforms.com** and sign in.
|
1. Go to [https://bot.zaloplatforms.com](https://bot.zaloplatforms.com) and sign in.
|
||||||
2. Create a new bot and configure its settings.
|
2. Create a new bot and configure its settings.
|
||||||
3. Copy the bot token (format: `12345689:abc-xyz`).
|
3. Copy the bot token (format: `12345689:abc-xyz`).
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
|
|||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list zalo`
|
- `openclaw pairing list zalo`
|
||||||
- `openclaw pairing approve zalo <CODE>`
|
- `openclaw pairing approve zalo <CODE>`
|
||||||
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||||
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
||||||
|
|
||||||
## Long-polling vs webhook
|
## Long-polling vs webhook
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Zalo Personal ships as a plugin and is not bundled with the core install.
|
|||||||
|
|
||||||
- Install via CLI: `openclaw plugins install @openclaw/zalouser`
|
- Install via CLI: `openclaw plugins install @openclaw/zalouser`
|
||||||
- Or from a source checkout: `openclaw plugins install ./extensions/zalouser`
|
- Or from a source checkout: `openclaw plugins install ./extensions/zalouser`
|
||||||
- Details: [Plugins](/plugin)
|
- Details: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Prerequisite: zca-cli
|
## Prerequisite: zca-cli
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,19 @@ Related:
|
|||||||
|
|
||||||
Tip: run `openclaw cron --help` for the full command surface.
|
Tip: run `openclaw cron --help` for the full command surface.
|
||||||
|
|
||||||
|
Note: isolated `cron add` jobs default to `--announce` delivery. Use `--no-deliver` to keep
|
||||||
|
output internal. `--deliver` remains as a deprecated alias for `--announce`.
|
||||||
|
|
||||||
|
Note: one-shot (`--at`) jobs delete after success by default. Use `--keep-after-run` to keep them.
|
||||||
|
|
||||||
|
Note: recurring jobs now use exponential retry backoff after consecutive errors (30s → 1m → 5m → 15m → 60m), then return to normal schedule after the next successful run.
|
||||||
|
|
||||||
## Common edits
|
## Common edits
|
||||||
|
|
||||||
Update delivery settings without changing the message:
|
Update delivery settings without changing the message:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron edit <job-id> --deliver --channel telegram --to "123456789"
|
openclaw cron edit <job-id> --announce --channel telegram --to "123456789"
|
||||||
```
|
```
|
||||||
|
|
||||||
Disable delivery for an isolated job:
|
Disable delivery for an isolated job:
|
||||||
@@ -29,3 +36,9 @@ Disable delivery for an isolated job:
|
|||||||
```bash
|
```bash
|
||||||
openclaw cron edit <job-id> --no-deliver
|
openclaw cron edit <job-id> --no-deliver
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Announce to a specific channel:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw cron edit <job-id> --announce --channel slack --to "channel:C1234567890"
|
||||||
|
```
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ openclaw devices revoke --device <deviceId> --role node
|
|||||||
- `--timeout <ms>`: RPC timeout.
|
- `--timeout <ms>`: RPC timeout.
|
||||||
- `--json`: JSON output (recommended for scripting).
|
- `--json`: JSON output (recommended for scripting).
|
||||||
|
|
||||||
|
Note: when you set `--url`, the CLI does not fall back to config or environment credentials.
|
||||||
|
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Token rotation returns a new token (sensitive). Treat it like a secret.
|
- Token rotation returns a new token (sensitive). Treat it like a secret.
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ Shared options (where supported):
|
|||||||
- `--timeout <ms>`: timeout/budget (varies per command).
|
- `--timeout <ms>`: timeout/budget (varies per command).
|
||||||
- `--expect-final`: wait for a “final” response (agent calls).
|
- `--expect-final`: wait for a “final” response (agent calls).
|
||||||
|
|
||||||
|
Note: when you set `--url`, the CLI does not fall back to config or environment credentials.
|
||||||
|
Pass `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||||
|
|
||||||
### `gateway health`
|
### `gateway health`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ Manage agent hooks (event-driven automations for commands like `/new`, `/reset`,
|
|||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Hooks: [Hooks](/hooks)
|
- Hooks: [Hooks](/automation/hooks)
|
||||||
- Plugin hooks: [Plugins](/plugin#plugin-hooks)
|
- Plugin hooks: [Plugins](/tools/plugin#plugin-hooks)
|
||||||
|
|
||||||
## List All Hooks
|
## List All Hooks
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ openclaw hooks enable session-memory
|
|||||||
|
|
||||||
**Output:** `~/.openclaw/workspace/memory/YYYY-MM-DD-slug.md`
|
**Output:** `~/.openclaw/workspace/memory/YYYY-MM-DD-slug.md`
|
||||||
|
|
||||||
**See:** [session-memory documentation](/hooks#session-memory)
|
**See:** [session-memory documentation](/automation/hooks#session-memory)
|
||||||
|
|
||||||
### command-logger
|
### command-logger
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ cat ~/.openclaw/logs/commands.log | jq .
|
|||||||
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .
|
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .
|
||||||
```
|
```
|
||||||
|
|
||||||
**See:** [command-logger documentation](/hooks#command-logger)
|
**See:** [command-logger documentation](/automation/hooks#command-logger)
|
||||||
|
|
||||||
### soul-evil
|
### soul-evil
|
||||||
|
|
||||||
@@ -301,4 +301,4 @@ Runs `BOOT.md` when the gateway starts (after channels start).
|
|||||||
openclaw hooks enable boot-md
|
openclaw hooks enable boot-md
|
||||||
```
|
```
|
||||||
|
|
||||||
**See:** [boot-md documentation](/hooks#boot-md)
|
**See:** [boot-md documentation](/automation/hooks#boot-md)
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ Manage extensions and their config:
|
|||||||
- `openclaw plugins enable <id>` / `disable <id>` — toggle `plugins.entries.<id>.enabled`.
|
- `openclaw plugins enable <id>` / `disable <id>` — toggle `plugins.entries.<id>.enabled`.
|
||||||
- `openclaw plugins doctor` — report plugin load errors.
|
- `openclaw plugins doctor` — report plugin load errors.
|
||||||
|
|
||||||
Most plugin changes require a gateway restart. See [/plugin](/plugin).
|
Most plugin changes require a gateway restart. See [/plugin](/tools/plugin).
|
||||||
|
|
||||||
## Memory
|
## Memory
|
||||||
|
|
||||||
@@ -303,7 +303,7 @@ Options:
|
|||||||
- `--non-interactive`
|
- `--non-interactive`
|
||||||
- `--mode <local|remote>`
|
- `--mode <local|remote>`
|
||||||
- `--flow <quickstart|advanced|manual>` (manual is an alias for advanced)
|
- `--flow <quickstart|advanced|manual>` (manual is an alias for advanced)
|
||||||
- `--auth-choice <setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip>`
|
- `--auth-choice <setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|moonshot-api-key-cn|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip>`
|
||||||
- `--token-provider <id>` (non-interactive; used with `--auth-choice token`)
|
- `--token-provider <id>` (non-interactive; used with `--auth-choice token`)
|
||||||
- `--token <token>` (non-interactive; used with `--auth-choice token`)
|
- `--token <token>` (non-interactive; used with `--auth-choice token`)
|
||||||
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
|
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
|
||||||
@@ -715,6 +715,8 @@ openclaw logs --no-color
|
|||||||
### `gateway <subcommand>`
|
### `gateway <subcommand>`
|
||||||
|
|
||||||
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
||||||
|
When you pass `--url`, the CLI does not auto-apply config or environment credentials.
|
||||||
|
Include `--token` or `--password` explicitly. Missing explicit credentials is an error.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Provided by the active memory plugin (default: `memory-core`; set `plugins.slots
|
|||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Memory concept: [Memory](/concepts/memory)
|
- Memory concept: [Memory](/concepts/memory)
|
||||||
- Plugins: [Plugins](/plugins)
|
- Plugins: [Plugins](/tools/plugin)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ title: "onboard"
|
|||||||
|
|
||||||
Interactive onboarding wizard (local or remote Gateway setup).
|
Interactive onboarding wizard (local or remote Gateway setup).
|
||||||
|
|
||||||
Related:
|
## Related guides
|
||||||
|
|
||||||
- Wizard guide: [Onboarding](/start/onboarding)
|
- CLI onboarding hub: [Onboarding Wizard (CLI)](/start/wizard)
|
||||||
|
- CLI onboarding reference: [CLI Onboarding Reference](/start/wizard-cli-reference)
|
||||||
|
- CLI automation: [CLI Automation](/start/wizard-cli-automation)
|
||||||
|
- macOS onboarding: [Onboarding (macOS App)](/start/onboarding)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -27,3 +30,14 @@ Flow notes:
|
|||||||
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
||||||
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
||||||
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
||||||
|
|
||||||
|
## Common follow-up commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw configure
|
||||||
|
openclaw agents add <name>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
`--json` does not imply non-interactive mode. Use `--non-interactive` for scripts.
|
||||||
|
</Note>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Approve or inspect DM pairing requests (for channels that support pairing).
|
|||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Pairing flow: [Pairing](/start/pairing)
|
- Pairing flow: [Pairing](/channels/pairing)
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Manage Gateway plugins/extensions (loaded in-process).
|
|||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Plugin system: [Plugins](/plugin)
|
- Plugin system: [Plugins](/tools/plugin)
|
||||||
- Plugin manifest + schema: [Plugin manifest](/plugins/manifest)
|
- Plugin manifest + schema: [Plugin manifest](/plugins/manifest)
|
||||||
- Security hardening: [Security](/gateway/security)
|
- Security hardening: [Security](/gateway/security)
|
||||||
|
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ openclaw security audit --deep
|
|||||||
openclaw security audit --fix
|
openclaw security audit --fix
|
||||||
```
|
```
|
||||||
|
|
||||||
The audit warns when multiple DM senders share the main session and recommends `session.dmScope="per-channel-peer"` (or `per-account-channel-peer` for multi-account channels) for shared inboxes.
|
The audit warns when multiple DM senders share the main session and recommends **secure DM mode**: `session.dmScope="per-channel-peer"` (or `per-account-channel-peer` for multi-account channels) for shared inboxes.
|
||||||
It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled.
|
It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Open the terminal UI connected to the Gateway.
|
|||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- TUI guide: [TUI](/tui)
|
- TUI guide: [TUI](/web/tui)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ OpenClaw has two hook systems:
|
|||||||
Use this to add/remove bootstrap context files.
|
Use this to add/remove bootstrap context files.
|
||||||
- **Command hooks**: `/new`, `/reset`, `/stop`, and other command events (see Hooks doc).
|
- **Command hooks**: `/new`, `/reset`, `/stop`, and other command events (see Hooks doc).
|
||||||
|
|
||||||
See [Hooks](/hooks) for setup and examples.
|
See [Hooks](/automation/hooks) for setup and examples.
|
||||||
|
|
||||||
### Plugin hooks (agent + gateway lifecycle)
|
### Plugin hooks (agent + gateway lifecycle)
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ These run inside the agent loop or gateway pipeline:
|
|||||||
- **`session_start` / `session_end`**: session lifecycle boundaries.
|
- **`session_start` / `session_end`**: session lifecycle boundaries.
|
||||||
- **`gateway_start` / `gateway_stop`**: gateway lifecycle events.
|
- **`gateway_start` / `gateway_stop`**: gateway lifecycle events.
|
||||||
|
|
||||||
See [Plugins](/plugin#plugin-hooks) for the hook API and registration details.
|
See [Plugins](/tools/plugin#plugin-hooks) for the hook API and registration details.
|
||||||
|
|
||||||
## Streaming + partial replies
|
## Streaming + partial replies
|
||||||
|
|
||||||
|
|||||||
@@ -228,6 +228,6 @@ Suggested `.gitignore` starter:
|
|||||||
## Advanced notes
|
## Advanced notes
|
||||||
|
|
||||||
- Multi-agent routing can use different workspaces per agent. See
|
- Multi-agent routing can use different workspaces per agent. See
|
||||||
[Channel routing](/concepts/channel-routing) for routing configuration.
|
[Channel routing](/channels/channel-routing) for routing configuration.
|
||||||
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
|
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
|
||||||
workspaces under `agents.defaults.sandbox.workspaceRoot`.
|
workspaces under `agents.defaults.sandbox.workspaceRoot`.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user