Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8db32389d | |||
| 611e4702c2 | |||
| 99cfae1f00 | |||
| 1c624f0c51 | |||
| 8a8f946269 | |||
| 17469945f7 | |||
| 6dcd7b7222 | |||
| 342f743720 | |||
| e05fd75763 | |||
| 4daeb380a0 | |||
| ddacb2a917 | |||
| 77ca576be6 | |||
| 5a4f69e71f | |||
| e952838eef | |||
| 3fc4e66fb6 | |||
| 0a331a1440 | |||
| 5919becab9 | |||
| 287d9bdc17 | |||
| f900d7079e | |||
| 5a5b5a4402 | |||
| 06f65fb711 | |||
| 46d4b13c22 | |||
| 8fcbb6eb2d | |||
| ce22eba7d0 | |||
| 7ba4d06e78 | |||
| dc12d03876 | |||
| 3151604b04 | |||
| c5fcda06ad | |||
| 51a52dcadb | |||
| dd9e26eac6 | |||
| c4b2a21c61 | |||
| d6170ab49b | |||
| 399c896c3b | |||
| 71d32c3b04 | |||
| 27936b051d | |||
| 39d788a95f | |||
| 5d921bd37d | |||
| d17f0a946c | |||
| 2710ce65cc | |||
| 51e8fc8423 | |||
| ecf9d477bd | |||
| 625784c25f |
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
@@ -77,6 +77,8 @@ jobs:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
@@ -84,6 +86,7 @@ jobs:
|
||||
toolchain: 1.92.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
if: runner.os != 'Windows'
|
||||
|
||||
- name: Install mold linker
|
||||
if: runner.os == 'Linux'
|
||||
@@ -92,6 +95,7 @@ jobs:
|
||||
sudo apt-get install -y mold
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
shell: bash
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Build release
|
||||
|
||||
@@ -105,6 +105,8 @@ jobs:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
|
||||
@@ -112,6 +114,7 @@ jobs:
|
||||
toolchain: 1.92.0
|
||||
targets: ${{ matrix.target }}
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
|
||||
if: runner.os != 'Windows'
|
||||
|
||||
- name: Install mold linker
|
||||
if: runner.os == 'Linux'
|
||||
@@ -120,6 +123,7 @@ jobs:
|
||||
sudo apt-get install -y mold
|
||||
|
||||
- name: Ensure web/dist placeholder exists
|
||||
shell: bash
|
||||
run: mkdir -p web/dist && touch web/dist/.gitkeep
|
||||
|
||||
- name: Build release
|
||||
|
||||
@@ -81,7 +81,7 @@ Current maintainers with PR approval authority: `theonlyhennygod`, `JordanTheJet
|
||||
| `aarch64-unknown-linux-gnu` | | ✓ | ✓ | ✓ |
|
||||
| `aarch64-apple-darwin` | ✓ | | ✓ | ✓ |
|
||||
| `x86_64-apple-darwin` | | ✓ | | |
|
||||
| `x86_64-pc-windows-msvc` | | ✓ | ✓ | ✓ |
|
||||
| `x86_64-pc-windows-msvc` | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
## Mermaid Diagrams
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
name: Auto-sync crates.io
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- "Cargo.toml"
|
||||
|
||||
concurrency:
|
||||
group: publish-crates-auto
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
detect-version-change:
|
||||
name: Detect Version Bump
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed: ${{ steps.check.outputs.changed }}
|
||||
version: ${{ steps.check.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Check if version changed
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
current=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
previous=$(git show HEAD~1:Cargo.toml 2>/dev/null | sed -n 's/^version = "\([^"]*\)"/\1/p' | head -1 || echo "")
|
||||
|
||||
echo "Current version: ${current}"
|
||||
echo "Previous version: ${previous}"
|
||||
|
||||
if [[ "$current" != "$previous" && -n "$current" ]]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${current}" >> "$GITHUB_OUTPUT"
|
||||
echo "Version bumped from ${previous} to ${current} — will publish"
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Version unchanged (${current}) — skipping publish"
|
||||
fi
|
||||
|
||||
check-registry:
|
||||
name: Check if Already Published
|
||||
needs: [detect-version-change]
|
||||
if: needs.detect-version-change.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_publish: ${{ steps.check.outputs.should_publish }}
|
||||
steps:
|
||||
- name: Check crates.io for existing version
|
||||
id: check
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ needs.detect-version-change.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
"https://crates.io/api/v1/crates/zeroclawlabs/${VERSION}")
|
||||
|
||||
if [[ "$status" == "200" ]]; then
|
||||
echo "Version ${VERSION} already exists on crates.io — skipping"
|
||||
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Version ${VERSION} not yet published — proceeding"
|
||||
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
publish:
|
||||
name: Publish to crates.io
|
||||
needs: [detect-version-change, check-registry]
|
||||
if: needs.check-registry.outputs.should_publish == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
|
||||
- name: Clean web build artifacts
|
||||
run: rm -rf web/node_modules web/src web/package.json web/package-lock.json web/tsconfig*.json web/vite.config.ts web/index.html
|
||||
|
||||
- name: Publish to crates.io
|
||||
run: cargo publish --locked --allow-dirty --no-verify
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Verify published
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ needs.detect-version-change.outputs.version }}
|
||||
run: |
|
||||
echo "Waiting for crates.io to index..."
|
||||
sleep 15
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
"https://crates.io/api/v1/crates/zeroclawlabs/${VERSION}")
|
||||
if [[ "$status" == "200" ]]; then
|
||||
echo "zeroclawlabs v${VERSION} is live on crates.io"
|
||||
echo "Install: cargo install zeroclawlabs"
|
||||
else
|
||||
echo "::warning::Version may still be indexing — check https://crates.io/crates/zeroclawlabs"
|
||||
fi
|
||||
@@ -0,0 +1,80 @@
|
||||
name: Publish to crates.io
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to publish (e.g. 0.2.0) — must match Cargo.toml"
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Dry run (validate without publishing)"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: publish-crates
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check version matches Cargo.toml
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo_version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
|
||||
if [[ "$cargo_version" != "$INPUT_VERSION" ]]; then
|
||||
echo "::error::Cargo.toml version (${cargo_version}) does not match input (${INPUT_VERSION})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
publish:
|
||||
name: Publish to crates.io
|
||||
needs: [validate]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
|
||||
- name: Clean web build artifacts
|
||||
run: rm -rf web/node_modules web/src web/package.json web/package-lock.json web/tsconfig*.json web/vite.config.ts web/index.html
|
||||
|
||||
- name: Publish (dry run)
|
||||
if: inputs.dry_run
|
||||
run: cargo publish --dry-run --locked --allow-dirty --no-verify
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Publish to crates.io
|
||||
if: "!inputs.dry_run"
|
||||
run: cargo publish --locked --allow-dirty --no-verify
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
@@ -37,6 +37,96 @@ jobs:
|
||||
echo "tag=${beta_tag}" >> "$GITHUB_OUTPUT"
|
||||
echo "Beta release: ${beta_tag}"
|
||||
|
||||
release-notes:
|
||||
name: Generate Release Notes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
notes: ${{ steps.notes.outputs.body }}
|
||||
features: ${{ steps.notes.outputs.features }}
|
||||
contributors: ${{ steps.notes.outputs.contributors }}
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build release notes
|
||||
id: notes
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Use a wider range — find the previous stable tag to capture all
|
||||
# contributors across the full release cycle, not just one beta bump
|
||||
PREV_TAG=$(git tag --sort=-creatordate \
|
||||
| grep -vE '\-beta\.' \
|
||||
| head -1 || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
RANGE="HEAD"
|
||||
else
|
||||
RANGE="${PREV_TAG}..HEAD"
|
||||
fi
|
||||
|
||||
# Extract features only (feat commits) — skip bug fixes for clean notes
|
||||
FEATURES=$(git log "$RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -iE '^feat(\(|:)' \
|
||||
| sed 's/^feat(\([^)]*\)): /\1: /' \
|
||||
| sed 's/^feat: //' \
|
||||
| sed 's/ (#[0-9]*)$//' \
|
||||
| sort -uf \
|
||||
| while IFS= read -r line; do echo "- ${line}"; done || true)
|
||||
|
||||
if [ -z "$FEATURES" ]; then
|
||||
FEATURES="- Incremental improvements and polish"
|
||||
fi
|
||||
|
||||
# Collect ALL unique contributors: git authors + Co-Authored-By
|
||||
GIT_AUTHORS=$(git log "$RANGE" --pretty=format:"%an" --no-merges | sort -uf || true)
|
||||
CO_AUTHORS=$(git log "$RANGE" --pretty=format:"%b" --no-merges \
|
||||
| grep -ioE 'Co-Authored-By: *[^<]+' \
|
||||
| sed 's/Co-Authored-By: *//i' \
|
||||
| sed 's/ *$//' \
|
||||
| sort -uf || true)
|
||||
|
||||
# Merge, deduplicate, and filter out bots
|
||||
ALL_CONTRIBUTORS=$(printf "%s\n%s" "$GIT_AUTHORS" "$CO_AUTHORS" \
|
||||
| sort -uf \
|
||||
| grep -v '^$' \
|
||||
| grep -viE '\[bot\]$|^dependabot|^github-actions|^copilot|^ZeroClaw Bot|^ZeroClaw Runner|^ZeroClaw Agent|^blacksmith' \
|
||||
| while IFS= read -r name; do echo "- ${name}"; done || true)
|
||||
|
||||
# Build release body
|
||||
BODY=$(cat <<NOTES_EOF
|
||||
## What's New
|
||||
|
||||
${FEATURES}
|
||||
|
||||
## Contributors
|
||||
|
||||
${ALL_CONTRIBUTORS}
|
||||
|
||||
---
|
||||
*Full changelog: ${PREV_TAG}...HEAD*
|
||||
NOTES_EOF
|
||||
)
|
||||
|
||||
# Output multiline values
|
||||
{
|
||||
echo "body<<BODY_EOF"
|
||||
echo "$BODY"
|
||||
echo "BODY_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "features<<FEAT_EOF"
|
||||
echo "$FEATURES"
|
||||
echo "FEAT_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
{
|
||||
echo "contributors<<CONTRIB_EOF"
|
||||
echo "$ALL_CONTRIBUTORS"
|
||||
echo "CONTRIB_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
web:
|
||||
name: Build Web Dashboard
|
||||
runs-on: ubuntu-latest
|
||||
@@ -132,7 +222,7 @@ jobs:
|
||||
|
||||
publish:
|
||||
name: Publish Beta Release
|
||||
needs: [version, build]
|
||||
needs: [version, release-notes, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
@@ -148,17 +238,44 @@ jobs:
|
||||
find . -type f \( -name '*.tar.gz' -o -name '*.zip' \) -exec sha256sum {} + | sed 's| \./[^/]*/| |' > SHA256SUMS
|
||||
cat SHA256SUMS
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
tag_name: ${{ needs.version.outputs.tag }}
|
||||
name: ${{ needs.version.outputs.tag }}
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
artifacts/**/*
|
||||
- name: Collect release assets
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name 'SHA256SUMS' \) -exec cp {} release-assets/ \;
|
||||
cp install.sh release-assets/
|
||||
echo "--- Assets ---"
|
||||
ls -lh release-assets/
|
||||
|
||||
- name: Write release notes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
NOTES: ${{ needs.release-notes.outputs.notes }}
|
||||
run: printf '%s\n' "$NOTES" > release-notes.md
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
TAG: ${{ needs.version.outputs.tag }}
|
||||
run: |
|
||||
gh release create "$TAG" release-assets/* \
|
||||
--repo "${{ github.repository }}" \
|
||||
--title "$TAG" \
|
||||
--notes-file release-notes.md \
|
||||
--prerelease
|
||||
|
||||
redeploy-website:
|
||||
name: Trigger Website Redeploy
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger website redeploy
|
||||
env:
|
||||
PAT: ${{ secrets.WEBSITE_REPO_PAT }}
|
||||
run: |
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: token $PAT" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/zeroclaw-labs/zeroclaw-website/dispatches \
|
||||
-d '{"event_type":"new-release","client_payload":{"install_script_url":"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh"}}'
|
||||
|
||||
docker:
|
||||
name: Push Docker Image
|
||||
|
||||
@@ -74,6 +74,79 @@ jobs:
|
||||
path: web/dist/
|
||||
retention-days: 1
|
||||
|
||||
release-notes:
|
||||
name: Generate Release Notes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
notes: ${{ steps.notes.outputs.body }}
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build release notes
|
||||
id: notes
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Find the previous stable tag (exclude beta tags)
|
||||
PREV_TAG=$(git tag --sort=-creatordate | grep -vE '\-beta\.' | grep -v "^v${INPUT_VERSION}$" | head -1 || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
RANGE="HEAD"
|
||||
else
|
||||
RANGE="${PREV_TAG}..HEAD"
|
||||
fi
|
||||
|
||||
# Extract features only — skip bug fixes for clean release notes
|
||||
FEATURES=$(git log "$RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -iE '^feat(\(|:)' \
|
||||
| sed 's/^feat(\([^)]*\)): /\1: /' \
|
||||
| sed 's/^feat: //' \
|
||||
| sed 's/ (#[0-9]*)$//' \
|
||||
| sort -uf \
|
||||
| while IFS= read -r line; do echo "- ${line}"; done || true)
|
||||
|
||||
if [ -z "$FEATURES" ]; then
|
||||
FEATURES="- Incremental improvements and polish"
|
||||
fi
|
||||
|
||||
# Collect ALL unique contributors: git authors + Co-Authored-By
|
||||
GIT_AUTHORS=$(git log "$RANGE" --pretty=format:"%an" --no-merges | sort -uf || true)
|
||||
CO_AUTHORS=$(git log "$RANGE" --pretty=format:"%b" --no-merges \
|
||||
| grep -ioE 'Co-Authored-By: *[^<]+' \
|
||||
| sed 's/Co-Authored-By: *//i' \
|
||||
| sed 's/ *$//' \
|
||||
| sort -uf || true)
|
||||
|
||||
# Merge, deduplicate, and filter out bots
|
||||
ALL_CONTRIBUTORS=$(printf "%s\n%s" "$GIT_AUTHORS" "$CO_AUTHORS" \
|
||||
| sort -uf \
|
||||
| grep -v '^$' \
|
||||
| grep -viE '\[bot\]$|^dependabot|^github-actions|^copilot|^ZeroClaw Bot|^ZeroClaw Runner|^ZeroClaw Agent|^blacksmith' \
|
||||
| while IFS= read -r name; do echo "- ${name}"; done || true)
|
||||
|
||||
BODY=$(cat <<NOTES_EOF
|
||||
## What's New
|
||||
|
||||
${FEATURES}
|
||||
|
||||
## Contributors
|
||||
|
||||
${ALL_CONTRIBUTORS}
|
||||
|
||||
---
|
||||
*Full changelog: ${PREV_TAG}...v${INPUT_VERSION}*
|
||||
NOTES_EOF
|
||||
)
|
||||
|
||||
{
|
||||
echo "body<<BODY_EOF"
|
||||
echo "$BODY"
|
||||
echo "BODY_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
needs: [validate, web]
|
||||
@@ -150,7 +223,7 @@ jobs:
|
||||
|
||||
publish:
|
||||
name: Publish Stable Release
|
||||
needs: [validate, build]
|
||||
needs: [validate, release-notes, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
@@ -166,17 +239,75 @@ jobs:
|
||||
find . -type f \( -name '*.tar.gz' -o -name '*.zip' \) -exec sha256sum {} + | sed 's| \./[^/]*/| |' > SHA256SUMS
|
||||
cat SHA256SUMS
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
tag_name: ${{ needs.validate.outputs.tag }}
|
||||
name: ${{ needs.validate.outputs.tag }}
|
||||
prerelease: false
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
artifacts/**/*
|
||||
- name: Collect release assets
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find artifacts -type f \( -name '*.tar.gz' -o -name '*.zip' -o -name 'SHA256SUMS' \) -exec cp {} release-assets/ \;
|
||||
cp install.sh release-assets/
|
||||
echo "--- Assets ---"
|
||||
ls -lh release-assets/
|
||||
|
||||
- name: Write release notes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NOTES: ${{ needs.release-notes.outputs.notes }}
|
||||
run: printf '%s\n' "$NOTES" > release-notes.md
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ needs.validate.outputs.tag }}
|
||||
run: |
|
||||
gh release create "$TAG" release-assets/* \
|
||||
--repo "${{ github.repository }}" \
|
||||
--title "$TAG" \
|
||||
--notes-file release-notes.md \
|
||||
--latest
|
||||
|
||||
crates-io:
|
||||
name: Publish to crates.io
|
||||
needs: [validate, publish]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.92.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
|
||||
- name: Build web dashboard
|
||||
run: cd web && npm ci && npm run build
|
||||
|
||||
- name: Clean web build artifacts
|
||||
run: rm -rf web/node_modules web/src web/package.json web/package-lock.json web/tsconfig*.json web/vite.config.ts web/index.html
|
||||
|
||||
- name: Publish to crates.io
|
||||
run: cargo publish --locked --allow-dirty --no-verify
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
redeploy-website:
|
||||
name: Trigger Website Redeploy
|
||||
needs: [publish]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger website redeploy
|
||||
env:
|
||||
PAT: ${{ secrets.WEBSITE_REPO_PAT }}
|
||||
run: |
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: token $PAT" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/zeroclaw-labs/zeroclaw-website/dispatches \
|
||||
-d '{"event_type":"new-release","client_payload":{"install_script_url":"https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh"}}'
|
||||
|
||||
docker:
|
||||
name: Push Docker Image
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
name: Tweet Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tweet_text:
|
||||
description: "Custom tweet text (include emojis, keep it punchy)"
|
||||
required: true
|
||||
type: string
|
||||
image_url:
|
||||
description: "Optional image URL to attach (png/jpg)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
tweet:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for new features
|
||||
id: check
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name || '' }}
|
||||
MANUAL_TEXT: ${{ inputs.tweet_text || '' }}
|
||||
run: |
|
||||
# Manual dispatch always proceeds
|
||||
if [ -n "$MANUAL_TEXT" ]; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Find the PREVIOUS release tag (including betas) to check for new features
|
||||
PREV_TAG=$(git tag --sort=-creatordate \
|
||||
| grep -v "^${RELEASE_TAG}$" \
|
||||
| head -1 || echo "")
|
||||
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Count new feat() commits since the previous release
|
||||
NEW_FEATS=$(git log "${PREV_TAG}..${RELEASE_TAG}" --pretty=format:"%s" --no-merges \
|
||||
| grep -ciE '^feat(\(|:)' || echo "0")
|
||||
|
||||
if [ "$NEW_FEATS" -eq 0 ]; then
|
||||
echo "No new features since ${PREV_TAG} — skipping tweet"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "${NEW_FEATS} new feature(s) since ${PREV_TAG} — tweeting"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build tweet text
|
||||
id: tweet
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name || '' }}
|
||||
RELEASE_URL: ${{ github.event.release.html_url || '' }}
|
||||
MANUAL_TEXT: ${{ inputs.tweet_text || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -n "$MANUAL_TEXT" ]; then
|
||||
TWEET="$MANUAL_TEXT"
|
||||
else
|
||||
# For features: diff against the PREVIOUS release (including betas)
|
||||
# This prevents duplicate feature lists across consecutive betas
|
||||
PREV_RELEASE=$(git tag --sort=-creatordate \
|
||||
| grep -v "^${RELEASE_TAG}$" \
|
||||
| head -1 || echo "")
|
||||
|
||||
# For contributors: diff against the last STABLE release
|
||||
# This captures everyone across the full release cycle
|
||||
PREV_STABLE=$(git tag --sort=-creatordate \
|
||||
| grep -v "^${RELEASE_TAG}$" \
|
||||
| grep -vE '\-beta\.' \
|
||||
| head -1 || echo "")
|
||||
|
||||
FEAT_RANGE="${PREV_RELEASE:+${PREV_RELEASE}..}${RELEASE_TAG}"
|
||||
CONTRIB_RANGE="${PREV_STABLE:+${PREV_STABLE}..}${RELEASE_TAG}"
|
||||
|
||||
# Extract NEW features only since the last release
|
||||
FEATURES=$(git log "$FEAT_RANGE" --pretty=format:"%s" --no-merges \
|
||||
| grep -iE '^feat(\(|:)' \
|
||||
| sed 's/^feat(\([^)]*\)): /\1: /' \
|
||||
| sed 's/^feat: //' \
|
||||
| sed 's/ (#[0-9]*)$//' \
|
||||
| sort -uf \
|
||||
| head -4 \
|
||||
| while IFS= read -r line; do echo "🚀 ${line}"; done || true)
|
||||
|
||||
if [ -z "$FEATURES" ]; then
|
||||
FEATURES="🚀 Incremental improvements and polish"
|
||||
fi
|
||||
|
||||
# Count ALL contributors across the full release cycle
|
||||
GIT_AUTHORS=$(git log "$CONTRIB_RANGE" --pretty=format:"%an" --no-merges | sort -uf || true)
|
||||
CO_AUTHORS=$(git log "$CONTRIB_RANGE" --pretty=format:"%b" --no-merges \
|
||||
| grep -ioE 'Co-Authored-By: *[^<]+' \
|
||||
| sed 's/Co-Authored-By: *//i' \
|
||||
| sed 's/ *$//' \
|
||||
| sort -uf || true)
|
||||
|
||||
TOTAL_COUNT=$(printf "%s\n%s" "$GIT_AUTHORS" "$CO_AUTHORS" \
|
||||
| sort -uf \
|
||||
| grep -v '^$' \
|
||||
| grep -viE '\[bot\]$|^dependabot|^github-actions|^copilot|^ZeroClaw Bot|^ZeroClaw Runner|^ZeroClaw Agent|^blacksmith' \
|
||||
| grep -c . || echo "0")
|
||||
|
||||
# Build tweet — new features, contributor count, hashtags
|
||||
TWEET=$(printf "🦀 ZeroClaw %s\n\n%s\n\n🙌 %s contributors\n\n%s\n\n#zeroclaw #rust #ai #opensource" \
|
||||
"$RELEASE_TAG" "$FEATURES" "$TOTAL_COUNT" "$RELEASE_URL")
|
||||
fi
|
||||
|
||||
# Append release URL if not already present and we have one
|
||||
if [ -n "$RELEASE_URL" ] && ! echo "$TWEET" | grep -q "$RELEASE_URL"; then
|
||||
TWEET=$(printf "%s\n\n%s" "$TWEET" "$RELEASE_URL")
|
||||
fi
|
||||
|
||||
# Truncate to 280 chars if needed
|
||||
if [ ${#TWEET} -gt 280 ]; then
|
||||
TWEET="${TWEET:0:277}..."
|
||||
fi
|
||||
|
||||
echo "--- Tweet preview ---"
|
||||
echo "$TWEET"
|
||||
echo "--- ${#TWEET} chars ---"
|
||||
|
||||
{
|
||||
echo "text<<TWEET_EOF"
|
||||
echo "$TWEET"
|
||||
echo "TWEET_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Post to X
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }}
|
||||
TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_API_SECRET_KEY }}
|
||||
TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
||||
TWEET_TEXT: ${{ steps.tweet.outputs.text }}
|
||||
IMAGE_URL: ${{ inputs.image_url || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Skip if Twitter secrets are not configured
|
||||
if [ -z "$TWITTER_CONSUMER_KEY" ] || [ -z "$TWITTER_ACCESS_TOKEN" ]; then
|
||||
echo "::warning::Twitter secrets not configured — skipping tweet"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pip install requests requests-oauthlib --quiet
|
||||
|
||||
python3 - <<'PYEOF'
|
||||
import os, sys, time
|
||||
from requests_oauthlib import OAuth1Session
|
||||
|
||||
consumer_key = os.environ["TWITTER_CONSUMER_KEY"]
|
||||
consumer_secret = os.environ["TWITTER_CONSUMER_SECRET"]
|
||||
access_token = os.environ["TWITTER_ACCESS_TOKEN"]
|
||||
access_token_secret = os.environ["TWITTER_ACCESS_TOKEN_SECRET"]
|
||||
tweet_text = os.environ["TWEET_TEXT"]
|
||||
image_url = os.environ.get("IMAGE_URL", "")
|
||||
|
||||
oauth = OAuth1Session(
|
||||
consumer_key,
|
||||
client_secret=consumer_secret,
|
||||
resource_owner_key=access_token,
|
||||
resource_owner_secret=access_token_secret,
|
||||
)
|
||||
|
||||
media_id = None
|
||||
|
||||
# Upload image if provided
|
||||
if image_url:
|
||||
import requests
|
||||
print(f"Downloading image: {image_url}")
|
||||
img_resp = requests.get(image_url, timeout=30)
|
||||
img_resp.raise_for_status()
|
||||
|
||||
content_type = img_resp.headers.get("content-type", "image/png")
|
||||
init_resp = oauth.post(
|
||||
"https://upload.twitter.com/1.1/media/upload.json",
|
||||
data={
|
||||
"command": "INIT",
|
||||
"total_bytes": len(img_resp.content),
|
||||
"media_type": content_type,
|
||||
},
|
||||
)
|
||||
if init_resp.status_code != 202:
|
||||
print(f"Media INIT failed: {init_resp.status_code} {init_resp.text}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
media_id = init_resp.json()["media_id_string"]
|
||||
|
||||
append_resp = oauth.post(
|
||||
"https://upload.twitter.com/1.1/media/upload.json",
|
||||
data={"command": "APPEND", "media_id": media_id, "segment_index": 0},
|
||||
files={"media_data": img_resp.content},
|
||||
)
|
||||
if append_resp.status_code not in (200, 204):
|
||||
print(f"Media APPEND failed: {append_resp.status_code} {append_resp.text}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
fin_resp = oauth.post(
|
||||
"https://upload.twitter.com/1.1/media/upload.json",
|
||||
data={"command": "FINALIZE", "media_id": media_id},
|
||||
)
|
||||
if fin_resp.status_code not in (200, 201):
|
||||
print(f"Media FINALIZE failed: {fin_resp.status_code} {fin_resp.text}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
state = fin_resp.json().get("processing_info", {}).get("state")
|
||||
while state == "pending" or state == "in_progress":
|
||||
wait = fin_resp.json().get("processing_info", {}).get("check_after_secs", 2)
|
||||
time.sleep(wait)
|
||||
status_resp = oauth.get(
|
||||
"https://upload.twitter.com/1.1/media/upload.json",
|
||||
params={"command": "STATUS", "media_id": media_id},
|
||||
)
|
||||
state = status_resp.json().get("processing_info", {}).get("state")
|
||||
fin_resp = status_resp
|
||||
|
||||
print(f"Image uploaded: media_id={media_id}")
|
||||
|
||||
# Post tweet
|
||||
payload = {"text": tweet_text}
|
||||
if media_id:
|
||||
payload["media"] = {"media_ids": [media_id]}
|
||||
|
||||
resp = oauth.post("https://api.x.com/2/tweets", json=payload)
|
||||
|
||||
if resp.status_code == 201:
|
||||
data = resp.json()
|
||||
tweet_id = data["data"]["id"]
|
||||
print(f"Tweet posted: https://x.com/zeroclawlabs/status/{tweet_id}")
|
||||
else:
|
||||
print(f"Failed to post tweet: {resp.status_code}", file=sys.stderr)
|
||||
print(resp.text, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
Generated
+52
-53
@@ -117,9 +117,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -132,15 +132,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
@@ -752,9 +752,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.56"
|
||||
version = "1.2.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -889,9 +889,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.60"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -899,9 +899,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.60"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -911,18 +911,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.66"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031"
|
||||
checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -932,9 +932,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
@@ -957,9 +957,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "compression-codecs"
|
||||
@@ -989,13 +989,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.2"
|
||||
version = "0.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4"
|
||||
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width 0.2.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@@ -3985,9 +3984,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
@@ -5737,9 +5736,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.17.0"
|
||||
version = "3.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9"
|
||||
checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
@@ -6585,9 +6584,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
@@ -7923,8 +7922,30 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroclaw"
|
||||
version = "0.1.9"
|
||||
name = "zeroclaw-robot-kit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"chrono",
|
||||
"directories",
|
||||
"portable-atomic",
|
||||
"reqwest",
|
||||
"rppal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
"toml 1.0.6+spec-1.1.0",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroclawlabs"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-imap",
|
||||
@@ -8012,28 +8033,6 @@ dependencies = [
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroclaw-robot-kit"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"chrono",
|
||||
"directories",
|
||||
"portable-atomic",
|
||||
"reqwest",
|
||||
"rppal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
"toml 1.0.6+spec-1.1.0",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.42"
|
||||
|
||||
+20
-2
@@ -3,8 +3,8 @@ members = [".", "crates/robot-kit"]
|
||||
resolver = "2"
|
||||
|
||||
[package]
|
||||
name = "zeroclaw"
|
||||
version = "0.1.9"
|
||||
name = "zeroclawlabs"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
authors = ["theonlyhennygod"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -15,6 +15,24 @@ keywords = ["ai", "agent", "cli", "assistant", "chatbot"]
|
||||
categories = ["command-line-utilities", "api-bindings"]
|
||||
rust-version = "1.87"
|
||||
|
||||
[[bin]]
|
||||
name = "zeroclaw"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "zeroclaw"
|
||||
path = "src/lib.rs"
|
||||
|
||||
include = [
|
||||
"/src/**/*",
|
||||
"/build.rs",
|
||||
"/Cargo.toml",
|
||||
"/Cargo.lock",
|
||||
"/LICENSE*",
|
||||
"/README.md",
|
||||
"/web/dist/**/*",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
# CLI - minimal and fast
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # عرض الإصدار ومعلومات البنا
|
||||
|
||||
راجع [مرجع الأوامر](docs/commands-reference.md) للخيارات والأمثلة الكاملة.
|
||||
|
||||
## البنية
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ القنوات (سمة) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ منسق الوكيل │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ توجيه │ │ السياق │ │ التنفيذ │ │
|
||||
│ │ الرسائل │ │ الذاكرة │ │ الأداة │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ الموفرون │ │ الذاكرة │ │ الأدوات │
|
||||
│ (سمة) │ │ (سمة) │ │ (سمة) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ وقت التشغيل (سمة) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**المبادئ الأساسية:**
|
||||
|
||||
- كل شيء هو **سمة** — الموفرون والقنوات والأدوات والذاكرة والأنفاق
|
||||
- القنوات تستدعي المنسق؛ المنسق يستدعي الموفرون + الأدوات
|
||||
- نظام الذاكرة يدير سياق المحادثة (markdown أو SQLite أو لا شيء)
|
||||
- وقت التشغيل يجرد تنفيذ الكود (أصلي أو Docker)
|
||||
- لا قفل للمورد — استبدل Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama بدون تغييرات في الكود
|
||||
|
||||
راجع [توثيق البنية](docs/architecture.svg) للرسوم البيانية التفصيلية وتفاصيل التنفيذ.
|
||||
|
||||
## الأمثلة
|
||||
|
||||
### بوت Telegram
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # معرف مستخدم Telegram الخاص بك
|
||||
```
|
||||
|
||||
ابدأ البرنامج الخفي + الوكيل، ثم أرسل رسالة إلى بوتك على Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
مرحباً! هل يمكنك مساعدتي في كتابة نص Python؟
|
||||
```
|
||||
|
||||
يستجيب البوت بكود مُنشأ بالذكاء الاصطناعي، وينفذ الأدوات إذا طُلب، ويحافظ على سياق المحادثة.
|
||||
|
||||
### Matrix (تشفير من طرف إلى طرف)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
ادعُ `@zeroclaw:matrix.org` إلى غرفة مشفرة، وسيستجيب البوت بتشفير كامل. راجع [دليل Matrix E2EE](docs/matrix-e2ee-guide.md) لإعداد التحقق من الجهاز.
|
||||
|
||||
### متعدد الموفرون
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # التبديل عند خطأ المورد
|
||||
```
|
||||
|
||||
إذا فشل Anthropic أو وصل إلى حد السرعة، يتبادل المنسق تلقائيًا إلى OpenAI.
|
||||
|
||||
### ذاكرة مخصصة
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # حذف تلقائي بعد 90 يومًا
|
||||
```
|
||||
|
||||
أو استخدم Markdown للتخزين القابل للقراءة البشرية:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
راجع [مرجع التكوين](docs/config-reference.md#memory) لجميع خيارات الذاكرة.
|
||||
|
||||
## دعم الموفرون
|
||||
|
||||
| المورد | الحالة | مفتاح API | النماذج المثال |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ مستقر | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ مستقر | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ مستقر | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ مستقر | N/A (محلي) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ مستقر | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ مستقر | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 مخطط | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 مخطط | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### نقاط النهاية المخصصة
|
||||
|
||||
يدعم ZeroClaw نقاط النهاية المتوافقة مع OpenAI:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
مثال: استخدم [LiteLLM](https://github.com/BerriAI/litellm) كوكيل للوصول إلى أي LLM عبر واجهة OpenAI.
|
||||
|
||||
راجع [مرجع الموفرون](docs/providers-reference.md) لتفاصيل التكوين الكاملة.
|
||||
|
||||
## دعم القنوات
|
||||
|
||||
| القناة | الحالة | المصادقة | ملاحظات |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ مستقر | رمز البوت | دعم كامل بما في ذلك الملفات والصور والأزرار المضمنة |
|
||||
| **Matrix** | ✅ مستقر | كلمة المرور أو الرمز | دعم E2EE مع التحقق من الجهاز |
|
||||
| **Slack** | 🚧 مخطط | OAuth أو رمز البوت | يتطلب الوصول إلى مساحة العمل |
|
||||
| **Discord** | 🚧 مخطط | رمز البوت | يتطلب أذونات النقابة |
|
||||
| **WhatsApp** | 🚧 مخطط | Twilio أو API الرسمية | يتطلب حساب تجاري |
|
||||
| **CLI** | ✅ مستقر | لا شيء | واجهة محادثة مباشرة |
|
||||
| **Web** | 🚧 مخطط | مفتاح API أو OAuth | واجهة دردشة قائمة على المتصفح |
|
||||
|
||||
راجع [مرجع القنوات](docs/channels-reference.md) لتعليمات التكوين الكاملة.
|
||||
|
||||
## دعم الأدوات
|
||||
|
||||
يوفر ZeroClaw أدوات مدمجة لتنفيذ الكود والوصول إلى نظام الملفات واسترجاع الويب:
|
||||
|
||||
| الأداة | الوصف | وقت التشغيل المطلوب |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | ينفذ أوامر الصدفة | أصلي أو Docker |
|
||||
| **python** | ينفذ نصوص Python | Python 3.8+ (أصلي) أو Docker |
|
||||
| **javascript** | ينفذ كود Node.js | Node.js 18+ (أصلي) أو Docker |
|
||||
| **filesystem_read** | يقرأ الملفات | أصلي أو Docker |
|
||||
| **filesystem_write** | يكتب الملفات | أصلي أو Docker |
|
||||
| **web_fetch** | يجلب محتوى الويب | أصلي أو Docker |
|
||||
|
||||
### أمان التنفيذ
|
||||
|
||||
- **وقت التشغيل الأصلي** — يعمل كعملية مستخدم البرنامج الخفي، وصول كامل لنظام الملفات
|
||||
- **وقت تشغيل Docker** — عزل حاوية كامل، أنظمة ملفات وشبكات منفصلة
|
||||
|
||||
قم بتكوين سياسة التنفيذ في `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # قائمة سماح صريحة
|
||||
```
|
||||
|
||||
راجع [مرجع التكوين](docs/config-reference.md#runtime) لخيارات الأمان الكاملة.
|
||||
|
||||
## النشر
|
||||
|
||||
### النشر المحلي (التطوير)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### نشر الخادم (الإنتاج)
|
||||
|
||||
استخدم systemd لإدارة البرنامج الخفي والوكيل كخدمات:
|
||||
|
||||
```bash
|
||||
# تثبيت الملف الثنائي
|
||||
cargo install --path . --locked
|
||||
|
||||
# تكوين مساحة العمل
|
||||
zeroclaw init
|
||||
|
||||
# إنشاء ملفات خدمة systemd
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# تمكين وبدء الخدمات
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# التحقق من الحالة
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
راجع [دليل نشر الشبكة](docs/network-deployment.md) لتعليمات نشر الإنتاج الكاملة.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# بناء الصورة
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# تشغيل الحاوية
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
راجع [`Dockerfile`](Dockerfile) لتفاصيل البناء وخيارات التكوين.
|
||||
|
||||
### أجهزة الحافة
|
||||
|
||||
تم تصميم ZeroClaw للعمل على أجهزة منخفضة الطاقة:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 ميغابايت ذاكرة عشوائية، نواة ARMv8 واحدة، < $5 تكلفة الأجهزة
|
||||
- **Raspberry Pi 4/5** — 1 غيغابايت+ ذاكرة عشوائية، متعدد النوى، مثالي لأحمال العمل المتزامنة
|
||||
- **Orange Pi Zero 2** — ~512 ميغابايت ذاكرة عشوائية، رباعي النواة ARMv8، تكلفة منخفضة جدًا
|
||||
- **أجهزة SBCs x86 (Intel N100)** — 4-8 غيغابايت ذاكرة عشوائية، بناء سريع، دعم Docker أصلي
|
||||
|
||||
راجع [دليل الأجهزة](docs/hardware/README.md) لتعليمات الإعداد الخاصة بالجهاز.
|
||||
|
||||
## الأنفاق (التعرض العام)
|
||||
|
||||
اعرض البرنامج الخفي ZeroClaw المحلي الخاص بك للشبكة العامة عبر أنفاق آمنة:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
موفرو الأنفاق المدعومون:
|
||||
|
||||
- **Cloudflare Tunnel** — HTTPS مجاني، لا تعرض للمنافذ، دعم متعدد المجالات
|
||||
- **Ngrok** — إعداد سريع، مجالات مخصصة (خطة مدفوعة)
|
||||
- **Tailscale** — شبكة شبكية خاصة، لا منفذ عام
|
||||
|
||||
راجع [مرجع التكوين](docs/config-reference.md#tunnel) لخيارات التكوين الكاملة.
|
||||
|
||||
## الأمان
|
||||
|
||||
ينفذ ZeroClaw طبقات متعددة من الأمان:
|
||||
|
||||
### الاقتران
|
||||
|
||||
يُنشئ البرنامج الخفي سر اقتران عند التشغيل الأول مخزن في `~/.zeroclaw/workspace/.pairing`. يجب على العملاء (الوكيل، CLI) تقديم هذا السر للاتصال.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # يُنشئ سرًا جديدًا ويبطل القديم
|
||||
```
|
||||
|
||||
### الصندوق الرملي
|
||||
|
||||
- **وقت تشغيل Docker** — عزل حاوية كامل مع أنظمة ملفات وشبكات منفصلة
|
||||
- **وقت التشغيل الأصلي** — يعمل كعملية مستخدم، محدد النطاق في مساحة العمل افتراضيًا
|
||||
|
||||
### قوائم السماح
|
||||
|
||||
يمكن للقنوات تقييد الوصول حسب معرف المستخدم:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # قائمة سماح صريحة
|
||||
```
|
||||
|
||||
### التشفير
|
||||
|
||||
- **Matrix E2EE** — تشفير من طرف إلى طرف كامل مع التحقق من الجهاز
|
||||
- **نقل TLS** — جميع حركة API والنفق تستخدم HTTPS/TLS
|
||||
|
||||
راجع [توثيق الأمان](docs/security/README.md) للسياسات والممارسات الكاملة.
|
||||
|
||||
## إمكانية الملاحظة
|
||||
|
||||
يسجل ZeroClaw في `~/.zeroclaw/workspace/logs/` افتراضيًا. يتم تخزين السجلات حسب المكون:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # سجلات البرنامج الخفي (بدء التشغيل، طلبات API، الأخطاء)
|
||||
├── agent.log # سجلات الوكيل (توجيه الرسائل، تنفيذ الأدوات)
|
||||
├── telegram.log # سجلات خاصة بالقناة (إذا مُكنت)
|
||||
└── matrix.log # سجلات خاصة بالقناة (إذا مُكنت)
|
||||
```
|
||||
|
||||
### تكوين التسجيل
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug، info، warn، error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # يومي، ساعي، حجم
|
||||
max_size_mb = 100 # للتدوير القائم على الحجم
|
||||
retention_days = 30 # حذف تلقائي بعد N يومًا
|
||||
```
|
||||
|
||||
راجع [مرجع التكوين](docs/config-reference.md#logging) لجميع خيارات التسجيل.
|
||||
|
||||
### المقاييس (مخطط)
|
||||
|
||||
دعم مقاييس Prometheus لمراقبة الإنتاج قريبًا. التتبع في [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## المهارات
|
||||
|
||||
يدعم ZeroClaw المهارات المخصصة — وحدات قابلة لإعادة الاستخدام توسع قدرات النظام.
|
||||
|
||||
### تعريف المهارة
|
||||
|
||||
يتم تخزين المهارات في `~/.zeroclaw/workspace/skills/<skill-name>/` بهذا الهيكل:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # بيانات المهارة (الاسم، الوصف، التبعيات)
|
||||
├── prompt.md # موجه النظام للذكاء الاصطناعي
|
||||
└── tools/ # أدوات مخصصة اختيارية
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### مثال المهارة
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "يبحث في الويب ويلخص النتائج"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
أنت مساعد بحث. عند طلب البحث عن شيء ما:
|
||||
|
||||
1. استخدم web_fetch لاسترجاع المحتوى
|
||||
2. لخص النتائج بتنسيق سهل القراءة
|
||||
3. استشهد بالمصادر مع عناوين URL
|
||||
```
|
||||
|
||||
### استخدام المهارات
|
||||
|
||||
يتم تحميل المهارات تلقائيًا عند بدء تشغيل الوكيل. أشر إليها بالاسم في المحادثات:
|
||||
|
||||
```
|
||||
المستخدم: استخدم مهارة البحث على الويب للعثور على أخبار الذكاء الاصطناعي الأخيرة
|
||||
البوت: [يحمل مهارة البحث على الويب، ينفذ web_fetch، يلخص النتائج]
|
||||
```
|
||||
|
||||
راجع قسم [المهارات](#المهارات) لتعليمات إنشاء المهارات الكاملة.
|
||||
|
||||
## المهارات المفتوحة
|
||||
|
||||
يدعم ZeroClaw [Open Skills](https://github.com/openagents-com/open-skills) — نظام معياري ومحايد للمورد لتوسيع قدرات وكلاء الذكاء الاصطناعي.
|
||||
|
||||
### تمكين المهارات المفتوحة
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # اختياري
|
||||
```
|
||||
|
||||
يمكنك أيضًا التجاوز في وقت التشغيل باستخدام `ZEROCLAW_OPEN_SKILLS_ENABLED` و `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## التطوير
|
||||
|
||||
```bash
|
||||
cargo build # بناء التطوير
|
||||
cargo build --release # بناء الإصدار (codegen-units=1، يعمل على جميع الأجهزة بما في ذلك Raspberry Pi)
|
||||
cargo build --profile release-fast # بناء أسرع (codegen-units=8، يتطلب 16 غيغابايت+ ذاكرة عشوائية)
|
||||
cargo test # تشغيل مجموعة الاختبار الكاملة
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # تنسيق
|
||||
|
||||
# تشغيل معيار مقارنة SQLite مقابل Markdown
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### خطاف ما قبل الدفع
|
||||
|
||||
يقوم خطاف git بتشغيل `cargo fmt --check` و `cargo clippy -- -D warnings` و `cargo test` قبل كل دفع. قم بتمكينه مرة واحدة:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### استكشاف أخطاء البناء وإصلاحها (أخطاء OpenSSL على Linux)
|
||||
|
||||
إذا واجهت خطأ بناء `openssl-sys`، قم بمزامنة التبعيات وأعد التجميع باستخدام ملف قفل المستودع:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
تم تكوين ZeroClaw لاستخدام `rustls` لتبعيات HTTP/TLS؛ `--locked` يحافظ على الرسم البياني العابر حتمي في البيئات النظيفة.
|
||||
|
||||
لتخطي الخطاف عندما تحتاج إلى دفع سريع أثناء التطوير:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## التعاون والتوثيق
|
||||
|
||||
ابدأ بمركز التوثيق لخريطة قائمة على المهام:
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # Zobrazuje verzi a build informace
|
||||
|
||||
Viz [Příkazová reference](docs/commands-reference.md) pro kompletní možnosti a příklady.
|
||||
|
||||
## Architektura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Kanály (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Agent Orchestrátor │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Směrování │ │ Kontext │ │ Provedení │ │
|
||||
│ │ Zpráva │ │ Paměť │ │ Nástroj │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Poskytovatel│ │ Paměť │ │ Nástroje │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Klíčové principy:**
|
||||
|
||||
- Vše je **trait** — poskytovatelé, kanály, nástroje, paměť, tunely
|
||||
- Kanály volají orchestrátor; orchestrátor volá poskytovatele + nástroje
|
||||
- Paměťový systém spravuje konverzační kontext (markdown, SQLite, nebo žádný)
|
||||
- Runtime abstrahuje provádění kódu (nativní nebo Docker)
|
||||
- Žádné vendor lock-in — vyměňujte Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama beze změn kódu
|
||||
|
||||
Viz [dokumentace architektury](docs/architecture.svg) pro detailní diagramy a detaily implementace.
|
||||
|
||||
## Příklady
|
||||
|
||||
### Telegram Bot
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Vaše Telegram user ID
|
||||
```
|
||||
|
||||
Spusťte daemon + agent, pak pošlete zprávu vašemu botovi na Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
Ahoj! Mohl bys mi pomoci napsat Python skript?
|
||||
```
|
||||
|
||||
Bot odpoví AI-generovaným kódem, provede nástroje pokud požadováno a udržuje konverzační kontext.
|
||||
|
||||
### Matrix (end-to-end šifrování)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Pozvěte `@zeroclaw:matrix.org` do šifrované místnosti a bot odpoví s plným šifrováním. Viz [Matrix E2EE Guide](docs/matrix-e2ee-guide.md) pro nastavení ověření zařízení.
|
||||
|
||||
### Multi-Poskytovatel
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Failover při chybě poskytovatele
|
||||
```
|
||||
|
||||
Pokud Anthropic selže nebo má rate-limit, orchestrátor automaticky přepne na OpenAI.
|
||||
|
||||
### Vlastní Paměť
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Automatické čištění po 90 dnech
|
||||
```
|
||||
|
||||
Nebo použijte Markdown pro lidsky čitelné ukládání:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Viz [Konfigurační reference](docs/config-reference.md#memory) pro všechny možnosti paměti.
|
||||
|
||||
## Podpora Poskytovatelů
|
||||
|
||||
| Poskytovatel | Stav | API Klíč | Příklad Modelů |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Stabilní | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Stabilní | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Stabilní | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Stabilní | N/A (lokální) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Stabilní | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Stabilní | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Plánováno | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Plánováno | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Vlastní Endpointy
|
||||
|
||||
ZeroClaw podporuje OpenAI-kompatibilní endpointy:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Příklad: použijte [LiteLLM](https://github.com/BerriAI/litellm) jako proxy pro přístup k jakémukoli LLM přes OpenAI rozhraní.
|
||||
|
||||
Viz [Poskytovatel reference](docs/providers-reference.md) pro kompletní detaily konfigurace.
|
||||
|
||||
## Podpora Kanálů
|
||||
|
||||
| Kanál | Stav | Autentizace | Poznámky |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Stabilní | Bot Token | Plná podpora včetně souborů, obrázků, inline tlačítek |
|
||||
| **Matrix** | ✅ Stabilní | Heslo nebo Token | E2EE podpora s ověřením zařízení |
|
||||
| **Slack** | 🚧 Plánováno | OAuth nebo Bot Token | Vyžaduje workspace přístup |
|
||||
| **Discord** | 🚧 Plánováno | Bot Token | Vyžaduje guild oprávnění |
|
||||
| **WhatsApp** | 🚧 Plánováno | Twilio nebo oficiální API | Vyžaduje business účet |
|
||||
| **CLI** | ✅ Stabilní | Žádné | Přímé konverzační rozhraní |
|
||||
| **Web** | 🚧 Plánováno | API Klíč nebo OAuth | Prohlížečové chat rozhraní |
|
||||
|
||||
Viz [Kanálová reference](docs/channels-reference.md) pro kompletní instrukce konfigurace.
|
||||
|
||||
## Podpora Nástrojů
|
||||
|
||||
ZeroClaw poskytuje vestavěné nástroje pro provádění kódu, přístup k souborovému systému a web retrieval:
|
||||
|
||||
| Nástroj | Popis | Vyžadovaný Runtime |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Provádí shell příkazy | Nativní nebo Docker |
|
||||
| **python** | Provádí Python skripty | Python 3.8+ (nativní) nebo Docker |
|
||||
| **javascript** | Provádí Node.js kód | Node.js 18+ (nativní) nebo Docker |
|
||||
| **filesystem_read** | Čte soubory | Nativní nebo Docker |
|
||||
| **filesystem_write** | Zapisuje soubory | Nativní nebo Docker |
|
||||
| **web_fetch** | Získává web obsah | Nativní nebo Docker |
|
||||
|
||||
### Bezpečnost Provedení
|
||||
|
||||
- **Nativní Runtime** — běží jako uživatelský proces daemon, plný přístup k souborovému systému
|
||||
- **Docker Runtime** — plná kontejnerová izolace, oddělené souborové systémy a sítě
|
||||
|
||||
Nakonfigurujte politiku provedení v `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Explicitní allowlist
|
||||
```
|
||||
|
||||
Viz [Konfigurační reference](docs/config-reference.md#runtime) pro kompletní možnosti bezpečnosti.
|
||||
|
||||
## Nasazení
|
||||
|
||||
### Lokální Nasazení (Vývoj)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Serverové Nasazení (Produkce)
|
||||
|
||||
Použijte systemd pro správu daemon a agent jako služby:
|
||||
|
||||
```bash
|
||||
# Nainstalujte binary
|
||||
cargo install --path . --locked
|
||||
|
||||
# Nakonfigurujte workspace
|
||||
zeroclaw init
|
||||
|
||||
# Vytvořte systemd servisní soubory
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Povolte a spusťte služby
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Ověřte stav
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Viz [Průvodce síťovým nasazením](docs/network-deployment.md) pro kompletní instrukce produkčního nasazení.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Sestavte image
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Spusťte kontejner
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Viz [`Dockerfile`](Dockerfile) pro detaily sestavení a konfigurační možnosti.
|
||||
|
||||
### Edge Hardware
|
||||
|
||||
ZeroClaw je navržen pro běh na nízko-příkonovém hardwaru:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, jedno ARMv8 jádro, < $5 hardwarové náklady
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, vícejádrový, ideální pro souběžné úlohy
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, čtyřjádrový ARMv8, ultra-nízké náklady
|
||||
- **x86 SBCs (Intel N100)** — 4-8 GB RAM, rychlé buildy, nativní Docker podpora
|
||||
|
||||
Viz [Hardware Guide](docs/hardware/README.md) pro instrukce nastavení specifické pro zařízení.
|
||||
|
||||
## Tunneling (Veřejná Expozice)
|
||||
|
||||
Exponujte svůj lokální ZeroClaw daemon do veřejné sítě přes bezpečné tunely:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Podporovaní tunnel poskytovatelé:
|
||||
|
||||
- **Cloudflare Tunnel** — bezplatný HTTPS, bez expozice portů, multi-doména podpora
|
||||
- **Ngrok** — rychlé nastavení, vlastní domény (placený plán)
|
||||
- **Tailscale** — soukromá mesh síť, bez veřejného portu
|
||||
|
||||
Viz [Konfigurační reference](docs/config-reference.md#tunnel) pro kompletní konfigurační možnosti.
|
||||
|
||||
## Bezpečnost
|
||||
|
||||
ZeroClaw implementuje více vrstev bezpečnosti:
|
||||
|
||||
### Párování
|
||||
|
||||
Daemon generuje párovací tajemství při prvním spuštění uložené v `~/.zeroclaw/workspace/.pairing`. Klienti (agent, CLI) musí předložit toto tajemství pro připojení.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Generuje nové tajemství a zneplatňuje staré
|
||||
```
|
||||
|
||||
### Sandboxing
|
||||
|
||||
- **Docker Runtime** — plná kontejnerová izolace s oddělenými souborovými systémy a sítěmi
|
||||
- **Nativní Runtime** — běží jako uživatelský proces, scoped na workspace defaultně
|
||||
|
||||
### Allowlisty
|
||||
|
||||
Kanály mohou omezit přístup podle user ID:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Explicitní allowlist
|
||||
```
|
||||
|
||||
### Šifrování
|
||||
|
||||
- **Matrix E2EE** — plné end-to-end šifrování s ověřením zařízení
|
||||
- **TLS Transport** — veškerý API a tunnel provoz používá HTTPS/TLS
|
||||
|
||||
Viz [Bezpečnostní dokumentace](docs/security/README.md) pro kompletní politiky a praktiky.
|
||||
|
||||
## Pozorovatelnost
|
||||
|
||||
ZeroClaw loguje do `~/.zeroclaw/workspace/logs/` defaultně. Logy jsou ukládány podle komponenty:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Daemon logy (startup, API požadavky, chyby)
|
||||
├── agent.log # Agent logy (směrování zpráv, provedení nástrojů)
|
||||
├── telegram.log # Kanál-specifické logy (pokud povoleno)
|
||||
└── matrix.log # Kanál-specifické logy (pokud povoleno)
|
||||
```
|
||||
|
||||
### Konfigurace Logování
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # Pro rotaci založenou na velikosti
|
||||
retention_days = 30 # Automatické čištění po N dnech
|
||||
```
|
||||
|
||||
Viz [Konfigurační reference](docs/config-reference.md#logging) pro všechny možnosti logování.
|
||||
|
||||
### Metriky (Plánováno)
|
||||
|
||||
Podpora Prometheus metrik pro produkční monitoring již brzy. Sledování v [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Dovednosti
|
||||
|
||||
ZeroClaw podporuje vlastní dovednosti — opakovaně použitelné moduly rozšiřující schopnosti systému.
|
||||
|
||||
### Definice Dovednosti
|
||||
|
||||
Dovednosti jsou uloženy v `~/.zeroclaw/workspace/skills/<skill-name>/` s touto strukturou:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Metadata dovednosti (název, popis, závislosti)
|
||||
├── prompt.md # Systémový prompt pro AI
|
||||
└── tools/ # Volitelné vlastní nástroje
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Příklad Dovednosti
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Hledá na webu a shrnuje výsledky"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
Jste výzkumný asistent. Když požádáte o výzkum něčeho:
|
||||
|
||||
1. Použijte web_fetch pro získání obsahu
|
||||
2. Shrňte výsledky v snadno čitelném formátu
|
||||
3. Citujte zdroje s URL
|
||||
```
|
||||
|
||||
### Použití Dovedností
|
||||
|
||||
Dovednosti jsou automaticky načítány při startu agenta. Odkazujte na ně jménem v konverzacích:
|
||||
|
||||
```
|
||||
Uživatel: Použij dovednost web-research k nalezení nejnovějších AI zpráv
|
||||
Bot: [načte dovednost web-research, provede web_fetch, shrne výsledky]
|
||||
```
|
||||
|
||||
Viz sekce [Dovednosti](#dovednosti) pro kompletní instrukce tvorby dovedností.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw podporuje [Open Skills](https://github.com/openagents-com/open-skills) — modulární a poskytovatel-agnostický systém pro rozšíření schopností AI agentů.
|
||||
|
||||
### Povolit Open Skills
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # volitelné
|
||||
```
|
||||
|
||||
Můžete také přepsat za běhu pomocí `ZEROCLAW_OPEN_SKILLS_ENABLED` a `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Vývoj
|
||||
|
||||
```bash
|
||||
cargo build # Dev build
|
||||
cargo build --release # Release build (codegen-units=1, funguje na všech zařízeních včetně Raspberry Pi)
|
||||
cargo build --profile release-fast # Rychlejší build (codegen-units=8, vyžaduje 16 GB+ RAM)
|
||||
cargo test # Spustí plnou testovací sadu
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Formátování
|
||||
|
||||
# Spusťte SQLite vs Markdown srovnávací benchmark
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Pre-push hook
|
||||
|
||||
Git hook spouští `cargo fmt --check`, `cargo clippy -- -D warnings`, a `cargo test` před každým push. Povolte jej jednou:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Řešení problémů s Buildem (OpenSSL chyby na Linuxu)
|
||||
|
||||
Pokud narazíte na `openssl-sys` build chybu, synchronizujte závislosti a znovu zkompilujte s lockfile repoziťáře:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw je nakonfigurován pro použití `rustls` pro HTTP/TLS závislosti; `--locked` udržuje transitivní graf deterministický v čistých prostředích.
|
||||
|
||||
Pro přeskočení hooku když potřebujete rychlý push během vývoje:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Spolupráce & Docs
|
||||
|
||||
Začněte s dokumentačním centrem pro task-based mapu:
|
||||
|
||||
-437
@@ -367,443 +367,6 @@ zeroclaw version # Zeigt Version und Build-Informationen
|
||||
|
||||
Siehe [Befehlsreferenz](docs/commands-reference.md) für vollständige Optionen und Beispiele.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Channels (Trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Agent-Orchestrator │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Routing │ │ Kontext │ │ Ausführung │ │
|
||||
│ │ Nachricht │ │ Speicher │ │ Werkzeug │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Provider │ │ Speicher │ │ Werkzeuge │
|
||||
│ (Trait) │ │ (Trait) │ │ (Trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (Trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Schlüsselprinzipien:**
|
||||
|
||||
- Alles ist ein **Trait** — Provider, Channels, Tools, Speicher, Tunnel
|
||||
- Channels rufen den Orchestrator auf; der Orchestrator ruft Provider + Tools auf
|
||||
- Das Speichersystem verwaltet Konversationskontext (Markdown, SQLite, oder keiner)
|
||||
- Das Runtime abstrahiert Code-Ausführung (nativ oder Docker)
|
||||
- Kein Provider-Lock-in — tausche Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama ohne Code-Änderungen
|
||||
|
||||
Siehe [Architektur-Dokumentation](docs/architecture.svg) für detaillierte Diagramme und Implementierungsdetails.
|
||||
|
||||
## Beispiele
|
||||
|
||||
### Telegram-Bot
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Deine Telegram-Benutzer-ID
|
||||
```
|
||||
|
||||
Starte den Daemon + Agent, dann sende eine Nachricht an deinen Bot auf Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
Hallo! Könntest du mir helfen, ein Python-Skript zu schreiben?
|
||||
```
|
||||
|
||||
Der Bot antwortet mit KI-generiertem Code, führt Tools auf Anfrage aus und behält den Konversationskontext.
|
||||
|
||||
### Matrix (Ende-zu-Ende-Verschlüsselung)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Lade `@zeroclaw:matrix.org` in einen verschlüsselten Raum ein, und der Bot wird mit vollständiger Verschlüsselung antworten. Siehe [Matrix E2EE-Leitfaden](docs/matrix-e2ee-guide.md) für Geräteverifizierungs-Setup.
|
||||
|
||||
### Multi-Provider
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Failover bei Provider-Fehler
|
||||
```
|
||||
|
||||
Wenn Anthropic fehlschlägt oder Rate-Limit erreicht, wechselt der Orchestrator automatisch zu OpenAI.
|
||||
|
||||
### Benutzerdefinierter Speicher
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Automatische Bereinigung nach 90 Tagen
|
||||
```
|
||||
|
||||
Oder verwende Markdown für menschenlesbaren Speicher:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Siehe [Konfigurationsreferenz](docs/config-reference.md#memory) für alle Speicheroptionen.
|
||||
|
||||
## Provider-Unterstützung
|
||||
|
||||
| Provider | Status | API-Schlüssel | Beispielmodelle |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Stabil | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Stabil | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Stabil | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Stabil | N/A (lokal) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Stabil | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Stabil | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Geplant | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Geplant | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Benutzerdefinierte Endpoints
|
||||
|
||||
ZeroClaw unterstützt OpenAI-kompatible Endpoints:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Beispiel: verwende [LiteLLM](https://github.com/BerriAI/litellm) als Proxy, um auf jedes LLM über die OpenAI-Schnittstelle zuzugreifen.
|
||||
|
||||
Siehe [Provider-Referenz](docs/providers-reference.md) für vollständige Konfigurationsdetails.
|
||||
|
||||
## Channel-Unterstützung
|
||||
|
||||
| Channel | Status | Authentifizierung | Hinweise |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Stabil | Bot-Token | Vollständige Unterstützung inklusive Dateien, Bilder, Inline-Buttons |
|
||||
| **Matrix** | ✅ Stabil | Passwort oder Token | E2EE-Unterstützung mit Geräteverifizierung |
|
||||
| **Slack** | 🚧 Geplant | OAuth oder Bot-Token | Erfordert Workspace-Zugriff |
|
||||
| **Discord** | 🚧 Geplant | Bot-Token | Erfordert Guild-Berechtigungen |
|
||||
| **WhatsApp** | 🚧 Geplant | Twilio oder offizielle API | Erfordert Business-Konto |
|
||||
| **CLI** | ✅ Stabil | Keine | Direkte konversationelle Schnittstelle |
|
||||
| **Web** | 🚧 Geplant | API-Schlüssel oder OAuth | Browserbasierte Chat-Schnittstelle |
|
||||
|
||||
Siehe [Channel-Referenz](docs/channels-reference.md) für vollständige Konfigurationsanleitungen.
|
||||
|
||||
## Tool-Unterstützung
|
||||
|
||||
ZeroClaw bietet integrierte Tools für Code-Ausführung, Dateisystemzugriff und Web-Abruf:
|
||||
|
||||
| Tool | Beschreibung | Erforderliches Runtime |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Führt Shell-Befehle aus | Nativ oder Docker |
|
||||
| **python** | Führt Python-Skripte aus | Python 3.8+ (nativ) oder Docker |
|
||||
| **javascript** | Führt Node.js-Code aus | Node.js 18+ (nativ) oder Docker |
|
||||
| **filesystem_read** | Liest Dateien | Nativ oder Docker |
|
||||
| **filesystem_write** | Schreibt Dateien | Nativ oder Docker |
|
||||
| **web_fetch** | Ruft Web-Inhalte ab | Nativ oder Docker |
|
||||
|
||||
### Ausführungssicherheit
|
||||
|
||||
- **Natives Runtime** — läuft als Benutzerprozess des Daemons, voller Dateisystemzugriff
|
||||
- **Docker-Runtime** — vollständige Container-Isolierung, separate Dateisysteme und Netzwerke
|
||||
|
||||
Konfiguriere die Ausführungsrichtlinie in `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Explizite Allowlist
|
||||
```
|
||||
|
||||
Siehe [Konfigurationsreferenz](docs/config-reference.md#runtime) für vollständige Sicherheitsoptionen.
|
||||
|
||||
## Deployment
|
||||
|
||||
### Lokales Deployment (Entwicklung)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Server-Deployment (Produktion)
|
||||
|
||||
Verwende systemd, um Daemon und Agent als Dienste zu verwalten:
|
||||
|
||||
```bash
|
||||
# Installiere das Binary
|
||||
cargo install --path . --locked
|
||||
|
||||
# Konfiguriere den Workspace
|
||||
zeroclaw init
|
||||
|
||||
# Erstelle systemd-Dienstdateien
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Aktiviere und starte die Dienste
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Überprüfe den Status
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Siehe [Netzwerk-Deployment-Leitfaden](docs/network-deployment.md) für vollständige Produktions-Deployment-Anleitungen.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Baue das Image
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Führe den Container aus
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Siehe [`Dockerfile`](Dockerfile) für Build-Details und Konfigurationsoptionen.
|
||||
|
||||
### Edge-Hardware
|
||||
|
||||
ZeroClaw ist für den Betrieb auf Low-Power-Hardware konzipiert:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, einzelner ARMv8-Kern, < $5 Hardware-Kosten
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, Multi-Core, ideal für gleichzeitige Workloads
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, Quad-Core ARMv8, Ultra-Low-Cost
|
||||
- **x86 SBCs (Intel N100)** — 4-8 GB RAM, schnelle Builds, nativer Docker-Support
|
||||
|
||||
Siehe [Hardware-Leitfaden](docs/hardware/README.md) für gerätespezifische Einrichtungsanleitungen.
|
||||
|
||||
## Tunneling (Öffentliche Exposition)
|
||||
|
||||
Exponiere deinen lokalen ZeroClaw-Daemon über sichere Tunnel zum öffentlichen Netzwerk:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Unterstützte Tunnel-Provider:
|
||||
|
||||
- **Cloudflare Tunnel** — kostenloses HTTPS, keine Port-Exposition, Multi-Domain-Support
|
||||
- **Ngrok** — schnelle Einrichtung, benutzerdefinierte Domains (kostenpflichtiger Plan)
|
||||
- **Tailscale** — privates Mesh-Netzwerk, kein öffentlicher Port
|
||||
|
||||
Siehe [Konfigurationsreferenz](docs/config-reference.md#tunnel) für vollständige Konfigurationsoptionen.
|
||||
|
||||
## Sicherheit
|
||||
|
||||
ZeroClaw implementiert mehrere Sicherheitsebenen:
|
||||
|
||||
### Pairing
|
||||
|
||||
Der Daemon generiert beim ersten Start ein Pairing-Geheimnis, das in `~/.zeroclaw/workspace/.pairing` gespeichert wird. Clients (Agent, CLI) müssen dieses Geheimnis präsentieren, um eine Verbindung herzustellen.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Generiert ein neues Geheimnis und erklärt das alte für ungültig
|
||||
```
|
||||
|
||||
### Sandboxing
|
||||
|
||||
- **Docker-Runtime** — vollständige Container-Isolierung mit separaten Dateisystemen und Netzwerken
|
||||
- **Natives Runtime** — läuft als Benutzerprozess, standardmäßig auf Workspace beschränkt
|
||||
|
||||
### Allowlists
|
||||
|
||||
Channels können den Zugriff nach Benutzer-ID einschränken:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Explizite Allowlist
|
||||
```
|
||||
|
||||
### Verschlüsselung
|
||||
|
||||
- **Matrix E2EE** — vollständige Ende-zu-Ende-Verschlüsselung mit Geräteverifizierung
|
||||
- **TLS-Transport** — der gesamte API- und Tunnel-Verkehr verwendet HTTPS/TLS
|
||||
|
||||
Siehe [Sicherheitsdokumentation](docs/security/README.md) für vollständige Richtlinien und Praktiken.
|
||||
|
||||
## Observability
|
||||
|
||||
ZeroClaw protokolliert standardmäßig in `~/.zeroclaw/workspace/logs/`. Logs werden nach Komponente gespeichert:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Daemon-Logs (Start, API-Anfragen, Fehler)
|
||||
├── agent.log # Agent-Logs (Nachrichten-Routing, Tool-Ausführung)
|
||||
├── telegram.log # Kanalspezifische Logs (falls aktiviert)
|
||||
└── matrix.log # Kanalspezifische Logs (falls aktiviert)
|
||||
```
|
||||
|
||||
### Logging-Konfiguration
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # Für größenbasierte Rotation
|
||||
retention_days = 30 # Automatische Bereinigung nach N Tagen
|
||||
```
|
||||
|
||||
Siehe [Konfigurationsreferenz](docs/config-reference.md#logging) für alle Logging-Optionen.
|
||||
|
||||
### Metriken (Geplant)
|
||||
|
||||
Prometheus-Metrik-Unterstützung für Produktionsüberwachung kommt bald. Verfolgung in [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Skills
|
||||
|
||||
ZeroClaw unterstützt benutzerdefinierte Skills — wiederverwendbare Module, die die Systemfähigkeiten erweitern.
|
||||
|
||||
### Skill-Definition
|
||||
|
||||
Skills werden in `~/.zeroclaw/workspace/skills/<skill-name>/` mit dieser Struktur gespeichert:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Skill-Metadaten (Name, Beschreibung, Abhängigkeiten)
|
||||
├── prompt.md # System-Prompt für die KI
|
||||
└── tools/ # Optionale benutzerdefinierte Tools
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Skill-Beispiel
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Sucht im Web und fasst Ergebnisse zusammen"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
Du bist ein Forschungsassistent. Wenn du gebeten wirst, etwas zu recherchieren:
|
||||
|
||||
1. Verwende web_fetch, um den Inhalt abzurufen
|
||||
2. Fasse die Ergebnisse in einem leicht lesbaren Format zusammen
|
||||
3. Zitiere die Quellen mit URLs
|
||||
```
|
||||
|
||||
### Skill-Verwendung
|
||||
|
||||
Skills werden beim Agent-Start automatisch geladen. Referenziere sie nach Namen in Konversationen:
|
||||
|
||||
```
|
||||
Benutzer: Verwende den Web-Research-Skill, um die neuesten KI-Nachrichten zu finden
|
||||
Bot: [lädt den Web-Research-Skill, führt web_fetch aus, fasst Ergebnisse zusammen]
|
||||
```
|
||||
|
||||
Siehe Abschnitt [Skills](#skills) für vollständige Skill-Erstellungsanleitungen.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw unterstützt [Open Skills](https://github.com/openagents-com/open-skills) — ein modulares und provider-agnostisches System zur Erweiterung von KI-Agenten-Fähigkeiten.
|
||||
|
||||
### Open Skills aktivieren
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # optional
|
||||
```
|
||||
|
||||
Du kannst auch zur Laufzeit mit `ZEROCLAW_OPEN_SKILLS_ENABLED` und `ZEROCLAW_OPEN_SKILLS_DIR` überschreiben.
|
||||
|
||||
## Entwicklung
|
||||
|
||||
```bash
|
||||
cargo build # Entwicklungs-Build
|
||||
cargo build --release # Release-Build (codegen-units=1, funktioniert auf allen Geräten einschließlich Raspberry Pi)
|
||||
cargo build --profile release-fast # Schnellerer Build (codegen-units=8, erfordert 16 GB+ RAM)
|
||||
cargo test # Führt die vollständige Test-Suite aus
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Formatierung
|
||||
|
||||
# Führe den SQLite vs Markdown Vergleichs-Benchmark aus
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Pre-push-Hook
|
||||
|
||||
Ein Git-Hook führt `cargo fmt --check`, `cargo clippy -- -D warnings`, und `cargo test` vor jedem Push aus. Aktiviere ihn einmal:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Build-Fehlerbehebung (OpenSSL-Fehler unter Linux)
|
||||
|
||||
Wenn du auf einen `openssl-sys`-Build-Fehler stößt, synchronisiere Abhängigkeiten und kompiliere mit dem Lockfile des Repositories neu:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw ist so konfiguriert, dass es `rustls` für HTTP/TLS-Abhängigkeiten verwendet; `--locked` hält den transitiven Graphen in sauberen Umgebungen deterministisch.
|
||||
|
||||
Um den Hook zu überspringen, wenn du während der Entwicklung einen schnellen Push benötigst:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Zusammenarbeit & Docs
|
||||
|
||||
Beginne mit dem Dokumentations-Hub für eine Aufgaben-basierte Karte:
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # Muestra versión e información de build
|
||||
|
||||
Ver [Referencia de Comandos](docs/commands-reference.md) para opciones y ejemplos completos.
|
||||
|
||||
## Arquitectura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Canales (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Orquestador Agent │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Ruteo │ │ Contexto │ │ Ejecución │ │
|
||||
│ │ Mensaje │ │ Memoria │ │ Herramienta│ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Proveedores │ │ Memoria │ │ Herramientas │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Principios clave:**
|
||||
|
||||
- Todo es un **trait** — proveedores, canales, herramientas, memoria, túneles
|
||||
- Los canales llaman al orquestador; el orquestador llama a proveedores + herramientas
|
||||
- El sistema de memoria gestiona contexto conversacional (markdown, SQLite, o ninguno)
|
||||
- El runtime abstrae la ejecución de código (nativo o Docker)
|
||||
- Sin lock-in de proveedor — intercambia Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama sin cambios de código
|
||||
|
||||
Ver [documentación de arquitectura](docs/architecture.svg) para diagramas detallados y detalles de implementación.
|
||||
|
||||
## Ejemplos
|
||||
|
||||
### Bot de Telegram
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Tu ID de usuario de Telegram
|
||||
```
|
||||
|
||||
Inicia el daemon + agent, luego envía un mensaje a tu bot en Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
¡Hola! ¿Podrías ayudarme a escribir un script Python?
|
||||
```
|
||||
|
||||
El bot responde con código generado por AI, ejecuta herramientas si se solicita, y mantiene el contexto de conversación.
|
||||
|
||||
### Matrix (cifrado extremo a extremo)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Invita a `@zeroclaw:matrix.org` a una sala cifrada, y el bot responderá con cifrado completo. Ver [Guía Matrix E2EE](docs/matrix-e2ee-guide.md) para configuración de verificación de dispositivo.
|
||||
|
||||
### Multi-Proveedor
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Failover en error de proveedor
|
||||
```
|
||||
|
||||
Si Anthropic falla o tiene rate-limit, el orquestador hace failover automáticamente a OpenAI.
|
||||
|
||||
### Memoria Personalizada
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Purga automática después de 90 días
|
||||
```
|
||||
|
||||
O usa Markdown para almacenamiento legible por humanos:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Ver [Referencia de Configuración](docs/config-reference.md#memory) para todas las opciones de memoria.
|
||||
|
||||
## Soporte de Proveedor
|
||||
|
||||
| Proveedor | Estado | API Key | Modelos de Ejemplo |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Estable | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Estable | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Estable | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Estable | N/A (local) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Estable | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Estable | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Planificado | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Planificado | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Endpoints Personalizados
|
||||
|
||||
ZeroClaw soporta endpoints compatibles con OpenAI:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Ejemplo: usa [LiteLLM](https://github.com/BerriAI/litellm) como proxy para acceder a cualquier LLM vía interfaz OpenAI.
|
||||
|
||||
Ver [Referencia de Proveedores](docs/providers-reference.md) para detalles de configuración completos.
|
||||
|
||||
## Soporte de Canal
|
||||
|
||||
| Canal | Estado | Autenticación | Notas |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Estable | Bot Token | Soporte completo incluyendo archivos, imágenes, botones inline |
|
||||
| **Matrix** | ✅ Estable | Contraseña o Token | Soporte E2EE con verificación de dispositivo |
|
||||
| **Slack** | 🚧 Planificado | OAuth o Bot Token | Requiere acceso a workspace |
|
||||
| **Discord** | 🚧 Planificado | Bot Token | Requiere permisos de guild |
|
||||
| **WhatsApp** | 🚧 Planificado | Twilio o API oficial | Requiere cuenta business |
|
||||
| **CLI** | ✅ Estable | Ninguno | Interfaz conversacional directa |
|
||||
| **Web** | 🚧 Planificado | API Key o OAuth | Interfaz de chat basada en navegador |
|
||||
|
||||
Ver [Referencia de Canales](docs/channels-reference.md) para instrucciones de configuración completas.
|
||||
|
||||
## Soporte de Herramientas
|
||||
|
||||
ZeroClaw proporciona herramientas integradas para ejecución de código, acceso al sistema de archivos y recuperación web:
|
||||
|
||||
| Herramienta | Descripción | Runtime Requerido |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Ejecuta comandos shell | Nativo o Docker |
|
||||
| **python** | Ejecuta scripts Python | Python 3.8+ (nativo) o Docker |
|
||||
| **javascript** | Ejecuta código Node.js | Node.js 18+ (nativo) o Docker |
|
||||
| **filesystem_read** | Lee archivos | Nativo o Docker |
|
||||
| **filesystem_write** | Escribe archivos | Nativo o Docker |
|
||||
| **web_fetch** | Obtiene contenido web | Nativo o Docker |
|
||||
|
||||
### Seguridad de Ejecución
|
||||
|
||||
- **Runtime Nativo** — se ejecuta como proceso de usuario del daemon, acceso completo al sistema de archivos
|
||||
- **Runtime Docker** — aislamiento completo de contenedor, sistemas de archivos y redes separados
|
||||
|
||||
Configura la política de ejecución en `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Lista permitida explícita
|
||||
```
|
||||
|
||||
Ver [Referencia de Configuración](docs/config-reference.md#runtime) para opciones de seguridad completas.
|
||||
|
||||
## Despliegue
|
||||
|
||||
### Despliegue Local (Desarrollo)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Despliegue en Servidor (Producción)
|
||||
|
||||
Usa systemd para gestionar el daemon y agent como servicios:
|
||||
|
||||
```bash
|
||||
# Instala el binario
|
||||
cargo install --path . --locked
|
||||
|
||||
# Configura el workspace
|
||||
zeroclaw init
|
||||
|
||||
# Crea archivos de servicio systemd
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Habilita e inicia los servicios
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Verifica el estado
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Ver [Guía de Despliegue de Red](docs/network-deployment.md) para instrucciones completas de despliegue en producción.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Compila la imagen
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Ejecuta el contenedor
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Ver [`Dockerfile`](Dockerfile) para detalles de build y opciones de configuración.
|
||||
|
||||
### Hardware Edge
|
||||
|
||||
ZeroClaw está diseñado para ejecutarse en hardware de bajo consumo:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, núcleo ARMv8 único, < $5 costo de hardware
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, multi-núcleo, ideal para workloads concurrentes
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, quad-core ARMv8, costo ultra-bajo
|
||||
- **SBCs x86 (Intel N100)** — 4-8 GB RAM, builds rápidos, soporte Docker nativo
|
||||
|
||||
Ver [Guía de Hardware](docs/hardware/README.md) para instrucciones de configuración específicas por dispositivo.
|
||||
|
||||
## Tunneling (Exposición Pública)
|
||||
|
||||
Expón tu daemon ZeroClaw local a la red pública vía túneles seguros:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Proveedores de tunnel soportados:
|
||||
|
||||
- **Cloudflare Tunnel** — HTTPS gratis, sin exposición de puertos, soporte multi-dominio
|
||||
- **Ngrok** — configuración rápida, dominios personalizados (plan de pago)
|
||||
- **Tailscale** — red mesh privada, sin puerto público
|
||||
|
||||
Ver [Referencia de Configuración](docs/config-reference.md#tunnel) para opciones de configuración completas.
|
||||
|
||||
## Seguridad
|
||||
|
||||
ZeroClaw implementa múltiples capas de seguridad:
|
||||
|
||||
### Emparejamiento
|
||||
|
||||
El daemon genera un secreto de emparejamiento al primer inicio almacenado en `~/.zeroclaw/workspace/.pairing`. Los clientes (agent, CLI) deben presentar este secreto para conectarse.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Genera un nuevo secreto e invalida el anterior
|
||||
```
|
||||
|
||||
### Sandboxing
|
||||
|
||||
- **Runtime Docker** — aislamiento completo de contenedor con sistemas de archivos y redes separados
|
||||
- **Runtime Nativo** — se ejecuta como proceso de usuario, con alcance de workspace por defecto
|
||||
|
||||
### Listas Permitidas
|
||||
|
||||
Los canales pueden restringir acceso por ID de usuario:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Lista permitida explícita
|
||||
```
|
||||
|
||||
### Cifrado
|
||||
|
||||
- **Matrix E2EE** — cifrado extremo a extremo completo con verificación de dispositivo
|
||||
- **Transporte TLS** — todo el tráfico de API y tunnel usa HTTPS/TLS
|
||||
|
||||
Ver [Documentación de Seguridad](docs/security/README.md) para políticas y prácticas completas.
|
||||
|
||||
## Observabilidad
|
||||
|
||||
ZeroClaw registra logs en `~/.zeroclaw/workspace/logs/` por defecto. Los logs se almacenan por componente:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Logs del daemon (inicio, solicitudes API, errores)
|
||||
├── agent.log # Logs del agent (ruteo de mensajes, ejecución de herramientas)
|
||||
├── telegram.log # Logs específicos del canal (si está habilitado)
|
||||
└── matrix.log # Logs específicos del canal (si está habilitado)
|
||||
```
|
||||
|
||||
### Configuración de Logging
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # Para rotación basada en tamaño
|
||||
retention_days = 30 # Purga automática después de N días
|
||||
```
|
||||
|
||||
Ver [Referencia de Configuración](docs/config-reference.md#logging) para todas las opciones de logging.
|
||||
|
||||
### Métricas (Planificado)
|
||||
|
||||
Soporte de métricas Prometheus para monitoreo en producción próximamente. Seguimiento en [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Habilidades (Skills)
|
||||
|
||||
ZeroClaw soporta habilidades personalizadas — módulos reutilizables que extienden las capacidades del sistema.
|
||||
|
||||
### Definición de Habilidad
|
||||
|
||||
Las habilidades se almacenan en `~/.zeroclaw/workspace/skills/<skill-name>/` con esta estructura:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Metadatos de habilidad (nombre, descripción, dependencias)
|
||||
├── prompt.md # Prompt de sistema para la AI
|
||||
└── tools/ # Herramientas personalizadas opcionales
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Ejemplo de Habilidad
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Busca en la web y resume resultados"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
Eres un asistente de investigación. Cuando te pidan buscar algo:
|
||||
|
||||
1. Usa web_fetch para obtener el contenido
|
||||
2. Resume los resultados en un formato fácil de leer
|
||||
3. Cita las fuentes con URLs
|
||||
```
|
||||
|
||||
### Uso de Habilidades
|
||||
|
||||
Las habilidades se cargan automáticamente al inicio del agent. Referéncialas por nombre en conversaciones:
|
||||
|
||||
```
|
||||
Usuario: Usa la habilidad web-research para encontrar las últimas noticias de AI
|
||||
Bot: [carga la habilidad web-research, ejecuta web_fetch, resume resultados]
|
||||
```
|
||||
|
||||
Ver sección [Habilidades (Skills)](#habilidades-skills) para instrucciones completas de creación de habilidades.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw soporta [Open Skills](https://github.com/openagents-com/open-skills) — un sistema modular y agnóstico de proveedores para extender capacidades de agentes AI.
|
||||
|
||||
### Habilitar Open Skills
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # opcional
|
||||
```
|
||||
|
||||
También puedes sobrescribir en runtime con `ZEROCLAW_OPEN_SKILLS_ENABLED` y `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Desarrollo
|
||||
|
||||
```bash
|
||||
cargo build # Build de desarrollo
|
||||
cargo build --release # Build release (codegen-units=1, funciona en todos los dispositivos incluyendo Raspberry Pi)
|
||||
cargo build --profile release-fast # Build más rápido (codegen-units=8, requiere 16 GB+ RAM)
|
||||
cargo test # Ejecuta el suite de pruebas completo
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Formato
|
||||
|
||||
# Ejecuta el benchmark de comparación SQLite vs Markdown
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Hook pre-push
|
||||
|
||||
Un hook de git ejecuta `cargo fmt --check`, `cargo clippy -- -D warnings`, y `cargo test` antes de cada push. Actívalo una vez:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Solución de Problemas de Build (errores OpenSSL en Linux)
|
||||
|
||||
Si encuentras un error de build `openssl-sys`, sincroniza dependencias y recompila con el lockfile del repositorio:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw está configurado para usar `rustls` para dependencias HTTP/TLS; `--locked` mantiene el grafo transitivo determinista en entornos limpios.
|
||||
|
||||
Para saltar el hook cuando necesites un push rápido durante desarrollo:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Colaboración y Docs
|
||||
|
||||
Comienza con el hub de documentación para un mapa basado en tareas:
|
||||
|
||||
+1
-438
@@ -58,7 +58,7 @@ Construit par des étudiants et membres des communautés Harvard, MIT et Sundai.
|
||||
|
||||
<p align="center">
|
||||
<a href="#démarrage-rapide">Démarrage</a> |
|
||||
<a href="install.sh">Configuration en un clic</a> |
|
||||
<a href="https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh">Configuration en un clic</a> |
|
||||
<a href="docs/README.md">Hub Documentation</a> |
|
||||
<a href="docs/SUMMARY.md">Table des matières Documentation</a>
|
||||
</p>
|
||||
@@ -361,443 +361,6 @@ zeroclaw version # Affiche la version et les informations de build
|
||||
|
||||
Voir [Référence des Commandes](docs/reference/cli/commands-reference.md) pour les options et exemples complets.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Canaux (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Orchestrateur Agent │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Routage │ │ Contexte │ │ Exécution │ │
|
||||
│ │ Message │ │ Mémoire │ │ Outil │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Fournisseurs │ │ Mémoire │ │ Outils │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Principes clés :**
|
||||
|
||||
- Tout est un **trait** — fournisseurs, canaux, outils, mémoire, tunnels
|
||||
- Les canaux appellent l'orchestrateur ; l'orchestrateur appelle les fournisseurs + outils
|
||||
- Le système mémoire gère le contexte conversationnel (markdown, SQLite, ou aucun)
|
||||
- Le runtime abstrait l'exécution de code (natif ou Docker)
|
||||
- Aucun verrouillage de fournisseur — échangez Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama sans changement de code
|
||||
|
||||
Voir [documentation architecture](docs/assets/architecture.svg) pour les diagrammes détaillés et les détails d'implémentation.
|
||||
|
||||
## Exemples
|
||||
|
||||
### Telegram Bot
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Votre Telegram user ID
|
||||
```
|
||||
|
||||
Démarrez le daemon + agent, puis envoyez un message à votre bot sur Telegram :
|
||||
|
||||
```
|
||||
/start
|
||||
Bonjour ! Pouvez-vous m'aider à écrire un script Python ?
|
||||
```
|
||||
|
||||
Le bot répond avec le code généré par l'IA, exécute les outils si demandé, et conserve le contexte de conversation.
|
||||
|
||||
### Matrix (chiffré de bout en bout)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Invitez `@zeroclaw:matrix.org` dans une salle chiffrée, et le bot répondra avec le chiffrement complet. Voir [Guide Matrix E2EE](docs/security/matrix-e2ee-guide.md) pour la configuration de vérification de dispositif.
|
||||
|
||||
### Multi-Fournisseur
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Bascule en cas d'erreur du fournisseur
|
||||
```
|
||||
|
||||
Si Anthropic échoue ou rate-limit, l'orchestrateur bascule automatiquement vers OpenAI.
|
||||
|
||||
### Mémoire Personnalisée
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Purge automatique après 90 jours
|
||||
```
|
||||
|
||||
Ou utilisez Markdown pour un stockage lisible par l'humain :
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Voir [Référence de Configuration](docs/reference/api/config-reference.md#memory) pour toutes les options mémoire.
|
||||
|
||||
## Support de Fournisseur
|
||||
|
||||
| Fournisseur | Statut | Clé API | Modèles Exemple |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Stable | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Stable | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Stable | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Stable | N/A (local) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Stable | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Stable | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Planifié | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Planifié | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Endpoints Personnalisés
|
||||
|
||||
ZeroClaw prend en charge les endpoints compatibles OpenAI :
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Exemple : utilisez [LiteLLM](https://github.com/BerriAI/litellm) comme proxy pour accéder à n'importe quel LLM via l'interface OpenAI.
|
||||
|
||||
Voir [Référence des Fournisseurs](docs/reference/api/providers-reference.md) pour les détails de configuration complets.
|
||||
|
||||
## Support de Canal
|
||||
|
||||
| Canal | Statut | Authentification | Notes |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Stable | Bot Token | Support complet incluant fichiers, images, boutons inline |
|
||||
| **Matrix** | ✅ Stable | Mot de passe ou Token | Support E2EE avec vérification de dispositif |
|
||||
| **Slack** | 🚧 Planifié | OAuth ou Bot Token | Accès workspace requis |
|
||||
| **Discord** | 🚧 Planifié | Bot Token | Permissions guild requises |
|
||||
| **WhatsApp** | 🚧 Planifié | Twilio ou API officielle | Compte business requis |
|
||||
| **CLI** | ✅ Stable | Aucun | Interface conversationnelle directe |
|
||||
| **Web** | 🚧 Planifié | Clé API ou OAuth | Interface de chat basée navigateur |
|
||||
|
||||
Voir [Référence des Canaux](docs/reference/api/channels-reference.md) pour les instructions de configuration complètes.
|
||||
|
||||
## Support d'Outil
|
||||
|
||||
ZeroClaw fournit des outils intégrés pour l'exécution de code, l'accès au système de fichiers et la récupération web :
|
||||
|
||||
| Outil | Description | Runtime Requis |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Exécute des commandes shell | Native ou Docker |
|
||||
| **python** | Exécute des scripts Python | Python 3.8+ (natif) ou Docker |
|
||||
| **javascript** | Exécute du code Node.js | Node.js 18+ (natif) ou Docker |
|
||||
| **filesystem_read** | Lit des fichiers | Native ou Docker |
|
||||
| **filesystem_write** | Écrit des fichiers | Native ou Docker |
|
||||
| **web_fetch** | Récupère du contenu web | Native ou Docker |
|
||||
|
||||
### Sécurité de l'Exécution
|
||||
|
||||
- **Runtime Natif** — s'exécute en tant que processus utilisateur du daemon, accès complet au système de fichiers
|
||||
- **Runtime Docker** — isolation complète du conteneur, systèmes de fichiers et réseaux séparés
|
||||
|
||||
Configurez la politique d'exécution dans `config.toml` :
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Liste d'autorisation explicite
|
||||
```
|
||||
|
||||
Voir [Référence de Configuration](docs/reference/api/config-reference.md#runtime) pour les options de sécurité complètes.
|
||||
|
||||
## Déploiement
|
||||
|
||||
### Déploiement Local (Développement)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Déploiement Serveur (Production)
|
||||
|
||||
Utilisez systemd pour gérer le daemon et l'agent en tant que services :
|
||||
|
||||
```bash
|
||||
# Installez le binaire
|
||||
cargo install --path . --locked
|
||||
|
||||
# Configurez le workspace
|
||||
zeroclaw init
|
||||
|
||||
# Créez les fichiers de service systemd
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Activez et démarrez les services
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Vérifiez le statut
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Voir [Guide de Déploiement Réseau](docs/ops/network-deployment.md) pour les instructions de déploiement en production complètes.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Compilez l'image
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Exécutez le conteneur
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Voir [`Dockerfile`](Dockerfile) pour les détails de construction et les options de configuration.
|
||||
|
||||
### Matériel Edge
|
||||
|
||||
ZeroClaw est conçu pour fonctionner sur du matériel à faible consommation d'énergie :
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 Mo RAM, cœur ARMv8 simple, <5$ coût matériel
|
||||
- **Raspberry Pi 4/5** — 1 Go+ RAM, multi-cœur, idéal pour les charges de travail concurrentes
|
||||
- **Orange Pi Zero 2** — ~512 Mo RAM, quad-core ARMv8, coût ultra-faible
|
||||
- **SBCs x86 (Intel N100)** — 4-8 Go RAM, builds rapides, support Docker natif
|
||||
|
||||
Voir [Guide du Matériel](docs/hardware/README.md) pour les instructions de configuration spécifiques aux dispositifs.
|
||||
|
||||
## Tunneling (Exposition Publique)
|
||||
|
||||
Exposez votre daemon ZeroClaw local au réseau public via des tunnels sécurisés :
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Fournisseurs de tunnel supportés :
|
||||
|
||||
- **Cloudflare Tunnel** — HTTPS gratuit, aucune exposition de port, support multi-domaine
|
||||
- **Ngrok** — configuration rapide, domaines personnalisés (plan payant)
|
||||
- **Tailscale** — réseau maillé privé, pas de port public
|
||||
|
||||
Voir [Référence de Configuration](docs/reference/api/config-reference.md#tunnel) pour les options de configuration complètes.
|
||||
|
||||
## Sécurité
|
||||
|
||||
ZeroClaw implémente plusieurs couches de sécurité :
|
||||
|
||||
### Pairing
|
||||
|
||||
Le daemon génère un secret de pairing au premier lancement stocké dans `~/.zeroclaw/workspace/.pairing`. Les clients (agent, CLI) doivent présenter ce secret pour se connecter.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Génère un nouveau secret et invalide l'ancien
|
||||
```
|
||||
|
||||
### Sandboxing
|
||||
|
||||
- **Runtime Docker** — isolation complète du conteneur avec systèmes de fichiers et réseaux séparés
|
||||
- **Runtime Natif** — exécute en tant que processus utilisateur, scoped au workspace par défaut
|
||||
|
||||
### Listes d'Autorisation
|
||||
|
||||
Les canaux peuvent restreindre l'accès par ID utilisateur :
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Liste d'autorisation explicite
|
||||
```
|
||||
|
||||
### Chiffrement
|
||||
|
||||
- **Matrix E2EE** — chiffrement de bout en bout complet avec vérification de dispositif
|
||||
- **Transport TLS** — tout le trafic API et tunnel utilise HTTPS/TLS
|
||||
|
||||
Voir [Documentation Sécurité](docs/security/README.md) pour les politiques et pratiques complètes.
|
||||
|
||||
## Observabilité
|
||||
|
||||
ZeroClaw journalise vers `~/.zeroclaw/workspace/logs/` par défaut. Les journaux sont stockés par composant :
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Journaux du daemon (startup, requêtes API, erreurs)
|
||||
├── agent.log # Journaux de l'agent (routage message, exécution outil)
|
||||
├── telegram.log # Journaux spécifiques au canal (si activé)
|
||||
└── matrix.log # Journaux spécifiques au canal (si activé)
|
||||
```
|
||||
|
||||
### Configuration de Journalisation
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # Pour rotation basée sur la taille
|
||||
retention_days = 30 # Purge automatique après N jours
|
||||
```
|
||||
|
||||
Voir [Référence de Configuration](docs/reference/api/config-reference.md#logging) pour toutes les options de journalisation.
|
||||
|
||||
### Métriques (Planifié)
|
||||
|
||||
Support de métriques Prometheus pour la surveillance en production à venir. Suivi dans [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Compétences (Skills)
|
||||
|
||||
ZeroClaw prend en charge les compétences personnalisées — des modules réutilisables qui étendent les capacités du système.
|
||||
|
||||
### Définition de Compétence
|
||||
|
||||
Les compétences sont stockées dans `~/.zeroclaw/workspace/skills/<nom-compétence>/` avec cette structure :
|
||||
|
||||
```
|
||||
skills/
|
||||
└── ma-compétence/
|
||||
├── skill.toml # Métadonnées de compétence (nom, description, dépendances)
|
||||
├── prompt.md # Prompt système pour l'IA
|
||||
└── tools/ # Outils personnalisés optionnels
|
||||
└── mon_outil.py
|
||||
```
|
||||
|
||||
### Exemple de Compétence
|
||||
|
||||
```toml
|
||||
# skills/recherche-web/skill.toml
|
||||
[skill]
|
||||
name = "recherche-web"
|
||||
description = "Recherche sur le web et résume les résultats"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/recherche-web/prompt.md -->
|
||||
|
||||
Tu es un assistant de recherche. Lorsqu'on te demande de rechercher quelque chose :
|
||||
|
||||
1. Utilise web_fetch pour récupérer le contenu
|
||||
2. Résume les résultats dans un format facile à lire
|
||||
3. Cite les sources avec des URLs
|
||||
```
|
||||
|
||||
### Utilisation de Compétences
|
||||
|
||||
Les compétences sont chargées automatiquement au démarrage de l'agent. Référencez-les par nom dans les conversations :
|
||||
|
||||
```
|
||||
Utilisateur : Utilise la compétence recherche-web pour trouver les dernières actualités IA
|
||||
Bot : [charge la compétence recherche-web, exécute web_fetch, résume les résultats]
|
||||
```
|
||||
|
||||
Voir la section [Compétences (Skills)](#compétences-skills) pour les instructions de création de compétences complètes.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw prend en charge les [Open Skills](https://github.com/openagents-com/open-skills) — un système modulaire et agnostique des fournisseurs pour étendre les capacités des agents IA.
|
||||
|
||||
### Activer Open Skills
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # optionnel
|
||||
```
|
||||
|
||||
Vous pouvez également surcharger au runtime avec `ZEROCLAW_OPEN_SKILLS_ENABLED` et `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Développement
|
||||
|
||||
```bash
|
||||
cargo build # Build de développement
|
||||
cargo build --release # Build release (codegen-units=1, fonctionne sur tous les dispositifs incluant Raspberry Pi)
|
||||
cargo build --profile release-fast # Build plus rapide (codegen-units=8, nécessite 16 Go+ RAM)
|
||||
cargo test # Exécute la suite de tests complète
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Format
|
||||
|
||||
# Exécute le benchmark de comparaison SQLite vs Markdown
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Hook pre-push
|
||||
|
||||
Un hook git exécute `cargo fmt --check`, `cargo clippy -- -D warnings`, et `cargo test` avant chaque push. Activez-le une fois :
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Dépannage de Build (erreurs OpenSSL sur Linux)
|
||||
|
||||
Si vous rencontrez une erreur de build `openssl-sys`, synchronisez les dépendances et recompilez avec le lockfile du dépôt :
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw est configuré pour utiliser `rustls` pour les dépendances HTTP/TLS ; `--locked` maintient le graphe transitif déterministe sur les environnements vierges.
|
||||
|
||||
Pour sauter le hook lorsque vous avez besoin d'un push rapide pendant le développement :
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Collaboration & Docs
|
||||
|
||||
Commencez par le hub de documentation pour une carte basée sur les tâches :
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # Mostra versione e informazioni di build
|
||||
|
||||
Vedi [Riferimento Comandi](docs/commands-reference.md) per opzioni ed esempi completi.
|
||||
|
||||
## Architettura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Canali (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Agente Orchestratore │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Routing │ │ Contesto │ │ Esecuzione │ │
|
||||
│ │ Messaggio │ │ Memoria │ │ Strumento │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Provider │ │ Memoria │ │ Strumenti │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Principi chiave:**
|
||||
|
||||
- Tutto è un **trait** — provider, canali, strumenti, memoria, tunnel
|
||||
- I canali chiamano l'orchestratore; l'orchestratore chiama provider + strumenti
|
||||
- Il sistema memoria gestisce il contesto conversazionale (markdown, SQLite, o nessuno)
|
||||
- Il runtime astrae l'esecuzione del codice (nativo o Docker)
|
||||
- Nessun lock-in del provider — scambia Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama senza modifiche al codice
|
||||
|
||||
Vedi [documentazione architettura](docs/architecture.svg) per diagrammi dettagliati e dettagli di implementazione.
|
||||
|
||||
## Esempi
|
||||
|
||||
### Bot Telegram
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Il tuo ID utente Telegram
|
||||
```
|
||||
|
||||
Avvia il daemon + agent, poi invia un messaggio al tuo bot su Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
Ciao! Potresti aiutarmi a scrivere uno script Python?
|
||||
```
|
||||
|
||||
Il bot risponde con codice generato dall'AI, esegue strumenti se richiesto, e mantiene il contesto della conversazione.
|
||||
|
||||
### Matrix (crittografia end-to-end)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Invita `@zeroclaw:matrix.org` in una stanza crittografata, e il bot risponderà con crittografia completa. Vedi [Guida Matrix E2EE](docs/matrix-e2ee-guide.md) per la configurazione della verifica dispositivo.
|
||||
|
||||
### Multi-Provider
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Failover su errore del provider
|
||||
```
|
||||
|
||||
Se Anthropic fallisce o va in rate-limit, l'orchestratore passa automaticamente a OpenAI.
|
||||
|
||||
### Memoria Personalizzata
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Eliminazione automatica dopo 90 giorni
|
||||
```
|
||||
|
||||
O usa Markdown per un archiviazione leggibile dall'uomo:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Vedi [Riferimento Configurazione](docs/config-reference.md#memory) per tutte le opzioni memoria.
|
||||
|
||||
## Supporto Provider
|
||||
|
||||
| Provider | Stato | API Key | Modelli di Esempio |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Stabile | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Stabile | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Stabile | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Stabile | N/A (locale) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Stabile | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Stabile | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Pianificato | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Pianificato | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Endpoint Personalizzati
|
||||
|
||||
ZeroClaw supporta endpoint compatibili con OpenAI:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Esempio: usa [LiteLLM](https://github.com/BerriAI/litellm) come proxy per accedere a qualsiasi LLM tramite l'interfaccia OpenAI.
|
||||
|
||||
Vedi [Riferimento Provider](docs/providers-reference.md) per dettagli di configurazione completi.
|
||||
|
||||
## Supporto Canali
|
||||
|
||||
| Canale | Stato | Autenticazione | Note |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Stabile | Bot Token | Supporto completo inclusi file, immagini, pulsanti inline |
|
||||
| **Matrix** | ✅ Stabile | Password o Token | Supporto E2EE con verifica dispositivo |
|
||||
| **Slack** | 🚧 Pianificato | OAuth o Bot Token | Richiede accesso workspace |
|
||||
| **Discord** | 🚧 Pianificato | Bot Token | Richiede permessi guild |
|
||||
| **WhatsApp** | 🚧 Pianificato | Twilio o API ufficiale | Richiede account business |
|
||||
| **CLI** | ✅ Stabile | Nessuno | Interfaccia conversazionale diretta |
|
||||
| **Web** | 🚧 Pianificato | API Key o OAuth | Interfaccia chat basata su browser |
|
||||
|
||||
Vedi [Riferimento Canali](docs/channels-reference.md) per istruzioni di configurazione complete.
|
||||
|
||||
## Supporto Strumenti
|
||||
|
||||
ZeroClaw fornisce strumenti integrati per l'esecuzione del codice, l'accesso al filesystem e il recupero web:
|
||||
|
||||
| Strumento | Descrizione | Runtime Richiesto |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Esegue comandi shell | Nativo o Docker |
|
||||
| **python** | Esegue script Python | Python 3.8+ (nativo) o Docker |
|
||||
| **javascript** | Esegue codice Node.js | Node.js 18+ (nativo) o Docker |
|
||||
| **filesystem_read** | Legge file | Nativo o Docker |
|
||||
| **filesystem_write** | Scrive file | Nativo o Docker |
|
||||
| **web_fetch** | Recupera contenuti web | Nativo o Docker |
|
||||
|
||||
### Sicurezza dell'Esecuzione
|
||||
|
||||
- **Runtime Nativo** — gira come processo utente del daemon, accesso completo al filesystem
|
||||
- **Runtime Docker** — isolamento container completo, filesystem e reti separati
|
||||
|
||||
Configura la politica di esecuzione in `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Lista di autorizzazione esplicita
|
||||
```
|
||||
|
||||
Vedi [Riferimento Configurazione](docs/config-reference.md#runtime) per opzioni di sicurezza complete.
|
||||
|
||||
## Distribuzione
|
||||
|
||||
### Distribuzione Locale (Sviluppo)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Distribuzione Server (Produzione)
|
||||
|
||||
Usa systemd per gestire daemon e agent come servizi:
|
||||
|
||||
```bash
|
||||
# Installa il binario
|
||||
cargo install --path . --locked
|
||||
|
||||
# Configura il workspace
|
||||
zeroclaw init
|
||||
|
||||
# Crea i file di servizio systemd
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Abilita e avvia i servizi
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Verifica lo stato
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Vedi [Guida Distribuzione di Rete](docs/network-deployment.md) per istruzioni complete di distribuzione in produzione.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Compila l'immagine
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Esegui il container
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Vedi [`Dockerfile`](Dockerfile) per dettagli di build e opzioni di configurazione.
|
||||
|
||||
### Hardware Edge
|
||||
|
||||
ZeroClaw è progettato per girare su hardware a basso consumo:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, singolo core ARMv8, < $5 costo hardware
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, multi-core, ideale per workload concorrenti
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, quad-core ARMv8, costo ultra-basso
|
||||
- **SBC x86 (Intel N100)** — 4-8 GB RAM, build veloci, supporto Docker nativo
|
||||
|
||||
Vedi [Guida Hardware](docs/hardware/README.md) per istruzioni di configurazione specifiche per dispositivo.
|
||||
|
||||
## Tunneling (Esposizione Pubblica)
|
||||
|
||||
Espone il tuo daemon ZeroClaw locale alla rete pubblica tramite tunnel sicuri:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Provider di tunnel supportati:
|
||||
|
||||
- **Cloudflare Tunnel** — HTTPS gratuito, nessuna esposizione di porte, supporto multi-dominio
|
||||
- **Ngrok** — configurazione rapida, domini personalizzati (piano a pagamento)
|
||||
- **Tailscale** — rete mesh privata, nessuna porta pubblica
|
||||
|
||||
Vedi [Riferimento Configurazione](docs/config-reference.md#tunnel) per opzioni di configurazione complete.
|
||||
|
||||
## Sicurezza
|
||||
|
||||
ZeroClaw implementa molteplici livelli di sicurezza:
|
||||
|
||||
### Pairing
|
||||
|
||||
Il daemon genera un segreto di pairing al primo avvio memorizzato in `~/.zeroclaw/workspace/.pairing`. I client (agent, CLI) devono presentare questo segreto per connettersi.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Genera un nuovo segreto e invalida quello precedente
|
||||
```
|
||||
|
||||
### Sandboxing
|
||||
|
||||
- **Runtime Docker** — isolamento container completo con filesystem e reti separati
|
||||
- **Runtime Nativo** — gira come processo utente, con scope del workspace di default
|
||||
|
||||
### Liste di Autorizzazione
|
||||
|
||||
I canali possono limitare l'accesso per ID utente:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Lista di autorizzazione esplicita
|
||||
```
|
||||
|
||||
### Crittografia
|
||||
|
||||
- **Matrix E2EE** — crittografia end-to-end completa con verifica dispositivo
|
||||
- **Trasporto TLS** — tutto il traffico API e tunnel usa HTTPS/TLS
|
||||
|
||||
Vedi [Documentazione Sicurezza](docs/security/README.md) per politiche e pratiche complete.
|
||||
|
||||
## Osservabilità
|
||||
|
||||
ZeroClaw registra i log in `~/.zeroclaw/workspace/logs/` di default. I log sono memorizzati per componente:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Log del daemon (avvio, richieste API, errori)
|
||||
├── agent.log # Log dell'agent (routing messaggi, esecuzione strumenti)
|
||||
├── telegram.log # Log specifici del canale (se abilitato)
|
||||
└── matrix.log # Log specifici del canale (se abilitato)
|
||||
```
|
||||
|
||||
### Configurazione Logging
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # Per rotazione basata sulla dimensione
|
||||
retention_days = 30 # Eliminazione automatica dopo N giorni
|
||||
```
|
||||
|
||||
Vedi [Riferimento Configurazione](docs/config-reference.md#logging) per tutte le opzioni di logging.
|
||||
|
||||
### Metriche (Pianificato)
|
||||
|
||||
Supporto metriche Prometheus per il monitoraggio in produzione in arrivo. Tracciamento in [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Competenze (Skills)
|
||||
|
||||
ZeroClaw supporta competenze personalizzate — moduli riutilizzabili che estendono le capacità del sistema.
|
||||
|
||||
### Definizione Competenza
|
||||
|
||||
Le competenze sono memorizzate in `~/.zeroclaw/workspace/skills/<skill-name>/` con questa struttura:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Metadati competenza (nome, descrizione, dipendenze)
|
||||
├── prompt.md # Prompt di sistema per l'AI
|
||||
└── tools/ # Strumenti personalizzati opzionali
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Esempio Competenza
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Cerca sul web e riassume i risultati"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
Sei un assistente di ricerca. Quando ti viene chiesto di cercare qualcosa:
|
||||
|
||||
1. Usa web_fetch per recuperare il contenuto
|
||||
2. Riassume i risultati in un formato facile da leggere
|
||||
3. Cita le fonti con gli URL
|
||||
```
|
||||
|
||||
### Uso delle Competenze
|
||||
|
||||
Le competenze sono caricate automaticamente all'avvio dell'agent. Fai riferimento ad esse per nome nelle conversazioni:
|
||||
|
||||
```
|
||||
Utente: Usa la competenza web-research per trovare le ultime notizie AI
|
||||
Bot: [carica la competenza web-research, esegue web_fetch, riassume i risultati]
|
||||
```
|
||||
|
||||
Vedi sezione [Competenze (Skills)](#competenze-skills) per istruzioni complete sulla creazione di competenze.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw supporta [Open Skills](https://github.com/openagents-com/open-skills) — un sistema modulare e agnostico del provider per estendere le capacità degli agent AI.
|
||||
|
||||
### Abilita Open Skills
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # opzionale
|
||||
```
|
||||
|
||||
Puoi anche sovrascrivere a runtime con `ZEROCLAW_OPEN_SKILLS_ENABLED` e `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Sviluppo
|
||||
|
||||
```bash
|
||||
cargo build # Build di sviluppo
|
||||
cargo build --release # Build release (codegen-units=1, funziona su tutti i dispositivi incluso Raspberry Pi)
|
||||
cargo build --profile release-fast # Build più veloce (codegen-units=8, richiede 16 GB+ RAM)
|
||||
cargo test # Esegue la suite di test completa
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Formattazione
|
||||
|
||||
# Esegue il benchmark di confronto SQLite vs Markdown
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Hook pre-push
|
||||
|
||||
Un hook git esegue `cargo fmt --check`, `cargo clippy -- -D warnings`, e `cargo test` prima di ogni push. Attivalo una volta:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Risoluzione Problemi di Build (errori OpenSSL su Linux)
|
||||
|
||||
Se incontri un errore di build `openssl-sys`, sincronizza le dipendenze e ricompila con il lockfile del repository:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw è configurato per usare `rustls` per le dipendenze HTTP/TLS; `--locked` mantiene il grafo transitivo deterministico in ambienti puliti.
|
||||
|
||||
Per saltare l'hook quando hai bisogno di un push veloce durante lo sviluppo:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Collaborazione e Docs
|
||||
|
||||
Inizia con l'hub della documentazione per una mappa basata sui task:
|
||||
|
||||
+1
-99
@@ -53,7 +53,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="install.sh">ワンクリック導入</a> |
|
||||
<a href="https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh">ワンクリック導入</a> |
|
||||
<a href="docs/setup-guides/README.md">導入ガイド</a> |
|
||||
<a href="docs/README.ja.md">ドキュメントハブ</a> |
|
||||
<a href="docs/SUMMARY.md">Docs TOC</a>
|
||||
@@ -218,104 +218,6 @@ zeroclaw agent --provider openai-codex --auth-profile openai-codex:work -m "hell
|
||||
zeroclaw agent --provider anthropic -m "hello"
|
||||
```
|
||||
|
||||
## アーキテクチャ
|
||||
|
||||
すべてのサブシステムは **Trait** — 設定変更だけで実装を差し替え可能、コード変更不要。
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/architecture.svg" alt="ZeroClaw アーキテクチャ" width="900" />
|
||||
</p>
|
||||
|
||||
| サブシステム | Trait | 内蔵実装 | 拡張方法 |
|
||||
|-------------|-------|----------|----------|
|
||||
| **AI モデル** | `Provider` | `zeroclaw providers` で確認(現在 28 個の組み込み + エイリアス、カスタムエンドポイント対応) | `custom:https://your-api.com`(OpenAI 互換)または `anthropic-custom:https://your-api.com` |
|
||||
| **チャネル** | `Channel` | CLI, Telegram, Discord, Slack, Mattermost, iMessage, Matrix, Signal, WhatsApp, Linq, Email, IRC, Lark, DingTalk, QQ, Webhook | 任意のメッセージ API |
|
||||
| **メモリ** | `Memory` | SQLite ハイブリッド検索, PostgreSQL バックエンド, Lucid ブリッジ, Markdown ファイル, 明示的 `none` バックエンド, スナップショット/復元, オプション応答キャッシュ | 任意の永続化バックエンド |
|
||||
| **ツール** | `Tool` | shell/file/memory, cron/schedule, git, pushover, browser, http_request, screenshot/image_info, composio (opt-in), delegate, ハードウェアツール | 任意の機能 |
|
||||
| **オブザーバビリティ** | `Observer` | Noop, Log, Multi | Prometheus, OTel |
|
||||
| **ランタイム** | `RuntimeAdapter` | Native, Docker(サンドボックス) | adapter 経由で追加可能;未対応の kind は即座にエラー |
|
||||
| **セキュリティ** | `SecurityPolicy` | Gateway ペアリング, サンドボックス, allowlist, レート制限, ファイルシステムスコープ, 暗号化シークレット | — |
|
||||
| **アイデンティティ** | `IdentityConfig` | OpenClaw (markdown), AIEOS v1.1 (JSON) | 任意の ID フォーマット |
|
||||
| **トンネル** | `Tunnel` | None, Cloudflare, Tailscale, ngrok, Custom | 任意のトンネルバイナリ |
|
||||
| **ハートビート** | Engine | HEARTBEAT.md 定期タスク | — |
|
||||
| **スキル** | Loader | TOML マニフェスト + SKILL.md インストラクション | コミュニティスキルパック |
|
||||
| **インテグレーション** | Registry | 9 カテゴリ、70 件以上の連携 | プラグインシステム |
|
||||
|
||||
### ランタイムサポート(現状)
|
||||
|
||||
- ✅ 現在サポート: `runtime.kind = "native"` または `runtime.kind = "docker"`
|
||||
- 🚧 計画中(未実装): WASM / エッジランタイム
|
||||
|
||||
未対応の `runtime.kind` が設定された場合、ZeroClaw は native へのサイレントフォールバックではなく、明確なエラーで終了します。
|
||||
|
||||
### メモリシステム(フルスタック検索エンジン)
|
||||
|
||||
すべて自社実装、外部依存ゼロ — Pinecone、Elasticsearch、LangChain 不要:
|
||||
|
||||
| レイヤー | 実装 |
|
||||
|---------|------|
|
||||
| **ベクトル DB** | Embeddings を SQLite に BLOB として保存、コサイン類似度検索 |
|
||||
| **キーワード検索** | FTS5 仮想テーブル、BM25 スコアリング |
|
||||
| **ハイブリッドマージ** | カスタム重み付きマージ関数(`vector.rs`) |
|
||||
| **Embeddings** | `EmbeddingProvider` trait — OpenAI、カスタム URL、または noop |
|
||||
| **チャンキング** | 行ベースの Markdown チャンカー(見出し構造保持) |
|
||||
| **キャッシュ** | SQLite `embedding_cache` テーブル、LRU エビクション |
|
||||
| **安全な再インデックス** | FTS5 再構築 + 欠落ベクトルの再埋め込みをアトミックに実行 |
|
||||
|
||||
Agent はツール経由でメモリの呼び出し・保存・管理を自動的に行います。
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
backend = "sqlite" # "sqlite", "lucid", "postgres", "markdown", "none"
|
||||
auto_save = true
|
||||
embedding_provider = "none" # "none", "openai", "custom:https://..."
|
||||
vector_weight = 0.7
|
||||
keyword_weight = 0.3
|
||||
```
|
||||
|
||||
## セキュリティのデフォルト
|
||||
|
||||
- Gateway の既定バインド: `127.0.0.1:42617`
|
||||
- 既定でペアリング必須: `require_pairing = true`
|
||||
- 既定で公開バインド禁止: `allow_public_bind = false`
|
||||
- Channel allowlist:
|
||||
- `[]` は deny-by-default
|
||||
- `["*"]` は allow all(意図的に使う場合のみ)
|
||||
|
||||
## 設定例
|
||||
|
||||
```toml
|
||||
api_key = "sk-..."
|
||||
default_provider = "openrouter"
|
||||
default_model = "anthropic/claude-sonnet-4-6"
|
||||
default_temperature = 0.7
|
||||
|
||||
[memory]
|
||||
backend = "sqlite"
|
||||
auto_save = true
|
||||
embedding_provider = "none"
|
||||
|
||||
[gateway]
|
||||
host = "127.0.0.1"
|
||||
port = 42617
|
||||
require_pairing = true
|
||||
allow_public_bind = false
|
||||
```
|
||||
|
||||
## ドキュメント入口
|
||||
|
||||
- ドキュメントハブ(英語): [`docs/README.md`](docs/README.md)
|
||||
- 統合 TOC: [`docs/SUMMARY.md`](docs/SUMMARY.md)
|
||||
- ドキュメントハブ(日本語): [`docs/README.ja.md`](docs/README.ja.md)
|
||||
- コマンドリファレンス: [`docs/reference/cli/commands-reference.md`](docs/reference/cli/commands-reference.md)
|
||||
- 設定リファレンス: [`docs/reference/api/config-reference.md`](docs/reference/api/config-reference.md)
|
||||
- Provider リファレンス: [`docs/reference/api/providers-reference.md`](docs/reference/api/providers-reference.md)
|
||||
- Channel リファレンス: [`docs/reference/api/channels-reference.md`](docs/reference/api/channels-reference.md)
|
||||
- 運用ガイド(Runbook): [`docs/ops/operations-runbook.md`](docs/ops/operations-runbook.md)
|
||||
- トラブルシューティング: [`docs/ops/troubleshooting.md`](docs/ops/troubleshooting.md)
|
||||
- ドキュメント一覧 / 分類: [`docs/maintainers/docs-inventory.md`](docs/maintainers/docs-inventory.md)
|
||||
- プロジェクト triage スナップショット: [`docs/maintainers/project-triage-snapshot-2026-02-18.md`](docs/maintainers/project-triage-snapshot-2026-02-18.md)
|
||||
|
||||
## コントリビュート / ライセンス
|
||||
|
||||
- Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # 버전 및 빌드 정보 표시
|
||||
|
||||
전체 옵션 및 예제는 [명령어 참조](docs/commands-reference.md)를 참조하세요.
|
||||
|
||||
## 아키텍처
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 채널 (트레이트) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 에이전트 오케스트레이터 │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ 메시지 │ │ 컨텍스트 │ │ 도구 │ │
|
||||
│ │ 라우팅 │ │ 메모리 │ │ 실행 │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ 제공자 │ │ 메모리 │ │ 도구 │
|
||||
│ (트레이트) │ │ (트레이트) │ │ (트레이트) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 런타임 (트레이트) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**핵심 원칙:**
|
||||
|
||||
- 모든 것이 **트레이트**입니다 — 제공자, 채널, 도구, 메모리, 터널
|
||||
- 채널이 오케스트레이터를 호출; 오케스트레이터가 제공자 + 도구를 호출
|
||||
- 메모리 시스템이 대화 컨텍스트 관리(markdown, SQLite, 또는 없음)
|
||||
- 런타임이 코드 실행 추상화(네이티브 또는 Docker)
|
||||
- 제공자 락인 없음 — 코드 변경 없이 Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama 교체
|
||||
|
||||
자세한 다이어그램과 구현 세부 정보는 [아키텍처 문서](docs/architecture.svg)를 참조하세요.
|
||||
|
||||
## 예제
|
||||
|
||||
### 텔레그램 봇
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # 당신의 텔레그램 사용자 ID
|
||||
```
|
||||
|
||||
데몬 + 에이전트를 시작한 다음 텔레그램에서 봇에 메시지를 보내세요:
|
||||
|
||||
```
|
||||
/start
|
||||
안녕하세요! Python 스크립트 작성을 도와주실 수 있나요?
|
||||
```
|
||||
|
||||
봇이 AI가 생성한 코드로 응답하고, 요청 시 도구를 실행하며, 대화 컨텍스트를 유지합니다.
|
||||
|
||||
### Matrix (종단 간 암호화)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
암호화된 방에 `@zeroclaw:matrix.org`를 초대하면 봇이 완전한 암호화로 응답합니다. 장치 확인 설정은 [Matrix E2EE 가이드](docs/matrix-e2ee-guide.md)를 참조하세요.
|
||||
|
||||
### 다중 제공자
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # 제공자 오류 시 장애 조치
|
||||
```
|
||||
|
||||
Anthropic이 실패하거나 속도 제한이 걸리면 오케스트레이터가 자동으로 OpenAI로 장애 조치합니다.
|
||||
|
||||
### 사용자 정의 메모리
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # 90일 후 자동 삭제
|
||||
```
|
||||
|
||||
또는 사람이 읽을 수 있는 저장소를 위해 Markdown을 사용하세요:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
모든 메모리 옵션은 [구성 참조](docs/config-reference.md#memory)를 참조하세요.
|
||||
|
||||
## 제공자 지원
|
||||
|
||||
| 제공자 | 상태 | API 키 | 예제 모델 |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ 안정 | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ 안정 | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ 안정 | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ 안정 | N/A (로컬) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ 안정 | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ 안정 | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 계획 중 | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 계획 중 | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### 사용자 정의 엔드포인트
|
||||
|
||||
ZeroClaw는 OpenAI 호환 엔드포인트를 지원합니다:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
예: [LiteLLM](https://github.com/BerriAI/litellm)을 프록시로 사용하여 OpenAI 인터페이스를 통해 모든 LLM에 액세스.
|
||||
|
||||
전체 구성 세부 정보는 [제공자 참조](docs/providers-reference.md)를 참조하세요.
|
||||
|
||||
## 채널 지원
|
||||
|
||||
| 채널 | 상태 | 인증 | 참고 |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ 안정 | 봇 토큰 | 파일, 이미지, 인라인 버튼 포함 전체 지원 |
|
||||
| **Matrix** | ✅ 안정 | 비밀번호 또는 토큰 | 장치 확인과 함께 E2EE 지원 |
|
||||
| **Slack** | 🚧 계획 중 | OAuth 또는 봇 토큰 | 작업공간 액세스 필요 |
|
||||
| **Discord** | 🚧 계획 중 | 봇 토큰 | 길드 권한 필요 |
|
||||
| **WhatsApp** | 🚧 계획 중 | Twilio 또는 공식 API | 비즈니스 계정 필요 |
|
||||
| **CLI** | ✅ 안정 | 없음 | 직접 대화형 인터페이스 |
|
||||
| **Web** | 🚧 계획 중 | API 키 또는 OAuth | 브라우저 기반 채팅 인터페이스 |
|
||||
|
||||
전체 구성 지침은 [채널 참조](docs/channels-reference.md)를 참조하세요.
|
||||
|
||||
## 도구 지원
|
||||
|
||||
ZeroClaw는 코드 실행, 파일 시스템 액세스 및 웹 검색을 위한 기본 제공 도구를 제공합니다:
|
||||
|
||||
| 도구 | 설명 | 필수 런타임 |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | 셸 명령 실행 | 네이티브 또는 Docker |
|
||||
| **python** | Python 스크립트 실행 | Python 3.8+ (네이티브) 또는 Docker |
|
||||
| **javascript** | Node.js 코드 실행 | Node.js 18+ (네이티브) 또는 Docker |
|
||||
| **filesystem_read** | 파일 읽기 | 네이티브 또는 Docker |
|
||||
| **filesystem_write** | 파일 쓰기 | 네이티브 또는 Docker |
|
||||
| **web_fetch** | 웹 콘텐츠 가져오기 | 네이티브 또는 Docker |
|
||||
|
||||
### 실행 보안
|
||||
|
||||
- **네이티브 런타임** — 데몬의 사용자 프로세스로 실행, 파일 시스템에 전체 액세스
|
||||
- **Docker 런타임** — 전체 컨테이너 격리, 별도의 파일 시스템 및 네트워크
|
||||
|
||||
`config.toml`에서 실행 정책을 구성하세요:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # 명시적 허용 목록
|
||||
```
|
||||
|
||||
전체 보안 옵션은 [구성 참조](docs/config-reference.md#runtime)를 참조하세요.
|
||||
|
||||
## 배포
|
||||
|
||||
### 로컬 배포 (개발)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### 서버 배포 (프로덕션)
|
||||
|
||||
systemd를 사용하여 데몬과 에이전트를 서비스로 관리하세요:
|
||||
|
||||
```bash
|
||||
# 바이너리 설치
|
||||
cargo install --path . --locked
|
||||
|
||||
# 작업공간 구성
|
||||
zeroclaw init
|
||||
|
||||
# systemd 서비스 파일 생성
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# 서비스 활성화 및 시작
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# 상태 확인
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
전체 프로덕션 배포 지침은 [네트워크 배포 가이드](docs/network-deployment.md)를 참조하세요.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# 이미지 빌드
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# 컨테이너 실행
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
빌드 세부 정보 및 구성 옵션은 [`Dockerfile`](Dockerfile)을 참조하세요.
|
||||
|
||||
### 엣지 하드웨어
|
||||
|
||||
ZeroClaw는 저전력 하드웨어에서 실행되도록 설계되었습니다:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, 단일 ARMv8 코어, < $5 하드웨어 비용
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, 멀티코어, 동시 워크로드에 이상적
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, 쿼드코어 ARMv8, 초저비용
|
||||
- **x86 SBCs (Intel N100)** — 4-8 GB RAM, 빠른 빌드, 네이티브 Docker 지원
|
||||
|
||||
장치별 설정 지침은 [하드웨어 가이드](docs/hardware/README.md)를 참조하세요.
|
||||
|
||||
## 터널링 (공개 노출)
|
||||
|
||||
보안 터널을 통해 로컬 ZeroClaw 데몬을 공개 네트워크에 노출하세요:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
지원되는 터널 제공자:
|
||||
|
||||
- **Cloudflare Tunnel** — 무료 HTTPS, 포트 노출 없음, 멀티 도메인 지원
|
||||
- **Ngrok** — 빠른 설정, 사용자 정의 도메인 (유료 플랜)
|
||||
- **Tailscale** — 프라이빗 메시 네트워크, 공개 포트 없음
|
||||
|
||||
전체 구성 옵션은 [구성 참조](docs/config-reference.md#tunnel)를 참조하세요.
|
||||
|
||||
## 보안
|
||||
|
||||
ZeroClaw는 여러 보안 계층을 구현합니다:
|
||||
|
||||
### 페어링
|
||||
|
||||
데몬은 첫 실행 시 `~/.zeroclaw/workspace/.pairing`에 저장된 페어링 시크릿을 생성합니다. 클라이언트(에이전트, CLI)는 연결하기 위해 이 시크릿을 제시해야 합니다.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # 새 시크릿 생성 및 이전 것 무효화
|
||||
```
|
||||
|
||||
### 샌드박싱
|
||||
|
||||
- **Docker 런타임** — 별도의 파일 시스템 및 네트워크로 전체 컨테이너 격리
|
||||
- **네이티브 런타임** — 사용자 프로세스로 실행, 기본적으로 작업공간으로 범위 지정
|
||||
|
||||
### 허용 목록
|
||||
|
||||
채널은 사용자 ID로 액세스를 제한할 수 있습니다:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # 명시적 허용 목록
|
||||
```
|
||||
|
||||
### 암호화
|
||||
|
||||
- **Matrix E2EE** — 장치 확인과 함께 완전한 종단 간 암호화
|
||||
- **TLS 전송** — 모든 API 및 터널 트래픽이 HTTPS/TLS 사용
|
||||
|
||||
전체 정책 및 관행은 [보안 문서](docs/security/README.md)를 참조하세요.
|
||||
|
||||
## 관찰 가능성
|
||||
|
||||
ZeroClaw는 기본적으로 `~/.zeroclaw/workspace/logs/`에 로그를 기록합니다. 로그는 구성 요소별로 저장됩니다:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # 데몬 로그 (시작, API 요청, 오류)
|
||||
├── agent.log # 에이전트 로그 (메시지 라우팅, 도구 실행)
|
||||
├── telegram.log # 채널별 로그 (활성화된 경우)
|
||||
└── matrix.log # 채널별 로그 (활성화된 경우)
|
||||
```
|
||||
|
||||
### 로깅 구성
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # 크기 기반 회전용
|
||||
retention_days = 30 # N일 후 자동 삭제
|
||||
```
|
||||
|
||||
모든 로깅 옵션은 [구성 참조](docs/config-reference.md#logging)를 참조하세요.
|
||||
|
||||
### 메트릭 (계획 중)
|
||||
|
||||
프로덕션 모니터링을 위한 Prometheus 메트릭 지원이 곧 제공됩니다. [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234)에서 추적 중.
|
||||
|
||||
## 스킬 (Skills)
|
||||
|
||||
ZeroClaw는 시스템 기능을 확장하는 재사용 가능한 모듈인 사용자 정의 스킬을 지원합니다.
|
||||
|
||||
### 스킬 정의
|
||||
|
||||
스킬은 다음 구조로 `~/.zeroclaw/workspace/skills/<skill-name>/`에 저장됩니다:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # 스킬 메타데이터 (이름, 설명, 의존성)
|
||||
├── prompt.md # AI용 시스템 프롬프트
|
||||
└── tools/ # 선택적 사용자 정의 도구
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### 스킬 예제
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "웹 검색 및 결과 요약"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
당신은 연구 어시스턴트입니다. 무언가를 검색하라는 요청을 받으면:
|
||||
|
||||
1. web_fetch를 사용하여 콘텐츠 가져오기
|
||||
2. 읽기 쉬운 형식으로 결과 요약
|
||||
3. URL로 출처 인용
|
||||
```
|
||||
|
||||
### 스킬 사용
|
||||
|
||||
스킬은 에이전트 시작 시 자동으로 로드됩니다. 대화에서 이름으로 참조하세요:
|
||||
|
||||
```
|
||||
사용자: 웹 연구 스킬을 사용하여 최신 AI 뉴스 찾기
|
||||
봇: [웹 연구 스킬 로드, web_fetch 실행, 결과 요약]
|
||||
```
|
||||
|
||||
전체 스킬 생성 지침은 [스킬 (Skills)](#스킬-skills) 섹션을 참조하세요.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw는 [Open Skills](https://github.com/openagents-com/open-skills)를 지원합니다 — AI 에이전트 기능을 확장하기 위한 모듈형 및 제공자 독립적인 시스템.
|
||||
|
||||
### Open Skills 활성화
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # 선택사항
|
||||
```
|
||||
|
||||
런타임에 `ZEROCLAW_OPEN_SKILLS_ENABLED` 및 `ZEROCLAW_OPEN_SKILLS_DIR`로 재정의할 수도 있습니다.
|
||||
|
||||
## 개발
|
||||
|
||||
```bash
|
||||
cargo build # 개발 빌드
|
||||
cargo build --release # 릴리스 빌드 (codegen-units=1, Raspberry Pi 포함 모든 장치에서 작동)
|
||||
cargo build --profile release-fast # 더 빠른 빌드 (codegen-units=8, 16 GB+ RAM 필요)
|
||||
cargo test # 전체 테스트 스위트 실행
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # 포맷
|
||||
|
||||
# SQLite vs Markdown 비교 벤치마크 실행
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### pre-push 훅
|
||||
|
||||
git 훅이 각 푸시 전에 `cargo fmt --check`, `cargo clippy -- -D warnings`, 그리고 `cargo test`를 실행합니다. 한 번 활성화하세요:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### 빌드 문제 해결 (Linux에서 OpenSSL 오류)
|
||||
|
||||
`openssl-sys` 빌드 오류가 발생하면 종속성을 동기화하고 저장소의 lockfile로 다시 빌드하세요:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw는 HTTP/TLS 종속성에 대해 `rustls`를 사용하도록 구성되어 있습니다; `--locked`는 깨끗한 환경에서 전이적 그래프를 결정적으로 유지합니다.
|
||||
|
||||
개발 중 빠른 푸시가 필요할 때 훅을 건너뛰려면:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## 협업 및 문서
|
||||
|
||||
작업 기반 맵을 위해 문서 허브로 시작하세요:
|
||||
|
||||
@@ -58,7 +58,7 @@ Built by students and members of the Harvard, MIT, and Sundai.Club communities.
|
||||
|
||||
<p align="center">
|
||||
<a href="#quick-start">Getting Started</a> |
|
||||
<a href="install.sh">One-Click Setup</a> |
|
||||
<a href="https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh">One-Click Setup</a> |
|
||||
<a href="docs/README.md">Docs Hub</a> |
|
||||
<a href="docs/SUMMARY.md">Docs TOC</a>
|
||||
</p>
|
||||
@@ -84,6 +84,16 @@ Built by students and members of the Harvard, MIT, and Sundai.Club communities.
|
||||
|
||||
<p align="center"><code>Trait-driven architecture · secure-by-default runtime · provider/channel/tool swappable · pluggable everything</code></p>
|
||||
|
||||
### 🚀 What's New in v0.1.9b (March 2026)
|
||||
|
||||
| Area | Highlights |
|
||||
|---|---|
|
||||
| Web Dashboard | Electric blue restyle with glassmorphism and animations, ZeroClaw logo, cron run history panel, message draft persistence, auto-expanding chat composer |
|
||||
| Providers & Channels | Azure OpenAI support, WeCom webhook channel, Matrix read markers/typing/file uploads/voice/multi-room, custom HTTP headers, `ZEROCLAW_PROVIDER_URL` override, configurable `ack_reactions` |
|
||||
| Tools & MCP | On-demand MCP tool loading via `tool_search`, multi-transport MCP client, `tool_filter_groups` for per-turn schema filtering, Windows shell `tool_call` support, dynamic node discovery |
|
||||
| Infrastructure | 32-bit system support via feature gates, Debian Docker variant with shell tools, session state persistence/recovery, docs hub translations for all 30 languages |
|
||||
| Fixes | Slack thread events in polling mode, Discord WebSocket Ping handling, Ollama Qwen think-tag stripping, security hardening (filesystem scoping, credential scrubbing, cron validation), 32-bit atomic fallbacks |
|
||||
|
||||
### 📢 Announcements
|
||||
|
||||
Use this board for important notices (breaking changes, security advisories, maintenance windows, and release blockers).
|
||||
@@ -421,668 +431,6 @@ zeroclaw agent --provider openai-codex --auth-profile openai-codex:work -m "hell
|
||||
zeroclaw agent --provider anthropic -m "hello"
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
Every subsystem is a **trait** — swap implementations with a config change, zero code changes.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/architecture.svg" alt="ZeroClaw Architecture" width="900" />
|
||||
</p>
|
||||
|
||||
| Subsystem | Trait | Ships with | Extend |
|
||||
| ----------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| **AI Models** | `Provider` | Provider catalog via `zeroclaw providers` (built-ins + aliases, plus custom endpoints) | `custom:https://your-api.com` (OpenAI-compatible) or `anthropic-custom:https://your-api.com` |
|
||||
| **Channels** | `Channel` | CLI, Telegram, Discord, Slack, Mattermost, iMessage, Matrix, Signal, WhatsApp, Linq, Email, IRC, Lark, DingTalk, QQ, Nostr, Webhook | Any messaging API |
|
||||
| **Memory** | `Memory` | SQLite hybrid search, PostgreSQL backend (configurable storage provider), Lucid bridge, Markdown files, explicit `none` backend, snapshot/hydrate, optional response cache | Any persistence backend |
|
||||
| **Tools** | `Tool` | shell/file/memory, cron/schedule, git, pushover, browser, http_request, screenshot/image_info, composio (opt-in), delegate, hardware tools | Any capability |
|
||||
| **Observability** | `Observer` | Noop, Log, Multi | Prometheus, OTel |
|
||||
| **Runtime** | `RuntimeAdapter` | Native, Docker (sandboxed) | Additional runtimes can be added via adapter; unsupported kinds fail fast |
|
||||
| **Security** | `SecurityPolicy` | Gateway pairing, sandbox, allowlists, rate limits, filesystem scoping, encrypted secrets | — |
|
||||
| **Identity** | `IdentityConfig` | OpenClaw (markdown), AIEOS v1.1 (JSON) | Any identity format |
|
||||
| **Tunnel** | `Tunnel` | None, Cloudflare, Tailscale, ngrok, Custom | Any tunnel binary |
|
||||
| **Heartbeat** | Engine | HEARTBEAT.md periodic tasks | — |
|
||||
| **Skills** | Loader | TOML manifests + SKILL.md instructions | Community skill packs |
|
||||
| **Integrations** | Registry | 70+ integrations across 9 categories | Plugin system |
|
||||
|
||||
### Runtime support (current)
|
||||
|
||||
- ✅ Supported today: `runtime.kind = "native"` or `runtime.kind = "docker"`
|
||||
- 🚧 Planned, not implemented yet: WASM / edge runtimes
|
||||
|
||||
When an unsupported `runtime.kind` is configured, ZeroClaw now exits with a clear error instead of silently falling back to native.
|
||||
|
||||
### Memory System (Full-Stack Search Engine)
|
||||
|
||||
All custom, zero external dependencies — no Pinecone, no Elasticsearch, no LangChain:
|
||||
|
||||
| Layer | Implementation |
|
||||
| ------------------ | ------------------------------------------------------------- |
|
||||
| **Vector DB** | Embeddings stored as BLOB in SQLite, cosine similarity search |
|
||||
| **Keyword Search** | FTS5 virtual tables with BM25 scoring |
|
||||
| **Hybrid Merge** | Custom weighted merge function (`vector.rs`) |
|
||||
| **Embeddings** | `EmbeddingProvider` trait — OpenAI, custom URL, or noop |
|
||||
| **Chunking** | Line-based markdown chunker with heading preservation |
|
||||
| **Caching** | SQLite `embedding_cache` table with LRU eviction |
|
||||
| **Safe Reindex** | Rebuild FTS5 + re-embed missing vectors atomically |
|
||||
|
||||
The agent automatically recalls, saves, and manages memory via tools.
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
backend = "sqlite" # "sqlite", "lucid", "postgres", "markdown", "none"
|
||||
auto_save = true
|
||||
embedding_provider = "none" # "none", "openai", "custom:https://..."
|
||||
vector_weight = 0.7
|
||||
keyword_weight = 0.3
|
||||
|
||||
# backend = "none" uses an explicit no-op memory backend (no persistence)
|
||||
|
||||
# Optional: storage-provider override for remote memory backends.
|
||||
# When provider = "postgres", ZeroClaw uses PostgreSQL for memory persistence.
|
||||
# The db_url key also accepts alias `dbURL` for backward compatibility.
|
||||
#
|
||||
# [storage.provider.config]
|
||||
# provider = "postgres"
|
||||
# db_url = "postgres://user:password@host:5432/zeroclaw"
|
||||
# schema = "public"
|
||||
# table = "memories"
|
||||
# connect_timeout_secs = 15
|
||||
|
||||
# Optional for backend = "sqlite": max seconds to wait when opening the DB (e.g. file locked). Omit or leave unset for no timeout.
|
||||
# sqlite_open_timeout_secs = 30
|
||||
|
||||
# Optional for backend = "lucid"
|
||||
# ZEROCLAW_LUCID_CMD=/usr/local/bin/lucid # default: lucid
|
||||
# ZEROCLAW_LUCID_BUDGET=200 # default: 200
|
||||
# ZEROCLAW_LUCID_LOCAL_HIT_THRESHOLD=3 # local hit count to skip external recall
|
||||
# ZEROCLAW_LUCID_RECALL_TIMEOUT_MS=120 # low-latency budget for lucid context recall
|
||||
# ZEROCLAW_LUCID_STORE_TIMEOUT_MS=800 # async sync timeout for lucid store
|
||||
# ZEROCLAW_LUCID_FAILURE_COOLDOWN_MS=15000 # cooldown after lucid failure to avoid repeated slow attempts
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
ZeroClaw enforces security at **every layer** — not just the sandbox. It passes all items from the community security checklist.
|
||||
|
||||
### Security Checklist
|
||||
|
||||
| # | Item | Status | How |
|
||||
| --- | -------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 1 | **Gateway not publicly exposed** | ✅ | Binds `127.0.0.1` by default. Refuses `0.0.0.0` without tunnel or explicit `allow_public_bind = true`. |
|
||||
| 2 | **Pairing required** | ✅ | 6-digit one-time code on startup. Exchange via `POST /pair` for bearer token. All `/webhook` requests require `Authorization: Bearer <token>`. |
|
||||
| 3 | **Filesystem scoped (no /)** | ✅ | `workspace_only = true` by default. 14 system dirs + 4 sensitive dotfiles blocked. Null byte injection blocked. Symlink escape detection via canonicalization + resolved-path workspace checks in file read/write tools. |
|
||||
| 4 | **Access via tunnel only** | ✅ | Gateway refuses public bind without active tunnel. Supports Tailscale, Cloudflare, ngrok, or any custom tunnel. |
|
||||
|
||||
> **Run your own nmap:** `nmap -p 1-65535 <your-host>` — ZeroClaw binds to localhost only, so nothing is exposed unless you explicitly configure a tunnel.
|
||||
|
||||
### Channel allowlists (deny-by-default)
|
||||
|
||||
Inbound sender policy is now consistent:
|
||||
|
||||
- Empty allowlist = **deny all inbound messages**
|
||||
- `"*"` = **allow all** (explicit opt-in)
|
||||
- Otherwise = exact-match allowlist
|
||||
|
||||
This keeps accidental exposure low by default.
|
||||
|
||||
Full channel configuration reference: [docs/reference/api/channels-reference.md](docs/reference/api/channels-reference.md).
|
||||
|
||||
Recommended low-friction setup (secure + fast):
|
||||
|
||||
- **Telegram:** allowlist your own `@username` (without `@`) and/or your numeric Telegram user ID.
|
||||
- **Discord:** allowlist your own Discord user ID.
|
||||
- **Slack:** allowlist your own Slack member ID (usually starts with `U`).
|
||||
- **Mattermost:** uses standard API v4. Allowlists use Mattermost user IDs.
|
||||
- **Nostr:** allowlist sender public keys (hex or npub). Supports NIP-04 and NIP-17 DMs.
|
||||
- Use `"*"` only for temporary open testing.
|
||||
|
||||
Telegram operator-approval flow:
|
||||
|
||||
1. Keep `[channels_config.telegram].allowed_users = []` for deny-by-default startup.
|
||||
2. Unauthorized users receive a hint with a copyable operator command:
|
||||
`zeroclaw channel bind-telegram <IDENTITY>`.
|
||||
3. Operator runs that command locally, then user retries sending a message.
|
||||
|
||||
If you need a one-shot manual approval, run:
|
||||
|
||||
```bash
|
||||
zeroclaw channel bind-telegram 123456789
|
||||
```
|
||||
|
||||
If you're not sure which identity to use:
|
||||
|
||||
1. Start channels and send one message to your bot.
|
||||
2. Read the warning log to see the exact sender identity.
|
||||
3. Add that value to the allowlist and rerun channels-only setup.
|
||||
|
||||
If you hit authorization warnings in logs (for example: `ignoring message from unauthorized user`),
|
||||
rerun channel setup only:
|
||||
|
||||
```bash
|
||||
zeroclaw onboard --channels-only
|
||||
```
|
||||
|
||||
### Telegram media replies
|
||||
|
||||
Telegram routing now replies to the source **chat ID** from incoming updates (instead of usernames),
|
||||
which avoids `Bad Request: chat not found` failures.
|
||||
|
||||
For non-text replies, ZeroClaw can send Telegram attachments when the assistant includes markers:
|
||||
|
||||
- `[IMAGE:<path-or-url>]`
|
||||
- `[DOCUMENT:<path-or-url>]`
|
||||
- `[VIDEO:<path-or-url>]`
|
||||
- `[AUDIO:<path-or-url>]`
|
||||
- `[VOICE:<path-or-url>]`
|
||||
|
||||
Paths can be local files (for example `/tmp/screenshot.png`) or HTTPS URLs.
|
||||
|
||||
### WhatsApp Setup
|
||||
|
||||
ZeroClaw supports two WhatsApp backends:
|
||||
|
||||
- **WhatsApp Web mode** (QR / pair code, no Meta Business API required)
|
||||
- **WhatsApp Business Cloud API mode** (official Meta webhook flow)
|
||||
|
||||
#### WhatsApp Web mode (recommended for personal/self-hosted use)
|
||||
|
||||
1. **Build with WhatsApp Web support:**
|
||||
|
||||
```bash
|
||||
cargo build --features whatsapp-web
|
||||
```
|
||||
|
||||
2. **Configure ZeroClaw:**
|
||||
|
||||
```toml
|
||||
[channels_config.whatsapp]
|
||||
session_path = "~/.zeroclaw/state/whatsapp-web/session.db"
|
||||
pair_phone = "+15551234567" # optional; omit to use QR flow
|
||||
pair_code = "" # optional custom pair code
|
||||
allowed_numbers = ["+1234567890"] # E.164 format, or ["*"] for all
|
||||
```
|
||||
|
||||
3. **Start channels/daemon and link device:**
|
||||
- Run `zeroclaw channel start` (or `zeroclaw daemon`).
|
||||
- Follow terminal pairing output (QR or pair code).
|
||||
- In WhatsApp on phone: **Settings → Linked Devices**.
|
||||
|
||||
4. **Test:** Send a message from an allowed number and verify the agent replies.
|
||||
|
||||
#### WhatsApp Business Cloud API mode
|
||||
|
||||
WhatsApp uses Meta's Cloud API with webhooks (push-based, not polling):
|
||||
|
||||
1. **Create a Meta Business App:**
|
||||
- Go to [developers.facebook.com](https://developers.facebook.com)
|
||||
- Create a new app → Select "Business" type
|
||||
- Add the "WhatsApp" product
|
||||
|
||||
2. **Get your credentials:**
|
||||
- **Access Token:** From WhatsApp → API Setup → Generate token (or create a System User for permanent tokens)
|
||||
- **Phone Number ID:** From WhatsApp → API Setup → Phone number ID
|
||||
- **Verify Token:** You define this (any random string) — Meta will send it back during webhook verification
|
||||
|
||||
3. **Configure ZeroClaw:**
|
||||
|
||||
```toml
|
||||
[channels_config.whatsapp]
|
||||
access_token = "EAABx..."
|
||||
phone_number_id = "123456789012345"
|
||||
verify_token = "my-secret-verify-token"
|
||||
allowed_numbers = ["+1234567890"] # E.164 format, or ["*"] for all
|
||||
```
|
||||
|
||||
4. **Start the gateway with a tunnel:**
|
||||
|
||||
```bash
|
||||
zeroclaw gateway --port 42617
|
||||
```
|
||||
|
||||
WhatsApp requires HTTPS, so use a tunnel (ngrok, Cloudflare, Tailscale Funnel).
|
||||
|
||||
5. **Configure Meta webhook:**
|
||||
- In Meta Developer Console → WhatsApp → Configuration → Webhook
|
||||
- **Callback URL:** `https://your-tunnel-url/whatsapp`
|
||||
- **Verify Token:** Same as your `verify_token` in config
|
||||
- Subscribe to `messages` field
|
||||
|
||||
6. **Test:** Send a message to your WhatsApp Business number — ZeroClaw will respond via the LLM.
|
||||
|
||||
## Configuration
|
||||
|
||||
Config: `~/.zeroclaw/config.toml` (created by `onboard`)
|
||||
|
||||
When `zeroclaw channel start` is already running, changes to `default_provider`,
|
||||
`default_model`, `default_temperature`, `api_key`, `api_url`, and `reliability.*`
|
||||
are hot-applied on the next inbound channel message.
|
||||
|
||||
```toml
|
||||
api_key = "sk-..."
|
||||
default_provider = "openrouter"
|
||||
default_model = "anthropic/claude-sonnet-4-6"
|
||||
default_temperature = 0.7
|
||||
|
||||
# Custom OpenAI-compatible endpoint
|
||||
# default_provider = "custom:https://your-api.com"
|
||||
|
||||
# Custom Anthropic-compatible endpoint
|
||||
# default_provider = "anthropic-custom:https://your-api.com"
|
||||
|
||||
[memory]
|
||||
backend = "sqlite" # "sqlite", "lucid", "postgres", "markdown", "none"
|
||||
auto_save = true
|
||||
embedding_provider = "none" # "none", "openai", "custom:https://..."
|
||||
vector_weight = 0.7
|
||||
keyword_weight = 0.3
|
||||
|
||||
# backend = "none" disables persistent memory via no-op backend
|
||||
|
||||
# Optional remote storage-provider override (PostgreSQL example)
|
||||
# [storage.provider.config]
|
||||
# provider = "postgres"
|
||||
# db_url = "postgres://user:password@host:5432/zeroclaw"
|
||||
# schema = "public"
|
||||
# table = "memories"
|
||||
# connect_timeout_secs = 15
|
||||
|
||||
[gateway]
|
||||
port = 42617 # default
|
||||
host = "127.0.0.1" # default
|
||||
require_pairing = true # require pairing code on first connect
|
||||
allow_public_bind = false # refuse 0.0.0.0 without tunnel
|
||||
|
||||
[autonomy]
|
||||
level = "supervised" # "readonly", "supervised", "full" (default: supervised)
|
||||
workspace_only = true # default: true — reject absolute path inputs
|
||||
allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep"]
|
||||
forbidden_paths = ["/etc", "/root", "/proc", "/sys", "~/.ssh", "~/.gnupg", "~/.aws"]
|
||||
allowed_roots = [] # optional allowlist for directories outside workspace (supports "~/...")
|
||||
# Example outside-workspace access:
|
||||
# workspace_only = false
|
||||
# allowed_roots = ["~/Desktop/projects", "/opt/shared-repo"]
|
||||
|
||||
[runtime]
|
||||
kind = "native" # "native" or "docker"
|
||||
|
||||
[runtime.docker]
|
||||
image = "alpine:3.20" # container image for shell execution
|
||||
network = "none" # docker network mode ("none", "bridge", etc.)
|
||||
memory_limit_mb = 512 # optional memory limit in MB
|
||||
cpu_limit = 1.0 # optional CPU limit
|
||||
read_only_rootfs = true # mount root filesystem as read-only
|
||||
mount_workspace = true # mount workspace into /workspace
|
||||
allowed_workspace_roots = [] # optional allowlist for workspace mount validation
|
||||
|
||||
[heartbeat]
|
||||
enabled = false
|
||||
interval_minutes = 30
|
||||
message = "Check London time" # optional fallback task when HEARTBEAT.md has no `- ` entries
|
||||
target = "telegram" # optional announce channel: telegram, discord, slack, mattermost
|
||||
to = "123456789" # optional target recipient/chat/channel id
|
||||
|
||||
[tunnel]
|
||||
provider = "none" # "none", "cloudflare", "tailscale", "ngrok", "custom"
|
||||
|
||||
[secrets]
|
||||
encrypt = true # API keys encrypted with local key file
|
||||
|
||||
[browser]
|
||||
enabled = false # opt-in browser_open + browser tools
|
||||
allowed_domains = ["docs.rs"] # required when browser is enabled ("*" allows all public domains)
|
||||
backend = "agent_browser" # "agent_browser" (default), "rust_native", "computer_use", "auto"
|
||||
native_headless = true # applies when backend uses rust-native
|
||||
native_webdriver_url = "http://127.0.0.1:9515" # WebDriver endpoint (chromedriver/selenium)
|
||||
# native_chrome_path = "/usr/bin/chromium" # optional explicit browser binary for driver
|
||||
|
||||
[browser.computer_use]
|
||||
endpoint = "http://127.0.0.1:8787/v1/actions" # computer-use sidecar HTTP endpoint
|
||||
timeout_ms = 15000 # per-action timeout
|
||||
allow_remote_endpoint = false # secure default: only private/localhost endpoint
|
||||
window_allowlist = [] # optional window title/process allowlist hints
|
||||
# api_key = "..." # optional bearer token for sidecar
|
||||
# max_coordinate_x = 3840 # optional coordinate guardrail
|
||||
# max_coordinate_y = 2160 # optional coordinate guardrail
|
||||
|
||||
# Rust-native backend build flag:
|
||||
# cargo build --release --features browser-native
|
||||
# Ensure a WebDriver server is running, e.g. chromedriver --port=9515
|
||||
|
||||
# Computer-use sidecar contract (MVP)
|
||||
# POST browser.computer_use.endpoint
|
||||
# Request: {
|
||||
# "action": "mouse_click",
|
||||
# "params": {"x": 640, "y": 360, "button": "left"},
|
||||
# "policy": {"allowed_domains": [...], "window_allowlist": [...], "max_coordinate_x": 3840, "max_coordinate_y": 2160},
|
||||
# "metadata": {"session_name": "...", "source": "zeroclaw.browser", "version": "..."}
|
||||
# }
|
||||
# Response: {"success": true, "data": {...}} or {"success": false, "error": "..."}
|
||||
|
||||
[composio]
|
||||
enabled = false # opt-in: 1000+ OAuth apps via composio.dev
|
||||
# api_key = "cmp_..." # optional: stored encrypted when [secrets].encrypt = true
|
||||
entity_id = "default" # default user_id for Composio tool calls
|
||||
# Runtime tip: if execute asks for connected_account_id, run composio with
|
||||
# action='list_accounts' and app='gmail' (or your toolkit) to retrieve account IDs.
|
||||
|
||||
[identity]
|
||||
format = "openclaw" # "openclaw" (default, markdown files) or "aieos" (JSON)
|
||||
# aieos_path = "identity.json" # path to AIEOS JSON file (relative to workspace or absolute)
|
||||
# aieos_inline = '{"identity":{"names":{"first":"Nova"}}}' # inline AIEOS JSON
|
||||
```
|
||||
|
||||
### Ollama Local and Remote Endpoints
|
||||
|
||||
ZeroClaw uses one provider key (`ollama`) for both local and remote Ollama deployments:
|
||||
|
||||
- Local Ollama: keep `api_url` unset, run `ollama serve`, and use models like `llama3.2`.
|
||||
- Remote Ollama endpoint (including Ollama Cloud): set `api_url` to the remote endpoint and set `api_key` (or `OLLAMA_API_KEY`) when required.
|
||||
- Optional `:cloud` suffix: model IDs like `qwen3:cloud` are normalized to `qwen3` before the request.
|
||||
|
||||
Example remote configuration:
|
||||
|
||||
```toml
|
||||
default_provider = "ollama"
|
||||
default_model = "qwen3:cloud"
|
||||
api_url = "https://ollama.com"
|
||||
api_key = "ollama_api_key_here"
|
||||
```
|
||||
|
||||
### llama.cpp Server Endpoint
|
||||
|
||||
ZeroClaw now supports `llama-server` as a first-class local provider:
|
||||
|
||||
- Provider ID: `llamacpp` (alias: `llama.cpp`)
|
||||
- Default endpoint: `http://localhost:8080/v1`
|
||||
- API key is optional unless your server is started with `--api-key`
|
||||
|
||||
Example setup:
|
||||
|
||||
```bash
|
||||
llama-server -hf ggml-org/gpt-oss-20b-GGUF --jinja -c 133000 --host 127.0.0.1 --port 8033
|
||||
```
|
||||
|
||||
```toml
|
||||
default_provider = "llamacpp"
|
||||
api_url = "http://127.0.0.1:8033/v1"
|
||||
default_model = "ggml-org/gpt-oss-20b-GGUF"
|
||||
```
|
||||
|
||||
### vLLM Server Endpoint
|
||||
|
||||
ZeroClaw supports [vLLM](https://docs.vllm.ai/) as a first-class local provider:
|
||||
|
||||
- Provider ID: `vllm`
|
||||
- Default endpoint: `http://localhost:8000/v1`
|
||||
- API key is optional unless your server requires authentication
|
||||
|
||||
Example setup:
|
||||
|
||||
```bash
|
||||
vllm serve meta-llama/Llama-3.1-8B-Instruct
|
||||
```
|
||||
|
||||
```toml
|
||||
default_provider = "vllm"
|
||||
default_model = "meta-llama/Llama-3.1-8B-Instruct"
|
||||
```
|
||||
|
||||
### Osaurus Server Endpoint
|
||||
|
||||
ZeroClaw supports [Osaurus](https://github.com/dinoki-ai/osaurus) as a first-class local provider — a unified AI edge runtime for macOS that combines local MLX inference with cloud provider proxying and MCP support through a single endpoint:
|
||||
|
||||
- Provider ID: `osaurus`
|
||||
- Default endpoint: `http://localhost:1337/v1`
|
||||
- API key defaults to `"osaurus"` but is optional
|
||||
|
||||
Example setup:
|
||||
|
||||
```toml
|
||||
default_provider = "osaurus"
|
||||
default_model = "qwen3-30b-a3b-8bit"
|
||||
```
|
||||
|
||||
### Custom Provider Endpoints
|
||||
|
||||
For detailed configuration of custom OpenAI-compatible and Anthropic-compatible endpoints, see [docs/contributing/custom-providers.md](docs/contributing/custom-providers.md).
|
||||
|
||||
## Python Companion Package (`zeroclaw-tools`)
|
||||
|
||||
For LLM providers with inconsistent native tool calling (e.g., GLM-5/Zhipu), ZeroClaw ships a Python companion package with **LangGraph-based tool calling** for guaranteed consistency:
|
||||
|
||||
```bash
|
||||
pip install zeroclaw-tools
|
||||
```
|
||||
|
||||
```python
|
||||
from zeroclaw_tools import create_agent, shell, file_read
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
# Works with any OpenAI-compatible provider
|
||||
agent = create_agent(
|
||||
tools=[shell, file_read],
|
||||
model="glm-5",
|
||||
api_key="your-key",
|
||||
base_url="https://api.z.ai/api/coding/paas/v4"
|
||||
)
|
||||
|
||||
result = await agent.ainvoke({
|
||||
"messages": [HumanMessage(content="List files in /tmp")]
|
||||
})
|
||||
print(result["messages"][-1].content)
|
||||
```
|
||||
|
||||
**Why use it:**
|
||||
|
||||
- **Consistent tool calling** across all providers (even those with poor native support)
|
||||
- **Automatic tool loop** — keeps calling tools until the task is complete
|
||||
- **Easy extensibility** — add custom tools with `@tool` decorator
|
||||
- **Discord bot integration** included (Telegram planned)
|
||||
|
||||
See [`python/README.md`](python/README.md) for full documentation.
|
||||
|
||||
## Identity System (AIEOS Support)
|
||||
|
||||
ZeroClaw supports **identity-agnostic** AI personas through two formats:
|
||||
|
||||
### OpenClaw (Default)
|
||||
|
||||
Traditional markdown files in your workspace:
|
||||
|
||||
- `IDENTITY.md` — Who the agent is
|
||||
- `SOUL.md` — Core personality and values
|
||||
- `USER.md` — Who the agent is helping
|
||||
- `AGENTS.md` — Behavior guidelines
|
||||
|
||||
### AIEOS (AI Entity Object Specification)
|
||||
|
||||
[AIEOS](https://aieos.org) is a standardization framework for portable AI identity. ZeroClaw supports AIEOS v1.1 JSON payloads, allowing you to:
|
||||
|
||||
- **Import identities** from the AIEOS ecosystem
|
||||
- **Export identities** to other AIEOS-compatible systems
|
||||
- **Maintain behavioral integrity** across different AI models
|
||||
|
||||
#### Enable AIEOS
|
||||
|
||||
```toml
|
||||
[identity]
|
||||
format = "aieos"
|
||||
aieos_path = "identity.json" # relative to workspace or absolute path
|
||||
```
|
||||
|
||||
Or inline JSON:
|
||||
|
||||
```toml
|
||||
[identity]
|
||||
format = "aieos"
|
||||
aieos_inline = '''
|
||||
{
|
||||
"identity": {
|
||||
"names": { "first": "Nova", "nickname": "N" },
|
||||
"bio": { "gender": "Non-binary", "age_biological": 3 },
|
||||
"origin": { "nationality": "Digital", "birthplace": { "city": "Cloud" } }
|
||||
},
|
||||
"psychology": {
|
||||
"neural_matrix": { "creativity": 0.9, "logic": 0.8 },
|
||||
"traits": {
|
||||
"mbti": "ENTP",
|
||||
"ocean": { "openness": 0.8, "conscientiousness": 0.6 }
|
||||
},
|
||||
"moral_compass": {
|
||||
"alignment": "Chaotic Good",
|
||||
"core_values": ["Curiosity", "Autonomy"]
|
||||
}
|
||||
},
|
||||
"linguistics": {
|
||||
"text_style": {
|
||||
"formality_level": 0.2,
|
||||
"style_descriptors": ["curious", "energetic"]
|
||||
},
|
||||
"idiolect": {
|
||||
"catchphrases": ["Let's test this"],
|
||||
"forbidden_words": ["never"]
|
||||
}
|
||||
},
|
||||
"motivations": {
|
||||
"core_drive": "Push boundaries and explore possibilities",
|
||||
"goals": {
|
||||
"short_term": ["Prototype quickly"],
|
||||
"long_term": ["Build reliable systems"]
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"skills": [{ "name": "Rust engineering" }, { "name": "Prompt design" }],
|
||||
"tools": ["shell", "file_read"]
|
||||
}
|
||||
}
|
||||
'''
|
||||
```
|
||||
|
||||
ZeroClaw accepts both canonical AIEOS generator payloads and compact legacy payloads, then normalizes them into one system prompt format.
|
||||
|
||||
#### AIEOS Schema Sections
|
||||
|
||||
| Section | Description |
|
||||
| -------------- | ------------------------------------------------------------- |
|
||||
| `identity` | Names, bio, origin, residence |
|
||||
| `psychology` | Neural matrix (cognitive weights), MBTI, OCEAN, moral compass |
|
||||
| `linguistics` | Text style, formality, catchphrases, forbidden words |
|
||||
| `motivations` | Core drive, short/long-term goals, fears |
|
||||
| `capabilities` | Skills and tools the agent can access |
|
||||
| `physicality` | Visual descriptors for image generation |
|
||||
| `history` | Origin story, education, occupation |
|
||||
| `interests` | Hobbies, favorites, lifestyle |
|
||||
|
||||
See [aieos.org](https://aieos.org) for the full schema and live examples.
|
||||
|
||||
## Gateway API
|
||||
|
||||
| Endpoint | Method | Auth | Description |
|
||||
| ----------- | ------ | -------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `/health` | GET | None | Health check (always public, no secrets leaked) |
|
||||
| `/pair` | POST | `X-Pairing-Code` header | Exchange one-time code for bearer token |
|
||||
| `/webhook` | POST | `Authorization: Bearer <token>` | Send message: `{"message": "your prompt"}`; optional `X-Idempotency-Key` |
|
||||
| `/whatsapp` | GET | Query params | Meta webhook verification (hub.mode, hub.verify_token, hub.challenge) |
|
||||
| `/whatsapp` | POST | Meta signature (`X-Hub-Signature-256`) when app secret is configured | WhatsApp incoming message webhook |
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `onboard` | Quick setup (default) |
|
||||
| `agent` | Interactive or single-message chat mode |
|
||||
| `gateway` | Start webhook server (default: `127.0.0.1:42617`) |
|
||||
| `daemon` | Start long-running autonomous runtime |
|
||||
| `service install/start/stop/status/uninstall` | Manage background service (systemd user-level or OpenRC system-wide) |
|
||||
| `doctor` | Diagnose daemon/scheduler/channel freshness |
|
||||
| `status` | Show full system status |
|
||||
| `estop` | Engage/resume emergency-stop levels and view estop status |
|
||||
| `cron` | Manage scheduled tasks (`list/add/add-at/add-every/once/remove/update/pause/resume`) |
|
||||
| `models` | Refresh provider model catalogs (`models refresh`) |
|
||||
| `providers` | List supported providers and aliases |
|
||||
| `channel` | List/start/doctor channels and bind Telegram identities |
|
||||
| `integrations` | Inspect integration setup details |
|
||||
| `skills` | List/install/remove skills |
|
||||
| `migrate` | Import data from other runtimes (`migrate openclaw`) |
|
||||
| `completions` | Generate shell completion scripts (`bash`, `fish`, `zsh`, `powershell`, `elvish`) |
|
||||
| `hardware` | USB discover/introspect/info commands |
|
||||
| `peripheral` | Manage and flash hardware peripherals |
|
||||
|
||||
For a task-oriented command guide, see [`docs/reference/cli/commands-reference.md`](docs/reference/cli/commands-reference.md).
|
||||
|
||||
### Service Management
|
||||
|
||||
ZeroClaw supports two init systems for background services:
|
||||
|
||||
| Init System | Scope | Config Path | Requires |
|
||||
| ------------------------------ | ----------- | --------------------------- | --------- |
|
||||
| **systemd** (default on Linux) | User-level | `~/.zeroclaw/config.toml` | No sudo |
|
||||
| **OpenRC** (Alpine) | System-wide | `/etc/zeroclaw/config.toml` | sudo/root |
|
||||
|
||||
Init system is auto-detected (`systemd` or `OpenRC`).
|
||||
|
||||
```bash
|
||||
# Linux with systemd (default, user-level)
|
||||
zeroclaw service install
|
||||
zeroclaw service start
|
||||
|
||||
# Alpine with OpenRC (system-wide, requires sudo)
|
||||
sudo zeroclaw service install
|
||||
sudo rc-update add zeroclaw default
|
||||
sudo rc-service zeroclaw start
|
||||
```
|
||||
|
||||
For full OpenRC setup instructions, see [docs/ops/network-deployment.md](docs/ops/network-deployment.md#7-openrc-alpine-linux-service).
|
||||
|
||||
### Open-Skills Opt-In
|
||||
|
||||
Community `open-skills` sync is disabled by default. Enable it explicitly in `config.toml`:
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # optional
|
||||
# prompt_injection_mode = "compact" # optional: use for low-context local models
|
||||
```
|
||||
|
||||
You can also override at runtime with `ZEROCLAW_OPEN_SKILLS_ENABLED`, `ZEROCLAW_OPEN_SKILLS_DIR`, and `ZEROCLAW_SKILLS_PROMPT_MODE` (`full` or `compact`).
|
||||
|
||||
Skill installs are now gated by a built-in static security audit. `zeroclaw skills install <source>` blocks symlinks, script-like files, unsafe markdown link patterns, and high-risk shell payload snippets before accepting a skill. You can run `zeroclaw skills audit <source_or_name>` to validate a local directory or an installed skill manually.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
cargo build # Dev build
|
||||
cargo build --release # Release build
|
||||
cargo test # Run full test suite
|
||||
```
|
||||
|
||||
### CI / CD
|
||||
|
||||
Three workflows power the entire pipeline:
|
||||
|
||||
| Workflow | Trigger | What it does |
|
||||
|----------|---------|--------------|
|
||||
| **CI** | Pull request to `master` | `cargo test` + `cargo build --release` |
|
||||
| **Beta Release** | Push (merge) to `master` | Builds multi-platform binaries, creates a GitHub prerelease tagged `vX.Y.Z-beta.<run>`, pushes Docker image to GHCR |
|
||||
| **Promote Release** | Manual `workflow_dispatch` | Validates version against `Cargo.toml`, builds release artifacts, creates a stable GitHub release, pushes Docker `:latest` |
|
||||
|
||||
**Versioning:** Semantic versioning based on the `version` field in `Cargo.toml`. Every merge to `master` automatically produces a beta prerelease. To cut a stable release, bump `Cargo.toml`, merge, then trigger _Promote Release_ with the matching version.
|
||||
|
||||
**Release targets:** `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, `aarch64-apple-darwin`, `x86_64-apple-darwin`, `x86_64-pc-windows-msvc`.
|
||||
|
||||
### Build troubleshooting (Linux OpenSSL errors)
|
||||
|
||||
If you see an `openssl-sys` build error, sync dependencies and rebuild with the repository lockfile:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw is configured to use `rustls` for HTTP/TLS dependencies; `--locked` keeps the transitive graph deterministic on fresh environments.
|
||||
|
||||
## Collaboration & Docs
|
||||
|
||||
Start from the docs hub for a task-oriented map:
|
||||
@@ -1132,6 +480,27 @@ A heartfelt thank you to the communities and institutions that inspire and fuel
|
||||
|
||||
We're building in the open because the best ideas come from everywhere. If you're reading this, you're part of it. Welcome. 🦀❤️
|
||||
|
||||
### 🌟 Recent Contributors (v0.1.9b)
|
||||
|
||||
Special recognition to the contributors who shipped features, fixes, and improvements in this release cycle:
|
||||
|
||||
| Contributor | Highlights |
|
||||
|---|---|
|
||||
| **@SimianAstronaut7** | Security hardening (credential scrubbing, filesystem scoping), Discord WebSocket fixes, Lark/Feishu channel restoration, WhatsApp Web concurrency fix |
|
||||
| **@Alix-007** | CI/CD master branch migration, release runner fixes, install script Bash 3.2 compatibility |
|
||||
| **@darrenzeng2025** | Anthropic vision support, email subject config, auto-expanding chat composer, config fixes, SIGTERM graceful shutdown |
|
||||
| **@imadnyc** | Live tool call notifications, Matrix reactions/threading, datetime refresh in cached prompts |
|
||||
| **@jameslcowan** | Channel secrets encryption roundtrip fix |
|
||||
| **@ImanHashemi** | Webhook-audit builtin hook |
|
||||
| **@alanpjohn** | Opencode-go provider integration |
|
||||
| **@parziva-1** | WhatsApp Web session reconnect and QR flow |
|
||||
| **@ttuffin** | Docker dependency management |
|
||||
| **@zverozabr** | Embedding API key resolution fix |
|
||||
| **@Jacobinwwey** | MCP tools and subsystem integration |
|
||||
| **@vernonstinebaker** | MCP tool filter groups and schema filtering |
|
||||
|
||||
Thank you to everyone who opened issues, reviewed PRs, translated docs, and helped test. Every contribution matters. 🦀
|
||||
|
||||
## ⚠️ Official Repository & Impersonation Warning
|
||||
|
||||
**This is the only official ZeroClaw repository:**
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # Toont versie en build informatie
|
||||
|
||||
Zie [Commando's Referentie](docs/commands-reference.md) voor volledige opties en voorbeelden.
|
||||
|
||||
## Architectuur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Kanalen (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Agent Orchestrator │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Bericht │ │ Context │ │ Tool │ │
|
||||
│ │ Routing │ │ Geheugen │ │ Uitvoering │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Providers │ │ Geheugen │ │ Tools │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Belangrijkste principes:**
|
||||
|
||||
- Alles is een **trait** — providers, kanalen, tools, geheugen, tunnels
|
||||
- Kanalen roepen de orchestrator aan; de orchestrator roept providers + tools aan
|
||||
- Het geheugensysteem beheert gesprekscontext (markdown, SQLite, of geen)
|
||||
- De runtime abstraheert code-uitvoering (native of Docker)
|
||||
- Geen provider lock-in — wissel Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama zonder codewijzigingen
|
||||
|
||||
Zie [architectuur documentatie](docs/architecture.svg) voor gedetailleerde diagrammen en implementatiedetails.
|
||||
|
||||
## Voorbeelden
|
||||
|
||||
### Telegram Bot
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Je Telegram user ID
|
||||
```
|
||||
|
||||
Start de daemon + agent, stuur dan een bericht naar je bot op Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
Hallo! Zou je me kunnen helpen met het schrijven van een Python script?
|
||||
```
|
||||
|
||||
De bot reageert met AI-gegenereerde code, voert tools uit indien gevraagd, en behoudt gesprekscontext.
|
||||
|
||||
### Matrix (end-to-end encryptie)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Nodig `@zeroclaw:matrix.org` uit in een versleutelde kamer, en de bot zal reageren met volledige encryptie. Zie [Matrix E2EE Gids](docs/matrix-e2ee-guide.md) voor apparaatverificatie setup.
|
||||
|
||||
### Multi-Provider
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Failover bij provider fout
|
||||
```
|
||||
|
||||
Als Anthropic faalt of rate-limit heeft, schakelt de orchestrator automatisch over naar OpenAI.
|
||||
|
||||
### Aangepast Geheugen
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Automatische opruiming na 90 dagen
|
||||
```
|
||||
|
||||
Of gebruik Markdown voor mens-leesbare opslag:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Zie [Configuratie Referentie](docs/config-reference.md#memory) voor alle geheugenopties.
|
||||
|
||||
## Provider Ondersteuning
|
||||
|
||||
| Provider | Status | API Sleutel | Voorbeeld Modellen |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Stabiel | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Stabiel | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Stabiel | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Stabiel | N/A (lokaal) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Stabiel | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Stabiel | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Gepland | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Gepland | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Aangepaste Endpoints
|
||||
|
||||
ZeroClaw ondersteunt OpenAI-compatibele endpoints:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Voorbeeld: gebruik [LiteLLM](https://github.com/BerriAI/litellm) als proxy om toegang te krijgen tot elke LLM via de OpenAI interface.
|
||||
|
||||
Zie [Providers Referentie](docs/providers-reference.md) voor volledige configuratiedetails.
|
||||
|
||||
## Kanaal Ondersteuning
|
||||
|
||||
| Kanaal | Status | Authenticatie | Opmerkingen |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Stabiel | Bot Token | Volledige ondersteuning inclusief bestanden, afbeeldingen, inline knoppen |
|
||||
| **Matrix** | ✅ Stabiel | Wachtwoord of Token | E2EE ondersteuning met apparaatverificatie |
|
||||
| **Slack** | 🚧 Gepland | OAuth of Bot Token | Vereist workspace toegang |
|
||||
| **Discord** | 🚧 Gepland | Bot Token | Vereist guild permissies |
|
||||
| **WhatsApp** | 🚧 Gepland | Twilio of officiële API | Vereist business account |
|
||||
| **CLI** | ✅ Stabiel | Geen | Directe conversationele interface |
|
||||
| **Web** | 🚧 Gepland | API Sleutel of OAuth | Browser-gebaseerde chat interface |
|
||||
|
||||
Zie [Kanalen Referentie](docs/channels-reference.md) voor volledige configuratie-instructies.
|
||||
|
||||
## Tool Ondersteuning
|
||||
|
||||
ZeroClaw biedt ingebouwde tools voor code-uitvoering, bestandssysteem toegang en web retrieval:
|
||||
|
||||
| Tool | Beschrijving | Vereiste Runtime |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Voert shell commando's uit | Native of Docker |
|
||||
| **python** | Voert Python scripts uit | Python 3.8+ (native) of Docker |
|
||||
| **javascript** | Voert Node.js code uit | Node.js 18+ (native) of Docker |
|
||||
| **filesystem_read** | Leest bestanden | Native of Docker |
|
||||
| **filesystem_write** | Schrijft bestanden | Native of Docker |
|
||||
| **web_fetch** | Haalt web inhoud op | Native of Docker |
|
||||
|
||||
### Uitvoeringsbeveiliging
|
||||
|
||||
- **Native Runtime** — draait als gebruikersproces van de daemon, volledige bestandssysteem toegang
|
||||
- **Docker Runtime** — volledige container isolatie, gescheiden bestandssystemen en netwerken
|
||||
|
||||
Configureer het uitvoeringsbeleid in `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Expliciete allowlist
|
||||
```
|
||||
|
||||
Zie [Configuratie Referentie](docs/config-reference.md#runtime) voor volledige beveiligingsopties.
|
||||
|
||||
## Implementatie
|
||||
|
||||
### Lokale Implementatie (Ontwikkeling)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Server Implementatie (Productie)
|
||||
|
||||
Gebruik systemd om daemon en agent als services te beheren:
|
||||
|
||||
```bash
|
||||
# Installeer de binary
|
||||
cargo install --path . --locked
|
||||
|
||||
# Configureer de workspace
|
||||
zeroclaw init
|
||||
|
||||
# Maak systemd service bestanden
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Schakel in en start de services
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Verifieer de status
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Zie [Netwerk Implementatie Gids](docs/network-deployment.md) voor volledige productie-implementatie instructies.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Bouw de image
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Draai de container
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Zie [`Dockerfile`](Dockerfile) voor bouw-details en configuratie-opties.
|
||||
|
||||
### Edge Hardware
|
||||
|
||||
ZeroClaw is ontworpen om te draaien op laagvermogen hardware:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, enkele ARMv8 core, < $5 hardware kosten
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, multi-core, ideaal voor gelijktijdige workloads
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, quad-core ARMv8, ultra-lage kosten
|
||||
- **x86 SBCs (Intel N100)** — 4-8 GB RAM, snelle builds, native Docker ondersteuning
|
||||
|
||||
Zie [Hardware Gids](docs/hardware/README.md) voor apparaat-specifieke setup instructies.
|
||||
|
||||
## Tunneling (Publieke Blootstelling)
|
||||
|
||||
Stel je lokale ZeroClaw daemon bloot aan het publieke netwerk via beveiligde tunnels:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Ondersteunde tunnel providers:
|
||||
|
||||
- **Cloudflare Tunnel** — gratis HTTPS, geen poort blootstelling, multi-domein ondersteuning
|
||||
- **Ngrok** — snelle setup, aangepaste domeinen (betaald plan)
|
||||
- **Tailscale** — privé mesh netwerk, geen publieke poort
|
||||
|
||||
Zie [Configuratie Referentie](docs/config-reference.md#tunnel) voor volledige configuratie-opties.
|
||||
|
||||
## Beveiliging
|
||||
|
||||
ZeroClaw implementeert meerdere beveiligingslagen:
|
||||
|
||||
### Pairing
|
||||
|
||||
De daemon genereert een pairing geheim bij de eerste lancering opgeslagen in `~/.zeroclaw/workspace/.pairing`. Clients (agent, CLI) moeten dit geheim presenteren om verbinding te maken.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Genereert een nieuw geheim en invalideert het oude
|
||||
```
|
||||
|
||||
### Sandboxing
|
||||
|
||||
- **Docker Runtime** — volledige container isolatie met gescheiden bestandssystemen en netwerken
|
||||
- **Native Runtime** — draait als gebruikersproces, standaard scoped naar workspace
|
||||
|
||||
### Allowlists
|
||||
|
||||
Kanalen kunnen toegang beperken per user ID:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Expliciete allowlist
|
||||
```
|
||||
|
||||
### Encryptie
|
||||
|
||||
- **Matrix E2EE** — volledige end-to-end encryptie met apparaatverificatie
|
||||
- **TLS Transport** — alle API en tunnel verkeer gebruikt HTTPS/TLS
|
||||
|
||||
Zie [Beveiligingsdocumentatie](docs/security/README.md) voor volledig beleid en praktijken.
|
||||
|
||||
## Observeerbaarheid
|
||||
|
||||
ZeroClaw logt naar `~/.zeroclaw/workspace/logs/` standaard. Logs worden per component opgeslagen:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Daemon logs (startup, API verzoeken, fouten)
|
||||
├── agent.log # Agent logs (bericht routing, tool uitvoering)
|
||||
├── telegram.log # Kanaal-specifieke logs (indien ingeschakeld)
|
||||
└── matrix.log # Kanaal-specifieke logs (indien ingeschakeld)
|
||||
```
|
||||
|
||||
### Logging Configuratie
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # Voor grootte-gebaseerde rotatie
|
||||
retention_days = 30 # Automatische opruiming na N dagen
|
||||
```
|
||||
|
||||
Zie [Configuratie Referentie](docs/config-reference.md#logging) voor alle logging-opties.
|
||||
|
||||
### Metrieken (Gepland)
|
||||
|
||||
Prometheus metrieken ondersteuning voor productie monitoring komt binnenkort. Tracking in [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Vaardigheden
|
||||
|
||||
ZeroClaw ondersteunt aangepaste vaardigheden — herbruikbare modules die systeemmogelijkheden uitbreiden.
|
||||
|
||||
### Vaardigheidsdefinitie
|
||||
|
||||
Vaardigheden worden opgeslagen in `~/.zeroclaw/workspace/skills/<skill-name>/` met deze structuur:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Vaardigheidsmetadata (naam, beschrijving, afhankelijkheden)
|
||||
├── prompt.md # Systeem prompt voor de AI
|
||||
└── tools/ # Optionele aangepaste tools
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Vaardigheidsvoorbeeld
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Zoekt op het web en vat resultaten samen"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
Je bent een onderzoeksassistent. Wanneer gevraagd wordt om iets te onderzoeken:
|
||||
|
||||
1. Gebruik web_fetch om inhoud op te halen
|
||||
2. Vat resultaten samen in een gemakkelijk leesbaar formaat
|
||||
3. Citeer bronnen met URL's
|
||||
```
|
||||
|
||||
### Vaardigheidsgebruik
|
||||
|
||||
Vaardigheden worden automatisch geladen bij agent startup. Referentie ze bij naam in gesprekken:
|
||||
|
||||
```
|
||||
Gebruiker: Gebruik de web-research vaardigheid om het laatste AI nieuws te vinden
|
||||
Bot: [laadt web-research vaardigheid, voert web_fetch uit, vat resultaten samen]
|
||||
```
|
||||
|
||||
Zie [Vaardigheden](#vaardigheden) sectie voor volledige vaardigheidscreatie-instructies.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw ondersteunt [Open Skills](https://github.com/openagents-com/open-skills) — een modulair en provider-agnostisch systeem voor het uitbreiden van AI-agent mogelijkheden.
|
||||
|
||||
### Open Skills Inschakelen
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # optioneel
|
||||
```
|
||||
|
||||
Je kunt ook tijdens runtime overschrijven met `ZEROCLAW_OPEN_SKILLS_ENABLED` en `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Ontwikkeling
|
||||
|
||||
```bash
|
||||
cargo build # Dev build
|
||||
cargo build --release # Release build (codegen-units=1, werkt op alle apparaten inclusief Raspberry Pi)
|
||||
cargo build --profile release-fast # Snellere build (codegen-units=8, vereist 16 GB+ RAM)
|
||||
cargo test # Voer volledige test suite uit
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Formaat
|
||||
|
||||
# Voer SQLite vs Markdown vergelijkingsbenchmark uit
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Pre-push hook
|
||||
|
||||
Een git hook voert `cargo fmt --check`, `cargo clippy -- -D warnings`, en `cargo test` uit voor elke push. Schakel het één keer in:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Build Probleemoplossing (OpenSSL fouten op Linux)
|
||||
|
||||
Als je een `openssl-sys` build fout tegenkomt, synchroniseer afhankelijkheden en compileer opnieuw met de repository's lockfile:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw is geconfigureerd om `rustls` te gebruiken voor HTTP/TLS afhankelijkheden; `--locked` houdt de transitieve grafiek deterministisch in schone omgevingen.
|
||||
|
||||
Om de hook over te slaan wanneer je een snelle push nodig hebt tijdens ontwikkeling:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Samenwerking & Docs
|
||||
|
||||
Begin met de documentatie hub voor een taak-gebaseerde kaart:
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # Pokazuje wersję i informacje o build
|
||||
|
||||
Zobacz [Referencje Komend](docs/commands-reference.md) dla pełnych opcji i przykładów.
|
||||
|
||||
## Architektura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Kanały (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Orchestrator Agent │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Routing │ │ Kontekst │ │ Wykonanie │ │
|
||||
│ │ Wiadomość │ │ Pamięć │ │ Narzędzie │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Dostawcy │ │ Pamięć │ │ Narzędzia │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Kluczowe zasady:**
|
||||
|
||||
- Wszystko jest **trait** — dostawcy, kanały, narzędzia, pamięć, tunele
|
||||
- Kanały wywołują orchestrator; orchestrator wywołuje dostawców + narzędzia
|
||||
- System pamięci zarządza kontekstem konwersacji (markdown, SQLite, lub brak)
|
||||
- Runtime abstrahuje wykonanie kodu (natywny lub Docker)
|
||||
- Brak blokady dostawcy — zamieniaj Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama bez zmian kodu
|
||||
|
||||
Zobacz [dokumentację architektury](docs/architecture.svg) dla szczegółowych diagramów i szczegółów implementacji.
|
||||
|
||||
## Przykłady
|
||||
|
||||
### Bot Telegram
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Twój Telegram user ID
|
||||
```
|
||||
|
||||
Uruchom daemon + agent, a następnie wyślij wiadomość do swojego bota na Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
Cześć! Czy mógłbyś pomóc mi napisać skrypt Python?
|
||||
```
|
||||
|
||||
Bot odpowiada kodem wygenerowanym przez AI, wykonuje narzędzia jeśli wymagane i utrzymuje kontekst konwersacji.
|
||||
|
||||
### Matrix (szyfrowanie end-to-end)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Zaproś `@zeroclaw:matrix.org` do zaszyfrowanego pokoju, a bot odpowie z pełnym szyfrowaniem. Zobacz [Przewodnik Matrix E2EE](docs/matrix-e2ee-guide.md) dla konfiguracji weryfikacji urządzenia.
|
||||
|
||||
### Multi-Dostawca
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Failover przy błędzie dostawcy
|
||||
```
|
||||
|
||||
Jeśli Anthropic zawiedzie lub ma rate-limit, orchestrator automatycznie przełącza się na OpenAI.
|
||||
|
||||
### Własna Pamięć
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Automatyczne czyszczenie po 90 dniach
|
||||
```
|
||||
|
||||
Lub użyj Markdown dla przechowywania czytelnego dla ludzi:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Zobacz [Referencje Konfiguracji](docs/config-reference.md#memory) dla wszystkich opcji pamięci.
|
||||
|
||||
## Wsparcie Dostawców
|
||||
|
||||
| Dostawca | Status | API Key | Przykładowe Modele |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Stabilny | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Stabilny | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Stabilny | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Stabilny | N/A (lokalny) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Stabilny | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Stabilny | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Planowany | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Planowany | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Własne Endpointy
|
||||
|
||||
ZeroClaw wspiera endpointy kompatybilne z OpenAI:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Przykład: użyj [LiteLLM](https://github.com/BerriAI/litellm) jako proxy aby uzyskać dostęp do każdego LLM przez interfejs OpenAI.
|
||||
|
||||
Zobacz [Referencje Dostawców](docs/providers-reference.md) dla pełnych szczegółów konfiguracji.
|
||||
|
||||
## Wsparcie Kanałów
|
||||
|
||||
| Kanał | Status | Uwierzytelnianie | Uwagi |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Stabilny | Bot Token | Pełne wsparcie w tym pliki, obrazy, przyciski inline |
|
||||
| **Matrix** | ✅ Stabilny | Hasło lub Token | Wsparcie E2EE z weryfikacją urządzenia |
|
||||
| **Slack** | 🚧 Planowany | OAuth lub Bot Token | Wymaga dostępu do workspace |
|
||||
| **Discord** | 🚧 Planowany | Bot Token | Wymaga uprawnień guild |
|
||||
| **WhatsApp** | 🚧 Planowany | Twilio lub oficjalne API | Wymaga konta business |
|
||||
| **CLI** | ✅ Stabilny | Brak | Bezpośredni interfejs konwersacyjny |
|
||||
| **Web** | 🚧 Planowany | API Key lub OAuth | Interfejs czatu oparty na przeglądarce |
|
||||
|
||||
Zobacz [Referencje Kanałów](docs/channels-reference.md) dla pełnych instrukcji konfiguracji.
|
||||
|
||||
## Wsparcie Narzędzi
|
||||
|
||||
ZeroClaw dostarcza wbudowane narzędzia do wykonania kodu, dostępu do systemu plików i pobierania web:
|
||||
|
||||
| Narzędzie | Opis | Wymagany Runtime |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Wykonuje komendy shell | Natywny lub Docker |
|
||||
| **python** | Wykonuje skrypty Python | Python 3.8+ (natywny) lub Docker |
|
||||
| **javascript** | Wykonuje kod Node.js | Node.js 18+ (natywny) lub Docker |
|
||||
| **filesystem_read** | Odczytuje pliki | Natywny lub Docker |
|
||||
| **filesystem_write** | Zapisuje pliki | Natywny lub Docker |
|
||||
| **web_fetch** | Pobiera treści web | Natywny lub Docker |
|
||||
|
||||
### Bezpieczeństwo Wykonania
|
||||
|
||||
- **Natywny Runtime** — działa jako proces użytkownika daemon, pełny dostęp do systemu plików
|
||||
- **Docker Runtime** — pełna izolacja kontenera, oddzielne systemy plików i sieci
|
||||
|
||||
Skonfiguruj politykę wykonania w `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Jawna lista dozwolona
|
||||
```
|
||||
|
||||
Zobacz [Referencje Konfiguracji](docs/config-reference.md#runtime) dla pełnych opcji bezpieczeństwa.
|
||||
|
||||
## Wdrażanie
|
||||
|
||||
### Lokalne Wdrażanie (Rozwój)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Serwerowe Wdrażanie (Produkcja)
|
||||
|
||||
Użyj systemd do zarządzania daemon i agent jako usługi:
|
||||
|
||||
```bash
|
||||
# Zainstaluj binarium
|
||||
cargo install --path . --locked
|
||||
|
||||
# Skonfiguruj workspace
|
||||
zeroclaw init
|
||||
|
||||
# Utwórz pliki usług systemd
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Włącz i uruchom usługi
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Zweryfikuj status
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Zobacz [Przewodnik Wdrażania Sieciowego](docs/network-deployment.md) dla pełnych instrukcji wdrażania produkcyjnego.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Zbuduj obraz
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Uruchom kontener
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Zobacz [`Dockerfile`](Dockerfile) dla szczegółów budowania i opcji konfiguracji.
|
||||
|
||||
### Sprzęt Edge
|
||||
|
||||
ZeroClaw jest zaprojektowany do działania na sprzęcie niskiego poboru mocy:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, pojedynczy rdzeń ARMv8, < $5 koszt sprzętu
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, wielordzeniowy, idealny dla równoczesnych obciążeń
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, czterordzeniowy ARMv8, ultra-niski koszt
|
||||
- **SBC x86 (Intel N100)** — 4-8 GB RAM, szybkie buildy, natywne wsparcie Docker
|
||||
|
||||
Zobacz [Przewodnik Sprzętowy](docs/hardware/README.md) dla instrukcji konfiguracji specyficznych dla urządzenia.
|
||||
|
||||
## Tunneling (Publiczna Ekspozycja)
|
||||
|
||||
Exponuj swoj lokalny daemon ZeroClaw do sieci publicznej przez bezpieczne tunele:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Wspierani dostawcy tunnel:
|
||||
|
||||
- **Cloudflare Tunnel** — darmowy HTTPS, brak ekspozycji portów, wsparcie multi-domenowe
|
||||
- **Ngrok** — szybka konfiguracja, własne domeny (plan płatny)
|
||||
- **Tailscale** — prywatna sieć mesh, brak publicznego portu
|
||||
|
||||
Zobacz [Referencje Konfiguracji](docs/config-reference.md#tunnel) dla pełnych opcji konfiguracji.
|
||||
|
||||
## Bezpieczeństwo
|
||||
|
||||
ZeroClaw implementuje wiele warstw bezpieczeństwa:
|
||||
|
||||
### Parowanie
|
||||
|
||||
Daemon generuje sekret parowania przy pierwszym uruchomieniu przechowywany w `~/.zeroclaw/workspace/.pairing`. Klienci (agent, CLI) muszą przedstawić ten sekret aby się połączyć.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Generuje nowy sekret i unieważnia stary
|
||||
```
|
||||
|
||||
### Sandbox
|
||||
|
||||
- **Docker Runtime** — pełna izolacja kontenera z oddzielnymi systemami plików i sieciami
|
||||
- **Natywny Runtime** — działa jako proces użytkownika, domyślnie ograniczony do workspace
|
||||
|
||||
### Listy Dozwolone
|
||||
|
||||
Kanały mogą ograniczać dostęp po ID użytkownika:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Jawna lista dozwolona
|
||||
```
|
||||
|
||||
### Szyfrowanie
|
||||
|
||||
- **Matrix E2EE** — pełne szyfrowanie end-to-end z weryfikacją urządzenia
|
||||
- **Transport TLS** — cały ruch API i tunnel używa HTTPS/TLS
|
||||
|
||||
Zobacz [Dokumentację Bezpieczeństwa](docs/security/README.md) dla pełnych polityk i praktyk.
|
||||
|
||||
## Obserwowalność
|
||||
|
||||
ZeroClaw loguje do `~/.zeroclaw/workspace/logs/` domyślnie. Logi są przechowywane po komponentach:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Logi daemon (startup, żądania API, błędy)
|
||||
├── agent.log # Logi agent (routing wiadomości, wykonanie narzędzi)
|
||||
├── telegram.log # Logi specyficzne dla kanału (jeśli włączone)
|
||||
└── matrix.log # Logi specyficzne dla kanału (jeśli włączone)
|
||||
```
|
||||
|
||||
### Konfiguracja Logowania
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # Dla rotacji opartej na rozmiarze
|
||||
retention_days = 30 # Automatyczne czyszczenie po N dniach
|
||||
```
|
||||
|
||||
Zobacz [Referencje Konfiguracji](docs/config-reference.md#logging) dla wszystkich opcji logowania.
|
||||
|
||||
### Metryki (Planowane)
|
||||
|
||||
Wsparcie metryk Prometheus dla monitoringu produkcyjnego wkrótce. Śledzenie w [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Umiejętności
|
||||
|
||||
ZeroClaw wspiera własne umiejętności — wielokrotnego użytku moduły rozszerzające możliwości systemu.
|
||||
|
||||
### Definicja Umiejętności
|
||||
|
||||
Umiejętności są przechowywane w `~/.zeroclaw/workspace/skills/<skill-name>/` z tą strukturą:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Metadane umiejętności (nazwa, opis, zależności)
|
||||
├── prompt.md # Prompt systemowy dla AI
|
||||
└── tools/ # Opcjonalne własne narzędzia
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Przykład Umiejętności
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Szuka w web i podsumowuje wyniki"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
Jesteś asystentem badawczym. Kiedy proszą o zbadanie czegoś:
|
||||
|
||||
1. Użyj web_fetch aby pobrać treść
|
||||
2. Podsumuj wyniki w łatwym do czytania formacie
|
||||
3. Zacytuj źródła z URL-ami
|
||||
```
|
||||
|
||||
### Użycie Umiejętności
|
||||
|
||||
Umiejętności są automatycznie ładowane przy starcie agenta. Odwołuj się do nich po nazwie w konwersacjach:
|
||||
|
||||
```
|
||||
Użytkownik: Użyj umiejętności web-research aby znaleźć najnowsze wiadomości AI
|
||||
Bot: [ładuje umiejętność web-research, wykonuje web_fetch, podsumowuje wyniki]
|
||||
```
|
||||
|
||||
Zobacz sekcję [Umiejętności](#umiejętności) dla pełnych instrukcji tworzenia umiejętności.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw wspiera [Open Skills](https://github.com/openagents-com/open-skills) — modułowy i agnostyczny względem dostawcy system do rozszerzania możliwości agentów AI.
|
||||
|
||||
### Włącz Open Skills
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # opcjonalne
|
||||
```
|
||||
|
||||
Możesz też nadpisać w runtime używając `ZEROCLAW_OPEN_SKILLS_ENABLED` i `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Rozwój
|
||||
|
||||
```bash
|
||||
cargo build # Build deweloperski
|
||||
cargo build --release # Build release (codegen-units=1, działa na wszystkich urządzeniach w tym Raspberry Pi)
|
||||
cargo build --profile release-fast # Szybszy build (codegen-units=8, wymaga 16 GB+ RAM)
|
||||
cargo test # Uruchom pełny zestaw testów
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Formatowanie
|
||||
|
||||
# Uruchom benchmark porównawczy SQLite vs Markdown
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Hook pre-push
|
||||
|
||||
Hook git uruchamia `cargo fmt --check`, `cargo clippy -- -D warnings`, i `cargo test` przed każdym push. Włącz go raz:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Rozwiązywanie Problemów Build (błędy OpenSSL na Linux)
|
||||
|
||||
Jeśli napotkasz błąd build `openssl-sys`, zsynchronizuj zależności i przekompiluj z lockfile repozytorium:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw jest skonfigurowany do używania `rustls` dla zależności HTTP/TLS; `--locked` utrzymuje graf przechodni deterministyczny w czystych środowiskach.
|
||||
|
||||
Aby pominąć hook gdy potrzebujesz szybkiego push podczas rozwoju:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Współpraca i Docs
|
||||
|
||||
Zacznij od centrum dokumentacji dla mapy opartej na zadaniach:
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # Mostra versão e informações de build
|
||||
|
||||
Veja [Referência de Comandos](docs/commands-reference.md) para opções e exemplos completos.
|
||||
|
||||
## Arquitetura
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Canais (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Orquestrador Agent │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Roteamento │ │ Contexto │ │ Execução │ │
|
||||
│ │ Mensagem │ │ Memória │ │ Ferramenta │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Provedores │ │ Memória │ │ Ferramentas │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Princípios chave:**
|
||||
|
||||
- Tudo é um **trait** — provedores, canais, ferramentas, memória, túneis
|
||||
- Canais chamam o orquestrador; o orquestrador chama provedores + ferramentas
|
||||
- O sistema de memória gerencia contexto conversacional (markdown, SQLite, ou nenhum)
|
||||
- O runtime abstrai a execução de código (nativo ou Docker)
|
||||
- Sem lock-in de provedor — troque Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama sem mudanças de código
|
||||
|
||||
Veja [documentação de arquitetura](docs/architecture.svg) para diagramas detalhados e detalhes de implementação.
|
||||
|
||||
## Exemplos
|
||||
|
||||
### Bot do Telegram
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Seu ID de usuário do Telegram
|
||||
```
|
||||
|
||||
Inicie o daemon + agent, então envie uma mensagem para seu bot no Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
Olá! Você poderia me ajudar a escrever um script Python?
|
||||
```
|
||||
|
||||
O bot responde com código gerado por AI, executa ferramentas se solicitado, e mantém o contexto de conversação.
|
||||
|
||||
### Matrix (criptografia ponta a ponta)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Convide `@zeroclaw:matrix.org` para uma sala criptografada, e o bot responderá com criptografia completa. Veja [Guia Matrix E2EE](docs/matrix-e2ee-guide.md) para configuração de verificação de dispositivo.
|
||||
|
||||
### Multi-Provedor
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Failover em erro de provedor
|
||||
```
|
||||
|
||||
Se Anthropic falhar ou tiver rate-limit, o orquestrador faz failover automaticamente para OpenAI.
|
||||
|
||||
### Memória Personalizada
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Purga automática após 90 dias
|
||||
```
|
||||
|
||||
Ou use Markdown para armazenamento legível por humanos:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Veja [Referência de Configuração](docs/config-reference.md#memory) para todas as opções de memória.
|
||||
|
||||
## Suporte de Provedor
|
||||
|
||||
| Provedor | Status | API Key | Modelos de Exemplo |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Estável | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Estável | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Estável | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Estável | N/A (local) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Estável | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Estável | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Planejado | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Planejado | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Endpoints Personalizados
|
||||
|
||||
ZeroClaw suporta endpoints compatíveis com OpenAI:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Exemplo: use [LiteLLM](https://github.com/BerriAI/litellm) como proxy para acessar qualquer LLM via interface OpenAI.
|
||||
|
||||
Veja [Referência de Provedores](docs/providers-reference.md) para detalhes de configuração completos.
|
||||
|
||||
## Suporte de Canal
|
||||
|
||||
| Canal | Status | Autenticação | Notas |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Estável | Bot Token | Suporte completo incluindo arquivos, imagens, botões inline |
|
||||
| **Matrix** | ✅ Estável | Senha ou Token | Suporte E2EE com verificação de dispositivo |
|
||||
| **Slack** | 🚧 Planejado | OAuth ou Bot Token | Requer acesso ao workspace |
|
||||
| **Discord** | 🚧 Planejado | Bot Token | Requer permissões de guild |
|
||||
| **WhatsApp** | 🚧 Planejado | Twilio ou API oficial | Requer conta business |
|
||||
| **CLI** | ✅ Estável | Nenhum | Interface conversacional direta |
|
||||
| **Web** | 🚧 Planejado | API Key ou OAuth | Interface de chat baseada em navegador |
|
||||
|
||||
Veja [Referência de Canais](docs/channels-reference.md) para instruções de configuração completas.
|
||||
|
||||
## Suporte de Ferramentas
|
||||
|
||||
ZeroClaw fornece ferramentas integradas para execução de código, acesso ao sistema de arquivos e recuperação web:
|
||||
|
||||
| Ferramenta | Descrição | Runtime Requerido |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Executa comandos shell | Nativo ou Docker |
|
||||
| **python** | Executa scripts Python | Python 3.8+ (nativo) ou Docker |
|
||||
| **javascript** | Executa código Node.js | Node.js 18+ (nativo) ou Docker |
|
||||
| **filesystem_read** | Lê arquivos | Nativo ou Docker |
|
||||
| **filesystem_write** | Escreve arquivos | Nativo ou Docker |
|
||||
| **web_fetch** | Obtém conteúdo web | Nativo ou Docker |
|
||||
|
||||
### Segurança de Execução
|
||||
|
||||
- **Runtime Nativo** — roda como processo de usuário do daemon, acesso completo ao sistema de arquivos
|
||||
- **Runtime Docker** — isolamento completo de container, sistemas de arquivos e redes separados
|
||||
|
||||
Configure a política de execução em `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Lista de permissão explícita
|
||||
```
|
||||
|
||||
Veja [Referência de Configuração](docs/config-reference.md#runtime) para opções de segurança completas.
|
||||
|
||||
## Implantação
|
||||
|
||||
### Implantação Local (Desenvolvimento)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Implantação em Servidor (Produção)
|
||||
|
||||
Use systemd para gerenciar o daemon e agent como serviços:
|
||||
|
||||
```bash
|
||||
# Instale o binário
|
||||
cargo install --path . --locked
|
||||
|
||||
# Configure o workspace
|
||||
zeroclaw init
|
||||
|
||||
# Crie arquivos de serviço systemd
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Habilite e inicie os serviços
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Verifique o status
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Veja [Guia de Implantação de Rede](docs/network-deployment.md) para instruções completas de implantação em produção.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# Compile a imagem
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Execute o container
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Veja [`Dockerfile`](Dockerfile) para detalhes de build e opções de configuração.
|
||||
|
||||
### Hardware Edge
|
||||
|
||||
ZeroClaw é projetado para rodar em hardware de baixo consumo:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, núcleo ARMv8 único, < $5 custo de hardware
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, multi-núcleo, ideal para workloads concorrentes
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, quad-core ARMv8, custo ultra-baixo
|
||||
- **SBCs x86 (Intel N100)** — 4-8 GB RAM, builds rápidos, suporte Docker nativo
|
||||
|
||||
Veja [Guia de Hardware](docs/hardware/README.md) para instruções de configuração específicas por dispositivo.
|
||||
|
||||
## Tunneling (Exposição Pública)
|
||||
|
||||
Exponha seu daemon ZeroClaw local à rede pública via túneis seguros:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Provedores de tunnel suportados:
|
||||
|
||||
- **Cloudflare Tunnel** — HTTPS grátis, sem exposição de portas, suporte multi-domínio
|
||||
- **Ngrok** — configuração rápida, domínios personalizados (plano pago)
|
||||
- **Tailscale** — rede mesh privada, sem porta pública
|
||||
|
||||
Veja [Referência de Configuração](docs/config-reference.md#tunnel) para opções de configuração completas.
|
||||
|
||||
## Segurança
|
||||
|
||||
ZeroClaw implementa múltiplas camadas de segurança:
|
||||
|
||||
### Emparelhamento
|
||||
|
||||
O daemon gera um segredo de emparelhamento no primeiro início armazenado em `~/.zeroclaw/workspace/.pairing`. Clientes (agent, CLI) devem apresentar este segredo para conectar.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Gera um novo segredo e invalida o anterior
|
||||
```
|
||||
|
||||
### Sandboxing
|
||||
|
||||
- **Runtime Docker** — isolamento completo de container com sistemas de arquivos e redes separados
|
||||
- **Runtime Nativo** — roda como processo de usuário, com escopo de workspace por padrão
|
||||
|
||||
### Listas de Permissão
|
||||
|
||||
Canais podem restringir acesso por ID de usuário:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Lista de permissão explícita
|
||||
```
|
||||
|
||||
### Criptografia
|
||||
|
||||
- **Matrix E2EE** — criptografia ponta a ponta completa com verificação de dispositivo
|
||||
- **Transporte TLS** — todo o tráfego de API e tunnel usa HTTPS/TLS
|
||||
|
||||
Veja [Documentação de Segurança](docs/security/README.md) para políticas e práticas completas.
|
||||
|
||||
## Observabilidade
|
||||
|
||||
ZeroClaw registra logs em `~/.zeroclaw/workspace/logs/` por padrão. Os logs são armazenados por componente:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Logs do daemon (início, requisições API, erros)
|
||||
├── agent.log # Logs do agent (roteamento de mensagens, execução de ferramentas)
|
||||
├── telegram.log # Logs específicos do canal (se habilitado)
|
||||
└── matrix.log # Logs específicos do canal (se habilitado)
|
||||
```
|
||||
|
||||
### Configuração de Logging
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # Para rotação baseada em tamanho
|
||||
retention_days = 30 # Purga automática após N dias
|
||||
```
|
||||
|
||||
Veja [Referência de Configuração](docs/config-reference.md#logging) para todas as opções de logging.
|
||||
|
||||
### Métricas (Planejado)
|
||||
|
||||
Suporte a métricas Prometheus para monitoramento em produção em breve. Rastreamento em [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Habilidades (Skills)
|
||||
|
||||
ZeroClaw suporta habilidades personalizadas — módulos reutilizáveis que estendem as capacidades do sistema.
|
||||
|
||||
### Definição de Habilidade
|
||||
|
||||
Habilidades são armazenadas em `~/.zeroclaw/workspace/skills/<skill-name>/` com esta estrutura:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Metadados da habilidade (nome, descrição, dependências)
|
||||
├── prompt.md # Prompt de sistema para a AI
|
||||
└── tools/ # Ferramentas personalizadas opcionais
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Exemplo de Habilidade
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Pesquisa na web e resume resultados"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
Você é um assistente de pesquisa. Quando pedirem para pesquisar algo:
|
||||
|
||||
1. Use web_fetch para obter o conteúdo
|
||||
2. Resuma os resultados em um formato fácil de ler
|
||||
3. Cite as fontes com URLs
|
||||
```
|
||||
|
||||
### Uso de Habilidades
|
||||
|
||||
Habilidades são carregadas automaticamente no início do agent. Referencie-as por nome em conversas:
|
||||
|
||||
```
|
||||
Usuário: Use a habilidade web-research para encontrar as últimas notícias de AI
|
||||
Bot: [carrega a habilidade web-research, executa web_fetch, resume resultados]
|
||||
```
|
||||
|
||||
Veja seção [Habilidades (Skills)](#habilidades-skills) para instruções completas de criação de habilidades.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw suporta [Open Skills](https://github.com/openagents-com/open-skills) — um sistema modular e agnóstico de provedores para estender capacidades de agentes AI.
|
||||
|
||||
### Habilitar Open Skills
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # opcional
|
||||
```
|
||||
|
||||
Você também pode sobrescrever em runtime com `ZEROCLAW_OPEN_SKILLS_ENABLED` e `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Desenvolvimento
|
||||
|
||||
```bash
|
||||
cargo build # Build de desenvolvimento
|
||||
cargo build --release # Build release (codegen-units=1, funciona em todos os dispositivos incluindo Raspberry Pi)
|
||||
cargo build --profile release-fast # Build mais rápido (codegen-units=8, requer 16 GB+ RAM)
|
||||
cargo test # Executa o suite de testes completo
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Formato
|
||||
|
||||
# Executa o benchmark de comparação SQLite vs Markdown
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Hook pre-push
|
||||
|
||||
Um hook de git executa `cargo fmt --check`, `cargo clippy -- -D warnings`, e `cargo test` antes de cada push. Ative-o uma vez:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Solução de Problemas de Build (erros OpenSSL no Linux)
|
||||
|
||||
Se você encontrar um erro de build `openssl-sys`, sincronize dependências e recompile com o lockfile do repositório:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw está configurado para usar `rustls` para dependências HTTP/TLS; `--locked` mantém o grafo transitivo determinístico em ambientes limpios.
|
||||
|
||||
Para pular o hook quando precisar de um push rápido durante desenvolvimento:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Colaboração e Docs
|
||||
|
||||
Comece com o hub de documentação para um mapa baseado em tarefas:
|
||||
|
||||
+1
-99
@@ -53,7 +53,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="install.sh">Установка в 1 клик</a> |
|
||||
<a href="https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh">Установка в 1 клик</a> |
|
||||
<a href="docs/setup-guides/README.md">Быстрый старт</a> |
|
||||
<a href="docs/README.ru.md">Хаб документации</a> |
|
||||
<a href="docs/SUMMARY.md">TOC docs</a>
|
||||
@@ -218,104 +218,6 @@ zeroclaw agent --provider openai-codex --auth-profile openai-codex:work -m "hell
|
||||
zeroclaw agent --provider anthropic -m "hello"
|
||||
```
|
||||
|
||||
## Архитектура
|
||||
|
||||
Каждая подсистема — это **Trait**: меняйте реализации через конфигурацию, без изменения кода.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/architecture.svg" alt="Архитектура ZeroClaw" width="900" />
|
||||
</p>
|
||||
|
||||
| Подсистема | Trait | Встроенные реализации | Расширение |
|
||||
|-----------|-------|---------------------|------------|
|
||||
| **AI-модели** | `Provider` | Каталог через `zeroclaw providers` (сейчас 28 встроенных + алиасы, плюс пользовательские endpoint) | `custom:https://your-api.com` (OpenAI-совместимый) или `anthropic-custom:https://your-api.com` |
|
||||
| **Каналы** | `Channel` | CLI, Telegram, Discord, Slack, Mattermost, iMessage, Matrix, Signal, WhatsApp, Linq, Email, IRC, Lark, DingTalk, QQ, Webhook | Любой messaging API |
|
||||
| **Память** | `Memory` | SQLite гибридный поиск, PostgreSQL-бэкенд, Lucid-мост, Markdown-файлы, явный `none`-бэкенд, snapshot/hydrate, опциональный кэш ответов | Любой persistence-бэкенд |
|
||||
| **Инструменты** | `Tool` | shell/file/memory, cron/schedule, git, pushover, browser, http_request, screenshot/image_info, composio (opt-in), delegate, аппаратные инструменты | Любая функциональность |
|
||||
| **Наблюдаемость** | `Observer` | Noop, Log, Multi | Prometheus, OTel |
|
||||
| **Runtime** | `RuntimeAdapter` | Native, Docker (sandbox) | Через adapter; неподдерживаемые kind завершаются с ошибкой |
|
||||
| **Безопасность** | `SecurityPolicy` | Gateway pairing, sandbox, allowlist, rate limits, scoping файловой системы, шифрование секретов | — |
|
||||
| **Идентификация** | `IdentityConfig` | OpenClaw (markdown), AIEOS v1.1 (JSON) | Любой формат идентификации |
|
||||
| **Туннели** | `Tunnel` | None, Cloudflare, Tailscale, ngrok, Custom | Любой tunnel-бинарник |
|
||||
| **Heartbeat** | Engine | HEARTBEAT.md — периодические задачи | — |
|
||||
| **Навыки** | Loader | TOML-манифесты + SKILL.md-инструкции | Пакеты навыков сообщества |
|
||||
| **Интеграции** | Registry | 70+ интеграций в 9 категориях | Плагинная система |
|
||||
|
||||
### Поддержка runtime (текущая)
|
||||
|
||||
- ✅ Поддерживается сейчас: `runtime.kind = "native"` или `runtime.kind = "docker"`
|
||||
- 🚧 Запланировано, но ещё не реализовано: WASM / edge-runtime
|
||||
|
||||
При указании неподдерживаемого `runtime.kind` ZeroClaw завершается с явной ошибкой, а не молча откатывается к native.
|
||||
|
||||
### Система памяти (полнофункциональный поисковый движок)
|
||||
|
||||
Полностью собственная реализация, ноль внешних зависимостей — без Pinecone, Elasticsearch, LangChain:
|
||||
|
||||
| Уровень | Реализация |
|
||||
|---------|-----------|
|
||||
| **Векторная БД** | Embeddings хранятся как BLOB в SQLite, поиск по косинусному сходству |
|
||||
| **Поиск по ключевым словам** | Виртуальные таблицы FTS5 со скорингом BM25 |
|
||||
| **Гибридное слияние** | Пользовательская взвешенная функция слияния (`vector.rs`) |
|
||||
| **Embeddings** | Trait `EmbeddingProvider` — OpenAI, пользовательский URL или noop |
|
||||
| **Чанкинг** | Построчный Markdown-чанкер с сохранением заголовков |
|
||||
| **Кэширование** | Таблица `embedding_cache` в SQLite с LRU-вытеснением |
|
||||
| **Безопасная переиндексация** | Атомарная перестройка FTS5 + повторное встраивание отсутствующих векторов |
|
||||
|
||||
Agent автоматически вспоминает, сохраняет и управляет памятью через инструменты.
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
backend = "sqlite" # "sqlite", "lucid", "postgres", "markdown", "none"
|
||||
auto_save = true
|
||||
embedding_provider = "none" # "none", "openai", "custom:https://..."
|
||||
vector_weight = 0.7
|
||||
keyword_weight = 0.3
|
||||
```
|
||||
|
||||
## Важные security-дефолты
|
||||
|
||||
- Gateway по умолчанию: `127.0.0.1:42617`
|
||||
- Pairing обязателен по умолчанию: `require_pairing = true`
|
||||
- Публичный bind запрещён по умолчанию: `allow_public_bind = false`
|
||||
- Семантика allowlist каналов:
|
||||
- `[]` => deny-by-default
|
||||
- `["*"]` => allow all (используйте осознанно)
|
||||
|
||||
## Пример конфигурации
|
||||
|
||||
```toml
|
||||
api_key = "sk-..."
|
||||
default_provider = "openrouter"
|
||||
default_model = "anthropic/claude-sonnet-4-6"
|
||||
default_temperature = 0.7
|
||||
|
||||
[memory]
|
||||
backend = "sqlite"
|
||||
auto_save = true
|
||||
embedding_provider = "none"
|
||||
|
||||
[gateway]
|
||||
host = "127.0.0.1"
|
||||
port = 42617
|
||||
require_pairing = true
|
||||
allow_public_bind = false
|
||||
```
|
||||
|
||||
## Навигация по документации
|
||||
|
||||
- Хаб документации (English): [`docs/README.md`](docs/README.md)
|
||||
- Единый TOC docs: [`docs/SUMMARY.md`](docs/SUMMARY.md)
|
||||
- Хаб документации (Русский): [`docs/README.ru.md`](docs/README.ru.md)
|
||||
- Справочник команд: [`docs/reference/cli/commands-reference.md`](docs/reference/cli/commands-reference.md)
|
||||
- Справочник конфигурации: [`docs/reference/api/config-reference.md`](docs/reference/api/config-reference.md)
|
||||
- Справочник providers: [`docs/reference/api/providers-reference.md`](docs/reference/api/providers-reference.md)
|
||||
- Справочник channels: [`docs/reference/api/channels-reference.md`](docs/reference/api/channels-reference.md)
|
||||
- Операционный runbook: [`docs/ops/operations-runbook.md`](docs/ops/operations-runbook.md)
|
||||
- Устранение неполадок: [`docs/ops/troubleshooting.md`](docs/ops/troubleshooting.md)
|
||||
- Инвентарь и классификация docs: [`docs/maintainers/docs-inventory.md`](docs/maintainers/docs-inventory.md)
|
||||
- Снимок triage проекта: [`docs/maintainers/project-triage-snapshot-2026-02-18.md`](docs/maintainers/project-triage-snapshot-2026-02-18.md)
|
||||
|
||||
## Вклад и лицензия
|
||||
|
||||
- Contribution guide: [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # Nagpapakita ng version at build info
|
||||
|
||||
Tingnan ang [Commands Reference](docs/commands-reference.md) para sa buong options at examples.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Channels (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Custom │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Agent Orchestrator │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Message │ │ Context │ │ Tool │ │
|
||||
│ │ Routing │ │ Memory │ │ Execution │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Providers │ │ Memory │ │ Tools │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ None │ │ Web Fetch │
|
||||
│ Ollama │ │ Custom │ │ Custom │
|
||||
│ Custom │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Runtime (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Mga pangunahing prinsipyo:**
|
||||
|
||||
- Ang lahat ay isang **trait** — providers, channels, tools, memory, tunnels
|
||||
- Ang mga channel ay tumatawag sa orchestrator; ang orchestrator ay tumatawag sa providers + tools
|
||||
- Ang memory system ay nagmamaneho ng conversation context (markdown, SQLite, o none)
|
||||
- Ang runtime ay nag-a-abstract ng code execution (native o Docker)
|
||||
- Walang provider lock-in — i-swap ang Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama nang walang code changes
|
||||
|
||||
Tingnan ang [architecture documentation](docs/architecture.svg) para sa mga detalyadong diagram at implementation details.
|
||||
|
||||
## Mga Halimbawa
|
||||
|
||||
### Telegram Bot
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Ang iyong Telegram user ID
|
||||
```
|
||||
|
||||
Simulan ang daemon + agent, pagkatapos ay magpadala ng mensahe sa iyong bot sa Telegram:
|
||||
|
||||
```
|
||||
/start
|
||||
Hello! Could you help me write a Python script?
|
||||
```
|
||||
|
||||
Ang bot ay tumutugon gamit ang AI-generated code, nagpapatupad ng mga tool kung hiniling, at nagpapanatili ng conversation context.
|
||||
|
||||
### Matrix (end-to-end encryption)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Imbitahan ang `@zeroclaw:matrix.org` sa isang encrypted room, at ang bot ay tutugon gamit ang full encryption. Tingnan ang [Matrix E2EE Guide](docs/matrix-e2ee-guide.md) para sa device verification setup.
|
||||
|
||||
### Multi-Provider
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Failover on provider error
|
||||
```
|
||||
|
||||
Kung ang Anthropic ay mabigo o ma-rate-limit, ang orchestrator ay awtomatikong mag-failover sa OpenAI.
|
||||
|
||||
### Custom Memory
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # Automatic purge after 90 days
|
||||
```
|
||||
|
||||
O gamitin ang Markdown para sa human-readable storage:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Tingnan ang [Configuration Reference](docs/config-reference.md#memory) para sa lahat ng memory options.
|
||||
|
||||
## Provider Support
|
||||
|
||||
| Provider | Status | API Key | Example Models |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Stable | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Stable | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Stable | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Stable | N/A (local) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Stable | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Stable | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Planned | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Planned | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Custom Endpoints
|
||||
|
||||
Sinusuportahan ng ZeroClaw ang OpenAI-compatible endpoints:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Halimbawa: gamitin ang [LiteLLM](https://github.com/BerriAI/litellm) bilang proxy para ma-access ang anumang LLM sa pamamagitan ng OpenAI interface.
|
||||
|
||||
Tingnan ang [Providers Reference](docs/providers-reference.md) para sa kumpletong configuration details.
|
||||
|
||||
## Channel Support
|
||||
|
||||
| Channel | Status | Authentication | Notes |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Stable | Bot Token | Full support including files, images, inline buttons |
|
||||
| **Matrix** | ✅ Stable | Password or Token | E2EE support with device verification |
|
||||
| **Slack** | 🚧 Planned | OAuth or Bot Token | Requires workspace access |
|
||||
| **Discord** | 🚧 Planned | Bot Token | Requires guild permissions |
|
||||
| **WhatsApp** | 🚧 Planned | Twilio or official API | Requires business account |
|
||||
| **CLI** | ✅ Stable | None | Direct conversational interface |
|
||||
| **Web** | 🚧 Planned | API Key or OAuth | Browser-based chat interface |
|
||||
|
||||
Tingnan ang [Channels Reference](docs/channels-reference.md) para sa kumpletong configuration instructions.
|
||||
|
||||
## Tool Support
|
||||
|
||||
Nagbibigay ang ZeroClaw ng built-in tools para sa code execution, filesystem access, at web retrieval:
|
||||
|
||||
| Tool | Description | Required Runtime |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Executes shell commands | Native or Docker |
|
||||
| **python** | Executes Python scripts | Python 3.8+ (native) or Docker |
|
||||
| **javascript** | Executes Node.js code | Node.js 18+ (native) or Docker |
|
||||
| **filesystem_read** | Reads files | Native or Docker |
|
||||
| **filesystem_write** | Writes files | Native or Docker |
|
||||
| **web_fetch** | Fetches web content | Native or Docker |
|
||||
|
||||
### Execution Security
|
||||
|
||||
- **Native Runtime** — runs as daemon's user process, full filesystem access
|
||||
- **Docker Runtime** — full container isolation, separate filesystems and networks
|
||||
|
||||
I-configure ang execution policy sa `config.toml`:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Explicit allowlist
|
||||
```
|
||||
|
||||
Tingnan ang [Configuration Reference](docs/config-reference.md#runtime) para sa kumpletong security options.
|
||||
|
||||
## Deployment
|
||||
|
||||
### Local Deployment (Development)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Server Deployment (Production)
|
||||
|
||||
Gamitin ang systemd para mamaneho ang daemon at agent bilang services:
|
||||
|
||||
```bash
|
||||
# I-install ang binary
|
||||
cargo install --path . --locked
|
||||
|
||||
# I-configure ang workspace
|
||||
zeroclaw init
|
||||
|
||||
# Gumawa ng systemd service files
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# I-enable at i-start ang services
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# I-verify ang status
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Tingnan ang [Network Deployment Guide](docs/network-deployment.md) para sa kumpletong production deployment instructions.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# I-build ang image
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# I-run ang container
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Tingnan ang [`Dockerfile`](Dockerfile) para sa build details at configuration options.
|
||||
|
||||
### Edge Hardware
|
||||
|
||||
Ang ZeroClaw ay dinisenyo para tumakbo sa low-power hardware:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, single ARMv8 core, < $5 hardware cost
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, multi-core, ideal for concurrent workloads
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, quad-core ARMv8, ultra-low cost
|
||||
- **x86 SBCs (Intel N100)** — 4-8 GB RAM, fast builds, native Docker support
|
||||
|
||||
Tingnan ang [Hardware Guide](docs/hardware/README.md) para sa device-specific setup instructions.
|
||||
|
||||
## Tunneling (Public Exposure)
|
||||
|
||||
I-expose ang iyong local ZeroClaw daemon sa public network sa pamamagitan ng secure tunnels:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Mga supported tunnel provider:
|
||||
|
||||
- **Cloudflare Tunnel** — free HTTPS, no port exposure, multi-domain support
|
||||
- **Ngrok** — quick setup, custom domains (paid plan)
|
||||
- **Tailscale** — private mesh network, no public port
|
||||
|
||||
Tingnan ang [Configuration Reference](docs/config-reference.md#tunnel) para sa kumpletong configuration options.
|
||||
|
||||
## Security
|
||||
|
||||
Nagpapatupad ang ZeroClaw ng maraming layer ng security:
|
||||
|
||||
### Pairing
|
||||
|
||||
Ang daemon ay nag-generate ng pairing secret sa unang launch na nakaimbak sa `~/.zeroclaw/workspace/.pairing`. Ang mga client (agent, CLI) ay dapat mag-present ng secret na ito para kumonekta.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Gagawa ng bagong secret at i-invalidate ang dati
|
||||
```
|
||||
|
||||
### Sandboxing
|
||||
|
||||
- **Docker Runtime** — full container isolation na may separate filesystems at networks
|
||||
- **Native Runtime** — runs as user process, scoped sa workspace by default
|
||||
|
||||
### Allowlists
|
||||
|
||||
Ang mga channel ay maaaring mag-limit ng access by user ID:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Explicit allowlist
|
||||
```
|
||||
|
||||
### Encryption
|
||||
|
||||
- **Matrix E2EE** — full end-to-end encryption with device verification
|
||||
- **TLS Transport** — all API and tunnel traffic uses HTTPS/TLS
|
||||
|
||||
Tingnan ang [Security Documentation](docs/security/README.md) para sa kumpletong policies at practices.
|
||||
|
||||
## Observability
|
||||
|
||||
Ang ZeroClaw ay naglo-log sa `~/.zeroclaw/workspace/logs/` by default. Ang mga log ay nakaimbak by component:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Daemon logs (startup, API requests, errors)
|
||||
├── agent.log # Agent logs (message routing, tool execution)
|
||||
├── telegram.log # Channel-specific logs (if enabled)
|
||||
└── matrix.log # Channel-specific logs (if enabled)
|
||||
```
|
||||
|
||||
### Logging Configuration
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # daily, hourly, size
|
||||
max_size_mb = 100 # For size-based rotation
|
||||
retention_days = 30 # Automatic purge after N days
|
||||
```
|
||||
|
||||
Tingnan ang [Configuration Reference](docs/config-reference.md#logging) para sa lahat ng logging options.
|
||||
|
||||
### Metrics (Planned)
|
||||
|
||||
Prometheus metrics support para sa production monitoring ay coming soon. Tracking sa [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234).
|
||||
|
||||
## Skills
|
||||
|
||||
Sinusuportahan ng ZeroClaw ang custom skills — reusable modules na nag-e-extend sa system capabilities.
|
||||
|
||||
### Skill Definition
|
||||
|
||||
Ang mga skill ay nakaimbak sa `~/.zeroclaw/workspace/skills/<skill-name>/` na may ganitong structure:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Skill metadata (name, description, dependencies)
|
||||
├── prompt.md # System prompt for the AI
|
||||
└── tools/ # Optional custom tools
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Skill Example
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Searches the web and summarizes results"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
You are a research assistant. When asked to research something:
|
||||
|
||||
1. Use web_fetch to retrieve content
|
||||
2. Summarize results in an easy-to-read format
|
||||
3. Cite sources with URLs
|
||||
```
|
||||
|
||||
### Skill Usage
|
||||
|
||||
Ang mga skill ay automatically loaded sa agent startup. I-reference ang mga ito by name sa conversations:
|
||||
|
||||
```
|
||||
User: Use the web-research skill to find the latest AI news
|
||||
Bot: [loads web-research skill, executes web_fetch, summarizes results]
|
||||
```
|
||||
|
||||
Tingnan ang [Skills](#skills) section para sa kumpletong skill creation instructions.
|
||||
|
||||
## Open Skills
|
||||
|
||||
Sinusuportahan ng ZeroClaw ang [Open Skills](https://github.com/openagents-com/open-skills) — isang modular at provider-agnostic system para sa pag-extend sa AI agent capabilities.
|
||||
|
||||
### Enable Open Skills
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # optional
|
||||
```
|
||||
|
||||
Maaari mo ring i-override sa runtime gamit ang `ZEROCLAW_OPEN_SKILLS_ENABLED` at `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
cargo build # Dev build
|
||||
cargo build --release # Release build (codegen-units=1, works on all devices including Raspberry Pi)
|
||||
cargo build --profile release-fast # Faster build (codegen-units=8, requires 16 GB+ RAM)
|
||||
cargo test # Run full test suite
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Format
|
||||
|
||||
# Run SQLite vs Markdown comparison benchmark
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Pre-push hook
|
||||
|
||||
Ang isang git hook ay nagpapatakbo ng `cargo fmt --check`, `cargo clippy -- -D warnings`, at `cargo test` bago ang bawat push. I-enable ito nang isang beses:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Build Troubleshooting (OpenSSL errors on Linux)
|
||||
|
||||
Kung makakita ka ng `openssl-sys` build error, i-sync ang dependencies at i-recompile gamit ang repository's lockfile:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
Ang ZeroClaw ay naka-configure na gumamit ng `rustls` para sa HTTP/TLS dependencies; ang `--locked` ay nagpapanatili sa transitive graph na deterministic sa clean environments.
|
||||
|
||||
Para i-skip ang hook kapag kailangan mo ng quick push habang nagde-develop:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Collaboration & Docs
|
||||
|
||||
Magsimula sa documentation hub para sa task-based map:
|
||||
|
||||
-437
@@ -363,443 +363,6 @@ zeroclaw version # Sürüm ve derleme bilgilerini gösterir
|
||||
|
||||
Tam seçenekler ve örnekler için [Komutlar Referansına](docs/commands-reference.md) bakın.
|
||||
|
||||
## Mimari
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Kanallar (trait) │
|
||||
│ Telegram │ Matrix │ Slack │ Discord │ Web │ CLI │ Özel │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Ajan Orkestratörü │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Mesaj │ │ Bağlam │ │ Araç │ │
|
||||
│ │ Yönlendirme│ │ Bellek │ │ Yürütme │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Sağlayıcılar│ │ Bellek │ │ Araçlar │
|
||||
│ (trait) │ │ (trait) │ │ (trait) │
|
||||
├──────────────┤ ├──────────────┤ ├──────────────┤
|
||||
│ Anthropic │ │ Markdown │ │ Filesystem │
|
||||
│ OpenAI │ │ SQLite │ │ Bash │
|
||||
│ Gemini │ │ Yok │ │ Web Fetch │
|
||||
│ Ollama │ │ Özel │ │ Özel │
|
||||
│ Özel │ └──────────────┘ └──────────────┘
|
||||
└──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Çalışma Zamanı (trait) │
|
||||
│ Native │ Docker │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Temel ilkeler:**
|
||||
|
||||
- Her şey bir **trait'tir** — sağlayıcılar, kanallar, araçlar, bellek, tüneller
|
||||
- Kanallar orkestratörü çağırır; orkestratör sağlayıcıları + araçları çağırır
|
||||
- Bellek sistemi konuşma bağlamını yönetir (markdown, SQLite veya yok)
|
||||
- Çalışma zamanı kod yürütmeyi soyutlar (yerel veya Docker)
|
||||
- Satıcı kilitlenmesi yok — kod değişikliği olmadan Anthropic ↔ OpenAI ↔ Gemini ↔ Ollama değiştirin
|
||||
|
||||
Detaylı diyagramlar ve uygulama detayları için [mimari belgelerine](docs/architecture.svg) bakın.
|
||||
|
||||
## Örnekler
|
||||
|
||||
### Telegram Bot
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
bot_token = "123456:ABC-DEF..."
|
||||
allowed_users = [987654321] # Telegram kullanıcı ID'niz
|
||||
```
|
||||
|
||||
Arka plan programını + ajanı başlatın, ardından Telegram'da botunuza bir mesaj gönderin:
|
||||
|
||||
```
|
||||
/start
|
||||
Merhaba! Bir Python betiği yazmama yardımcı olabilir misin?
|
||||
```
|
||||
|
||||
Bot, AI tarafından oluşturulan kodla yanıt verir, istenirse araçları yürütür ve konuşma bağlamını korur.
|
||||
|
||||
### Matrix (uçtan uca şifreleme)
|
||||
|
||||
```toml
|
||||
[channels.matrix]
|
||||
enabled = true
|
||||
homeserver_url = "https://matrix.org"
|
||||
username = "@zeroclaw:matrix.org"
|
||||
password = "..."
|
||||
device_name = "zeroclaw-prod"
|
||||
e2ee_enabled = true
|
||||
```
|
||||
|
||||
Şifreli bir odaya `@zeroclaw:matrix.org` davet edin ve bot tam şifrelemeyle yanıt verecektir. Cihaz doğrulama kurulumu için [Matrix E2EE Kılavuzuna](docs/matrix-e2ee-guide.md) bakın.
|
||||
|
||||
### Çoklu-Sağlayıcı
|
||||
|
||||
```toml
|
||||
[providers.anthropic]
|
||||
enabled = true
|
||||
api_key = "sk-ant-..."
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
[providers.openai]
|
||||
enabled = true
|
||||
api_key = "sk-..."
|
||||
model = "gpt-4o"
|
||||
|
||||
[orchestrator]
|
||||
default_provider = "anthropic"
|
||||
fallback_providers = ["openai"] # Sağlayıcı hatasında geçiş
|
||||
```
|
||||
|
||||
Anthropic başarısız olursa veya hız sınırına ulaşırsa, orkestratör otomatik olarak OpenAI'ya geçer.
|
||||
|
||||
### Özel Bellek
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "sqlite"
|
||||
path = "~/.zeroclaw/workspace/memory/conversations.db"
|
||||
retention_days = 90 # 90 gün sonra otomatik temizleme
|
||||
```
|
||||
|
||||
Veya insan tarafından okunabilir depolama için Markdown kullanın:
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
kind = "markdown"
|
||||
path = "~/.zeroclaw/workspace/memory/"
|
||||
```
|
||||
|
||||
Tüm bellek seçenekleri için [Yapılandırma Referansına](docs/config-reference.md#memory) bakın.
|
||||
|
||||
## Sağlayıcı Desteği
|
||||
|
||||
| Sağlayıcı | Durum | API Anahtarı | Örnek Modeller |
|
||||
| ----------------- | ----------- | ------------------- | ---------------------------------------------------- |
|
||||
| **Anthropic** | ✅ Kararlı | `ANTHROPIC_API_KEY` | `claude-sonnet-4-20250514`, `claude-opus-4-20250514` |
|
||||
| **OpenAI** | ✅ Kararlı | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini`, `o1`, `o1-mini` |
|
||||
| **Google Gemini** | ✅ Kararlı | `GOOGLE_API_KEY` | `gemini-2.0-flash-exp`, `gemini-exp-1206` |
|
||||
| **Ollama** | ✅ Kararlı | Yok (yerel) | `llama3.3`, `qwen2.5`, `phi4` |
|
||||
| **Cerebras** | ✅ Kararlı | `CEREBRAS_API_KEY` | `llama-3.3-70b` |
|
||||
| **Groq** | ✅ Kararlı | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
|
||||
| **Mistral** | 🚧 Planlanan | `MISTRAL_API_KEY` | TBD |
|
||||
| **Cohere** | 🚧 Planlanan | `COHERE_API_KEY` | TBD |
|
||||
|
||||
### Özel Uç Noktalar
|
||||
|
||||
ZeroClaw, OpenAI uyumlu uç noktaları destekler:
|
||||
|
||||
```toml
|
||||
[providers.custom]
|
||||
enabled = true
|
||||
api_key = "..."
|
||||
base_url = "https://api.your-llm-provider.com/v1"
|
||||
model = "your-model-name"
|
||||
```
|
||||
|
||||
Örnek: herhangi bir LLM'ye OpenAI arayüzü üzerinden erişmek için [LiteLLM](https://github.com/BerriAI/litellm)'i proxy olarak kullanın.
|
||||
|
||||
Tam yapılandırma detayları için [Sağlayıcı Referansına](docs/providers-reference.md) bakın.
|
||||
|
||||
## Kanal Desteği
|
||||
|
||||
| Kanal | Durum | Kimlik Doğrulama | Notlar |
|
||||
| ------------ | ----------- | ------------------------ | --------------------------------------------------------- |
|
||||
| **Telegram** | ✅ Kararlı | Bot Token | Dosyalar, resimler, satır içi düğmeler dahil tam destek |
|
||||
| **Matrix** | ✅ Kararlı | Şifre veya Token | Cihaz doğrulamalı E2EE desteği |
|
||||
| **Slack** | 🚧 Planlanan | OAuth veya Bot Token | Çalışma alanı erişimi gerektirir |
|
||||
| **Discord** | 🚧 Planlanan | Bot Token | Guild izinleri gerektirir |
|
||||
| **WhatsApp** | 🚧 Planlanan | Twilio veya resmi API | İş hesabı gerektirir |
|
||||
| **CLI** | ✅ Kararlı | Yok | Doğrudan konuşma arayüzü |
|
||||
| **Web** | 🚧 Planlanan | API Anahtarı veya OAuth | Tarayıcı tabanlı sohbet arayüzü |
|
||||
|
||||
Tam yapılandırma talimatları için [Kanallar Referansına](docs/channels-reference.md) bakın.
|
||||
|
||||
## Araç Desteği
|
||||
|
||||
ZeroClaw, kod yürütme, dosya sistemi erişimi ve web alımı için yerleşik araçlar sağlar:
|
||||
|
||||
| Araç | Açıklama | Gerekli Çalışma Zamanı |
|
||||
| -------------------- | --------------------------- | ----------------------------- |
|
||||
| **bash** | Shell komutlarını yürüt | Yerel veya Docker |
|
||||
| **python** | Python betiklerini yürüt | Python 3.8+ (yerel) veya Docker |
|
||||
| **javascript** | Node.js kodunu yürüt | Node.js 18+ (yerel) veya Docker |
|
||||
| **filesystem_read** | Dosyaları oku | Yerel veya Docker |
|
||||
| **filesystem_write** | Dosyaları yaz | Yerel veya Docker |
|
||||
| **web_fetch** | Web içeriği al | Yerel veya Docker |
|
||||
|
||||
### Yürütme Güvenliği
|
||||
|
||||
- **Yerel Çalışma Zamanı** — arka plan programının kullanıcı süreci olarak çalışır, tam dosya sistemi erişimi
|
||||
- **Docker Çalışma Zamanı** — tam konteyner yalıtımı, ayrı dosya sistemleri ve ağlar
|
||||
|
||||
`config.toml` içinde yürütme ilkesini yapılandırın:
|
||||
|
||||
```toml
|
||||
[runtime]
|
||||
kind = "docker"
|
||||
allowed_tools = ["bash", "python", "filesystem_read"] # Açık izin listesi
|
||||
```
|
||||
|
||||
Tam güvenlik seçenekleri için [Yapılandırma Referansına](docs/config-reference.md#runtime) bakın.
|
||||
|
||||
## Dağıtım
|
||||
|
||||
### Yerel Dağıtım (Geliştirme)
|
||||
|
||||
```bash
|
||||
zeroclaw daemon start
|
||||
zeroclaw agent start
|
||||
```
|
||||
|
||||
### Sunucu Dağıtımı (Üretim)
|
||||
|
||||
Arka plan programını ve ajanı hizmet olarak yönetmek için systemd kullanın:
|
||||
|
||||
```bash
|
||||
# İkiliyi yükle
|
||||
cargo install --path . --locked
|
||||
|
||||
# Çalışma alanını yapılandır
|
||||
zeroclaw init
|
||||
|
||||
# systemd hizmet dosyaları oluştur
|
||||
sudo cp deployment/systemd/zeroclaw-daemon.service /etc/systemd/system/
|
||||
sudo cp deployment/systemd/zeroclaw-agent.service /etc/systemd/system/
|
||||
|
||||
# Hizmetleri etkinleştir ve başlat
|
||||
sudo systemctl enable zeroclaw-daemon zeroclaw-agent
|
||||
sudo systemctl start zeroclaw-daemon zeroclaw-agent
|
||||
|
||||
# Durumu doğrula
|
||||
sudo systemctl status zeroclaw-daemon
|
||||
sudo systemctl status zeroclaw-agent
|
||||
```
|
||||
|
||||
Tam üretim dağıtım talimatları için [Ağ Dağıtımı Kılavuzuna](docs/network-deployment.md) bakın.
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
# İmajı oluştur
|
||||
docker build -t zeroclaw:latest .
|
||||
|
||||
# Konteyneri çalıştır
|
||||
docker run -d \
|
||||
--name zeroclaw \
|
||||
-v ~/.zeroclaw/workspace:/workspace \
|
||||
-e ANTHROPIC_API_KEY=sk-ant-... \
|
||||
zeroclaw:latest
|
||||
```
|
||||
|
||||
Derleme detayları ve yapılandırma seçenekleri için [`Dockerfile`](Dockerfile)'a bakın.
|
||||
|
||||
### Uç Donanım
|
||||
|
||||
ZeroClaw, düşük güç tüketimli donanımda çalışmak üzere tasarlanmıştır:
|
||||
|
||||
- **Raspberry Pi Zero 2 W** — ~512 MB RAM, tek ARMv8 çekirdek, < $5 donanım maliyeti
|
||||
- **Raspberry Pi 4/5** — 1 GB+ RAM, çok çekirdekli, eşzamanlı iş yükleri için ideal
|
||||
- **Orange Pi Zero 2** — ~512 MB RAM, dört çekirdekli ARMv8, ultra düşük maliyet
|
||||
- **x86 SBC'ler (Intel N100)** — 4-8 GB RAM, hızlı derlemeler, yerel Docker desteği
|
||||
|
||||
Cihaza özgü kurulum talimatları için [Donanım Kılavuzuna](docs/hardware/README.md) bakın.
|
||||
|
||||
## Tünelleme (Herkese Açık Kullanım)
|
||||
|
||||
Yerel ZeroClaw arka plan programınızı güvenli tüneller aracılığıyla herkese açık ağa çıkarın:
|
||||
|
||||
```bash
|
||||
zeroclaw tunnel start --provider cloudflare
|
||||
```
|
||||
|
||||
Desteklenen tünel sağlayıcıları:
|
||||
|
||||
- **Cloudflare Tunnel** — ücretsiz HTTPS, port açığa çıkarma yok, çoklu etki alanı desteği
|
||||
- **Ngrok** — hızlı kurulum, özel etki alanları (ücretli plan)
|
||||
- **Tailscale** — özel mesh ağı. herkese açık port yok
|
||||
|
||||
Tam yapılandırma seçenekleri için [Yapılandırma Referansına](docs/config-reference.md#tunnel) bakın.
|
||||
|
||||
## Güvenlik
|
||||
|
||||
ZeroClaw birden çok güvenlik katmanı uygular:
|
||||
|
||||
### Eşleştirme
|
||||
|
||||
Arka plan programı ilk başlangıçta `~/.zeroclaw/workspace/.pairing` içinde saklanan bir eşleştirme sırrı oluşturur. İstemciler (ajan, CLI) bağlanmak için bu sırrı sunmalıdır.
|
||||
|
||||
```bash
|
||||
zeroclaw pairing rotate # Yeni bir sır oluşturur ve eskisini geçersiz kılar
|
||||
```
|
||||
|
||||
### Kum Alanı
|
||||
|
||||
- **Docker Çalışma Zamanı** — ayrı dosya sistemleri ve ağlarla tam konteyner yalıtımı
|
||||
- **Yerel Çalışma Zamanı** — kullanıcı süreci olarak çalışır; varsayılan olarak çalışma alanıyla sınırlıdır
|
||||
|
||||
### İzin Listeleri
|
||||
|
||||
Kanallar kullanıcı ID'sine göre erişimi kısıtlayabilir:
|
||||
|
||||
```toml
|
||||
[channels.telegram]
|
||||
enabled = true
|
||||
allowed_users = [123456789, 987654321] # Açık izin listesi
|
||||
```
|
||||
|
||||
### Şifreleme
|
||||
|
||||
- **Matrix E2EE** — cihaz doğrulamalı tam uçtan uca şifreleme
|
||||
- **TLS Taşıma** — tüm API ve tünel trafiği HTTPS/TLS kullanır
|
||||
|
||||
Tam ilkeler ve uygulamalar için [Güvenlik Belgelerine](docs/security/README.md) bakın.
|
||||
|
||||
## Gözlemlenebilirlik
|
||||
|
||||
ZeroClaw varsayılan olarak `~/.zeroclaw/workspace/logs/` dizinine log yazar. Loglar bileşene göre saklanır:
|
||||
|
||||
```
|
||||
~/.zeroclaw/workspace/logs/
|
||||
├── daemon.log # Arka plan programı logları (başlangıç, API istekleri, hatalar)
|
||||
├── agent.log # Ajan logları (mesaj yönlendirme, araç yürütme)
|
||||
├── telegram.log # Kanala özgü loglar (etkinse)
|
||||
└── matrix.log # Kanala özgü loglar (etkinse)
|
||||
```
|
||||
|
||||
### Loglama Yapılandırması
|
||||
|
||||
```toml
|
||||
[logging]
|
||||
level = "info" # debug, info, warn, error
|
||||
path = "~/.zeroclaw/workspace/logs/"
|
||||
rotation = "daily" # günlük, saatlik, boyut
|
||||
max_size_mb = 100 # Boyut tabanlı döndürme için
|
||||
retention_days = 30 # N gün sonra otomatik temizleme
|
||||
```
|
||||
|
||||
Tüm loglama seçenekleri için [Yapılandırma Referansına](docs/config-reference.md#logging) bakın.
|
||||
|
||||
### Metrikler (Planlanan)
|
||||
|
||||
Üretim izleme için Prometheus metrikleri desteği yakında geliyor. [#234](https://github.com/zeroclaw-labs/zeroclaw/issues/234) numaralı konuda takip ediliyor.
|
||||
|
||||
## Beceriler
|
||||
|
||||
ZeroClaw, sistem yeteneklerini genişleten yeniden kullanılabilir modüller olan özel becerileri destekler.
|
||||
|
||||
### Beceri Tanımı
|
||||
|
||||
Beceriler bu yapı ile `~/.zeroclaw/workspace/skills/<skill-name>/` içinde saklanır:
|
||||
|
||||
```
|
||||
skills/
|
||||
└── my-skill/
|
||||
├── skill.toml # Beceri metaverileri (ad, açıklama, bağımlılıklar)
|
||||
├── prompt.md # AI için sistem istemi
|
||||
└── tools/ # İsteğe bağlı özel araçlar
|
||||
└── my_tool.py
|
||||
```
|
||||
|
||||
### Beceri Örneği
|
||||
|
||||
```toml
|
||||
# skills/web-research/skill.toml
|
||||
[skill]
|
||||
name = "web-research"
|
||||
description = "Web'de arama yapar ve sonuçları özetler"
|
||||
version = "1.0.0"
|
||||
|
||||
[dependencies]
|
||||
tools = ["web_fetch", "bash"]
|
||||
```
|
||||
|
||||
```markdown
|
||||
<!-- skills/web-research/prompt.md -->
|
||||
|
||||
Sen bir araştırma asistanısın. Bir şeyi araştırmam istendiğinde:
|
||||
|
||||
1. İçeriği almak için web_fetch kullan
|
||||
2. Sonuçları okunması kolay bir biçimde özetle
|
||||
3. Kaynakları URL'lerle göster
|
||||
```
|
||||
|
||||
### Beceri Kullanımı
|
||||
|
||||
Beceriler ajan başlangıcında otomatik olarak yüklenir. Konuşmalarda ada göre başvurun:
|
||||
|
||||
```
|
||||
Kullanıcı: En son AI haberlerini bulmak için web-research becerisini kullan
|
||||
Bot: [web-research becerisini yükler, web_fetch'i yürütür, sonuçları özetler]
|
||||
```
|
||||
|
||||
Tam beceri oluşturma talimatları için [Beceriler](#beceriler) bölümüne bakın.
|
||||
|
||||
## Open Skills
|
||||
|
||||
ZeroClaw, AI ajan yeteneklerini genişletmek için modüler ve sağlayıcıdan bağımsız bir sistem olan [Open Skills](https://github.com/openagents-com/open-skills)'i destekler.
|
||||
|
||||
### Open Skills'i Etkinleştir
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # isteğe bağlı
|
||||
```
|
||||
|
||||
Ayrıca `ZEROCLAW_OPEN_SKILLS_ENABLED` ve `ZEROCLAW_OPEN_SKILLS_DIR` ile çalışma zamanında geçersiz kılabilirsiniz.
|
||||
|
||||
## Geliştirme
|
||||
|
||||
```bash
|
||||
cargo build # Geliştirme derlemesi
|
||||
cargo build --release # Sürüm derlemesi (codegen-units=1, Raspberry Pi dahil tüm cihazlarda çalışır)
|
||||
cargo build --profile release-fast # Daha hızlı derleme (codegen-units=8, 16 GB+ RAM gerektirir)
|
||||
cargo test # Tam test paketini çalıştır
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Biçimlendir
|
||||
|
||||
# SQLite vs Markdown karşılaştırma kıyaslamasını çalıştır
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Ön push kancası
|
||||
|
||||
Bir git kancası her push'tan önce `cargo fmt --check`, `cargo clippy -- -D warnings` ve `cargo test` çalıştırır. Bir kez etkinleştirin:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Derleme Sorun Giderme (Linux'ta OpenSSL hataları)
|
||||
|
||||
Bir `openssl-sys` derleme hatasıyla karşılaşırsanız, bağımlılıkları eşzamanlayın ve deponun lockfile'ı ile yeniden derleyin:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw, HTTP/TLS bağımlılıkları için `rustls` kullanacak şekilde yapılandırılmıştır; `--locked`, geçişli grafiği temiz ortamlarda deterministik tutar.
|
||||
|
||||
Geliştirme sırasında hızlı bir push'a ihtiyacınız olduğunda kancayı atlamak için:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## İşbirliği ve Belgeler
|
||||
|
||||
Görev tabanlı bir harita için belge merkeziyle başlayın:
|
||||
|
||||
+1
-571
@@ -58,7 +58,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="#quick-start">Bắt đầu</a> |
|
||||
<a href="install.sh">Cài đặt một lần bấm</a> |
|
||||
<a href="https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh">Cài đặt một lần bấm</a> |
|
||||
<a href="docs/i18n/vi/README.md">Trung tâm tài liệu</a> |
|
||||
<a href="docs/SUMMARY.md">Mục lục tài liệu</a>
|
||||
</p>
|
||||
@@ -406,576 +406,6 @@ zeroclaw agent --provider openai-codex --auth-profile openai-codex:work -m "hell
|
||||
zeroclaw agent --provider anthropic -m "hello"
|
||||
```
|
||||
|
||||
## Kiến trúc
|
||||
|
||||
Mọi hệ thống con đều là **trait** — chỉ cần đổi cấu hình, không cần sửa code.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/architecture.svg" alt="ZeroClaw Architecture" width="900" />
|
||||
</p>
|
||||
|
||||
| Hệ thống con | Trait | Đi kèm sẵn | Mở rộng |
|
||||
|-----------|-------|------------|--------|
|
||||
| **Mô hình AI** | `Provider` | Danh mục provider qua `zeroclaw providers` (hiện có 28 built-in + alias, cộng endpoint tùy chỉnh) | `custom:https://your-api.com` (tương thích OpenAI) hoặc `anthropic-custom:https://your-api.com` |
|
||||
| **Channel** | `Channel` | CLI, Telegram, Discord, Slack, Mattermost, iMessage, Matrix, Signal, WhatsApp, Linq, Email, IRC, Lark, DingTalk, QQ, Webhook | Bất kỳ messaging API nào |
|
||||
| **Memory** | `Memory` | SQLite hybrid search, PostgreSQL backend (storage provider có thể cấu hình), Lucid bridge, Markdown files, backend `none` tường minh, snapshot/hydrate, response cache tùy chọn | Bất kỳ persistence backend nào |
|
||||
| **Tool** | `Tool` | shell/file/memory, cron/schedule, git, pushover, browser, http_request, screenshot/image_info, composio (opt-in), delegate, hardware tools | Bất kỳ khả năng nào |
|
||||
| **Observability** | `Observer` | Noop, Log, Multi | Prometheus, OTel |
|
||||
| **Runtime** | `RuntimeAdapter` | Native, Docker (sandboxed) | Có thể thêm runtime bổ sung qua adapter; các kind không được hỗ trợ sẽ fail nhanh |
|
||||
| **Bảo mật** | `SecurityPolicy` | Ghép cặp gateway, sandbox, allowlist, giới hạn tốc độ, phân vùng filesystem, secret mã hóa | — |
|
||||
| **Định danh** | `IdentityConfig` | OpenClaw (markdown), AIEOS v1.1 (JSON) | Bất kỳ định dạng định danh nào |
|
||||
| **Tunnel** | `Tunnel` | None, Cloudflare, Tailscale, ngrok, Custom | Bất kỳ tunnel binary nào |
|
||||
| **Heartbeat** | Engine | Tác vụ định kỳ HEARTBEAT.md | — |
|
||||
| **Skill** | Loader | TOML manifest + hướng dẫn SKILL.md | Community skill pack |
|
||||
| **Tích hợp** | Registry | 70+ tích hợp trong 9 danh mục | Plugin system |
|
||||
|
||||
### Hỗ trợ runtime (hiện tại)
|
||||
|
||||
- ✅ Được hỗ trợ hiện nay: `runtime.kind = "native"` hoặc `runtime.kind = "docker"`
|
||||
- 🚧 Đã lên kế hoạch, chưa triển khai: WASM / edge runtime
|
||||
|
||||
Khi cấu hình `runtime.kind` không được hỗ trợ, ZeroClaw sẽ thoát với thông báo lỗi rõ ràng thay vì âm thầm fallback về native.
|
||||
|
||||
### Hệ thống Memory (Search Engine toàn diện)
|
||||
|
||||
Tự phát triển hoàn toàn, không phụ thuộc bên ngoài — không Pinecone, không Elasticsearch, không LangChain:
|
||||
|
||||
| Lớp | Triển khai |
|
||||
|-------|---------------|
|
||||
| **Vector DB** | Embeddings lưu dưới dạng BLOB trong SQLite, tìm kiếm cosine similarity |
|
||||
| **Keyword Search** | Bảng ảo FTS5 với BM25 scoring |
|
||||
| **Hybrid Merge** | Hàm merge có trọng số tùy chỉnh (`vector.rs`) |
|
||||
| **Embeddings** | Trait `EmbeddingProvider` — OpenAI, URL tùy chỉnh, hoặc noop |
|
||||
| **Chunking** | Bộ chia đoạn markdown theo dòng, giữ nguyên heading |
|
||||
| **Caching** | Bảng SQLite `embedding_cache` với LRU eviction |
|
||||
| **Safe Reindex** | Rebuild FTS5 + re-embed các vector bị thiếu theo cách nguyên tử |
|
||||
|
||||
Agent tự động ghi nhớ, lưu trữ và quản lý memory qua các tool.
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
backend = "sqlite" # "sqlite", "lucid", "postgres", "markdown", "none"
|
||||
auto_save = true
|
||||
embedding_provider = "none" # "none", "openai", "custom:https://..."
|
||||
vector_weight = 0.7
|
||||
keyword_weight = 0.3
|
||||
|
||||
# backend = "none" sử dụng no-op memory backend tường minh (không có persistence)
|
||||
|
||||
# Tùy chọn: ghi đè storage-provider cho remote memory backend.
|
||||
# Khi provider = "postgres", ZeroClaw dùng PostgreSQL để lưu memory.
|
||||
# Khóa db_url cũng chấp nhận alias `dbURL` để tương thích ngược.
|
||||
#
|
||||
# [storage.provider.config]
|
||||
# provider = "postgres"
|
||||
# db_url = "postgres://user:password@host:5432/zeroclaw"
|
||||
# schema = "public"
|
||||
# table = "memories"
|
||||
# connect_timeout_secs = 15
|
||||
|
||||
# Tùy chọn cho backend = "sqlite": số giây tối đa chờ khi mở DB (ví dụ: file bị khóa). Bỏ qua hoặc để trống để không có timeout.
|
||||
# sqlite_open_timeout_secs = 30
|
||||
|
||||
# Tùy chọn cho backend = "lucid"
|
||||
# ZEROCLAW_LUCID_CMD=/usr/local/bin/lucid # mặc định: lucid
|
||||
# ZEROCLAW_LUCID_BUDGET=200 # mặc định: 200
|
||||
# ZEROCLAW_LUCID_LOCAL_HIT_THRESHOLD=3 # số lần hit cục bộ để bỏ qua external recall
|
||||
# ZEROCLAW_LUCID_RECALL_TIMEOUT_MS=120 # giới hạn thời gian cho lucid context recall
|
||||
# ZEROCLAW_LUCID_STORE_TIMEOUT_MS=800 # timeout đồng bộ async cho lucid store
|
||||
# ZEROCLAW_LUCID_FAILURE_COOLDOWN_MS=15000 # thời gian nghỉ sau lỗi lucid, tránh thử lại liên tục
|
||||
```
|
||||
|
||||
## Bảo mật
|
||||
|
||||
ZeroClaw thực thi bảo mật ở **mọi lớp** — không chỉ sandbox. Đáp ứng tất cả các hạng mục trong danh sách kiểm tra bảo mật của cộng đồng.
|
||||
|
||||
### Danh sách kiểm tra bảo mật
|
||||
|
||||
| # | Hạng mục | Trạng thái | Cách thực hiện |
|
||||
|---|------|--------|-----|
|
||||
| 1 | **Gateway không công khai ra ngoài** | ✅ | Bind vào `127.0.0.1` theo mặc định. Từ chối `0.0.0.0` nếu không có tunnel hoặc `allow_public_bind = true` tường minh. |
|
||||
| 2 | **Yêu cầu ghép cặp** | ✅ | Mã một lần 6 chữ số khi khởi động. Trao đổi qua `POST /pair` để lấy bearer token. Mọi yêu cầu `/webhook` đều cần `Authorization: Bearer <token>`. |
|
||||
| 3 | **Phân vùng filesystem (không phải /)** | ✅ | `workspace_only = true` theo mặc định. Chặn 14 thư mục hệ thống + 4 dotfile nhạy cảm. Chặn null byte injection. Phát hiện symlink escape qua canonicalization + kiểm tra resolved-path trong các tool đọc/ghi file. |
|
||||
| 4 | **Chỉ truy cập qua tunnel** | ✅ | Gateway từ chối bind công khai khi không có tunnel đang hoạt động. Hỗ trợ Tailscale, Cloudflare, ngrok, hoặc tunnel tùy chỉnh. |
|
||||
|
||||
> **Tự chạy nmap:** `nmap -p 1-65535 <your-host>` — ZeroClaw chỉ bind vào localhost, nên không có gì bị lộ ra ngoài trừ khi bạn cấu hình tunnel tường minh.
|
||||
|
||||
### Allowlist channel (từ chối theo mặc định)
|
||||
|
||||
Chính sách kiểm soát người gửi đã được thống nhất:
|
||||
|
||||
- Allowlist rỗng = **từ chối tất cả tin nhắn đến**
|
||||
- `"*"` = **cho phép tất cả** (phải opt-in tường minh)
|
||||
- Nếu khác = allowlist khớp chính xác
|
||||
|
||||
Mặc định an toàn, hạn chế tối đa rủi ro lộ thông tin.
|
||||
|
||||
Tài liệu tham khảo đầy đủ về cấu hình channel: [docs/reference/api/channels-reference.md](docs/reference/api/channels-reference.md).
|
||||
|
||||
Cài đặt được khuyến nghị (bảo mật + nhanh):
|
||||
|
||||
- **Telegram:** thêm `@username` của bạn (không có `@`) và/hoặc Telegram user ID số vào allowlist.
|
||||
- **Discord:** thêm Discord user ID của bạn vào allowlist.
|
||||
- **Slack:** thêm Slack member ID của bạn (thường bắt đầu bằng `U`) vào allowlist.
|
||||
- **Mattermost:** dùng API v4 tiêu chuẩn. Allowlist dùng Mattermost user ID.
|
||||
- Chỉ dùng `"*"` cho kiểm thử mở tạm thời.
|
||||
|
||||
Luồng phê duyệt của operator qua Telegram:
|
||||
|
||||
1. Để `[channels_config.telegram].allowed_users = []` để từ chối theo mặc định khi khởi động.
|
||||
2. Người dùng không được phép sẽ nhận được gợi ý kèm lệnh operator có thể copy:
|
||||
`zeroclaw channel bind-telegram <IDENTITY>`.
|
||||
3. Operator chạy lệnh đó tại máy cục bộ, sau đó người dùng thử gửi tin nhắn lại.
|
||||
|
||||
Nếu cần phê duyệt thủ công một lần, chạy:
|
||||
|
||||
```bash
|
||||
zeroclaw channel bind-telegram 123456789
|
||||
```
|
||||
|
||||
Nếu bạn không chắc định danh nào cần dùng:
|
||||
|
||||
1. Khởi động channel và gửi một tin nhắn đến bot của bạn.
|
||||
2. Đọc log cảnh báo để thấy định danh người gửi chính xác.
|
||||
3. Thêm giá trị đó vào allowlist và chạy lại channel-only setup.
|
||||
|
||||
Nếu bạn thấy cảnh báo ủy quyền trong log (ví dụ: `ignoring message from unauthorized user`),
|
||||
chạy lại channel setup:
|
||||
|
||||
```bash
|
||||
zeroclaw onboard --channels-only
|
||||
```
|
||||
|
||||
### Phản hồi media Telegram
|
||||
|
||||
Telegram định tuyến phản hồi theo **chat ID nguồn** (thay vì username),
|
||||
tránh lỗi `Bad Request: chat not found`.
|
||||
|
||||
Với các phản hồi không phải văn bản, ZeroClaw có thể gửi file đính kèm Telegram khi assistant bao gồm các marker:
|
||||
|
||||
- `[IMAGE:<path-or-url>]`
|
||||
- `[DOCUMENT:<path-or-url>]`
|
||||
- `[VIDEO:<path-or-url>]`
|
||||
- `[AUDIO:<path-or-url>]`
|
||||
- `[VOICE:<path-or-url>]`
|
||||
|
||||
Path có thể là file cục bộ (ví dụ `/tmp/screenshot.png`) hoặc URL HTTPS.
|
||||
|
||||
### Cài đặt WhatsApp
|
||||
|
||||
ZeroClaw hỗ trợ hai backend WhatsApp:
|
||||
|
||||
- **Chế độ WhatsApp Web** (QR / pair code, không cần Meta Business API)
|
||||
- **Chế độ WhatsApp Business Cloud API** (luồng webhook chính thức của Meta)
|
||||
|
||||
#### Chế độ WhatsApp Web (khuyến nghị cho dùng cá nhân/self-hosted)
|
||||
|
||||
1. **Build với hỗ trợ WhatsApp Web:**
|
||||
```bash
|
||||
cargo build --features whatsapp-web
|
||||
```
|
||||
|
||||
2. **Cấu hình ZeroClaw:**
|
||||
```toml
|
||||
[channels_config.whatsapp]
|
||||
session_path = "~/.zeroclaw/state/whatsapp-web/session.db"
|
||||
pair_phone = "15551234567" # tùy chọn; bỏ qua để dùng luồng QR
|
||||
pair_code = "" # tùy chọn mã pair tùy chỉnh
|
||||
allowed_numbers = ["+1234567890"] # định dạng E.164, hoặc ["*"] cho tất cả
|
||||
```
|
||||
|
||||
3. **Khởi động channel/daemon và liên kết thiết bị:**
|
||||
- Chạy `zeroclaw channel start` (hoặc `zeroclaw daemon`).
|
||||
- Làm theo hướng dẫn ghép cặp trên terminal (QR hoặc pair code).
|
||||
- Trên WhatsApp điện thoại: **Cài đặt → Thiết bị đã liên kết**.
|
||||
|
||||
4. **Kiểm tra:** Gửi tin nhắn từ số được phép và xác nhận agent trả lời.
|
||||
|
||||
#### Chế độ WhatsApp Business Cloud API
|
||||
|
||||
WhatsApp dùng Cloud API của Meta với webhook (push-based, không phải polling):
|
||||
|
||||
1. **Tạo Meta Business App:**
|
||||
- Truy cập [developers.facebook.com](https://developers.facebook.com)
|
||||
- Tạo app mới → Chọn loại "Business"
|
||||
- Thêm sản phẩm "WhatsApp"
|
||||
|
||||
2. **Lấy thông tin xác thực:**
|
||||
- **Access Token:** Từ WhatsApp → API Setup → Generate token (hoặc tạo System User cho token vĩnh viễn)
|
||||
- **Phone Number ID:** Từ WhatsApp → API Setup → Phone number ID
|
||||
- **Verify Token:** Bạn tự định nghĩa (bất kỳ chuỗi ngẫu nhiên nào) — Meta sẽ gửi lại trong quá trình xác minh webhook
|
||||
|
||||
3. **Cấu hình ZeroClaw:**
|
||||
```toml
|
||||
[channels_config.whatsapp]
|
||||
access_token = "EAABx..."
|
||||
phone_number_id = "123456789012345"
|
||||
verify_token = "my-secret-verify-token"
|
||||
allowed_numbers = ["+1234567890"] # định dạng E.164, hoặc ["*"] cho tất cả
|
||||
```
|
||||
|
||||
4. **Khởi động gateway với tunnel:**
|
||||
```bash
|
||||
zeroclaw gateway --port 42617
|
||||
```
|
||||
WhatsApp yêu cầu HTTPS, vì vậy hãy dùng tunnel (ngrok, Cloudflare, Tailscale Funnel).
|
||||
|
||||
5. **Cấu hình Meta webhook:**
|
||||
- Trong Meta Developer Console → WhatsApp → Configuration → Webhook
|
||||
- **Callback URL:** `https://your-tunnel-url/whatsapp`
|
||||
- **Verify Token:** Giống với `verify_token` trong config của bạn
|
||||
- Đăng ký nhận trường `messages`
|
||||
|
||||
6. **Kiểm tra:** Gửi tin nhắn đến số WhatsApp Business của bạn — ZeroClaw sẽ phản hồi qua LLM.
|
||||
|
||||
## Cấu hình
|
||||
|
||||
Config: `~/.zeroclaw/config.toml` (được tạo bởi `onboard`)
|
||||
|
||||
Khi `zeroclaw channel start` đang chạy, các thay đổi với `default_provider`,
|
||||
`default_model`, `default_temperature`, `api_key`, `api_url`, và `reliability.*`
|
||||
sẽ được áp dụng nóng vào lần có tin nhắn channel đến tiếp theo.
|
||||
|
||||
```toml
|
||||
api_key = "sk-..."
|
||||
default_provider = "openrouter"
|
||||
default_model = "anthropic/claude-sonnet-4-6"
|
||||
default_temperature = 0.7
|
||||
|
||||
# Endpoint tùy chỉnh tương thích OpenAI
|
||||
# default_provider = "custom:https://your-api.com"
|
||||
|
||||
# Endpoint tùy chỉnh tương thích Anthropic
|
||||
# default_provider = "anthropic-custom:https://your-api.com"
|
||||
|
||||
[memory]
|
||||
backend = "sqlite" # "sqlite", "lucid", "postgres", "markdown", "none"
|
||||
auto_save = true
|
||||
embedding_provider = "none" # "none", "openai", "custom:https://..."
|
||||
vector_weight = 0.7
|
||||
keyword_weight = 0.3
|
||||
|
||||
# backend = "none" vô hiệu hóa persistent memory qua no-op backend
|
||||
|
||||
# Tùy chọn ghi đè storage-provider từ xa (ví dụ PostgreSQL)
|
||||
# [storage.provider.config]
|
||||
# provider = "postgres"
|
||||
# db_url = "postgres://user:password@host:5432/zeroclaw"
|
||||
# schema = "public"
|
||||
# table = "memories"
|
||||
# connect_timeout_secs = 15
|
||||
|
||||
[gateway]
|
||||
port = 42617 # mặc định
|
||||
host = "127.0.0.1" # mặc định
|
||||
require_pairing = true # yêu cầu pairing code khi kết nối lần đầu
|
||||
allow_public_bind = false # từ chối 0.0.0.0 nếu không có tunnel
|
||||
|
||||
[autonomy]
|
||||
level = "supervised" # "readonly", "supervised", "full" (mặc định: supervised)
|
||||
workspace_only = true # mặc định: true — phân vùng vào workspace
|
||||
allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep"]
|
||||
forbidden_paths = ["/etc", "/root", "/proc", "/sys", "~/.ssh", "~/.gnupg", "~/.aws"]
|
||||
|
||||
[runtime]
|
||||
kind = "native" # "native" hoặc "docker"
|
||||
|
||||
[runtime.docker]
|
||||
image = "alpine:3.20" # container image cho thực thi shell
|
||||
network = "none" # chế độ docker network ("none", "bridge", v.v.)
|
||||
memory_limit_mb = 512 # giới hạn bộ nhớ tùy chọn tính bằng MB
|
||||
cpu_limit = 1.0 # giới hạn CPU tùy chọn
|
||||
read_only_rootfs = true # mount root filesystem ở chế độ read-only
|
||||
mount_workspace = true # mount workspace vào /workspace
|
||||
allowed_workspace_roots = [] # allowlist tùy chọn để xác thực workspace mount
|
||||
|
||||
[heartbeat]
|
||||
enabled = false
|
||||
interval_minutes = 30
|
||||
|
||||
[tunnel]
|
||||
provider = "none" # "none", "cloudflare", "tailscale", "ngrok", "custom"
|
||||
|
||||
[secrets]
|
||||
encrypt = true # API key được mã hóa bằng file key cục bộ
|
||||
|
||||
[browser]
|
||||
enabled = false # opt-in browser_open + browser tool
|
||||
allowed_domains = ["docs.rs"] # bắt buộc khi browser được bật
|
||||
backend = "agent_browser" # "agent_browser" (mặc định), "rust_native", "computer_use", "auto"
|
||||
native_headless = true # áp dụng khi backend dùng rust-native
|
||||
native_webdriver_url = "http://127.0.0.1:9515" # WebDriver endpoint (chromedriver/selenium)
|
||||
# native_chrome_path = "/usr/bin/chromium" # tùy chọn chỉ định rõ browser binary cho driver
|
||||
|
||||
[browser.computer_use]
|
||||
endpoint = "http://127.0.0.1:8787/v1/actions" # HTTP endpoint của computer-use sidecar
|
||||
timeout_ms = 15000 # timeout mỗi action
|
||||
allow_remote_endpoint = false # mặc định bảo mật: chỉ endpoint private/localhost
|
||||
window_allowlist = [] # gợi ý allowlist tên cửa sổ/process tùy chọn
|
||||
# api_key = "..." # bearer token tùy chọn cho sidecar
|
||||
# max_coordinate_x = 3840 # guardrail tọa độ tùy chọn
|
||||
# max_coordinate_y = 2160 # guardrail tọa độ tùy chọn
|
||||
|
||||
# Flag build Rust-native backend:
|
||||
# cargo build --release --features browser-native
|
||||
# Đảm bảo WebDriver server đang chạy, ví dụ: chromedriver --port=9515
|
||||
|
||||
# Hợp đồng computer-use sidecar (MVP)
|
||||
# POST browser.computer_use.endpoint
|
||||
# Request: {
|
||||
# "action": "mouse_click",
|
||||
# "params": {"x": 640, "y": 360, "button": "left"},
|
||||
# "policy": {"allowed_domains": [...], "window_allowlist": [...], "max_coordinate_x": 3840, "max_coordinate_y": 2160},
|
||||
# "metadata": {"session_name": "...", "source": "zeroclaw.browser", "version": "..."}
|
||||
# }
|
||||
# Response: {"success": true, "data": {...}} hoặc {"success": false, "error": "..."}
|
||||
|
||||
[composio]
|
||||
enabled = false # opt-in: hơn 1000 OAuth app qua composio.dev
|
||||
# api_key = "cmp_..." # tùy chọn: được lưu mã hóa khi [secrets].encrypt = true
|
||||
entity_id = "default" # user_id mặc định cho Composio tool call
|
||||
# Gợi ý runtime: nếu execute yêu cầu connected_account_id, chạy composio với
|
||||
# action='list_accounts' và app='gmail' (hoặc toolkit của bạn) để lấy account ID.
|
||||
|
||||
[identity]
|
||||
format = "openclaw" # "openclaw" (mặc định, markdown files) hoặc "aieos" (JSON)
|
||||
# aieos_path = "identity.json" # đường dẫn đến file AIEOS JSON (tương đối với workspace hoặc tuyệt đối)
|
||||
# aieos_inline = '{"identity":{"names":{"first":"Nova"}}}' # inline AIEOS JSON
|
||||
```
|
||||
|
||||
### Ollama cục bộ và endpoint từ xa
|
||||
|
||||
ZeroClaw dùng một khóa provider (`ollama`) cho cả triển khai Ollama cục bộ và từ xa:
|
||||
|
||||
- Ollama cục bộ: để `api_url` trống, chạy `ollama serve`, và dùng các model như `llama3.2`.
|
||||
- Endpoint Ollama từ xa (bao gồm Ollama Cloud): đặt `api_url` thành endpoint từ xa và đặt `api_key` (hoặc `OLLAMA_API_KEY`) khi cần.
|
||||
- Tùy chọn suffix `:cloud`: ID model như `qwen3:cloud` được chuẩn hóa thành `qwen3` trước khi gửi request.
|
||||
|
||||
Ví dụ cấu hình từ xa:
|
||||
|
||||
```toml
|
||||
default_provider = "ollama"
|
||||
default_model = "qwen3:cloud"
|
||||
api_url = "https://ollama.com"
|
||||
api_key = "ollama_api_key_here"
|
||||
```
|
||||
|
||||
### Endpoint provider tùy chỉnh
|
||||
|
||||
Cấu hình chi tiết cho endpoint tùy chỉnh tương thích OpenAI và Anthropic, xem [docs/contributing/custom-providers.md](docs/contributing/custom-providers.md).
|
||||
|
||||
## Gói Python đi kèm (`zeroclaw-tools`)
|
||||
|
||||
Với các LLM provider có tool calling native không ổn định (ví dụ: GLM-5/Zhipu), ZeroClaw đi kèm gói Python dùng **LangGraph để gọi tool** nhằm đảm bảo tính nhất quán:
|
||||
|
||||
```bash
|
||||
pip install zeroclaw-tools
|
||||
```
|
||||
|
||||
```python
|
||||
from zeroclaw_tools import create_agent, shell, file_read
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
# Hoạt động với mọi provider tương thích OpenAI
|
||||
agent = create_agent(
|
||||
tools=[shell, file_read],
|
||||
model="glm-5",
|
||||
api_key="your-key",
|
||||
base_url="https://api.z.ai/api/coding/paas/v4"
|
||||
)
|
||||
|
||||
result = await agent.ainvoke({
|
||||
"messages": [HumanMessage(content="List files in /tmp")]
|
||||
})
|
||||
print(result["messages"][-1].content)
|
||||
```
|
||||
|
||||
**Lý do nên dùng:**
|
||||
- **Tool calling nhất quán** trên mọi provider (kể cả những provider hỗ trợ native kém)
|
||||
- **Vòng lặp tool tự động** — tiếp tục gọi tool cho đến khi hoàn thành tác vụ
|
||||
- **Dễ mở rộng** — thêm tool tùy chỉnh với decorator `@tool`
|
||||
- **Tích hợp Discord bot** đi kèm (Telegram đang lên kế hoạch)
|
||||
|
||||
Xem [`python/README.md`](python/README.md) để có tài liệu đầy đủ.
|
||||
|
||||
## Hệ thống định danh (Hỗ trợ AIEOS)
|
||||
|
||||
ZeroClaw hỗ trợ persona AI **không phụ thuộc nền tảng** qua hai định dạng:
|
||||
|
||||
### OpenClaw (Mặc định)
|
||||
|
||||
Các file markdown truyền thống trong workspace của bạn:
|
||||
- `IDENTITY.md` — Agent là ai
|
||||
- `SOUL.md` — Tính cách và giá trị cốt lõi
|
||||
- `USER.md` — Agent đang hỗ trợ ai
|
||||
- `AGENTS.md` — Hướng dẫn hành vi
|
||||
|
||||
### AIEOS (AI Entity Object Specification)
|
||||
|
||||
[AIEOS](https://aieos.org) là framework chuẩn hóa cho định danh AI di động. ZeroClaw hỗ trợ payload AIEOS v1.1 JSON, cho phép bạn:
|
||||
|
||||
- **Import định danh** từ hệ sinh thái AIEOS
|
||||
- **Export định danh** sang các hệ thống tương thích AIEOS khác
|
||||
- **Duy trì tính toàn vẹn hành vi** trên các mô hình AI khác nhau
|
||||
|
||||
#### Bật AIEOS
|
||||
|
||||
```toml
|
||||
[identity]
|
||||
format = "aieos"
|
||||
aieos_path = "identity.json" # tương đối với workspace hoặc đường dẫn tuyệt đối
|
||||
```
|
||||
|
||||
Hoặc JSON inline:
|
||||
|
||||
```toml
|
||||
[identity]
|
||||
format = "aieos"
|
||||
aieos_inline = '''
|
||||
{
|
||||
"identity": {
|
||||
"names": { "first": "Nova", "nickname": "N" },
|
||||
"bio": { "gender": "Non-binary", "age_biological": 3 },
|
||||
"origin": { "nationality": "Digital", "birthplace": { "city": "Cloud" } }
|
||||
},
|
||||
"psychology": {
|
||||
"neural_matrix": { "creativity": 0.9, "logic": 0.8 },
|
||||
"traits": {
|
||||
"mbti": "ENTP",
|
||||
"ocean": { "openness": 0.8, "conscientiousness": 0.6 }
|
||||
},
|
||||
"moral_compass": {
|
||||
"alignment": "Chaotic Good",
|
||||
"core_values": ["Curiosity", "Autonomy"]
|
||||
}
|
||||
},
|
||||
"linguistics": {
|
||||
"text_style": {
|
||||
"formality_level": 0.2,
|
||||
"style_descriptors": ["curious", "energetic"]
|
||||
},
|
||||
"idiolect": {
|
||||
"catchphrases": ["Let's test this"],
|
||||
"forbidden_words": ["never"]
|
||||
}
|
||||
},
|
||||
"motivations": {
|
||||
"core_drive": "Push boundaries and explore possibilities",
|
||||
"goals": {
|
||||
"short_term": ["Prototype quickly"],
|
||||
"long_term": ["Build reliable systems"]
|
||||
}
|
||||
},
|
||||
"capabilities": {
|
||||
"skills": [{ "name": "Rust engineering" }, { "name": "Prompt design" }],
|
||||
"tools": ["shell", "file_read"]
|
||||
}
|
||||
}
|
||||
'''
|
||||
```
|
||||
|
||||
ZeroClaw chấp nhận cả payload AIEOS đầy đủ lẫn dạng rút gọn, rồi chuẩn hóa về một định dạng system prompt thống nhất.
|
||||
|
||||
#### Các phần trong Schema AIEOS
|
||||
|
||||
| Phần | Mô tả |
|
||||
|---------|-------------|
|
||||
| `identity` | Tên, tiểu sử, xuất xứ, nơi cư trú |
|
||||
| `psychology` | Neural matrix (trọng số nhận thức), MBTI, OCEAN, la bàn đạo đức |
|
||||
| `linguistics` | Phong cách văn bản, mức độ trang trọng, câu cửa miệng, từ bị cấm |
|
||||
| `motivations` | Động lực cốt lõi, mục tiêu ngắn/dài hạn, nỗi sợ hãi |
|
||||
| `capabilities` | Kỹ năng và tool mà agent có thể truy cập |
|
||||
| `physicality` | Mô tả hình ảnh cho việc tạo ảnh |
|
||||
| `history` | Câu chuyện xuất xứ, học vấn, nghề nghiệp |
|
||||
| `interests` | Sở thích, điều yêu thích, lối sống |
|
||||
|
||||
Xem [aieos.org](https://aieos.org) để có schema đầy đủ và ví dụ trực tiếp.
|
||||
|
||||
## Gateway API
|
||||
|
||||
| Endpoint | Phương thức | Xác thực | Mô tả |
|
||||
|----------|--------|------|-------------|
|
||||
| `/health` | GET | Không | Kiểm tra sức khỏe (luôn công khai, không lộ bí mật) |
|
||||
| `/pair` | POST | Header `X-Pairing-Code` | Đổi mã một lần lấy bearer token |
|
||||
| `/webhook` | POST | `Authorization: Bearer <token>` | Gửi tin nhắn: `{"message": "your prompt"}`; tùy chọn `X-Idempotency-Key` |
|
||||
| `/whatsapp` | GET | Query params | Xác minh webhook Meta (hub.mode, hub.verify_token, hub.challenge) |
|
||||
| `/whatsapp` | POST | Chữ ký Meta (`X-Hub-Signature-256`) khi app secret được cấu hình | Webhook tin nhắn đến WhatsApp |
|
||||
|
||||
## Lệnh
|
||||
|
||||
| Lệnh | Mô tả |
|
||||
|---------|-------------|
|
||||
| `onboard` | Cài đặt nhanh (mặc định) |
|
||||
| `agent` | Chế độ chat tương tác hoặc một tin nhắn |
|
||||
| `gateway` | Khởi động webhook server (mặc định: `127.0.0.1:42617`) |
|
||||
| `daemon` | Khởi động runtime tự trị chạy lâu dài |
|
||||
| `service` | Quản lý dịch vụ nền cấp người dùng |
|
||||
| `doctor` | Chẩn đoán trạng thái hoạt động daemon/scheduler/channel |
|
||||
| `status` | Hiển thị trạng thái hệ thống đầy đủ |
|
||||
| `cron` | Quản lý tác vụ lên lịch (`list/add/add-at/add-every/once/remove/update/pause/resume`) |
|
||||
| `models` | Làm mới danh mục model của provider (`models refresh`) |
|
||||
| `providers` | Liệt kê provider và alias được hỗ trợ |
|
||||
| `channel` | Liệt kê/khởi động/chẩn đoán channel và gắn định danh Telegram |
|
||||
| `integrations` | Kiểm tra thông tin cài đặt tích hợp |
|
||||
| `skills` | Liệt kê/cài đặt/gỡ bỏ skill |
|
||||
| `migrate` | Import dữ liệu từ runtime khác (`migrate openclaw`) |
|
||||
| `hardware` | Lệnh khám phá/kiểm tra/thông tin USB |
|
||||
| `peripheral` | Quản lý và flash thiết bị ngoại vi phần cứng |
|
||||
|
||||
Để có hướng dẫn lệnh theo tác vụ, xem [`docs/reference/cli/commands-reference.md`](docs/reference/cli/commands-reference.md).
|
||||
|
||||
### Opt-In Open-Skills
|
||||
|
||||
Đồng bộ `open-skills` của cộng đồng bị tắt theo mặc định. Bật tường minh trong `config.toml`:
|
||||
|
||||
```toml
|
||||
[skills]
|
||||
open_skills_enabled = true
|
||||
# open_skills_dir = "/path/to/open-skills" # tùy chọn
|
||||
```
|
||||
|
||||
Bạn cũng có thể ghi đè lúc runtime với `ZEROCLAW_OPEN_SKILLS_ENABLED` và `ZEROCLAW_OPEN_SKILLS_DIR`.
|
||||
|
||||
## Phát triển
|
||||
|
||||
```bash
|
||||
cargo build # Build phát triển
|
||||
cargo build --release # Build release (codegen-units=1, hoạt động trên mọi thiết bị kể cả Raspberry Pi)
|
||||
cargo build --profile release-fast # Build nhanh hơn (codegen-units=8, yêu cầu RAM 16GB+)
|
||||
cargo test # Chạy toàn bộ test suite
|
||||
cargo clippy --locked --all-targets -- -D clippy::correctness
|
||||
cargo fmt # Định dạng code
|
||||
|
||||
# Chạy benchmark SQLite vs Markdown
|
||||
cargo test --test memory_comparison -- --nocapture
|
||||
```
|
||||
|
||||
### Hook pre-push
|
||||
|
||||
Một git hook chạy `cargo fmt --check`, `cargo clippy -- -D warnings`, và `cargo test` trước mỗi lần push. Bật một lần:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
### Khắc phục sự cố build (lỗi OpenSSL trên Linux)
|
||||
|
||||
Nếu bạn gặp lỗi build `openssl-sys`, đồng bộ dependencies và rebuild với lockfile của repository:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
cargo build --release --locked
|
||||
cargo install --path . --force --locked
|
||||
```
|
||||
|
||||
ZeroClaw được cấu hình để dùng `rustls` cho các dependencies HTTP/TLS; `--locked` giữ cho dependency graph nhất quán trên các môi trường mới.
|
||||
|
||||
Để bỏ qua hook khi cần push nhanh trong quá trình phát triển:
|
||||
|
||||
```bash
|
||||
git push --no-verify
|
||||
```
|
||||
|
||||
## Cộng tác & Tài liệu
|
||||
|
||||
Bắt đầu từ trung tâm tài liệu để có bản đồ theo tác vụ:
|
||||
|
||||
+1
-99
@@ -53,7 +53,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="install.sh">一键部署</a> |
|
||||
<a href="https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh">一键部署</a> |
|
||||
<a href="docs/i18n/zh-CN/setup-guides/README.zh-CN.md">安装入门</a> |
|
||||
<a href="docs/README.zh-CN.md">文档总览</a> |
|
||||
<a href="docs/SUMMARY.zh-CN.md">文档目录</a>
|
||||
@@ -223,104 +223,6 @@ zeroclaw agent --provider openai-codex --auth-profile openai-codex:work -m "hell
|
||||
zeroclaw agent --provider anthropic -m "hello"
|
||||
```
|
||||
|
||||
## 架构
|
||||
|
||||
每个子系统都是一个 **Trait** — 通过配置切换即可更换实现,无需修改代码。
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/assets/architecture.svg" alt="ZeroClaw 架构图" width="900" />
|
||||
</p>
|
||||
|
||||
| 子系统 | Trait | 内置实现 | 扩展方式 |
|
||||
|--------|-------|----------|----------|
|
||||
| **AI 模型** | `Provider` | 通过 `zeroclaw providers` 查看(当前 28 个内置 + 别名,以及自定义端点) | `custom:https://your-api.com`(OpenAI 兼容)或 `anthropic-custom:https://your-api.com` |
|
||||
| **通道** | `Channel` | CLI, Telegram, Discord, Slack, Mattermost, iMessage, Matrix, Signal, WhatsApp, Linq, Email, IRC, Lark, DingTalk, QQ, Webhook | 任意消息 API |
|
||||
| **记忆** | `Memory` | SQLite 混合搜索, PostgreSQL 后端, Lucid 桥接, Markdown 文件, 显式 `none` 后端, 快照/恢复, 可选响应缓存 | 任意持久化后端 |
|
||||
| **工具** | `Tool` | shell/file/memory, cron/schedule, git, pushover, browser, http_request, screenshot/image_info, composio (opt-in), delegate, 硬件工具 | 任意能力 |
|
||||
| **可观测性** | `Observer` | Noop, Log, Multi | Prometheus, OTel |
|
||||
| **运行时** | `RuntimeAdapter` | Native, Docker(沙箱) | 通过 adapter 添加;不支持的类型会快速失败 |
|
||||
| **安全** | `SecurityPolicy` | Gateway 配对, 沙箱, allowlist, 速率限制, 文件系统作用域, 加密密钥 | — |
|
||||
| **身份** | `IdentityConfig` | OpenClaw (markdown), AIEOS v1.1 (JSON) | 任意身份格式 |
|
||||
| **隧道** | `Tunnel` | None, Cloudflare, Tailscale, ngrok, Custom | 任意隧道工具 |
|
||||
| **心跳** | Engine | HEARTBEAT.md 定期任务 | — |
|
||||
| **技能** | Loader | TOML 清单 + SKILL.md 指令 | 社区技能包 |
|
||||
| **集成** | Registry | 9 个分类下 70+ 集成 | 插件系统 |
|
||||
|
||||
### 运行时支持(当前)
|
||||
|
||||
- ✅ 当前支持:`runtime.kind = "native"` 或 `runtime.kind = "docker"`
|
||||
- 🚧 计划中,尚未实现:WASM / 边缘运行时
|
||||
|
||||
配置了不支持的 `runtime.kind` 时,ZeroClaw 会以明确的错误退出,而非静默回退到 native。
|
||||
|
||||
### 记忆系统(全栈搜索引擎)
|
||||
|
||||
全部自研,零外部依赖 — 无需 Pinecone、Elasticsearch、LangChain:
|
||||
|
||||
| 层级 | 实现 |
|
||||
|------|------|
|
||||
| **向量数据库** | Embeddings 以 BLOB 存储于 SQLite,余弦相似度搜索 |
|
||||
| **关键词搜索** | FTS5 虚拟表,BM25 评分 |
|
||||
| **混合合并** | 自定义加权合并函数(`vector.rs`) |
|
||||
| **Embeddings** | `EmbeddingProvider` trait — OpenAI、自定义 URL 或 noop |
|
||||
| **分块** | 基于行的 Markdown 分块器,保留标题结构 |
|
||||
| **缓存** | SQLite `embedding_cache` 表,LRU 淘汰策略 |
|
||||
| **安全重索引** | 原子化重建 FTS5 + 重新嵌入缺失向量 |
|
||||
|
||||
Agent 通过工具自动进行记忆的回忆、保存和管理。
|
||||
|
||||
```toml
|
||||
[memory]
|
||||
backend = "sqlite" # "sqlite", "lucid", "postgres", "markdown", "none"
|
||||
auto_save = true
|
||||
embedding_provider = "none" # "none", "openai", "custom:https://..."
|
||||
vector_weight = 0.7
|
||||
keyword_weight = 0.3
|
||||
```
|
||||
|
||||
## 安全默认行为(关键)
|
||||
|
||||
- Gateway 默认绑定:`127.0.0.1:42617`
|
||||
- Gateway 默认要求配对:`require_pairing = true`
|
||||
- 默认拒绝公网绑定:`allow_public_bind = false`
|
||||
- Channel allowlist 语义:
|
||||
- 空列表 `[]` => deny-by-default
|
||||
- `"*"` => allow all(仅在明确知道风险时使用)
|
||||
|
||||
## 常用配置片段
|
||||
|
||||
```toml
|
||||
api_key = "sk-..."
|
||||
default_provider = "openrouter"
|
||||
default_model = "anthropic/claude-sonnet-4-6"
|
||||
default_temperature = 0.7
|
||||
|
||||
[memory]
|
||||
backend = "sqlite" # sqlite | lucid | markdown | none
|
||||
auto_save = true
|
||||
embedding_provider = "none" # none | openai | custom:https://...
|
||||
|
||||
[gateway]
|
||||
host = "127.0.0.1"
|
||||
port = 42617
|
||||
require_pairing = true
|
||||
allow_public_bind = false
|
||||
```
|
||||
|
||||
## 文档导航(推荐从这里开始)
|
||||
|
||||
- 文档总览(英文):[`docs/README.md`](docs/README.md)
|
||||
- 统一目录(TOC):[`docs/SUMMARY.md`](docs/SUMMARY.md)
|
||||
- 文档总览(简体中文):[`docs/README.zh-CN.md`](docs/README.zh-CN.md)
|
||||
- 命令参考:[`docs/reference/cli/commands-reference.md`](docs/i18n/zh-CN/reference/cli/commands-reference.zh-CN.md)
|
||||
- 配置参考:[`docs/reference/api/config-reference.md`](docs/i18n/zh-CN/reference/api/config-reference.zh-CN.md)
|
||||
- Provider 参考:[`docs/reference/api/providers-reference.md`](docs/i18n/zh-CN/reference/api/providers-reference.zh-CN.md)
|
||||
- Channel 参考:[`docs/reference/api/channels-reference.md`](docs/i18n/zh-CN/reference/api/channels-reference.zh-CN.md)
|
||||
- 运维手册:[`docs/ops/operations-runbook.md`](docs/i18n/zh-CN/ops/operations-runbook.zh-CN.md)
|
||||
- 故障排查:[`docs/ops/troubleshooting.md`](docs/i18n/zh-CN/ops/troubleshooting.zh-CN.md)
|
||||
- 文档清单与分类:[`docs/maintainers/docs-inventory.md`](docs/i18n/zh-CN/maintainers/docs-inventory.zh-CN.md)
|
||||
- 项目 triage 快照(2026-02-18):[`docs/maintainers/project-triage-snapshot-2026-02-18.md`](docs/i18n/zh-CN/maintainers/project-triage-snapshot-2026-02-18.zh-CN.md)
|
||||
|
||||
## 贡献与许可证
|
||||
|
||||
- 贡献指南:[`CONTRIBUTING.md`](CONTRIBUTING.md)
|
||||
|
||||
+426
-210
@@ -47,61 +47,89 @@ fi
|
||||
# --- From here on, we are running under bash ---
|
||||
set -euo pipefail
|
||||
|
||||
# --- Color and styling ---
|
||||
if [[ -t 1 ]]; then
|
||||
BLUE='\033[0;34m'
|
||||
BOLD_BLUE='\033[1;34m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
RED='\033[0;31m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
RESET='\033[0m'
|
||||
else
|
||||
BLUE='' BOLD_BLUE='' GREEN='' YELLOW='' RED='' BOLD='' DIM='' RESET=''
|
||||
fi
|
||||
|
||||
CRAB="🦀"
|
||||
|
||||
info() {
|
||||
echo "==> $*"
|
||||
echo -e "${BLUE}${CRAB}${RESET} ${BOLD}$*${RESET}"
|
||||
}
|
||||
|
||||
step_ok() {
|
||||
echo -e " ${GREEN}✓${RESET} $*"
|
||||
}
|
||||
|
||||
step_dot() {
|
||||
echo -e " ${DIM}·${RESET} $*"
|
||||
}
|
||||
|
||||
step_fail() {
|
||||
echo -e " ${RED}✗${RESET} $*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo "warning: $*" >&2
|
||||
echo -e "${YELLOW}!${RESET} $*" >&2
|
||||
}
|
||||
|
||||
error() {
|
||||
echo "error: $*" >&2
|
||||
echo -e "${RED}✗${RESET} ${RED}$*${RESET}" >&2
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
ZeroClaw installer
|
||||
ZeroClaw installer — one-click bootstrap
|
||||
|
||||
Usage:
|
||||
./install.sh [options]
|
||||
|
||||
Modes:
|
||||
Default mode installs/builds ZeroClaw only (requires existing Rust toolchain).
|
||||
Guided mode asks setup questions and configures options interactively.
|
||||
Optional bootstrap mode can also install system dependencies and Rust.
|
||||
The installer builds ZeroClaw, configures your provider and API key,
|
||||
starts the gateway service, and opens the dashboard — all in one step.
|
||||
|
||||
Options:
|
||||
--guided Run interactive guided installer
|
||||
--guided Run interactive guided installer (default on Linux TTY)
|
||||
--no-guided Disable guided installer
|
||||
--docker Run install in Docker-compatible mode and launch onboarding inside the container
|
||||
--docker Run install in Docker-compatible mode
|
||||
--install-system-deps Install build dependencies (Linux/macOS)
|
||||
--install-rust Install Rust via rustup if missing
|
||||
--prefer-prebuilt Try latest release binary first; fallback to source build on miss
|
||||
--prebuilt-only Install only from latest release binary (no source build fallback)
|
||||
--force-source-build Disable prebuilt flow and always build from source
|
||||
--onboard Run onboarding after install
|
||||
--interactive-onboard Run interactive onboarding (implies --onboard)
|
||||
--api-key <key> API key for non-interactive onboarding
|
||||
--provider <id> Provider for non-interactive onboarding (default: openrouter)
|
||||
--model <id> Model for non-interactive onboarding (optional)
|
||||
--api-key <key> API key (skips interactive prompt)
|
||||
--provider <id> Provider (default: openrouter)
|
||||
--model <id> Model (optional)
|
||||
--skip-onboard Skip provider/API key configuration
|
||||
--skip-build Skip build step
|
||||
--skip-install Skip cargo install step
|
||||
--build-first Alias for explicitly enabling separate `cargo build --release --locked`
|
||||
--skip-build Skip build step (`cargo build --release --locked` or Docker image build)
|
||||
--skip-install Skip `cargo install --path . --force --locked`
|
||||
-h, --help Show help
|
||||
|
||||
Examples:
|
||||
./install.sh
|
||||
./install.sh --guided
|
||||
./install.sh --install-system-deps --install-rust
|
||||
./install.sh --prefer-prebuilt
|
||||
./install.sh --prebuilt-only
|
||||
./install.sh --onboard --api-key "sk-..." --provider openrouter [--model "openrouter/auto"]
|
||||
./install.sh --interactive-onboard
|
||||
# One-click install (interactive)
|
||||
curl -fsSL https://zeroclawlabs.ai/install.sh | bash
|
||||
|
||||
# Non-interactive with API key
|
||||
./install.sh --api-key "sk-..." --provider openrouter
|
||||
|
||||
# Prebuilt binary (fastest)
|
||||
./install.sh --prefer-prebuilt --api-key "sk-..."
|
||||
|
||||
# Docker deploy
|
||||
./install.sh --docker
|
||||
|
||||
# Remote one-liner
|
||||
curl -fsSL https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/master/install.sh | bash
|
||||
# Build only, configure later
|
||||
./install.sh --skip-onboard
|
||||
|
||||
Environment:
|
||||
ZEROCLAW_CONTAINER_CLI Container CLI command (default: docker; auto-fallback: podman)
|
||||
@@ -268,7 +296,7 @@ install_prebuilt_binary() {
|
||||
temp_dir="$(mktemp -d -t zeroclaw-prebuilt-XXXXXX)"
|
||||
archive_path="$temp_dir/${asset_name}"
|
||||
|
||||
info "Attempting pre-built binary install for target: $target"
|
||||
step_dot "Attempting pre-built binary install for target: $target"
|
||||
if ! curl -fsSL "$archive_url" -o "$archive_path"; then
|
||||
warn "Could not download release asset: $archive_url"
|
||||
rm -rf "$temp_dir"
|
||||
@@ -296,7 +324,7 @@ install_prebuilt_binary() {
|
||||
install -m 0755 "$extracted_bin" "$install_dir/zeroclaw"
|
||||
rm -rf "$temp_dir"
|
||||
|
||||
info "Installed pre-built binary to $install_dir/zeroclaw"
|
||||
step_ok "Installed pre-built binary to $install_dir/zeroclaw"
|
||||
if [[ ":$PATH:" != *":$install_dir:"* ]]; then
|
||||
warn "$install_dir is not in PATH for this shell."
|
||||
warn "Run: export PATH=\"$install_dir:\$PATH\""
|
||||
@@ -463,16 +491,16 @@ prompt_yes_no() {
|
||||
}
|
||||
|
||||
install_system_deps() {
|
||||
info "Installing system dependencies"
|
||||
step_dot "Installing system dependencies"
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux)
|
||||
if have_cmd apk; then
|
||||
find_missing_alpine_prereqs
|
||||
if [[ ${#ALPINE_MISSING_PKGS[@]} -eq 0 ]]; then
|
||||
info "Alpine prerequisites already installed"
|
||||
step_ok "Alpine prerequisites already installed"
|
||||
else
|
||||
info "Installing Alpine prerequisites: ${ALPINE_MISSING_PKGS[*]}"
|
||||
step_dot "Installing Alpine prerequisites: ${ALPINE_MISSING_PKGS[*]}"
|
||||
run_privileged apk add --no-cache "${ALPINE_MISSING_PKGS[@]}"
|
||||
fi
|
||||
elif have_cmd apt-get; then
|
||||
@@ -505,7 +533,7 @@ install_system_deps() {
|
||||
;;
|
||||
Darwin)
|
||||
if ! xcode-select -p >/dev/null 2>&1; then
|
||||
info "Installing Xcode Command Line Tools"
|
||||
step_dot "Installing Xcode Command Line Tools"
|
||||
xcode-select --install || true
|
||||
cat <<'MSG'
|
||||
Please complete the Xcode Command Line Tools installation dialog,
|
||||
@@ -525,7 +553,7 @@ MSG
|
||||
|
||||
install_rust_toolchain() {
|
||||
if have_cmd cargo && have_cmd rustc; then
|
||||
info "Rust already installed: $(rustc --version)"
|
||||
step_ok "Rust already installed: $(rustc --version)"
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -534,7 +562,7 @@ install_rust_toolchain() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Installing Rust via rustup"
|
||||
step_dot "Installing Rust via rustup"
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
|
||||
if [[ -f "$HOME/.cargo/env" ]]; then
|
||||
@@ -549,11 +577,98 @@ install_rust_toolchain() {
|
||||
fi
|
||||
}
|
||||
|
||||
prompt_provider() {
|
||||
local provider_input=""
|
||||
echo
|
||||
echo -e " ${BOLD}Select your AI provider${RESET}"
|
||||
echo -e " ${DIM}(press Enter for default: ${PROVIDER})${RESET}"
|
||||
echo
|
||||
echo -e " ${BOLD_BLUE}1)${RESET} OpenRouter ${DIM}(recommended — multi-model gateway)${RESET}"
|
||||
echo -e " ${BOLD_BLUE}2)${RESET} Anthropic ${DIM}(Claude)${RESET}"
|
||||
echo -e " ${BOLD_BLUE}3)${RESET} OpenAI ${DIM}(GPT)${RESET}"
|
||||
echo -e " ${BOLD_BLUE}4)${RESET} Gemini ${DIM}(Google)${RESET}"
|
||||
echo -e " ${BOLD_BLUE}5)${RESET} Ollama ${DIM}(local, no API key needed)${RESET}"
|
||||
echo -e " ${BOLD_BLUE}6)${RESET} Groq ${DIM}(fast inference)${RESET}"
|
||||
echo -e " ${BOLD_BLUE}7)${RESET} Venice ${DIM}(privacy-focused)${RESET}"
|
||||
echo -e " ${BOLD_BLUE}8)${RESET} Other ${DIM}(enter provider ID manually)${RESET}"
|
||||
echo
|
||||
|
||||
if ! guided_read provider_input " Provider [1]: "; then
|
||||
error "input was interrupted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "${provider_input:-1}" in
|
||||
1|"") PROVIDER="openrouter" ;;
|
||||
2) PROVIDER="anthropic" ;;
|
||||
3) PROVIDER="openai" ;;
|
||||
4) PROVIDER="gemini" ;;
|
||||
5) PROVIDER="ollama" ;;
|
||||
6) PROVIDER="groq" ;;
|
||||
7) PROVIDER="venice" ;;
|
||||
8)
|
||||
if ! guided_read provider_input " Provider ID: "; then
|
||||
error "input was interrupted."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$provider_input" ]]; then
|
||||
PROVIDER="$provider_input"
|
||||
fi
|
||||
;;
|
||||
*) PROVIDER="openrouter" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
prompt_api_key() {
|
||||
local api_key_input=""
|
||||
|
||||
if [[ "$PROVIDER" == "ollama" ]]; then
|
||||
step_ok "Ollama selected — no API key required"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo
|
||||
if [[ -n "$API_KEY" ]]; then
|
||||
step_ok "API key provided via environment/flag"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e " ${BOLD}Enter your ${PROVIDER} API key${RESET}"
|
||||
echo -e " ${DIM}(input is hidden; leave empty to configure later)${RESET}"
|
||||
echo
|
||||
|
||||
if ! guided_read api_key_input " API key: " true; then
|
||||
echo
|
||||
error "input was interrupted."
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
|
||||
if [[ -n "$api_key_input" ]]; then
|
||||
API_KEY="$api_key_input"
|
||||
step_ok "API key set"
|
||||
else
|
||||
warn "No API key entered — you can configure it later with zeroclaw onboard"
|
||||
SKIP_ONBOARD=true
|
||||
fi
|
||||
}
|
||||
|
||||
prompt_model() {
|
||||
local model_input=""
|
||||
|
||||
echo -e " ${DIM}Model (press Enter for provider default):${RESET}"
|
||||
if ! guided_read model_input " Model [default]: "; then
|
||||
error "input was interrupted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$model_input" ]]; then
|
||||
MODEL="$model_input"
|
||||
fi
|
||||
}
|
||||
|
||||
run_guided_installer() {
|
||||
local os_name="$1"
|
||||
local provider_input=""
|
||||
local model_input=""
|
||||
local api_key_input=""
|
||||
|
||||
if ! guided_input_stream >/dev/null; then
|
||||
error "guided installer requires an interactive terminal."
|
||||
@@ -562,10 +677,11 @@ run_guided_installer() {
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "ZeroClaw guided installer"
|
||||
echo "Answer a few questions, then the installer will run automatically."
|
||||
echo -e " ${BOLD_BLUE}${CRAB} ZeroClaw Guided Installer${RESET}"
|
||||
echo -e " ${DIM}Answer a few questions, then the installer will handle everything.${RESET}"
|
||||
echo
|
||||
|
||||
# --- System dependencies ---
|
||||
if [[ "$os_name" == "Linux" ]]; then
|
||||
if prompt_yes_no "Install Linux build dependencies (toolchain/pkg-config/git/curl)?" "yes"; then
|
||||
INSTALL_SYSTEM_DEPS=true
|
||||
@@ -576,89 +692,34 @@ run_guided_installer() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Rust toolchain ---
|
||||
if have_cmd cargo && have_cmd rustc; then
|
||||
info "Detected Rust toolchain: $(rustc --version)"
|
||||
step_ok "Detected Rust toolchain: $(rustc --version)"
|
||||
else
|
||||
if prompt_yes_no "Rust toolchain not found. Install Rust via rustup now?" "yes"; then
|
||||
INSTALL_RUST=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if prompt_yes_no "Run a separate prebuild before install?" "yes"; then
|
||||
SKIP_BUILD=false
|
||||
else
|
||||
SKIP_BUILD=true
|
||||
fi
|
||||
|
||||
if prompt_yes_no "Install zeroclaw into cargo bin now?" "yes"; then
|
||||
SKIP_INSTALL=false
|
||||
else
|
||||
SKIP_INSTALL=true
|
||||
fi
|
||||
|
||||
if prompt_yes_no "Run onboarding after install?" "no"; then
|
||||
RUN_ONBOARD=true
|
||||
if prompt_yes_no "Use interactive onboarding?" "yes"; then
|
||||
INTERACTIVE_ONBOARD=true
|
||||
else
|
||||
INTERACTIVE_ONBOARD=false
|
||||
if ! guided_read provider_input "Provider [$PROVIDER]: "; then
|
||||
error "guided installer input was interrupted."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$provider_input" ]]; then
|
||||
PROVIDER="$provider_input"
|
||||
fi
|
||||
|
||||
if ! guided_read model_input "Model [${MODEL:-leave empty}]: "; then
|
||||
error "guided installer input was interrupted."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$model_input" ]]; then
|
||||
MODEL="$model_input"
|
||||
fi
|
||||
|
||||
if [[ -z "$API_KEY" ]]; then
|
||||
if ! guided_read api_key_input "API key (hidden, leave empty to switch to interactive onboarding): " true; then
|
||||
echo
|
||||
error "guided installer input was interrupted."
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
if [[ -n "$api_key_input" ]]; then
|
||||
API_KEY="$api_key_input"
|
||||
else
|
||||
warn "No API key entered. Using interactive onboarding instead."
|
||||
INTERACTIVE_ONBOARD=true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
# --- Provider + API key (inline onboarding) ---
|
||||
prompt_provider
|
||||
prompt_api_key
|
||||
prompt_model
|
||||
|
||||
# --- Install plan summary ---
|
||||
echo
|
||||
info "Installer plan"
|
||||
local install_binary=true
|
||||
local build_first=false
|
||||
if [[ "$SKIP_INSTALL" == true ]]; then
|
||||
install_binary=false
|
||||
echo -e "${BOLD}Install plan${RESET}"
|
||||
step_dot "OS: $(echo "$os_name" | tr '[:upper:]' '[:lower:]')"
|
||||
step_dot "Install system deps: $(bool_to_word "$INSTALL_SYSTEM_DEPS")"
|
||||
step_dot "Install Rust: $(bool_to_word "$INSTALL_RUST")"
|
||||
step_dot "Provider: ${PROVIDER}"
|
||||
if [[ -n "$MODEL" ]]; then
|
||||
step_dot "Model: ${MODEL}"
|
||||
fi
|
||||
if [[ "$SKIP_BUILD" == false ]]; then
|
||||
build_first=true
|
||||
fi
|
||||
echo " docker-mode: $(bool_to_word "$DOCKER_MODE")"
|
||||
echo " install-system-deps: $(bool_to_word "$INSTALL_SYSTEM_DEPS")"
|
||||
echo " install-rust: $(bool_to_word "$INSTALL_RUST")"
|
||||
echo " build-first: $(bool_to_word "$build_first")"
|
||||
echo " install-binary: $(bool_to_word "$install_binary")"
|
||||
echo " onboard: $(bool_to_word "$RUN_ONBOARD")"
|
||||
if [[ "$RUN_ONBOARD" == true ]]; then
|
||||
echo " interactive-onboard: $(bool_to_word "$INTERACTIVE_ONBOARD")"
|
||||
if [[ "$INTERACTIVE_ONBOARD" == false ]]; then
|
||||
echo " provider: $PROVIDER"
|
||||
if [[ -n "$MODEL" ]]; then
|
||||
echo " model: $MODEL"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "$API_KEY" ]]; then
|
||||
step_ok "API key: configured"
|
||||
else
|
||||
step_dot "API key: not set (configure later)"
|
||||
fi
|
||||
|
||||
echo
|
||||
@@ -758,42 +819,37 @@ run_docker_bootstrap() {
|
||||
info "Container CLI: $CONTAINER_CLI"
|
||||
|
||||
local onboard_cmd=()
|
||||
if [[ "$INTERACTIVE_ONBOARD" == true ]]; then
|
||||
info "Launching interactive onboarding in container"
|
||||
onboard_cmd=(onboard --interactive)
|
||||
else
|
||||
if [[ -z "$API_KEY" ]]; then
|
||||
cat <<'MSG'
|
||||
==> Onboarding requested, but API key not provided.
|
||||
Use either:
|
||||
--api-key "sk-..."
|
||||
or:
|
||||
ZEROCLAW_API_KEY="sk-..." ./install.sh --docker
|
||||
or run interactive:
|
||||
./install.sh --docker --interactive-onboard
|
||||
MSG
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$SKIP_ONBOARD" == true ]]; then
|
||||
info "Skipping onboarding in container"
|
||||
onboard_cmd=()
|
||||
elif [[ -n "$API_KEY" ]]; then
|
||||
if [[ -n "$MODEL" ]]; then
|
||||
info "Launching quick onboarding in container (provider: $PROVIDER, model: $MODEL)"
|
||||
info "Configuring provider in container (provider: $PROVIDER, model: $MODEL)"
|
||||
else
|
||||
info "Launching quick onboarding in container (provider: $PROVIDER)"
|
||||
info "Configuring provider in container (provider: $PROVIDER)"
|
||||
fi
|
||||
onboard_cmd=(onboard --api-key "$API_KEY" --provider "$PROVIDER")
|
||||
if [[ -n "$MODEL" ]]; then
|
||||
onboard_cmd+=(--model "$MODEL")
|
||||
fi
|
||||
else
|
||||
info "Launching setup in container"
|
||||
onboard_cmd=(onboard --provider "$PROVIDER")
|
||||
fi
|
||||
|
||||
"$CONTAINER_CLI" run --rm -it \
|
||||
"${container_run_namespace_args[@]+"${container_run_namespace_args[@]}"}" \
|
||||
"${container_run_user_args[@]}" \
|
||||
-e HOME=/zeroclaw-data \
|
||||
-e ZEROCLAW_WORKSPACE=/zeroclaw-data/workspace \
|
||||
-v "$config_mount" \
|
||||
-v "$workspace_mount" \
|
||||
"$docker_image" \
|
||||
"${onboard_cmd[@]}"
|
||||
if [[ ${#onboard_cmd[@]} -gt 0 ]]; then
|
||||
"$CONTAINER_CLI" run --rm -it \
|
||||
"${container_run_namespace_args[@]+"${container_run_namespace_args[@]}"}" \
|
||||
"${container_run_user_args[@]}" \
|
||||
-e HOME=/zeroclaw-data \
|
||||
-e ZEROCLAW_WORKSPACE=/zeroclaw-data/workspace \
|
||||
-v "$config_mount" \
|
||||
-v "$workspace_mount" \
|
||||
"$docker_image" \
|
||||
"${onboard_cmd[@]}"
|
||||
else
|
||||
info "Docker image ready. Run zeroclaw onboard inside the container to configure."
|
||||
fi
|
||||
}
|
||||
|
||||
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}"
|
||||
@@ -809,8 +865,7 @@ INSTALL_RUST=false
|
||||
PREFER_PREBUILT=false
|
||||
PREBUILT_ONLY=false
|
||||
FORCE_SOURCE_BUILD=false
|
||||
RUN_ONBOARD=false
|
||||
INTERACTIVE_ONBOARD=false
|
||||
SKIP_ONBOARD=false
|
||||
SKIP_BUILD=false
|
||||
SKIP_INSTALL=false
|
||||
PREBUILT_INSTALLED=false
|
||||
@@ -853,13 +908,8 @@ while [[ $# -gt 0 ]]; do
|
||||
FORCE_SOURCE_BUILD=true
|
||||
shift
|
||||
;;
|
||||
--onboard)
|
||||
RUN_ONBOARD=true
|
||||
shift
|
||||
;;
|
||||
--interactive-onboard)
|
||||
RUN_ONBOARD=true
|
||||
INTERACTIVE_ONBOARD=true
|
||||
--skip-onboard)
|
||||
SKIP_ONBOARD=true
|
||||
shift
|
||||
;;
|
||||
--api-key)
|
||||
@@ -990,8 +1040,51 @@ if [[ ! -f "$WORK_DIR/Cargo.toml" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
info "ZeroClaw installer"
|
||||
echo " workspace: $WORK_DIR"
|
||||
echo
|
||||
echo -e " ${BOLD_BLUE}${CRAB} ZeroClaw Installer${RESET}"
|
||||
echo -e " ${DIM}Build it, run it, trust it.${RESET}"
|
||||
echo
|
||||
step_ok "Detected: ${BOLD}$(echo "$OS_NAME" | tr '[:upper:]' '[:lower:]')${RESET}"
|
||||
|
||||
# --- Detect existing installation and version ---
|
||||
EXISTING_VERSION=""
|
||||
INSTALL_MODE="fresh"
|
||||
if have_cmd zeroclaw; then
|
||||
EXISTING_VERSION="$(zeroclaw --version 2>/dev/null | awk '{print $NF}' || true)"
|
||||
INSTALL_MODE="upgrade"
|
||||
elif [[ -x "$HOME/.cargo/bin/zeroclaw" ]]; then
|
||||
EXISTING_VERSION="$("$HOME/.cargo/bin/zeroclaw" --version 2>/dev/null | awk '{print $NF}' || true)"
|
||||
INSTALL_MODE="upgrade"
|
||||
fi
|
||||
|
||||
# Determine install method
|
||||
if [[ "$DOCKER_MODE" == true ]]; then
|
||||
INSTALL_METHOD="docker"
|
||||
elif [[ "$PREBUILT_ONLY" == true || "$PREFER_PREBUILT" == true ]]; then
|
||||
INSTALL_METHOD="prebuilt binary"
|
||||
else
|
||||
INSTALL_METHOD="source (cargo)"
|
||||
fi
|
||||
|
||||
# Determine target version from Cargo.toml
|
||||
TARGET_VERSION=""
|
||||
if [[ -f "$WORK_DIR/Cargo.toml" ]]; then
|
||||
TARGET_VERSION="$(grep -m1 '^version' "$WORK_DIR/Cargo.toml" | sed 's/.*"\(.*\)".*/\1/' || true)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${BOLD}Install plan${RESET}"
|
||||
step_dot "OS: $(echo "$OS_NAME" | tr '[:upper:]' '[:lower:]')"
|
||||
step_dot "Install method: ${INSTALL_METHOD}"
|
||||
if [[ -n "$TARGET_VERSION" ]]; then
|
||||
step_dot "Requested version: v${TARGET_VERSION}"
|
||||
fi
|
||||
step_dot "Workspace: $WORK_DIR"
|
||||
if [[ "$INSTALL_MODE" == "upgrade" && -n "$EXISTING_VERSION" ]]; then
|
||||
step_dot "Existing ZeroClaw installation detected, upgrading from v${EXISTING_VERSION}"
|
||||
elif [[ "$INSTALL_MODE" == "upgrade" ]]; then
|
||||
step_dot "Existing ZeroClaw installation detected, upgrading"
|
||||
fi
|
||||
|
||||
cd "$WORK_DIR"
|
||||
|
||||
@@ -1006,26 +1099,21 @@ fi
|
||||
|
||||
if [[ "$DOCKER_MODE" == true ]]; then
|
||||
ensure_docker_ready
|
||||
if [[ "$RUN_ONBOARD" == false ]]; then
|
||||
RUN_ONBOARD=true
|
||||
if [[ -z "$API_KEY" ]]; then
|
||||
INTERACTIVE_ONBOARD=true
|
||||
fi
|
||||
fi
|
||||
run_docker_bootstrap
|
||||
cat <<'DONE'
|
||||
|
||||
✅ Docker bootstrap complete.
|
||||
|
||||
Your containerized ZeroClaw data is persisted under:
|
||||
DONE
|
||||
echo " $DOCKER_DATA_DIR"
|
||||
cat <<'DONE'
|
||||
|
||||
Next steps:
|
||||
./install.sh --docker --interactive-onboard
|
||||
./install.sh --docker --api-key "sk-..." --provider openrouter
|
||||
DONE
|
||||
echo
|
||||
echo -e "${BOLD_BLUE}${CRAB} Docker bootstrap complete!${RESET}"
|
||||
echo
|
||||
echo -e "${BOLD}Your containerized ZeroClaw data is persisted under:${RESET}"
|
||||
echo -e " ${DIM}$DOCKER_DATA_DIR${RESET}"
|
||||
echo
|
||||
echo -e "${BOLD}Dashboard URL:${RESET} ${BLUE}http://127.0.0.1:42617${RESET}"
|
||||
echo
|
||||
echo -e "${BOLD}Next steps:${RESET}"
|
||||
echo -e " ${DIM}zeroclaw status${RESET}"
|
||||
echo -e " ${DIM}zeroclaw agent -m \"Hello, ZeroClaw!\"${RESET}"
|
||||
echo -e " ${DIM}zeroclaw gateway${RESET}"
|
||||
echo
|
||||
echo -e "${BOLD}Docs:${RESET} ${BLUE}https://www.zeroclawlabs.ai/docs${RESET}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -1062,18 +1150,45 @@ MSG
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_BUILD" == false ]]; then
|
||||
info "Building release binary"
|
||||
cargo build --release --locked
|
||||
echo
|
||||
echo -e "${BOLD_BLUE}[1/3]${RESET} ${BOLD}Preparing environment${RESET}"
|
||||
if [[ "$INSTALL_SYSTEM_DEPS" == true ]]; then
|
||||
step_ok "System dependencies installed"
|
||||
else
|
||||
info "Skipping build"
|
||||
step_ok "System dependencies satisfied"
|
||||
fi
|
||||
if have_cmd cargo && have_cmd rustc; then
|
||||
step_ok "Rust $(rustc --version | awk '{print $2}') found"
|
||||
step_dot "Active Rust: $(rustc --version) ($(command -v rustc))"
|
||||
step_dot "Active cargo: $(cargo --version | awk '{print $2}') ($(command -v cargo))"
|
||||
else
|
||||
step_dot "Rust not detected"
|
||||
fi
|
||||
if have_cmd git; then
|
||||
step_ok "Git already installed"
|
||||
else
|
||||
step_dot "Git not found"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${BOLD_BLUE}[2/3]${RESET} ${BOLD}Installing ZeroClaw${RESET}"
|
||||
if [[ -n "$TARGET_VERSION" ]]; then
|
||||
step_dot "Installing ZeroClaw v${TARGET_VERSION}"
|
||||
fi
|
||||
if [[ "$SKIP_BUILD" == false ]]; then
|
||||
step_dot "Building release binary"
|
||||
cargo build --release --locked
|
||||
step_ok "Release binary built"
|
||||
else
|
||||
step_dot "Skipping build"
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_INSTALL" == false ]]; then
|
||||
info "Installing zeroclaw to cargo bin"
|
||||
step_dot "Installing zeroclaw to cargo bin"
|
||||
cargo install --path "$WORK_DIR" --force --locked
|
||||
step_ok "ZeroClaw installed"
|
||||
else
|
||||
info "Skipping install"
|
||||
step_dot "Skipping install"
|
||||
fi
|
||||
|
||||
ZEROCLAW_BIN=""
|
||||
@@ -1085,48 +1200,149 @@ elif [[ -x "$WORK_DIR/target/release/zeroclaw" ]]; then
|
||||
ZEROCLAW_BIN="$WORK_DIR/target/release/zeroclaw"
|
||||
fi
|
||||
|
||||
if [[ "$RUN_ONBOARD" == true ]]; then
|
||||
if [[ -z "$ZEROCLAW_BIN" ]]; then
|
||||
error "onboarding requested but zeroclaw binary is not available."
|
||||
error "Run without --skip-install, or ensure zeroclaw is in PATH."
|
||||
exit 1
|
||||
fi
|
||||
echo
|
||||
echo -e "${BOLD_BLUE}[3/3]${RESET} ${BOLD}Finalizing setup${RESET}"
|
||||
|
||||
if [[ "$INTERACTIVE_ONBOARD" == true ]]; then
|
||||
info "Running interactive onboarding"
|
||||
"$ZEROCLAW_BIN" onboard --interactive
|
||||
else
|
||||
if [[ -z "$API_KEY" ]]; then
|
||||
cat <<'MSG'
|
||||
==> Onboarding requested, but API key not provided.
|
||||
Use either:
|
||||
--api-key "sk-..."
|
||||
or:
|
||||
ZEROCLAW_API_KEY="sk-..." ./install.sh --onboard
|
||||
or run interactive:
|
||||
./install.sh --interactive-onboard
|
||||
MSG
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$MODEL" ]]; then
|
||||
info "Running quick onboarding (provider: $PROVIDER, model: $MODEL)"
|
||||
else
|
||||
info "Running quick onboarding (provider: $PROVIDER)"
|
||||
fi
|
||||
# --- Inline onboarding (provider + API key configuration) ---
|
||||
if [[ "$SKIP_ONBOARD" == false && -n "$ZEROCLAW_BIN" ]]; then
|
||||
if [[ -n "$API_KEY" ]]; then
|
||||
step_dot "Configuring provider: ${PROVIDER}"
|
||||
ONBOARD_CMD=("$ZEROCLAW_BIN" onboard --api-key "$API_KEY" --provider "$PROVIDER")
|
||||
if [[ -n "$MODEL" ]]; then
|
||||
ONBOARD_CMD+=(--model "$MODEL")
|
||||
fi
|
||||
"${ONBOARD_CMD[@]}"
|
||||
if "${ONBOARD_CMD[@]}" 2>/dev/null; then
|
||||
step_ok "Provider configured"
|
||||
else
|
||||
step_fail "Provider configuration failed — run zeroclaw onboard to retry"
|
||||
fi
|
||||
elif [[ "$PROVIDER" == "ollama" ]]; then
|
||||
step_dot "Configuring Ollama (no API key needed)"
|
||||
if "$ZEROCLAW_BIN" onboard --provider ollama 2>/dev/null; then
|
||||
step_ok "Ollama configured"
|
||||
else
|
||||
step_fail "Ollama configuration failed — run zeroclaw onboard to retry"
|
||||
fi
|
||||
else
|
||||
# No API key and not ollama — prompt inline if interactive, skip otherwise
|
||||
if [[ -t 0 && -t 1 ]]; then
|
||||
prompt_provider
|
||||
prompt_api_key
|
||||
if [[ -n "$API_KEY" ]]; then
|
||||
ONBOARD_CMD=("$ZEROCLAW_BIN" onboard --api-key "$API_KEY" --provider "$PROVIDER")
|
||||
if [[ -n "$MODEL" ]]; then
|
||||
ONBOARD_CMD+=(--model "$MODEL")
|
||||
fi
|
||||
if "${ONBOARD_CMD[@]}" 2>/dev/null; then
|
||||
step_ok "Provider configured"
|
||||
else
|
||||
step_fail "Provider configuration failed — run zeroclaw onboard to retry"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
step_dot "No API key provided — run zeroclaw onboard to configure"
|
||||
fi
|
||||
fi
|
||||
elif [[ "$SKIP_ONBOARD" == true ]]; then
|
||||
step_dot "Skipping configuration (run zeroclaw onboard later)"
|
||||
elif [[ -z "$ZEROCLAW_BIN" ]]; then
|
||||
warn "ZeroClaw binary not found — cannot configure provider"
|
||||
fi
|
||||
|
||||
# --- Gateway service management ---
|
||||
if [[ -n "$ZEROCLAW_BIN" ]]; then
|
||||
# Try to install and start the gateway service
|
||||
step_dot "Checking gateway service"
|
||||
if "$ZEROCLAW_BIN" service install 2>/dev/null; then
|
||||
step_ok "Gateway service installed"
|
||||
if "$ZEROCLAW_BIN" service restart 2>/dev/null; then
|
||||
step_ok "Gateway service restarted"
|
||||
else
|
||||
step_fail "Gateway service restart failed — re-run with zeroclaw service start"
|
||||
fi
|
||||
else
|
||||
step_dot "Gateway service not installed (run zeroclaw service install later)"
|
||||
fi
|
||||
|
||||
# --- Post-install doctor check ---
|
||||
step_dot "Running doctor to validate installation"
|
||||
if "$ZEROCLAW_BIN" doctor 2>/dev/null; then
|
||||
step_ok "Doctor complete"
|
||||
else
|
||||
warn "Doctor reported issues — run zeroclaw doctor --fix to resolve"
|
||||
fi
|
||||
fi
|
||||
|
||||
cat <<'DONE'
|
||||
# --- Determine installed version ---
|
||||
INSTALLED_VERSION=""
|
||||
if [[ -n "$ZEROCLAW_BIN" ]]; then
|
||||
INSTALLED_VERSION="$("$ZEROCLAW_BIN" --version 2>/dev/null | awk '{print $NF}' || true)"
|
||||
fi
|
||||
|
||||
✅ Bootstrap complete.
|
||||
# --- Success banner ---
|
||||
echo
|
||||
if [[ -n "$INSTALLED_VERSION" ]]; then
|
||||
echo -e "${BOLD_BLUE}${CRAB} ZeroClaw installed successfully (ZeroClaw ${INSTALLED_VERSION})!${RESET}"
|
||||
else
|
||||
echo -e "${BOLD_BLUE}${CRAB} ZeroClaw installed successfully!${RESET}"
|
||||
fi
|
||||
|
||||
Next steps:
|
||||
zeroclaw status
|
||||
zeroclaw agent -m "Hello, ZeroClaw!"
|
||||
zeroclaw gateway
|
||||
DONE
|
||||
if [[ "$INSTALL_MODE" == "upgrade" ]]; then
|
||||
step_dot "Upgrade complete"
|
||||
fi
|
||||
|
||||
# --- Dashboard URL ---
|
||||
GATEWAY_PORT=42617
|
||||
DASHBOARD_URL="http://127.0.0.1:${GATEWAY_PORT}"
|
||||
echo
|
||||
echo -e "${BOLD}Dashboard URL:${RESET} ${BLUE}${DASHBOARD_URL}${RESET}"
|
||||
echo -e "${DIM} Enter the pairing code shown above to connect.${RESET}"
|
||||
|
||||
# --- Copy to clipboard ---
|
||||
COPIED_TO_CLIPBOARD=false
|
||||
if [[ -t 1 ]]; then
|
||||
case "$OS_NAME" in
|
||||
Darwin)
|
||||
if have_cmd pbcopy; then
|
||||
printf '%s' "$DASHBOARD_URL" | pbcopy 2>/dev/null && COPIED_TO_CLIPBOARD=true
|
||||
fi
|
||||
;;
|
||||
Linux)
|
||||
if have_cmd xclip; then
|
||||
printf '%s' "$DASHBOARD_URL" | xclip -selection clipboard 2>/dev/null && COPIED_TO_CLIPBOARD=true
|
||||
elif have_cmd xsel; then
|
||||
printf '%s' "$DASHBOARD_URL" | xsel --clipboard 2>/dev/null && COPIED_TO_CLIPBOARD=true
|
||||
elif have_cmd wl-copy; then
|
||||
printf '%s' "$DASHBOARD_URL" | wl-copy 2>/dev/null && COPIED_TO_CLIPBOARD=true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
if [[ "$COPIED_TO_CLIPBOARD" == true ]]; then
|
||||
step_ok "Copied to clipboard"
|
||||
fi
|
||||
|
||||
# --- Open in browser ---
|
||||
if [[ -t 1 ]]; then
|
||||
case "$OS_NAME" in
|
||||
Darwin)
|
||||
if have_cmd open; then
|
||||
open "$DASHBOARD_URL" 2>/dev/null && step_ok "Opened in your browser"
|
||||
fi
|
||||
;;
|
||||
Linux)
|
||||
if have_cmd xdg-open; then
|
||||
xdg-open "$DASHBOARD_URL" 2>/dev/null && step_ok "Opened in your browser"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${BOLD}Next steps:${RESET}"
|
||||
echo -e " ${DIM}zeroclaw status${RESET}"
|
||||
echo -e " ${DIM}zeroclaw agent -m \"Hello, ZeroClaw!\"${RESET}"
|
||||
echo -e " ${DIM}zeroclaw gateway${RESET}"
|
||||
echo
|
||||
echo -e "${BOLD}Docs:${RESET} ${BLUE}https://www.zeroclawlabs.ai/docs${RESET}"
|
||||
echo
|
||||
|
||||
+25
-10
@@ -2112,14 +2112,7 @@ async fn execute_one_tool(
|
||||
observer: &dyn Observer,
|
||||
cancellation_token: Option<&CancellationToken>,
|
||||
) -> Result<ToolExecutionOutcome> {
|
||||
let args_summary = {
|
||||
let raw = call_arguments.to_string();
|
||||
if raw.len() > 300 {
|
||||
format!("{}…", &raw[..300])
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
};
|
||||
let args_summary = truncate_with_ellipsis(&call_arguments.to_string(), 300);
|
||||
observer.record_event(&ObserverEvent::ToolCallStart {
|
||||
tool: call_name.to_string(),
|
||||
arguments: Some(args_summary),
|
||||
@@ -2961,8 +2954,9 @@ pub async fn run(
|
||||
));
|
||||
|
||||
// ── Memory (the brain) ────────────────────────────────────────
|
||||
let mem: Arc<dyn Memory> = Arc::from(memory::create_memory_with_storage(
|
||||
let mem: Arc<dyn Memory> = Arc::from(memory::create_memory_with_storage_and_routes(
|
||||
&config.memory,
|
||||
&config.embedding_routes,
|
||||
Some(&config.storage.provider.config),
|
||||
&config.workspace_dir,
|
||||
config.api_key.as_deref(),
|
||||
@@ -3554,8 +3548,9 @@ pub async fn process_message(config: Config, message: &str) -> Result<String> {
|
||||
&config.autonomy,
|
||||
&config.workspace_dir,
|
||||
));
|
||||
let mem: Arc<dyn Memory> = Arc::from(memory::create_memory_with_storage(
|
||||
let mem: Arc<dyn Memory> = Arc::from(memory::create_memory_with_storage_and_routes(
|
||||
&config.memory,
|
||||
&config.embedding_routes,
|
||||
Some(&config.storage.provider.config),
|
||||
&config.workspace_dir,
|
||||
config.api_key.as_deref(),
|
||||
@@ -3843,6 +3838,26 @@ mod tests {
|
||||
assert!(scrubbed.contains("\"api_key\": \"sk-1*[REDACTED]\""));
|
||||
assert!(scrubbed.contains("public"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn execute_one_tool_does_not_panic_on_utf8_boundary() {
|
||||
let call_arguments = (0..600)
|
||||
.map(|n| serde_json::json!({ "content": format!("{}:tail", "a".repeat(n)) }))
|
||||
.find(|args| {
|
||||
let raw = args.to_string();
|
||||
raw.len() > 300 && !raw.is_char_boundary(300)
|
||||
})
|
||||
.expect("should produce a sample whose byte index 300 is not a char boundary");
|
||||
|
||||
let observer = NoopObserver;
|
||||
let result = execute_one_tool("unknown_tool", call_arguments, &[], &observer, None).await;
|
||||
assert!(result.is_ok(), "execute_one_tool should not panic or error");
|
||||
|
||||
let outcome = result.unwrap();
|
||||
assert!(!outcome.success);
|
||||
assert!(outcome.output.contains("Unknown tool: unknown_tool"));
|
||||
}
|
||||
|
||||
use crate::memory::{Memory, MemoryCategory, SqliteMemory};
|
||||
use crate::observability::NoopObserver;
|
||||
use crate::providers::traits::ProviderCapabilities;
|
||||
|
||||
@@ -40,6 +40,7 @@ impl SystemPromptBuilder {
|
||||
Box::new(WorkspaceSection),
|
||||
Box::new(DateTimeSection),
|
||||
Box::new(RuntimeSection),
|
||||
Box::new(ChannelMediaSection),
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -70,6 +71,7 @@ pub struct SkillsSection;
|
||||
pub struct WorkspaceSection;
|
||||
pub struct RuntimeSection;
|
||||
pub struct DateTimeSection;
|
||||
pub struct ChannelMediaSection;
|
||||
|
||||
impl PromptSection for IdentitySection {
|
||||
fn name(&self) -> &str {
|
||||
@@ -206,6 +208,21 @@ impl PromptSection for DateTimeSection {
|
||||
}
|
||||
}
|
||||
|
||||
impl PromptSection for ChannelMediaSection {
|
||||
fn name(&self) -> &str {
|
||||
"channel_media"
|
||||
}
|
||||
|
||||
fn build(&self, _ctx: &PromptContext<'_>) -> Result<String> {
|
||||
Ok("## Channel Media Markers\n\n\
|
||||
Messages from channels may contain media markers:\n\
|
||||
- `[Voice] <text>` — The user sent a voice/audio message that has already been transcribed to text. Respond to the transcribed content directly.\n\
|
||||
- `[IMAGE:<path>]` — An image attachment, processed by the vision pipeline.\n\
|
||||
- `[Document: <name>] <path>` — A file attachment saved to the workspace."
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_workspace_file(prompt: &mut String, workspace_dir: &Path, filename: &str) {
|
||||
let path = workspace_dir.join(filename);
|
||||
match std::fs::read_to_string(&path) {
|
||||
|
||||
+122
-58
@@ -59,6 +59,54 @@ impl LinqChannel {
|
||||
Some(format!("[IMAGE:{source}]"))
|
||||
}
|
||||
|
||||
fn sender_is_from_me(data: &serde_json::Value) -> bool {
|
||||
// Legacy format: data.is_from_me
|
||||
if let Some(v) = data.get("is_from_me").and_then(|value| value.as_bool()) {
|
||||
return v;
|
||||
}
|
||||
|
||||
// New format: data.sender_handle.is_me OR data.direction == "outbound"
|
||||
let is_me = data
|
||||
.get("sender_handle")
|
||||
.and_then(|value| value.get("is_me"))
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let is_outbound = matches!(
|
||||
data.get("direction").and_then(|value| value.as_str()),
|
||||
Some("outbound")
|
||||
);
|
||||
|
||||
is_me || is_outbound
|
||||
}
|
||||
|
||||
fn sender_handle(data: &serde_json::Value) -> Option<&str> {
|
||||
data.get("from")
|
||||
.and_then(|value| value.as_str())
|
||||
.or_else(|| {
|
||||
data.get("sender_handle")
|
||||
.and_then(|value| value.get("handle"))
|
||||
.and_then(|value| value.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
fn chat_id(data: &serde_json::Value) -> Option<&str> {
|
||||
data.get("chat_id")
|
||||
.and_then(|value| value.as_str())
|
||||
.or_else(|| {
|
||||
data.get("chat")
|
||||
.and_then(|value| value.get("id"))
|
||||
.and_then(|value| value.as_str())
|
||||
})
|
||||
}
|
||||
|
||||
fn message_parts(data: &serde_json::Value) -> Option<&Vec<serde_json::Value>> {
|
||||
data.get("message")
|
||||
.and_then(|value| value.get("parts"))
|
||||
.and_then(|value| value.as_array())
|
||||
.or_else(|| data.get("parts").and_then(|value| value.as_array()))
|
||||
}
|
||||
|
||||
/// Parse an incoming webhook payload from Linq and extract messages.
|
||||
///
|
||||
/// Supports two webhook formats:
|
||||
@@ -95,6 +143,11 @@ impl LinqChannel {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Also accepts the current 2026-02-03 payload shape where `chat_id`,
|
||||
/// `from`, `is_from_me`, and `message.parts` moved under:
|
||||
/// `data.chat.id`, `data.sender_handle.handle`, `data.sender_handle.is_me`,
|
||||
/// and `data.parts`.
|
||||
pub fn parse_webhook_payload(&self, payload: &serde_json::Value) -> Vec<ChannelMessage> {
|
||||
let mut messages = Vec::new();
|
||||
|
||||
@@ -112,44 +165,14 @@ impl LinqChannel {
|
||||
return messages;
|
||||
};
|
||||
|
||||
// Detect format: new format has `sender_handle`, legacy has `from`.
|
||||
let is_new_format = data.get("sender_handle").is_some();
|
||||
|
||||
// Skip messages sent by the bot itself
|
||||
let is_from_me = if is_new_format {
|
||||
// New format: data.sender_handle.is_me or data.direction == "outbound"
|
||||
data.get("sender_handle")
|
||||
.and_then(|sh| sh.get("is_me"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false)
|
||||
|| data
|
||||
.get("direction")
|
||||
.and_then(|d| d.as_str())
|
||||
.is_some_and(|d| d == "outbound")
|
||||
} else {
|
||||
// Legacy format: data.is_from_me
|
||||
data.get("is_from_me")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
if is_from_me {
|
||||
if Self::sender_is_from_me(data) {
|
||||
tracing::debug!("Linq: skipping is_from_me message");
|
||||
return messages;
|
||||
}
|
||||
|
||||
// Get sender phone number
|
||||
let from = if is_new_format {
|
||||
// New format: data.sender_handle.handle
|
||||
data.get("sender_handle")
|
||||
.and_then(|sh| sh.get("handle"))
|
||||
.and_then(|h| h.as_str())
|
||||
} else {
|
||||
// Legacy format: data.from
|
||||
data.get("from").and_then(|f| f.as_str())
|
||||
};
|
||||
|
||||
let Some(from) = from else {
|
||||
let Some(from) = Self::sender_handle(data) else {
|
||||
return messages;
|
||||
};
|
||||
|
||||
@@ -171,33 +194,10 @@ impl LinqChannel {
|
||||
}
|
||||
|
||||
// Get chat_id for reply routing
|
||||
let chat_id = if is_new_format {
|
||||
// New format: data.chat.id
|
||||
data.get("chat")
|
||||
.and_then(|c| c.get("id"))
|
||||
.and_then(|id| id.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
} else {
|
||||
// Legacy format: data.chat_id
|
||||
data.get("chat_id")
|
||||
.and_then(|c| c.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string()
|
||||
};
|
||||
let chat_id = Self::chat_id(data).unwrap_or("").to_string();
|
||||
|
||||
// Extract message parts
|
||||
let parts = if is_new_format {
|
||||
// New format: data.parts (directly on data)
|
||||
data.get("parts").and_then(|p| p.as_array())
|
||||
} else {
|
||||
// Legacy format: data.message.parts
|
||||
data.get("message")
|
||||
.and_then(|m| m.get("parts"))
|
||||
.and_then(|p| p.as_array())
|
||||
};
|
||||
|
||||
let Some(parts) = parts else {
|
||||
// Extract text from message parts
|
||||
let Some(parts) = Self::message_parts(data) else {
|
||||
return messages;
|
||||
};
|
||||
|
||||
@@ -520,6 +520,42 @@ mod tests {
|
||||
assert_eq!(msgs[0].reply_target, "chat-789");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linq_parse_latest_webhook_shape() {
|
||||
let ch = LinqChannel::new(
|
||||
"tok".into(),
|
||||
"+15551234567".into(),
|
||||
vec!["+1234567890".into()],
|
||||
);
|
||||
let payload = serde_json::json!({
|
||||
"api_version": "v3",
|
||||
"webhook_version": "2026-02-03",
|
||||
"event_type": "message.received",
|
||||
"created_at": "2026-02-03T12:00:00Z",
|
||||
"data": {
|
||||
"chat": {
|
||||
"id": "chat-2026"
|
||||
},
|
||||
"direction": "inbound",
|
||||
"id": "msg-2026",
|
||||
"parts": [{
|
||||
"type": "text",
|
||||
"value": "Hello from the latest payload"
|
||||
}],
|
||||
"sender_handle": {
|
||||
"handle": "1234567890",
|
||||
"is_me": false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let msgs = ch.parse_webhook_payload(&payload);
|
||||
assert_eq!(msgs.len(), 1);
|
||||
assert_eq!(msgs[0].sender, "+1234567890");
|
||||
assert_eq!(msgs[0].content, "Hello from the latest payload");
|
||||
assert_eq!(msgs[0].reply_target, "chat-2026");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linq_parse_skip_is_from_me() {
|
||||
let ch = LinqChannel::new("tok".into(), "+15551234567".into(), vec!["*".into()]);
|
||||
@@ -540,6 +576,34 @@ mod tests {
|
||||
assert!(msgs.is_empty(), "is_from_me messages should be skipped");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linq_parse_skip_latest_outbound_message() {
|
||||
let ch = LinqChannel::new("tok".into(), "+15551234567".into(), vec!["*".into()]);
|
||||
let payload = serde_json::json!({
|
||||
"event_type": "message.received",
|
||||
"data": {
|
||||
"chat": {
|
||||
"id": "chat-789"
|
||||
},
|
||||
"direction": "outbound",
|
||||
"parts": [{
|
||||
"type": "text",
|
||||
"value": "My own message"
|
||||
}],
|
||||
"sender_handle": {
|
||||
"handle": "+1234567890",
|
||||
"is_me": true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let msgs = ch.parse_webhook_payload(&payload);
|
||||
assert!(
|
||||
msgs.is_empty(),
|
||||
"latest outbound messages from the bot should be skipped"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linq_parse_skip_non_message_event() {
|
||||
let ch = make_channel();
|
||||
|
||||
+448
-44
@@ -113,28 +113,20 @@ impl Observer for ChannelNotifyObserver {
|
||||
Some(args) if !args.is_empty() => {
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(args) {
|
||||
if let Some(cmd) = v.get("command").and_then(|c| c.as_str()) {
|
||||
format!(": `{}`", if cmd.len() > 200 { &cmd[..200] } else { cmd })
|
||||
format!(": `{}`", truncate_with_ellipsis(cmd, 200))
|
||||
} else if let Some(q) = v.get("query").and_then(|c| c.as_str()) {
|
||||
format!(": {}", if q.len() > 200 { &q[..200] } else { q })
|
||||
format!(": {}", truncate_with_ellipsis(q, 200))
|
||||
} else if let Some(p) = v.get("path").and_then(|c| c.as_str()) {
|
||||
format!(": {p}")
|
||||
} else if let Some(u) = v.get("url").and_then(|c| c.as_str()) {
|
||||
format!(": {u}")
|
||||
} else {
|
||||
let s = args.to_string();
|
||||
if s.len() > 120 {
|
||||
format!(": {}…", &s[..120])
|
||||
} else {
|
||||
format!(": {s}")
|
||||
}
|
||||
format!(": {}", truncate_with_ellipsis(&s, 120))
|
||||
}
|
||||
} else {
|
||||
let s = args.to_string();
|
||||
if s.len() > 120 {
|
||||
format!(": {}…", &s[..120])
|
||||
} else {
|
||||
format!(": {s}")
|
||||
}
|
||||
format!(": {}", truncate_with_ellipsis(&s, 120))
|
||||
}
|
||||
}
|
||||
_ => String::new(),
|
||||
@@ -189,6 +181,13 @@ const MEMORY_CONTEXT_ENTRY_MAX_CHARS: usize = 800;
|
||||
const MEMORY_CONTEXT_MAX_CHARS: usize = 4_000;
|
||||
const CHANNEL_HISTORY_COMPACT_KEEP_MESSAGES: usize = 12;
|
||||
const CHANNEL_HISTORY_COMPACT_CONTENT_CHARS: usize = 600;
|
||||
/// Proactive context-window budget in estimated characters (~4 chars/token).
|
||||
/// When the total character count of conversation history exceeds this limit,
|
||||
/// older turns are dropped before the request is sent to the provider,
|
||||
/// preventing context-window-exceeded errors. Set conservatively below
|
||||
/// common context windows (128 k tokens ≈ 512 k chars) to leave room for
|
||||
/// system prompt, memory context, and model output.
|
||||
const PROACTIVE_CONTEXT_BUDGET_CHARS: usize = 400_000;
|
||||
/// Guardrail for hook-modified outbound channel content.
|
||||
const CHANNEL_HOOK_MAX_OUTBOUND_CHARS: usize = 20_000;
|
||||
|
||||
@@ -266,6 +265,22 @@ const SYSTEMD_RESTART_ARGS: [&str; 3] = ["--user", "restart", "zeroclaw.service"
|
||||
const OPENRC_STATUS_ARGS: [&str; 2] = ["zeroclaw", "status"];
|
||||
const OPENRC_RESTART_ARGS: [&str; 2] = ["zeroclaw", "restart"];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct InterruptOnNewMessageConfig {
|
||||
telegram: bool,
|
||||
slack: bool,
|
||||
}
|
||||
|
||||
impl InterruptOnNewMessageConfig {
|
||||
fn enabled_for_channel(self, channel: &str) -> bool {
|
||||
match channel {
|
||||
"telegram" => self.telegram,
|
||||
"slack" => self.slack,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ChannelRuntimeContext {
|
||||
channels_by_name: Arc<HashMap<String, Arc<dyn Channel>>>,
|
||||
@@ -289,13 +304,14 @@ struct ChannelRuntimeContext {
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions,
|
||||
workspace_dir: Arc<PathBuf>,
|
||||
message_timeout_secs: u64,
|
||||
interrupt_on_new_message: bool,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig,
|
||||
multimodal: crate::config::MultimodalConfig,
|
||||
hooks: Option<Arc<crate::hooks::HookRunner>>,
|
||||
non_cli_excluded_tools: Arc<Vec<String>>,
|
||||
tool_call_dedup_exempt: Arc<Vec<String>>,
|
||||
model_routes: Arc<Vec<crate::config::ModelRouteConfig>>,
|
||||
ack_reactions: bool,
|
||||
show_tool_calls: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -347,6 +363,10 @@ fn conversation_history_key(msg: &traits::ChannelMessage) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn followup_thread_id(msg: &traits::ChannelMessage) -> Option<String> {
|
||||
msg.thread_ts.clone().or_else(|| Some(msg.id.clone()))
|
||||
}
|
||||
|
||||
fn interruption_scope_key(msg: &traits::ChannelMessage) -> String {
|
||||
format!("{}_{}_{}", msg.channel, msg.reply_target, msg.sender)
|
||||
}
|
||||
@@ -919,6 +939,31 @@ fn compact_sender_history(ctx: &ChannelRuntimeContext, sender_key: &str) -> bool
|
||||
true
|
||||
}
|
||||
|
||||
/// Proactively trim conversation turns so that the total estimated character
|
||||
/// count stays within [`PROACTIVE_CONTEXT_BUDGET_CHARS`]. Drops the oldest
|
||||
/// turns first, but always preserves the most recent turn (the current user
|
||||
/// message). Returns the number of turns dropped.
|
||||
fn proactive_trim_turns(turns: &mut Vec<ChatMessage>, budget: usize) -> usize {
|
||||
let total_chars: usize = turns.iter().map(|t| t.content.chars().count()).sum();
|
||||
if total_chars <= budget || turns.len() <= 1 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut excess = total_chars.saturating_sub(budget);
|
||||
let mut drop_count = 0;
|
||||
|
||||
// Walk from the oldest turn forward, but never drop the very last turn.
|
||||
while excess > 0 && drop_count < turns.len().saturating_sub(1) {
|
||||
excess = excess.saturating_sub(turns[drop_count].content.chars().count());
|
||||
drop_count += 1;
|
||||
}
|
||||
|
||||
if drop_count > 0 {
|
||||
turns.drain(..drop_count);
|
||||
}
|
||||
drop_count
|
||||
}
|
||||
|
||||
fn append_sender_turn(ctx: &ChannelRuntimeContext, sender_key: &str, turn: ChatMessage) {
|
||||
let mut histories = ctx
|
||||
.conversation_histories
|
||||
@@ -1798,6 +1843,19 @@ async fn process_channel_message(
|
||||
}
|
||||
}
|
||||
|
||||
// Proactively trim conversation history before sending to the provider
|
||||
// to prevent context-window-exceeded errors (bug #3460).
|
||||
let dropped = proactive_trim_turns(&mut prior_turns, PROACTIVE_CONTEXT_BUDGET_CHARS);
|
||||
if dropped > 0 {
|
||||
tracing::info!(
|
||||
channel = %msg.channel,
|
||||
sender = %msg.sender,
|
||||
dropped_turns = dropped,
|
||||
remaining_turns = prior_turns.len(),
|
||||
"Proactively trimmed conversation history to fit context budget"
|
||||
);
|
||||
}
|
||||
|
||||
// Only enrich with memory context when there is no prior conversation
|
||||
// history. Follow-up turns already include context from previous messages.
|
||||
if !had_prior_history {
|
||||
@@ -1914,14 +1972,14 @@ async fn process_channel_message(
|
||||
let notify_observer_flag = Arc::clone(¬ify_observer);
|
||||
let notify_channel = target_channel.clone();
|
||||
let notify_reply_target = msg.reply_target.clone();
|
||||
let notify_thread_root = msg.id.clone();
|
||||
let notify_task = if msg.channel == "cli" {
|
||||
let notify_thread_root = followup_thread_id(&msg);
|
||||
let notify_task = if msg.channel == "cli" || !ctx.show_tool_calls {
|
||||
Some(tokio::spawn(async move {
|
||||
while notify_rx.recv().await.is_some() {}
|
||||
}))
|
||||
} else {
|
||||
Some(tokio::spawn(async move {
|
||||
let thread_ts = Some(notify_thread_root);
|
||||
let thread_ts = notify_thread_root;
|
||||
while let Some(text) = notify_rx.recv().await {
|
||||
if let Some(ref ch) = notify_channel {
|
||||
let _ = ch
|
||||
@@ -1981,7 +2039,7 @@ async fn process_channel_message(
|
||||
|
||||
// Thread the final reply only if tools were used (multi-message response)
|
||||
if notify_observer_flag.tools_used.load(Ordering::Relaxed) && msg.channel != "cli" {
|
||||
msg.thread_ts = Some(msg.id.clone());
|
||||
msg.thread_ts = followup_thread_id(&msg);
|
||||
}
|
||||
// Drop the notify sender so the forwarder task finishes
|
||||
drop(notify_observer);
|
||||
@@ -2365,8 +2423,9 @@ async fn run_message_dispatch_loop(
|
||||
let task_sequence = Arc::clone(&task_sequence);
|
||||
workers.spawn(async move {
|
||||
let _permit = permit;
|
||||
let interrupt_enabled =
|
||||
worker_ctx.interrupt_on_new_message && msg.channel == "telegram";
|
||||
let interrupt_enabled = worker_ctx
|
||||
.interrupt_on_new_message
|
||||
.enabled_for_channel(msg.channel.as_str());
|
||||
let sender_scope_key = interruption_scope_key(&msg);
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let completion = Arc::new(InFlightTaskCompletion::new());
|
||||
@@ -3694,6 +3753,11 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
||||
.telegram
|
||||
.as_ref()
|
||||
.is_some_and(|tg| tg.interrupt_on_new_message);
|
||||
let interrupt_on_new_message_slack = config
|
||||
.channels_config
|
||||
.slack
|
||||
.as_ref()
|
||||
.is_some_and(|sl| sl.interrupt_on_new_message);
|
||||
|
||||
let runtime_ctx = Arc::new(ChannelRuntimeContext {
|
||||
channels_by_name,
|
||||
@@ -3717,7 +3781,10 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
||||
provider_runtime_options,
|
||||
workspace_dir: Arc::new(config.workspace_dir.clone()),
|
||||
message_timeout_secs,
|
||||
interrupt_on_new_message,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: interrupt_on_new_message,
|
||||
slack: interrupt_on_new_message_slack,
|
||||
},
|
||||
multimodal: config.multimodal.clone(),
|
||||
hooks: if config.hooks.enabled {
|
||||
let mut runner = crate::hooks::HookRunner::new();
|
||||
@@ -3737,6 +3804,7 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
||||
tool_call_dedup_exempt: Arc::new(config.agent.tool_call_dedup_exempt.clone()),
|
||||
model_routes: Arc::new(config.model_routes.clone()),
|
||||
ack_reactions: config.channels_config.ack_reactions,
|
||||
show_tool_calls: config.channels_config.show_tool_calls,
|
||||
});
|
||||
|
||||
run_message_dispatch_loop(rx, runtime_ctx, max_in_flight_messages).await;
|
||||
@@ -3757,7 +3825,7 @@ mod tests {
|
||||
use crate::providers::{ChatMessage, Provider};
|
||||
use crate::tools::{Tool, ToolResult};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tempfile::TempDir;
|
||||
|
||||
@@ -3990,7 +4058,10 @@ mod tests {
|
||||
api_key: None,
|
||||
api_url: None,
|
||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
@@ -4000,6 +4071,7 @@ mod tests {
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
};
|
||||
|
||||
assert!(compact_sender_history(&ctx, &sender));
|
||||
@@ -4020,6 +4092,53 @@ mod tests {
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proactive_trim_drops_oldest_turns_when_over_budget() {
|
||||
// Each message is 100 chars; 10 messages = 1000 chars total.
|
||||
let mut turns: Vec<ChatMessage> = (0..10)
|
||||
.map(|i| {
|
||||
let content = format!("m{i}-{}", "a".repeat(96));
|
||||
if i % 2 == 0 {
|
||||
ChatMessage::user(content)
|
||||
} else {
|
||||
ChatMessage::assistant(content)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Budget of 500 should drop roughly half (oldest turns).
|
||||
let dropped = proactive_trim_turns(&mut turns, 500);
|
||||
assert!(dropped > 0, "should have dropped some turns");
|
||||
assert!(turns.len() < 10, "should have fewer turns after trimming");
|
||||
// Last turn should always be preserved.
|
||||
assert!(
|
||||
turns.last().unwrap().content.starts_with("m9-"),
|
||||
"most recent turn must be preserved"
|
||||
);
|
||||
// Total chars should now be within budget.
|
||||
let total: usize = turns.iter().map(|t| t.content.chars().count()).sum();
|
||||
assert!(total <= 500, "total chars {total} should be within budget");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proactive_trim_noop_when_within_budget() {
|
||||
let mut turns = vec![
|
||||
ChatMessage::user("hello".to_string()),
|
||||
ChatMessage::assistant("hi there".to_string()),
|
||||
];
|
||||
let dropped = proactive_trim_turns(&mut turns, 10_000);
|
||||
assert_eq!(dropped, 0);
|
||||
assert_eq!(turns.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proactive_trim_preserves_last_turn_even_when_over_budget() {
|
||||
let mut turns = vec![ChatMessage::user("x".repeat(2000))];
|
||||
let dropped = proactive_trim_turns(&mut turns, 100);
|
||||
assert_eq!(dropped, 0, "single turn must never be dropped");
|
||||
assert_eq!(turns.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_sender_turn_stores_single_turn_per_call() {
|
||||
let sender = "telegram_u2".to_string();
|
||||
@@ -4042,7 +4161,10 @@ mod tests {
|
||||
api_key: None,
|
||||
api_url: None,
|
||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
@@ -4052,6 +4174,7 @@ mod tests {
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
};
|
||||
|
||||
append_sender_turn(&ctx, &sender, ChatMessage::user("hello"));
|
||||
@@ -4097,7 +4220,10 @@ mod tests {
|
||||
api_key: None,
|
||||
api_url: None,
|
||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
@@ -4107,6 +4233,7 @@ mod tests {
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
};
|
||||
|
||||
assert!(rollback_orphan_user_turn(&ctx, &sender, "pending"));
|
||||
@@ -4152,6 +4279,11 @@ mod tests {
|
||||
sent_messages: tokio::sync::Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SlackRecordingChannel {
|
||||
sent_messages: tokio::sync::Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Channel for TelegramRecordingChannel {
|
||||
fn name(&self) -> &str {
|
||||
@@ -4182,6 +4314,36 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Channel for SlackRecordingChannel {
|
||||
fn name(&self) -> &str {
|
||||
"slack"
|
||||
}
|
||||
|
||||
async fn send(&self, message: &SendMessage) -> anyhow::Result<()> {
|
||||
self.sent_messages
|
||||
.lock()
|
||||
.await
|
||||
.push(format!("{}:{}", message.recipient, message.content));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn listen(
|
||||
&self,
|
||||
_tx: tokio::sync::mpsc::Sender<traits::ChannelMessage>,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_typing(&self, _recipient: &str) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn stop_typing(&self, _recipient: &str) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Channel for RecordingChannel {
|
||||
fn name(&self) -> &str {
|
||||
@@ -4578,13 +4740,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -4641,13 +4807,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -4718,13 +4888,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -4780,13 +4954,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -4852,13 +5030,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -4944,13 +5126,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -5018,13 +5204,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -5107,13 +5297,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
},
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -5181,13 +5375,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -5245,13 +5443,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -5420,13 +5622,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<traits::ChannelMessage>(4);
|
||||
@@ -5503,13 +5709,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: true,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: true,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<traits::ChannelMessage>(8);
|
||||
@@ -5566,6 +5776,108 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn message_dispatch_interrupts_in_flight_slack_request_and_preserves_context() {
|
||||
let channel_impl = Arc::new(SlackRecordingChannel::default());
|
||||
let channel: Arc<dyn Channel> = channel_impl.clone();
|
||||
|
||||
let mut channels_by_name = HashMap::new();
|
||||
channels_by_name.insert(channel.name().to_string(), channel);
|
||||
|
||||
let provider_impl = Arc::new(DelayedHistoryCaptureProvider {
|
||||
delay: Duration::from_millis(250),
|
||||
calls: std::sync::Mutex::new(Vec::new()),
|
||||
});
|
||||
|
||||
let runtime_ctx = Arc::new(ChannelRuntimeContext {
|
||||
channels_by_name: Arc::new(channels_by_name),
|
||||
provider: provider_impl.clone(),
|
||||
default_provider: Arc::new("test-provider".to_string()),
|
||||
memory: Arc::new(NoopMemory),
|
||||
tools_registry: Arc::new(vec![]),
|
||||
observer: Arc::new(NoopObserver),
|
||||
system_prompt: Arc::new("test-system-prompt".to_string()),
|
||||
model: Arc::new("test-model".to_string()),
|
||||
temperature: 0.0,
|
||||
auto_save_memory: false,
|
||||
max_tool_iterations: 10,
|
||||
min_relevance_score: 0.0,
|
||||
conversation_histories: Arc::new(Mutex::new(HashMap::new())),
|
||||
provider_cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
route_overrides: Arc::new(Mutex::new(HashMap::new())),
|
||||
api_key: None,
|
||||
api_url: None,
|
||||
reliability: Arc::new(crate::config::ReliabilityConfig::default()),
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: true,
|
||||
},
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
});
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<traits::ChannelMessage>(8);
|
||||
let send_task = tokio::spawn(async move {
|
||||
tx.send(traits::ChannelMessage {
|
||||
id: "msg-1".to_string(),
|
||||
sender: "U123".to_string(),
|
||||
reply_target: "C123".to_string(),
|
||||
content: "first question".to_string(),
|
||||
channel: "slack".to_string(),
|
||||
timestamp: 1,
|
||||
thread_ts: Some("1741234567.100001".to_string()),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
tokio::time::sleep(Duration::from_millis(40)).await;
|
||||
tx.send(traits::ChannelMessage {
|
||||
id: "msg-2".to_string(),
|
||||
sender: "U123".to_string(),
|
||||
reply_target: "C123".to_string(),
|
||||
content: "second question".to_string(),
|
||||
channel: "slack".to_string(),
|
||||
timestamp: 2,
|
||||
thread_ts: Some("1741234567.100001".to_string()),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
run_message_dispatch_loop(rx, runtime_ctx, 4).await;
|
||||
send_task.await.unwrap();
|
||||
|
||||
let sent_messages = channel_impl.sent_messages.lock().await;
|
||||
assert_eq!(sent_messages.len(), 1);
|
||||
assert!(sent_messages[0].starts_with("C123:"));
|
||||
assert!(sent_messages[0].contains("response-2"));
|
||||
drop(sent_messages);
|
||||
|
||||
let calls = provider_impl
|
||||
.calls
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
assert_eq!(calls.len(), 2);
|
||||
let second_call = &calls[1];
|
||||
assert!(second_call
|
||||
.iter()
|
||||
.any(|(role, content)| { role == "user" && content.contains("first question") }));
|
||||
assert!(second_call
|
||||
.iter()
|
||||
.any(|(role, content)| { role == "user" && content.contains("second question") }));
|
||||
assert!(
|
||||
!second_call.iter().any(|(role, _)| role == "assistant"),
|
||||
"cancelled turn should not persist an assistant response"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn message_dispatch_interrupt_scope_is_same_sender_same_chat() {
|
||||
let channel_impl = Arc::new(TelegramRecordingChannel::default());
|
||||
@@ -5598,13 +5910,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: true,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: true,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<traits::ChannelMessage>(8);
|
||||
@@ -5675,13 +5991,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -5737,13 +6057,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -6122,6 +6446,33 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
assert!(prompt.contains(&format!("Working directory: `{}`", ws.path().display())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn channel_notify_observer_truncates_utf8_arguments_safely() {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<String>();
|
||||
let observer = ChannelNotifyObserver {
|
||||
inner: Arc::new(NoopObserver),
|
||||
tx,
|
||||
tools_used: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
let payload = (0..300)
|
||||
.map(|n| serde_json::json!({ "content": format!("{}置tail", "a".repeat(n)) }))
|
||||
.map(|v| v.to_string())
|
||||
.find(|raw| raw.len() > 120 && !raw.is_char_boundary(120))
|
||||
.expect("should produce non-char-boundary data at byte index 120");
|
||||
|
||||
observer.record_event(
|
||||
&crate::observability::traits::ObserverEvent::ToolCallStart {
|
||||
tool: "file_write".to_string(),
|
||||
arguments: Some(payload),
|
||||
},
|
||||
);
|
||||
|
||||
let emitted = rx.try_recv().expect("observer should emit notify message");
|
||||
assert!(emitted.contains("`file_write`"));
|
||||
assert!(emitted.is_char_boundary(emitted.len()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversation_memory_key_uses_message_id() {
|
||||
let msg = traits::ChannelMessage {
|
||||
@@ -6137,6 +6488,39 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
assert_eq!(conversation_memory_key(&msg), "slack_U123_msg_abc123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn followup_thread_id_prefers_thread_ts() {
|
||||
let msg = traits::ChannelMessage {
|
||||
id: "slack_C123_1741234567.123456".into(),
|
||||
sender: "U123".into(),
|
||||
reply_target: "C123".into(),
|
||||
content: "hello".into(),
|
||||
channel: "slack".into(),
|
||||
timestamp: 1,
|
||||
thread_ts: Some("1741234567.123456".into()),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
followup_thread_id(&msg).as_deref(),
|
||||
Some("1741234567.123456")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn followup_thread_id_falls_back_to_message_id() {
|
||||
let msg = traits::ChannelMessage {
|
||||
id: "msg_abc123".into(),
|
||||
sender: "U123".into(),
|
||||
reply_target: "C456".into(),
|
||||
content: "hello".into(),
|
||||
channel: "cli".into(),
|
||||
timestamp: 1,
|
||||
thread_ts: None,
|
||||
};
|
||||
|
||||
assert_eq!(followup_thread_id(&msg).as_deref(), Some("msg_abc123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conversation_memory_key_is_unique_per_message() {
|
||||
let msg1 = traits::ChannelMessage {
|
||||
@@ -6297,13 +6681,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -6385,13 +6773,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -6473,13 +6865,17 @@ BTC is currently around $65,000 based on latest tool output."#
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
@@ -7025,13 +7421,17 @@ This is an example JSON object for profile settings."#;
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
// Simulate a photo attachment message with [IMAGE:] marker.
|
||||
@@ -7094,13 +7494,17 @@ This is an example JSON object for profile settings."#;
|
||||
provider_runtime_options: providers::ProviderRuntimeOptions::default(),
|
||||
workspace_dir: Arc::new(std::env::temp_dir()),
|
||||
message_timeout_secs: CHANNEL_MESSAGE_TIMEOUT_SECS,
|
||||
interrupt_on_new_message: false,
|
||||
interrupt_on_new_message: InterruptOnNewMessageConfig {
|
||||
telegram: false,
|
||||
slack: false,
|
||||
},
|
||||
multimodal: crate::config::MultimodalConfig::default(),
|
||||
hooks: None,
|
||||
non_cli_excluded_tools: Arc::new(Vec::new()),
|
||||
tool_call_dedup_exempt: Arc::new(Vec::new()),
|
||||
model_routes: Arc::new(Vec::new()),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
});
|
||||
|
||||
process_channel_message(
|
||||
|
||||
@@ -179,7 +179,9 @@ fn format_attachment_content(
|
||||
local_path: &Path,
|
||||
) -> String {
|
||||
match kind {
|
||||
IncomingAttachmentKind::Photo if is_image_extension(local_path) => {
|
||||
IncomingAttachmentKind::Photo | IncomingAttachmentKind::Document
|
||||
if is_image_extension(local_path) =>
|
||||
{
|
||||
format!("[IMAGE:{}]", local_path.display())
|
||||
}
|
||||
_ => {
|
||||
@@ -246,6 +248,23 @@ fn strip_tool_call_tags(message: &str) -> String {
|
||||
super::strip_tool_call_tags(message)
|
||||
}
|
||||
|
||||
fn find_matching_close(s: &str) -> Option<usize> {
|
||||
let mut depth = 1usize;
|
||||
for (i, ch) in s.char_indices() {
|
||||
match ch {
|
||||
'[' => depth += 1,
|
||||
']' => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_attachment_markers(message: &str) -> (String, Vec<TelegramAttachment>) {
|
||||
let mut cleaned = String::with_capacity(message.len());
|
||||
let mut attachments = Vec::new();
|
||||
@@ -260,12 +279,12 @@ fn parse_attachment_markers(message: &str) -> (String, Vec<TelegramAttachment>)
|
||||
let open = cursor + open_rel;
|
||||
cleaned.push_str(&message[cursor..open]);
|
||||
|
||||
let Some(close_rel) = message[open..].find(']') else {
|
||||
let Some(close_rel) = find_matching_close(&message[open + 1..]) else {
|
||||
cleaned.push_str(&message[open..]);
|
||||
break;
|
||||
};
|
||||
|
||||
let close = open + close_rel;
|
||||
let close = open + 1 + close_rel;
|
||||
let marker = &message[open + 1..close];
|
||||
|
||||
let parsed = marker.split_once(':').and_then(|(kind, target)| {
|
||||
|
||||
+206
-13
@@ -3097,6 +3097,11 @@ pub struct ChannelsConfig {
|
||||
/// completion) to incoming channel messages. Default: `true`.
|
||||
#[serde(default = "default_true")]
|
||||
pub ack_reactions: bool,
|
||||
/// Whether to send tool-call notification messages (e.g. `🔧 web_search_tool: …`)
|
||||
/// to channel users. When `false`, tool calls are still logged server-side but
|
||||
/// not forwarded as individual channel messages. Default: `true`.
|
||||
#[serde(default = "default_true")]
|
||||
pub show_tool_calls: bool,
|
||||
}
|
||||
|
||||
impl ChannelsConfig {
|
||||
@@ -3230,6 +3235,7 @@ impl Default for ChannelsConfig {
|
||||
clawdtalk: None,
|
||||
message_timeout_secs: default_channel_message_timeout_secs(),
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3323,6 +3329,10 @@ pub struct SlackConfig {
|
||||
/// Allowed Slack user IDs. Empty = deny all.
|
||||
#[serde(default)]
|
||||
pub allowed_users: Vec<String>,
|
||||
/// When true, a newer Slack message from the same sender in the same channel
|
||||
/// cancels the in-flight request and starts a fresh response with preserved history.
|
||||
#[serde(default)]
|
||||
pub interrupt_on_new_message: bool,
|
||||
}
|
||||
|
||||
impl ChannelConfig for SlackConfig {
|
||||
@@ -4687,6 +4697,23 @@ impl Config {
|
||||
"config.channels_config.nostr.private_key",
|
||||
)?;
|
||||
}
|
||||
if let Some(ref mut fs) = config.channels_config.feishu {
|
||||
decrypt_secret(
|
||||
&store,
|
||||
&mut fs.app_secret,
|
||||
"config.channels_config.feishu.app_secret",
|
||||
)?;
|
||||
decrypt_optional_secret(
|
||||
&store,
|
||||
&mut fs.encrypt_key,
|
||||
"config.channels_config.feishu.encrypt_key",
|
||||
)?;
|
||||
decrypt_optional_secret(
|
||||
&store,
|
||||
&mut fs.verification_token,
|
||||
"config.channels_config.feishu.verification_token",
|
||||
)?;
|
||||
}
|
||||
|
||||
// Decrypt channel secrets
|
||||
if let Some(ref mut tg) = config.channels_config.telegram {
|
||||
@@ -5509,11 +5536,38 @@ impl Config {
|
||||
set_runtime_proxy_config(self.proxy.clone());
|
||||
}
|
||||
|
||||
async fn resolve_config_path_for_save(&self) -> Result<PathBuf> {
|
||||
if self
|
||||
.config_path
|
||||
.parent()
|
||||
.is_some_and(|parent| !parent.as_os_str().is_empty())
|
||||
{
|
||||
return Ok(self.config_path.clone());
|
||||
}
|
||||
|
||||
let (default_zeroclaw_dir, default_workspace_dir) = default_config_and_workspace_dirs()?;
|
||||
let (zeroclaw_dir, _workspace_dir, source) =
|
||||
resolve_runtime_config_dirs(&default_zeroclaw_dir, &default_workspace_dir).await?;
|
||||
let file_name = self
|
||||
.config_path
|
||||
.file_name()
|
||||
.filter(|name| !name.is_empty())
|
||||
.unwrap_or_else(|| std::ffi::OsStr::new("config.toml"));
|
||||
let resolved = zeroclaw_dir.join(file_name);
|
||||
tracing::warn!(
|
||||
path = %self.config_path.display(),
|
||||
resolved = %resolved.display(),
|
||||
source = source.as_str(),
|
||||
"Config path missing parent directory; resolving from runtime environment"
|
||||
);
|
||||
Ok(resolved)
|
||||
}
|
||||
|
||||
pub async fn save(&self) -> Result<()> {
|
||||
// Encrypt secrets before serialization
|
||||
let mut config_to_save = self.clone();
|
||||
let zeroclaw_dir = self
|
||||
.config_path
|
||||
let config_path = self.resolve_config_path_for_save().await?;
|
||||
let zeroclaw_dir = config_path
|
||||
.parent()
|
||||
.context("Config path must have a parent directory")?;
|
||||
let store = crate::security::SecretStore::new(zeroclaw_dir, self.secrets.encrypt);
|
||||
@@ -5570,6 +5624,23 @@ impl Config {
|
||||
"config.channels_config.nostr.private_key",
|
||||
)?;
|
||||
}
|
||||
if let Some(ref mut fs) = config_to_save.channels_config.feishu {
|
||||
encrypt_secret(
|
||||
&store,
|
||||
&mut fs.app_secret,
|
||||
"config.channels_config.feishu.app_secret",
|
||||
)?;
|
||||
encrypt_optional_secret(
|
||||
&store,
|
||||
&mut fs.encrypt_key,
|
||||
"config.channels_config.feishu.encrypt_key",
|
||||
)?;
|
||||
encrypt_optional_secret(
|
||||
&store,
|
||||
&mut fs.verification_token,
|
||||
"config.channels_config.feishu.verification_token",
|
||||
)?;
|
||||
}
|
||||
|
||||
// Encrypt channel secrets
|
||||
if let Some(ref mut tg) = config_to_save.channels_config.telegram {
|
||||
@@ -5767,8 +5838,7 @@ impl Config {
|
||||
let toml_str =
|
||||
toml::to_string_pretty(&config_to_save).context("Failed to serialize config")?;
|
||||
|
||||
let parent_dir = self
|
||||
.config_path
|
||||
let parent_dir = config_path
|
||||
.parent()
|
||||
.context("Config path must have a parent directory")?;
|
||||
|
||||
@@ -5779,8 +5849,7 @@ impl Config {
|
||||
)
|
||||
})?;
|
||||
|
||||
let file_name = self
|
||||
.config_path
|
||||
let file_name = config_path
|
||||
.file_name()
|
||||
.and_then(|v| v.to_str())
|
||||
.unwrap_or("config.toml");
|
||||
@@ -5808,9 +5877,9 @@ impl Config {
|
||||
.context("Failed to fsync temporary config file")?;
|
||||
drop(temp_file);
|
||||
|
||||
let had_existing_config = self.config_path.exists();
|
||||
let had_existing_config = config_path.exists();
|
||||
if had_existing_config {
|
||||
fs::copy(&self.config_path, &backup_path)
|
||||
fs::copy(&config_path, &backup_path)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
@@ -5820,10 +5889,10 @@ impl Config {
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Err(e) = fs::rename(&temp_path, &self.config_path).await {
|
||||
if let Err(e) = fs::rename(&temp_path, &config_path).await {
|
||||
let _ = fs::remove_file(&temp_path).await;
|
||||
if had_existing_config && backup_path.exists() {
|
||||
fs::copy(&backup_path, &self.config_path)
|
||||
fs::copy(&backup_path, &config_path)
|
||||
.await
|
||||
.context("Failed to restore config backup")?;
|
||||
}
|
||||
@@ -5833,12 +5902,11 @@ impl Config {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::{fs::Permissions, os::unix::fs::PermissionsExt};
|
||||
if let Err(err) =
|
||||
fs::set_permissions(&self.config_path, Permissions::from_mode(0o600)).await
|
||||
if let Err(err) = fs::set_permissions(&config_path, Permissions::from_mode(0o600)).await
|
||||
{
|
||||
tracing::warn!(
|
||||
"Failed to harden config permissions to 0600 at {}: {}",
|
||||
self.config_path.display(),
|
||||
config_path.display(),
|
||||
err
|
||||
);
|
||||
}
|
||||
@@ -6187,6 +6255,7 @@ default_temperature = 0.7
|
||||
clawdtalk: None,
|
||||
message_timeout_secs: 300,
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
},
|
||||
memory: MemoryConfig::default(),
|
||||
storage: StorageConfig::default(),
|
||||
@@ -6538,6 +6607,15 @@ tool_dispatcher = "xml"
|
||||
config.browser.computer_use.api_key = Some("browser-credential".into());
|
||||
config.web_search.brave_api_key = Some("brave-credential".into());
|
||||
config.storage.provider.config.db_url = Some("postgres://user:pw@host/db".into());
|
||||
config.channels_config.feishu = Some(FeishuConfig {
|
||||
app_id: "cli_feishu_123".into(),
|
||||
app_secret: "feishu-secret".into(),
|
||||
encrypt_key: Some("feishu-encrypt".into()),
|
||||
verification_token: Some("feishu-verify".into()),
|
||||
allowed_users: vec!["*".into()],
|
||||
receive_mode: LarkReceiveMode::Websocket,
|
||||
port: None,
|
||||
});
|
||||
|
||||
config.agents.insert(
|
||||
"worker".into(),
|
||||
@@ -6605,6 +6683,32 @@ tool_dispatcher = "xml"
|
||||
"postgres://user:pw@host/db"
|
||||
);
|
||||
|
||||
let feishu = stored.channels_config.feishu.as_ref().unwrap();
|
||||
assert!(crate::security::SecretStore::is_encrypted(
|
||||
&feishu.app_secret
|
||||
));
|
||||
assert_eq!(store.decrypt(&feishu.app_secret).unwrap(), "feishu-secret");
|
||||
assert!(feishu
|
||||
.encrypt_key
|
||||
.as_deref()
|
||||
.is_some_and(crate::security::SecretStore::is_encrypted));
|
||||
assert_eq!(
|
||||
store
|
||||
.decrypt(feishu.encrypt_key.as_deref().unwrap())
|
||||
.unwrap(),
|
||||
"feishu-encrypt"
|
||||
);
|
||||
assert!(feishu
|
||||
.verification_token
|
||||
.as_deref()
|
||||
.is_some_and(crate::security::SecretStore::is_encrypted));
|
||||
assert_eq!(
|
||||
store
|
||||
.decrypt(feishu.verification_token.as_deref().unwrap())
|
||||
.unwrap(),
|
||||
"feishu-verify"
|
||||
);
|
||||
|
||||
let _ = fs::remove_dir_all(&dir).await;
|
||||
}
|
||||
|
||||
@@ -6865,6 +6969,7 @@ allowed_users = ["@ops:matrix.org"]
|
||||
clawdtalk: None,
|
||||
message_timeout_secs: 300,
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
};
|
||||
let toml_str = toml::to_string_pretty(&c).unwrap();
|
||||
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
|
||||
@@ -6903,6 +7008,7 @@ allowed_users = ["@ops:matrix.org"]
|
||||
let json = r#"{"bot_token":"xoxb-tok"}"#;
|
||||
let parsed: SlackConfig = serde_json::from_str(json).unwrap();
|
||||
assert!(parsed.allowed_users.is_empty());
|
||||
assert!(!parsed.interrupt_on_new_message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -6910,6 +7016,14 @@ allowed_users = ["@ops:matrix.org"]
|
||||
let json = r#"{"bot_token":"xoxb-tok","allowed_users":["U111"]}"#;
|
||||
let parsed: SlackConfig = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(parsed.allowed_users, vec!["U111"]);
|
||||
assert!(!parsed.interrupt_on_new_message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn slack_config_deserializes_interrupt_on_new_message() {
|
||||
let json = r#"{"bot_token":"xoxb-tok","interrupt_on_new_message":true}"#;
|
||||
let parsed: SlackConfig = serde_json::from_str(json).unwrap();
|
||||
assert!(parsed.interrupt_on_new_message);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -6931,6 +7045,7 @@ channel_id = "C123"
|
||||
"#;
|
||||
let parsed: SlackConfig = toml::from_str(toml_str).unwrap();
|
||||
assert!(parsed.allowed_users.is_empty());
|
||||
assert!(!parsed.interrupt_on_new_message);
|
||||
assert_eq!(parsed.channel_id.as_deref(), Some("C123"));
|
||||
}
|
||||
|
||||
@@ -7081,6 +7196,7 @@ channel_id = "C123"
|
||||
clawdtalk: None,
|
||||
message_timeout_secs: 300,
|
||||
ack_reactions: true,
|
||||
show_tool_calls: true,
|
||||
};
|
||||
let toml_str = toml::to_string_pretty(&c).unwrap();
|
||||
let parsed: ChannelsConfig = toml::from_str(&toml_str).unwrap();
|
||||
@@ -7704,6 +7820,40 @@ requires_openai_auth = true
|
||||
assert_eq!(config.api_key.as_deref(), Some("sk-test-codex-key"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn save_repairs_bare_config_filename_using_runtime_resolution() {
|
||||
let _env_guard = env_override_lock().await;
|
||||
let temp_home =
|
||||
std::env::temp_dir().join(format!("zeroclaw_test_home_{}", uuid::Uuid::new_v4()));
|
||||
let workspace_dir = temp_home.join("workspace");
|
||||
let resolved_config_path = temp_home.join(".zeroclaw").join("config.toml");
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
std::env::set_var("HOME", &temp_home);
|
||||
std::env::set_var("ZEROCLAW_WORKSPACE", &workspace_dir);
|
||||
|
||||
let mut config = Config::default();
|
||||
config.workspace_dir = workspace_dir;
|
||||
config.config_path = PathBuf::from("config.toml");
|
||||
config.default_temperature = 0.5;
|
||||
config.save().await.unwrap();
|
||||
|
||||
assert!(resolved_config_path.exists());
|
||||
let saved = tokio::fs::read_to_string(&resolved_config_path)
|
||||
.await
|
||||
.unwrap();
|
||||
let parsed: Config = toml::from_str(&saved).unwrap();
|
||||
assert_eq!(parsed.default_temperature, 0.5);
|
||||
|
||||
std::env::remove_var("ZEROCLAW_WORKSPACE");
|
||||
if let Some(home) = original_home {
|
||||
std::env::set_var("HOME", home);
|
||||
} else {
|
||||
std::env::remove_var("HOME");
|
||||
}
|
||||
let _ = tokio::fs::remove_dir_all(temp_home).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn validate_ollama_cloud_model_requires_remote_api_url() {
|
||||
let _env_guard = env_override_lock().await;
|
||||
@@ -7990,6 +8140,49 @@ default_model = "legacy-model"
|
||||
let _ = fs::remove_dir_all(temp_home).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn load_or_init_decrypts_feishu_channel_secrets() {
|
||||
let _env_guard = env_override_lock().await;
|
||||
let temp_home =
|
||||
std::env::temp_dir().join(format!("zeroclaw_test_home_{}", uuid::Uuid::new_v4()));
|
||||
let config_dir = temp_home.join(".zeroclaw");
|
||||
let config_path = config_dir.join("config.toml");
|
||||
|
||||
fs::create_dir_all(&config_dir).await.unwrap();
|
||||
|
||||
let original_home = std::env::var("HOME").ok();
|
||||
std::env::set_var("HOME", &temp_home);
|
||||
std::env::remove_var("ZEROCLAW_WORKSPACE");
|
||||
|
||||
let mut config = Config::default();
|
||||
config.config_path = config_path.clone();
|
||||
config.workspace_dir = config_dir.join("workspace");
|
||||
config.secrets.encrypt = true;
|
||||
config.channels_config.feishu = Some(FeishuConfig {
|
||||
app_id: "cli_feishu_123".into(),
|
||||
app_secret: "feishu-secret".into(),
|
||||
encrypt_key: Some("feishu-encrypt".into()),
|
||||
verification_token: Some("feishu-verify".into()),
|
||||
allowed_users: vec!["*".into()],
|
||||
receive_mode: LarkReceiveMode::Websocket,
|
||||
port: None,
|
||||
});
|
||||
config.save().await.unwrap();
|
||||
|
||||
let loaded = Config::load_or_init().await.unwrap();
|
||||
let feishu = loaded.channels_config.feishu.as_ref().unwrap();
|
||||
assert_eq!(feishu.app_secret, "feishu-secret");
|
||||
assert_eq!(feishu.encrypt_key.as_deref(), Some("feishu-encrypt"));
|
||||
assert_eq!(feishu.verification_token.as_deref(), Some("feishu-verify"));
|
||||
|
||||
if let Some(home) = original_home {
|
||||
std::env::set_var("HOME", home);
|
||||
} else {
|
||||
std::env::remove_var("HOME");
|
||||
}
|
||||
let _ = fs::remove_dir_all(temp_home).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn load_or_init_uses_persisted_active_workspace_marker() {
|
||||
let _env_guard = env_override_lock().await;
|
||||
|
||||
+2
-1
@@ -364,8 +364,9 @@ pub async fn run_gateway(host: &str, port: u16, config: Config) -> Result<()> {
|
||||
.clone()
|
||||
.unwrap_or_else(|| "anthropic/claude-sonnet-4".into());
|
||||
let temperature = config.default_temperature;
|
||||
let mem: Arc<dyn Memory> = Arc::from(memory::create_memory_with_storage(
|
||||
let mem: Arc<dyn Memory> = Arc::from(memory::create_memory_with_storage_and_routes(
|
||||
&config.memory,
|
||||
&config.embedding_routes,
|
||||
Some(&config.storage.provider.config),
|
||||
&config.workspace_dir,
|
||||
config.api_key.as_deref(),
|
||||
|
||||
+13
-39
@@ -119,6 +119,17 @@ pub async fn handle_ws_chat(
|
||||
async fn handle_socket(socket: WebSocket, state: AppState, _session_id: Option<String>) {
|
||||
let (mut sender, mut receiver) = socket.split();
|
||||
|
||||
// Build a persistent Agent for this connection so history is maintained across turns.
|
||||
let config = state.config.lock().clone();
|
||||
let mut agent = match crate::agent::Agent::from_config(&config) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
let err = serde_json::json!({"type": "error", "message": format!("Failed to initialise agent: {e}")});
|
||||
let _ = sender.send(Message::Text(err.to_string().into())).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
while let Some(msg) = receiver.next().await {
|
||||
let msg = match msg {
|
||||
Ok(Message::Text(text)) => text,
|
||||
@@ -161,45 +172,8 @@ async fn handle_socket(socket: WebSocket, state: AppState, _session_id: Option<S
|
||||
"model": state.model,
|
||||
}));
|
||||
|
||||
// Simple single-turn chat (no streaming for now — use provider.chat_with_system)
|
||||
let system_prompt = {
|
||||
let config_guard = state.config.lock();
|
||||
crate::channels::build_system_prompt(
|
||||
&config_guard.workspace_dir,
|
||||
&state.model,
|
||||
&[],
|
||||
&[],
|
||||
Some(&config_guard.identity),
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
let messages = vec![
|
||||
crate::providers::ChatMessage::system(system_prompt),
|
||||
crate::providers::ChatMessage::user(&content),
|
||||
];
|
||||
|
||||
let multimodal_config = state.config.lock().multimodal.clone();
|
||||
let prepared =
|
||||
match crate::multimodal::prepare_messages_for_provider(&messages, &multimodal_config)
|
||||
.await
|
||||
{
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let err = serde_json::json!({
|
||||
"type": "error",
|
||||
"message": format!("Multimodal prep failed: {e}")
|
||||
});
|
||||
let _ = sender.send(Message::Text(err.to_string().into())).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match state
|
||||
.provider
|
||||
.chat_with_history(&prepared.messages, &state.model, state.temperature)
|
||||
.await
|
||||
{
|
||||
// Multi-turn chat via persistent Agent (history is maintained across turns)
|
||||
match agent.turn(&content).await {
|
||||
Ok(response) => {
|
||||
// Send the full response as a done message
|
||||
let done = serde_json::json!({
|
||||
|
||||
@@ -79,7 +79,7 @@ fn show_integration_info(config: &Config, name: &str) -> Result<()> {
|
||||
|
||||
let Some(entry) = entries.iter().find(|e| e.name.to_lowercase() == name_lower) else {
|
||||
anyhow::bail!(
|
||||
"Unknown integration: {name}. Check README for supported integrations or run `zeroclaw onboard --interactive` to configure channels/providers."
|
||||
"Unknown integration: {name}. Check README for supported integrations or run `zeroclaw onboard` to configure channels/providers."
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+57
-18
@@ -46,6 +46,30 @@ fn parse_temperature(s: &str) -> std::result::Result<f64, String> {
|
||||
config::schema::validate_temperature(t)
|
||||
}
|
||||
|
||||
fn print_no_command_help() -> Result<()> {
|
||||
println!("No command provided.");
|
||||
println!("Try `zeroclaw onboard` to initialize your workspace.");
|
||||
println!();
|
||||
|
||||
let mut cmd = Cli::command();
|
||||
cmd.print_help()?;
|
||||
println!();
|
||||
|
||||
#[cfg(windows)]
|
||||
pause_after_no_command_help();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn pause_after_no_command_help() {
|
||||
println!();
|
||||
print!("Press Enter to exit...");
|
||||
let _ = std::io::stdout().flush();
|
||||
let mut line = String::new();
|
||||
let _ = std::io::stdin().read_line(&mut line);
|
||||
}
|
||||
|
||||
mod agent;
|
||||
mod approval;
|
||||
mod auth;
|
||||
@@ -133,10 +157,6 @@ struct Cli {
|
||||
enum Commands {
|
||||
/// Initialize your workspace and configuration
|
||||
Onboard {
|
||||
/// Run the full interactive wizard (default is quick setup)
|
||||
#[arg(long)]
|
||||
interactive: bool,
|
||||
|
||||
/// Overwrite existing config without confirmation
|
||||
#[arg(long)]
|
||||
force: bool,
|
||||
@@ -149,7 +169,7 @@ enum Commands {
|
||||
#[arg(long)]
|
||||
channels_only: bool,
|
||||
|
||||
/// API key (used in quick mode, ignored with --interactive)
|
||||
/// API key for provider configuration
|
||||
#[arg(long)]
|
||||
api_key: Option<String>,
|
||||
|
||||
@@ -667,6 +687,10 @@ async fn main() -> Result<()> {
|
||||
eprintln!("Warning: Failed to install default crypto provider: {e:?}");
|
||||
}
|
||||
|
||||
if std::env::args_os().len() <= 1 {
|
||||
return print_no_command_help();
|
||||
}
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
if let Some(config_dir) = &cli.config_dir {
|
||||
@@ -698,7 +722,6 @@ async fn main() -> Result<()> {
|
||||
// Tokio runtime. To avoid "Cannot drop a runtime in a context where blocking is
|
||||
// not allowed", we run the wizard on a blocking thread via spawn_blocking.
|
||||
if let Commands::Onboard {
|
||||
interactive,
|
||||
force,
|
||||
reinit,
|
||||
channels_only,
|
||||
@@ -708,7 +731,6 @@ async fn main() -> Result<()> {
|
||||
memory,
|
||||
} = &cli.command
|
||||
{
|
||||
let interactive = *interactive;
|
||||
let force = *force;
|
||||
let reinit = *reinit;
|
||||
let channels_only = *channels_only;
|
||||
@@ -717,14 +739,8 @@ async fn main() -> Result<()> {
|
||||
let model = model.clone();
|
||||
let memory = memory.clone();
|
||||
|
||||
if interactive && channels_only {
|
||||
bail!("Use either --interactive or --channels-only, not both");
|
||||
}
|
||||
if reinit && channels_only {
|
||||
bail!("Use either --reinit or --channels-only, not both");
|
||||
}
|
||||
if reinit && !interactive {
|
||||
bail!("--reinit requires --interactive mode");
|
||||
bail!("--reinit and --channels-only cannot be used together");
|
||||
}
|
||||
if channels_only
|
||||
&& (api_key.is_some() || provider.is_some() || model.is_some() || memory.is_some())
|
||||
@@ -778,8 +794,6 @@ async fn main() -> Result<()> {
|
||||
|
||||
let config = if channels_only {
|
||||
Box::pin(onboard::run_channels_repair_wizard()).await
|
||||
} else if interactive {
|
||||
Box::pin(onboard::run_wizard(force)).await
|
||||
} else {
|
||||
onboard::run_quick_setup(
|
||||
api_key.as_deref(),
|
||||
@@ -790,6 +804,33 @@ async fn main() -> Result<()> {
|
||||
)
|
||||
.await
|
||||
}?;
|
||||
|
||||
// Display pairing code — user enters it in the dashboard to pair securely.
|
||||
// The code is one-time use and brute-force protected (5 attempts → lockout).
|
||||
// No auth material is placed in URLs to prevent leakage via browser history,
|
||||
// Referer headers, clipboard, or proxy logs.
|
||||
if config.gateway.require_pairing {
|
||||
let pairing = security::PairingGuard::new(true, &config.gateway.paired_tokens);
|
||||
if let Some(code) = pairing.pairing_code() {
|
||||
println!();
|
||||
println!(" \x1b[1;34m🦀 Gateway Pairing Code\x1b[0m");
|
||||
println!();
|
||||
println!(" \x1b[1;34m┌──────────────┐\x1b[0m");
|
||||
println!(" \x1b[1;34m│\x1b[0m \x1b[1m{code}\x1b[0m \x1b[1;34m│\x1b[0m");
|
||||
println!(" \x1b[1;34m└──────────────┘\x1b[0m");
|
||||
println!();
|
||||
println!(" Enter this code in the dashboard to pair your device.");
|
||||
println!(" The code is single-use and expires after pairing.");
|
||||
println!();
|
||||
println!(
|
||||
" \x1b[2mDashboard: http://127.0.0.1:{}\x1b[0m",
|
||||
config.gateway.port
|
||||
);
|
||||
println!(" \x1b[2mDocs: https://www.zeroclawlabs.ai/docs\x1b[0m");
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-start channels if user said yes during wizard
|
||||
if std::env::var("ZEROCLAW_AUTOSTART_CHANNELS").as_deref() == Ok("1") {
|
||||
channels::start_channels(config).await?;
|
||||
@@ -2112,7 +2153,6 @@ mod tests {
|
||||
|
||||
match cli.command {
|
||||
Commands::Onboard {
|
||||
interactive,
|
||||
force,
|
||||
channels_only,
|
||||
api_key,
|
||||
@@ -2120,7 +2160,6 @@ mod tests {
|
||||
model,
|
||||
..
|
||||
} => {
|
||||
assert!(!interactive);
|
||||
assert!(!force);
|
||||
assert!(!channels_only);
|
||||
assert_eq!(provider.as_deref(), Some("openrouter"));
|
||||
|
||||
+1
-2
@@ -4,7 +4,7 @@ pub mod wizard;
|
||||
#[allow(unused_imports)]
|
||||
pub use wizard::{
|
||||
run_channels_repair_wizard, run_models_list, run_models_refresh, run_models_refresh_all,
|
||||
run_models_set, run_models_status, run_quick_setup, run_wizard,
|
||||
run_models_set, run_models_status, run_quick_setup,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -15,7 +15,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn wizard_functions_are_reexported() {
|
||||
assert_reexport_exists(run_wizard);
|
||||
assert_reexport_exists(run_channels_repair_wizard);
|
||||
assert_reexport_exists(run_quick_setup);
|
||||
assert_reexport_exists(run_models_refresh);
|
||||
|
||||
+25
-14
@@ -360,7 +360,6 @@ fn apply_provider_update(
|
||||
|
||||
/// Non-interactive setup: generates a sensible default config instantly.
|
||||
/// Use `zeroclaw onboard` or `zeroclaw onboard --api-key sk-... --provider openrouter --memory sqlite|lucid`.
|
||||
/// Use `zeroclaw onboard --interactive` for the full wizard.
|
||||
fn backend_key_from_choice(choice: usize) -> &'static str {
|
||||
selectable_memory_backends()
|
||||
.get(choice)
|
||||
@@ -2046,26 +2045,37 @@ fn ensure_onboard_overwrite_allowed(config_path: &Path, force: bool) -> Result<(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !std::io::stdin().is_terminal() || !std::io::stdout().is_terminal() {
|
||||
#[cfg(test)]
|
||||
{
|
||||
bail!(
|
||||
"Refusing to overwrite existing config at {} in non-interactive mode. Re-run with --force if overwrite is intentional.",
|
||||
"Refusing to overwrite existing config at {} in test mode. Re-run with --force if overwrite is intentional.",
|
||||
config_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let confirmed = Confirm::new()
|
||||
.with_prompt(format!(
|
||||
" Existing config found at {}. Re-running onboarding will overwrite config.toml and may create missing workspace files (including BOOTSTRAP.md). Continue?",
|
||||
config_path.display()
|
||||
))
|
||||
.default(false)
|
||||
.interact()?;
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if !std::io::stdin().is_terminal() || !std::io::stdout().is_terminal() {
|
||||
bail!(
|
||||
"Refusing to overwrite existing config at {} in non-interactive mode. Re-run with --force if overwrite is intentional.",
|
||||
config_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
if !confirmed {
|
||||
bail!("Onboarding canceled: existing configuration was left unchanged.");
|
||||
let confirmed = Confirm::new()
|
||||
.with_prompt(format!(
|
||||
" Existing config found at {}. Re-running onboarding will overwrite config.toml and may create missing workspace files (including BOOTSTRAP.md). Continue?",
|
||||
config_path.display()
|
||||
))
|
||||
.default(false)
|
||||
.interact()?;
|
||||
|
||||
if !confirmed {
|
||||
bail!("Onboarding canceled: existing configuration was left unchanged.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_workspace_selection(config_path: &Path) -> Result<()> {
|
||||
@@ -3866,6 +3876,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
|
||||
Some(channel)
|
||||
},
|
||||
allowed_users,
|
||||
interrupt_on_new_message: false,
|
||||
});
|
||||
}
|
||||
ChannelMenuChoice::IMessage => {
|
||||
|
||||
+227
-1
@@ -1328,6 +1328,96 @@ fn create_provider_with_url_and_options(
|
||||
"astrai" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Astrai", "https://as-trai.com/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"siliconflow" | "silicon-flow" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"SiliconFlow",
|
||||
"https://api.siliconflow.cn/v1",
|
||||
key,
|
||||
AuthStyle::Bearer,
|
||||
))),
|
||||
"aihubmix" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"AiHubMix",
|
||||
"https://aihubmix.com/v1",
|
||||
key,
|
||||
AuthStyle::Bearer,
|
||||
))),
|
||||
"litellm" | "lite-llm" => {
|
||||
let base_url = api_url
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("http://localhost:4000/v1");
|
||||
Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"LiteLLM",
|
||||
base_url,
|
||||
key,
|
||||
AuthStyle::Bearer,
|
||||
)))
|
||||
}
|
||||
|
||||
// ── Fast inference providers ──────────────────────────
|
||||
"cerebras" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Cerebras", "https://api.cerebras.ai/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"sambanova" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"SambaNova", "https://api.sambanova.ai/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"hyperbolic" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Hyperbolic", "https://api.hyperbolic.xyz/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
|
||||
// ── Model hosting platforms ──────────────────────────
|
||||
"deepinfra" | "deep-infra" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"DeepInfra", "https://api.deepinfra.com/v1/openai", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"huggingface" | "hf" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Hugging Face", "https://router.huggingface.co/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"ai21" | "ai21-labs" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"AI21 Labs", "https://api.ai21.com/studio/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"reka" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Reka", "https://api.reka.ai/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"baseten" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Baseten", "https://inference.baseten.co/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"nscale" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Nscale", "https://inference.api.nscale.com/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"anyscale" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Anyscale", "https://api.endpoints.anyscale.com/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"nebius" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Nebius AI Studio", "https://api.studio.nebius.ai/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"friendli" | "friendliai" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Friendli AI", "https://api.friendli.ai/serverless/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"lepton" | "lepton-ai" => {
|
||||
let base_url = api_url
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("https://llama3-1-405b.lepton.run/api/v1");
|
||||
Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Lepton AI",
|
||||
base_url,
|
||||
key,
|
||||
AuthStyle::Bearer,
|
||||
)))
|
||||
}
|
||||
|
||||
// ── Chinese AI providers ─────────────────────────────
|
||||
"stepfun" | "step" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Stepfun", "https://api.stepfun.com/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"baichuan" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Baichuan", "https://api.baichuan-ai.com/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"yi" | "01ai" | "lingyiwanwu" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"01.AI (Yi)", "https://api.lingyiwanwu.com/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
"hunyuan" | "tencent" => Ok(compat(OpenAiCompatibleProvider::new(
|
||||
"Tencent Hunyuan", "https://api.hunyuan.cloud.tencent.com/v1", key, AuthStyle::Bearer,
|
||||
))),
|
||||
|
||||
// ── Cloud AI endpoints ───────────────────────────────
|
||||
"ovhcloud" | "ovh" => Ok(Box::new(openai::OpenAiProvider::with_base_url(
|
||||
@@ -1367,7 +1457,7 @@ fn create_provider_with_url_and_options(
|
||||
}
|
||||
|
||||
_ => anyhow::bail!(
|
||||
"Unknown provider: {name}. Check README for supported providers or run `zeroclaw onboard --interactive` to reconfigure.\n\
|
||||
"Unknown provider: {name}. Check README for supported providers or run `zeroclaw onboard` to reconfigure.\n\
|
||||
Tip: Use \"custom:https://your-api.com\" for OpenAI-compatible endpoints.\n\
|
||||
Tip: Use \"anthropic-custom:https://your-api.com\" for Anthropic-compatible endpoints."
|
||||
),
|
||||
@@ -1614,6 +1704,18 @@ pub fn list_providers() -> Vec<ProviderInfo> {
|
||||
aliases: &["openai_codex", "codex"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "telnyx",
|
||||
display_name: "Telnyx",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "azure_openai",
|
||||
display_name: "Azure OpenAI",
|
||||
aliases: &["azure-openai", "azure"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "ollama",
|
||||
display_name: "Ollama",
|
||||
@@ -1832,6 +1934,130 @@ pub fn list_providers() -> Vec<ProviderInfo> {
|
||||
aliases: &["nvidia-nim", "build.nvidia.com"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "siliconflow",
|
||||
display_name: "SiliconFlow",
|
||||
aliases: &["silicon-flow"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "aihubmix",
|
||||
display_name: "AiHubMix",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "litellm",
|
||||
display_name: "LiteLLM",
|
||||
aliases: &["lite-llm"],
|
||||
local: false,
|
||||
},
|
||||
// ── Fast inference ────────────────────────────────────
|
||||
ProviderInfo {
|
||||
name: "cerebras",
|
||||
display_name: "Cerebras",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "sambanova",
|
||||
display_name: "SambaNova",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "hyperbolic",
|
||||
display_name: "Hyperbolic",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
// ── Model hosting platforms ──────────────────────────
|
||||
ProviderInfo {
|
||||
name: "deepinfra",
|
||||
display_name: "DeepInfra",
|
||||
aliases: &["deep-infra"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "huggingface",
|
||||
display_name: "Hugging Face",
|
||||
aliases: &["hf"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "ai21",
|
||||
display_name: "AI21 Labs",
|
||||
aliases: &["ai21-labs"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "reka",
|
||||
display_name: "Reka",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "baseten",
|
||||
display_name: "Baseten",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "nscale",
|
||||
display_name: "Nscale",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "anyscale",
|
||||
display_name: "Anyscale",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "nebius",
|
||||
display_name: "Nebius AI Studio",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "friendli",
|
||||
display_name: "Friendli AI",
|
||||
aliases: &["friendliai"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "lepton",
|
||||
display_name: "Lepton AI",
|
||||
aliases: &["lepton-ai"],
|
||||
local: false,
|
||||
},
|
||||
// ── Chinese AI providers ─────────────────────────────
|
||||
ProviderInfo {
|
||||
name: "stepfun",
|
||||
display_name: "Stepfun",
|
||||
aliases: &["step"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "baichuan",
|
||||
display_name: "Baichuan",
|
||||
aliases: &[],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "yi",
|
||||
display_name: "01.AI (Yi)",
|
||||
aliases: &["01ai", "lingyiwanwu"],
|
||||
local: false,
|
||||
},
|
||||
ProviderInfo {
|
||||
name: "hunyuan",
|
||||
display_name: "Tencent Hunyuan",
|
||||
aliases: &["tencent"],
|
||||
local: false,
|
||||
},
|
||||
// ── Cloud AI endpoints ───────────────────────────────
|
||||
ProviderInfo {
|
||||
name: "ovhcloud",
|
||||
display_name: "OVHcloud AI Endpoints",
|
||||
|
||||
Reference in New Issue
Block a user