mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-29 14:03:17 +00:00
Compare commits
21 Commits
ci-assets-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fe239f8cd | |||
| 64c008501f | |||
| 16b6288ea6 | |||
| e9cac42d5d | |||
| 8072c2442d | |||
| 547d28d9fa | |||
| 8b8199592d | |||
| e20c177e88 | |||
| 16379f9cb2 | |||
| 2aa6fe6b57 | |||
| 8da670ca14 | |||
| 6d6beed72a | |||
| f339850ffb | |||
| 192e63cf52 | |||
| 91fc5093d1 | |||
| 5afe8be432 | |||
| 87f8fc1485 | |||
| 1acc933dc8 | |||
| ac087aedb7 | |||
| 9beb3b558e | |||
| 8cdcbf70fb |
85
.github/workflows/pages.yml
vendored
Normal file
85
.github/workflows/pages.yml
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
name: Pages Mirror
|
||||
|
||||
# 介绍站(site/)的 GitHub Pages 镜像。
|
||||
#
|
||||
# 为什么是「镜像」而不是主站:主站自托管在 NAS(192.168.2.106),
|
||||
# 国内访问是内网直连(毫秒级);GitHub Pages 负责海外可达性与灾备。
|
||||
# 两边内容同源(同一个 site/ 目录),不存在谁是「真身」的问题。
|
||||
#
|
||||
# 手动触发也留着:改完 site/ 想立刻发布、或主站回滚后要重新对齐时用。
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'site/**'
|
||||
- '.github/workflows/pages.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# 同一时刻只跑一个 Pages 部署;排队中的旧任务直接取消
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build site artifact
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
# 静态站:没有构建步骤,site/ 就是要发布的内容。
|
||||
# 这里只做**完整性检查**——少一个文件会让线上 404,而 404 不会让
|
||||
# 部署失败(Pages 照样返回 200 + 404 页),所以必须在发布前拦住。
|
||||
- name: Verify site completeness
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f site/index.html || { echo "缺少 site/index.html"; exit 1; }
|
||||
test -f site/assets/logo.svg || {
|
||||
echo "缺少 site/assets/logo.svg"; exit 1
|
||||
}
|
||||
|
||||
# index.html 引用的本地资源必须都在(防「改了引用忘了加文件」)
|
||||
missing=0
|
||||
grep -oE '(src|href)="(assets|\./assets)/[^"]+"' site/index.html \
|
||||
| sed -E 's/.*="(\.\/)?([^"]*)"/\2/' > /tmp/refs.txt
|
||||
while read -r ref; do
|
||||
[ -z "$ref" ] && continue
|
||||
if [ ! -e "site/$ref" ]; then
|
||||
echo "引用缺失: site/$ref"
|
||||
missing=1
|
||||
fi
|
||||
done < /tmp/refs.txt
|
||||
[ "$missing" -eq 0 ] || exit 1
|
||||
|
||||
echo "site/ 文件数: $(find site -type f | wc -l)"
|
||||
echo "index.html 字节: $(wc -c < site/index.html)"
|
||||
|
||||
- name: Configure Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: site
|
||||
# Pages 产物必须保留;不设 retention 会被仓库默认值(30 天)回收——
|
||||
# 与部署无关,但重跑 build 时会少掉旧产物对比。
|
||||
retention-days: 7
|
||||
|
||||
deploy:
|
||||
name: Deploy to Pages
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Deploy
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
398
.github/workflows/release.yml
vendored
Normal file
398
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,398 @@
|
||||
# 发布流水线:release/** 分支推送即发版。
|
||||
#
|
||||
# 设计依据 docs/git-branching.md §七(发版产物清单)与 git-release-discipline
|
||||
# skill。核心事实:**推 tag ≠ 完成发版** —— 完整发版是四件事:
|
||||
# bump meta.Version → 打 tag → 打包产物 → 建 release 条目并上传附件。
|
||||
# (v1.3.1–v1.3.6 曾只推了 tag,产物与 release 条目全缺,事后补做。)
|
||||
#
|
||||
# 版本号来源:internal/meta/meta.go 的 Version(唯一事实源)。
|
||||
# 所以发版动作 = 在 release/vX.Y.x 上把 meta.Version 改成目标版本后推送。
|
||||
# 版本未变的推送(如改文档)会因 tag 已存在而**整轮跳过**,不会重复发版。
|
||||
#
|
||||
# 发版前的 go test 门可以显式跳过(见下面 skip_tests 的说明)。
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['release/**']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_tests:
|
||||
description: '跳过发版前的 go test 门(仅用于已知红的历史维护线)'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# 发布必须能写仓库(打 tag、建 release、传附件)。
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# 发布不允许并发/取消:半途中断会留下 tag 存在但附件不全的状态。
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
# gojieba 需要 cgo;onnxruntime 版本经 dlopen 加载,编译期无需装 ORT。
|
||||
CGO_ENABLED: 1
|
||||
GOFLAGS: -buildvcs=false
|
||||
# CI 用的大资产(模型/运行库)存于这个 release。
|
||||
ASSETS_TAG: ci-assets-v1
|
||||
|
||||
jobs:
|
||||
# ── 读版本号并判断是否需要发版 ──
|
||||
prepare:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
version: ${{ steps.ver.outputs.version }}
|
||||
tag: ${{ steps.ver.outputs.tag }}
|
||||
prerelease: ${{ steps.ver.outputs.prerelease }}
|
||||
exists: ${{ steps.ver.outputs.exists }}
|
||||
skip_tests: ${{ steps.ver.outputs.skip_tests }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# 发版前的 go test 门为什么可以跳过:
|
||||
#
|
||||
# 新旧发布线的测试健康状况不同。实测 release/v1.3.x(历史维护线)上
|
||||
# internal/plugins 的 TestRealPlugin_DeepSearchKeepsSharedBackendOnStop
|
||||
# 失败、GUI 尚无 npm test 脚本、csrc 基础设施不存在 —— 而这三项在 main
|
||||
# 上都正常。给旧线补新流水线等于用今天的门去量旧代码,硬门会让该线
|
||||
# **完全无法发版**。
|
||||
#
|
||||
# 故:默认严格(测试必跑);发版人若确知该线测试是既存红的,可在
|
||||
# 发版 commit 里写 [skip-release-tests] 显式跳过 —— 决定因此记录在
|
||||
# **定义该次发版的那个 commit** 里,git 历史可审计。
|
||||
# go build 仍是硬门(产物不可能建立在编译失败的代码上)。
|
||||
- id: ver
|
||||
name: 读取 meta.Version 并检查 tag
|
||||
run: |
|
||||
set -euo pipefail
|
||||
V=$(sed -n 's/^[[:space:]]*Version = "\(.*\)"/\1/p' \
|
||||
internal/meta/meta.go | head -1)
|
||||
if [ -z "$V" ]; then
|
||||
echo "ERROR: 无法从 internal/meta/meta.go 读出 Version"
|
||||
exit 1
|
||||
fi
|
||||
echo "version=$V" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=v$V" >> "$GITHUB_OUTPUT"
|
||||
# SemVer 预发布(1.3.13-beta.1)⇒ release 标记为预发布
|
||||
case "$V" in
|
||||
*-*) echo "prerelease=true" >> "$GITHUB_OUTPUT" ;;
|
||||
*) echo "prerelease=false" >> "$GITHUB_OUTPUT" ;;
|
||||
esac
|
||||
# 幂等闸门:tag 已存在说明该版本发过了,整轮跳过。
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/v$V" \
|
||||
>/dev/null 2>&1; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
echo " tag v$V 已存在 —— 跳过发版"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
echo " 将为 v$V 发版"
|
||||
fi
|
||||
|
||||
# 是否跳过发版前的 go test 门(默认不跳)。
|
||||
# 两个来源:手动触发的输入,或发版 commit 里的显式标记。
|
||||
# 后者使决定落在定义该次发版的 commit 上,可以从 git 历史审计。
|
||||
#
|
||||
# 标记查在**改动 meta.Version 的那个提交**上,而不是 HEAD:
|
||||
# 发版提交之后往往还会跟几个提交(如同步 workflow、改文档),
|
||||
# 若只看 HEAD,标记就会被后续提交顶掉,静默失效。
|
||||
SKIP="${{ inputs.skip_tests }}"
|
||||
MARKER=0
|
||||
REL_COMMIT=$(git log -1 --format=%H -- internal/meta/meta.go)
|
||||
REL_MSG=$(git log -1 --pretty=%B "$REL_COMMIT")
|
||||
case "$REL_MSG" in
|
||||
*'[skip-release-tests]'*) MARKER=1 ;;
|
||||
*) MARKER=0 ;;
|
||||
esac
|
||||
echo " 发版提交: ${REL_COMMIT:0:12}"
|
||||
if [ "$SKIP" = "true" ] || [ "$MARKER" = "1" ]; then
|
||||
echo "skip_tests=true" >> "$GITHUB_OUTPUT"
|
||||
echo ""
|
||||
echo " ⚠️ **已请求跳过发版前的 go test 门**"
|
||||
echo " 来源:${SKIP} = true / commit 标记 = $MARKER"
|
||||
echo " 后果:产物可能建立在单元测试失败的代码上。"
|
||||
echo " 理由应当记录在发版 commit 的正文里。"
|
||||
else
|
||||
echo "skip_tests=false" >> "$GITHUB_OUTPUT"
|
||||
echo " 发版前会跑 go test 门(可用 [skip-release-tests] 标记跳过)"
|
||||
fi
|
||||
|
||||
# ── 构建 Linux 产物(amd64)──
|
||||
#
|
||||
# 三个 deb + 一个 tar.gz,总约 2.4GB(server/full/tar 含 719MB 模型)。
|
||||
# 编译不需要 ONNX Runtime —— onnxruntime_go 是 dlopen 方式,运行期才加载
|
||||
# libonnxruntime.so;但**打包**需要它(要打进 deb),故从 ASSETS_TAG 下载。
|
||||
build-linux:
|
||||
name: Build linux/amd64
|
||||
needs: prepare
|
||||
if: needs.prepare.outputs.exists == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: 确认 cgo 工具链
|
||||
run: |
|
||||
gcc --version | head -1
|
||||
g++ --version | head -1
|
||||
|
||||
# 发版前的门。
|
||||
#
|
||||
# go build 是**硬门**:产物不可能建立在编译失败的代码上。
|
||||
# go test 默认也跑,但可在发版 commit 里写 [skip-release-tests] 跳过
|
||||
# —— 历史维护线的既有红测试不应阻断该线的一切发版(详见 prepare job)。
|
||||
- name: go build(硬门)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
go build ./...
|
||||
|
||||
- name: go test(发版前验证)
|
||||
if: needs.prepare.outputs.skip_tests != 'true'
|
||||
run: go test ./... -count=1 -timeout 20m
|
||||
|
||||
- name: go test 被跳过(显式声明的后果)
|
||||
if: needs.prepare.outputs.skip_tests == 'true'
|
||||
run: |
|
||||
echo "::warning title=go test 门已跳过::本次发版未跑 go test,产物可能建立在单元测试失败的代码上。"
|
||||
|
||||
# GUI 依赖 Electron 运行时。打包脚本从两处找它:
|
||||
# 1) ~/.cache/electron 里的 electron-v<ver>-linux-<arch>.zip
|
||||
# 2) cmd/gui/node_modules/electron/dist(同架构时)
|
||||
# 全新 runner 两处都没有 —— 而脚本在都没有时**只能跳过 GUI**,
|
||||
# 于是 client/full 包会静默地不含界面(这正是脚本作者担心的“假包”)。
|
||||
# 所以这里显式装一份:npm 会解析出 ^33.0.0 的实际版本并落到
|
||||
# node_modules,脚本便走第 2 条路径(runner 是 amd64 == 目标架构)。
|
||||
#
|
||||
# ⚠️ 不能用 `npm install --production`(那会跳过 devDependencies,
|
||||
# 而 electron 正是 devDependency)。
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: 安装 Electron(GUI 打包需要)
|
||||
working-directory: cmd/gui
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# 用 npm ci 而非 `npm install electron@<range>`:后者是非确定性的
|
||||
# (range 会随上游发布漂到新版本),且锁不住传递依赖。
|
||||
# package-lock.json 里已锁定 electron(lockfileVersion 3),
|
||||
# ci 严格按 lock 安装,同一个 commit 永远得到同一套依赖。
|
||||
npm ci --no-audit --no-fund
|
||||
test -f node_modules/electron/dist/electron
|
||||
echo " 已就绪:$(node_modules/electron/dist/electron --version)"
|
||||
|
||||
- name: 下载构建资产(模型 + ONNX Runtime)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASE="https://github.com/${GITHUB_REPOSITORY}/releases/download/${ASSETS_TAG}"
|
||||
mkdir -p /tmp/assets/model /tmp/assets/ort
|
||||
for f in chinese-clip-vit-b16-onnx.tar \
|
||||
onnxruntime-linux-amd64-1.28.0.tar SHA256SUMS; do
|
||||
echo " 下载 $f"
|
||||
curl -sSL --retry 3 -o "/tmp/assets/$f" "$BASE/$f"
|
||||
done
|
||||
# 校验(资产是构建输入,损坏会打出坏包)
|
||||
(cd /tmp/assets && sha256sum -c SHA256SUMS)
|
||||
tar -xf /tmp/assets/chinese-clip-vit-b16-onnx.tar \
|
||||
-C /tmp/assets/model
|
||||
ORT_TAR=/tmp/assets/onnxruntime-linux-amd64-1.28.0.tar
|
||||
tar -xf "$ORT_TAR" -C /tmp/assets/ort
|
||||
echo " 模型文件:"
|
||||
ls /tmp/assets/model/chinese-clip-vit-b16-onnx
|
||||
echo " ORT 文件:"
|
||||
ls /tmp/assets/ort
|
||||
|
||||
- name: 打包(tar.gz + full/server/client deb)
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
CHINESECLIP_BUNDLE_DIR: /tmp/assets/model/chinese-clip-vit-b16-onnx
|
||||
ONNXRUNTIME_ASSET_DIR: /tmp/assets/ort
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash deploy/packaging/package-linux.sh amd64 all
|
||||
|
||||
- name: 平铺产物(附件必须同目录,SHA256SUMS 用平铺名)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p /tmp/out
|
||||
cp dist/linux/deb/*.deb /tmp/out/
|
||||
cp dist/linux/tar/*.tar.gz /tmp/out/
|
||||
cp dist/linux/SHA256SUMS /tmp/out/
|
||||
echo " 产物:"
|
||||
for f in /tmp/out/*; do
|
||||
printf " %8.1fMB %s\n" \
|
||||
"$(stat -c %s "$f" | awk '{print $1/1048576}')" "$(basename "$f")"
|
||||
done
|
||||
|
||||
- name: 验证产物(deb 元数据 + 校验和自验)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd /tmp/out
|
||||
for f in *.deb; do
|
||||
echo " $f"
|
||||
dpkg-deb -f "$f" Package Version Architecture | sed 's/^/ /'
|
||||
done
|
||||
# full/server 必须真的带模型,否则是“默认启用但装完不能用”的假包。
|
||||
#
|
||||
# ★ 不能用 grep -q:它匹配到就退出,关闭管道读端,dpkg-deb 内部
|
||||
# 的 tar 写 stdout 时收到 EPIPE(“stdout: write error”),
|
||||
# 在 pipefail 下整条 pipeline 变成失败 —— 检测项本身是好的,
|
||||
# 却被检测手段误杀(首次试发布就死在这里)。改用 >/dev/null,
|
||||
# grep 会读完整个输入再退出,不产生 SIGPIPE。
|
||||
dpkg-deb -c homeagent-full_*_amd64.deb \
|
||||
| grep "chinese-clip-vit-b16-onnx/TextEncoder.onnx" >/dev/null
|
||||
echo " ✓ full 包含模型"
|
||||
dpkg-deb -c homeagent-full_*_amd64.deb \
|
||||
| grep "libonnxruntime.so" >/dev/null
|
||||
echo " ✓ full 包含 ONNX Runtime"
|
||||
dpkg-deb -c homeagent-server_*_amd64.deb \
|
||||
| grep "chinese-clip-vit-b16-onnx/TextEncoder.onnx" >/dev/null
|
||||
echo " ✓ server 包含模型"
|
||||
sha256sum -c SHA256SUMS
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: linux-amd64
|
||||
path: /tmp/out/*
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
# ── 建 tag、建 release、上传附件 ──
|
||||
publish:
|
||||
name: Publish
|
||||
needs: [prepare, build-linux]
|
||||
if: needs.prepare.outputs.exists == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: linux-amd64
|
||||
path: dist
|
||||
|
||||
- name: 打 tag(打在触发本次发版的 commit 上)
|
||||
env:
|
||||
TAG: ${{ needs.prepare.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -a "$TAG" -m "$TAG"
|
||||
git push origin "$TAG"
|
||||
|
||||
- name: 建 release 并上传附件
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ needs.prepare.outputs.tag }}
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
PRE: ${{ needs.prepare.outputs.prerelease }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd dist
|
||||
FLAGS=()
|
||||
[ "$PRE" = "true" ] && FLAGS+=(--prerelease)
|
||||
gh release create "$TAG" \
|
||||
--title "$TAG" \
|
||||
--notes "HomeAgent $VERSION
|
||||
|
||||
产物清单与校验见 SHA256SUMS。
|
||||
- \`homeagent_${VERSION}_linux_amd64.tar.gz\` — 内核 + CLI + GUI 打包
|
||||
- \`homeagent-client_${VERSION}_amd64.deb\` — 客户端
|
||||
- \`homeagent-server_${VERSION}_amd64.deb\` — 服务端(含向量模型)
|
||||
- \`homeagent-full_${VERSION}_amd64.deb\` — 全量" \
|
||||
"${FLAGS[@]}" \
|
||||
./*.deb ./*.tar.gz ./SHA256SUMS
|
||||
echo "=== release 内容 ==="
|
||||
gh release view "$TAG" --json assets \
|
||||
--jq '.assets[] | " \(.name) \(.size) 字节"'
|
||||
|
||||
- name: 回读校验(下载回来验证附件可读且校验和成立)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ needs.prepare.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p /tmp/back
|
||||
cd /tmp/back
|
||||
# ★ 必须显式 --repo:gh 默认从**当前目录的 git 上下文**推断仓库,
|
||||
# 而 /tmp/back 不是 git 仓库 ⇒ 报
|
||||
# "failed to run git: fatal: not a git repository"。
|
||||
# 首次试发布就死在这里 —— 产物其实全部上传成功(tag 与
|
||||
# release 已建、2.40GB 附件齐备),只是这道回读校验自己失败了。
|
||||
gh release download "$TAG" --repo "$GITHUB_REPOSITORY"
|
||||
for f in *; do
|
||||
printf " %8.1fMB %s\n" \
|
||||
"$(stat -c %s "$f" | awk '{print $1/1048576}')" "$f"
|
||||
done
|
||||
sha256sum -c SHA256SUMS
|
||||
echo " ✓ 回读校验通过"
|
||||
|
||||
# ── 同步到 gitcode(国内镜像)──
|
||||
#
|
||||
# 需要仓库 secret GITCODE_TOKEN;未配置则跳过(不阻断 GitHub 侧发布)。
|
||||
# gitcode 的 release 附件是"同名只写一次",故只在此处上传一次。
|
||||
sync-gitcode:
|
||||
name: Sync to gitcode
|
||||
needs: [prepare, publish]
|
||||
if: needs.prepare.outputs.exists == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- id: tok
|
||||
name: 检查 gitcode 凭据
|
||||
run: |
|
||||
if [ -n "${{ secrets.GITCODE_TOKEN }}" ]; then
|
||||
echo "ok=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "ok=false" >> "$GITHUB_OUTPUT"
|
||||
echo " 未配置 GITCODE_TOKEN —— 跳过 gitcode 同步"
|
||||
fi
|
||||
- uses: actions/download-artifact@v8
|
||||
if: steps.tok.outputs.ok == 'true'
|
||||
with:
|
||||
name: linux-amd64
|
||||
path: dist
|
||||
- name: 推 tag 与附件到 gitcode
|
||||
if: steps.tok.outputs.ok == 'true'
|
||||
env:
|
||||
GC_TOKEN: ${{ secrets.GITCODE_TOKEN }}
|
||||
TAG: ${{ needs.prepare.outputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# 1) 推 tag(附件上传前 release 条目必须先存在)
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -a "$TAG" -m "$TAG" 2>/dev/null || true
|
||||
GC_URL="https://JianFeeeee:${GC_TOKEN}@gitcode.com"
|
||||
git push "${GC_URL}/JianFeeeee/HomeAgent.git" "$TAG"
|
||||
# 2) 建 release 条目
|
||||
curl -sS --max-time 60 -X POST \
|
||||
-H "private-token: ${GC_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://gitcode.com/api/v5/repos/JianFeeeee/HomeAgent/releases" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"body\":\"同步自 GitHub\"}" \
|
||||
-o /tmp/.gcrel -w " 建 release → %{http_code}\n"
|
||||
# 3) 上传附件(用仓库既有脚本,它处理 OBS 预签名两步流程)
|
||||
cd dist
|
||||
# 脚本的路径语义是 os.path.join(ASSET_DIR, name)。
|
||||
# 不传文件名时它扫描 ASSET_DIR 并按后缀识别发布产物 —— 这里正合适
|
||||
# (.deb/.tar.gz/SHA256SUMS 都在白名单里)。
|
||||
# 不要传 "./x" 或 "dist/x":那会拼成 dist/dist/x。
|
||||
ASSET_DIR=. GITCODE_REPO=JianFeeeee/HomeAgent \
|
||||
python3 ../deploy/scripts/upload_assets.py "$TAG" "$GC_TOKEN"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -36,6 +36,9 @@ third_party/homeagent-sdk/scripts/
|
||||
# skills/ 是 SDK 仓的 skill 源(hmapdev skill install 的来源),
|
||||
# 与 tools/、docs/ 同理属 SDK 仓自治范围,不进本仓。
|
||||
third_party/homeagent-sdk/skills/
|
||||
# .github/ 同理:SDK 仓的 workflow 由 SDK 仓自己管(它在那边被跟踪),
|
||||
# 本仓不参与构建,不需要在这边重复一份。
|
||||
third_party/homeagent-sdk/.github/
|
||||
third_party/homeagent-sdk/.gitignore
|
||||
third_party/homeagent-sdk/README*
|
||||
third_party/homeagent-sdk/example/
|
||||
|
||||
24
.mailmap
Normal file
24
.mailmap
Normal file
@ -0,0 +1,24 @@
|
||||
# .mailmap —— 提交身份归并
|
||||
#
|
||||
# 为什么需要:GitHub 的 Contributors 列表按 **提交邮箱** 归并身份,而本项目历史里
|
||||
# 同一个人的提交来自多个邮箱(本地 root、个人 QQ、gitcode noreply、GitHub noreply),
|
||||
# 于是列表里出现若干「虚拟贡献者」,看起来像有多个协作者,实际只有一个人。
|
||||
#
|
||||
# 本文件只影响 **展示层**(git shortlog / git log --use-mailmap / GitHub Contributors),
|
||||
# 不改写任何提交对象,历史 SHA 全部保持不变。
|
||||
#
|
||||
# 格式:<归并到的姓名> <归并到的邮箱> <历史姓名> <历史邮箱>
|
||||
#
|
||||
# 注意:HomeAgent Agent <agent@homeagent.local> **不归并** —— agent 的自动提交
|
||||
# 保留独立身份,便于区分人类提交与自动化提交。
|
||||
|
||||
JianFeeeee <JianFeeeee@users.noreply.gitcode.com> JianFeeeee <JianFeeeee@users.noreply.gitcode.com>
|
||||
JianFeeeee <JianFeeeee@users.noreply.gitcode.com> <root@qq.com>
|
||||
JianFeeeee <JianFeeeee@users.noreply.gitcode.com> <2198972886@qq.com>
|
||||
JianFeeeee <JianFeeeee@users.noreply.gitcode.com> <jianfeeeee@homeagent.local>
|
||||
JianFeeeee <JianFeeeee@users.noreply.gitcode.com> <dev@local>
|
||||
JianFeeeee <JianFeeeee@users.noreply.gitcode.com> <root@minecraft-server>
|
||||
JianFeeeee <JianFeeeee@users.noreply.gitcode.com> <109188060+JianFeeeee@users.noreply.github.com>
|
||||
|
||||
# 说明:上表用同一主身份覆盖所有历史邮箱;`git shortlog -sne --use-mailmap` 应只剩
|
||||
# JianFeeeee 与 HomeAgent Agent 两条。
|
||||
14
.pi-lens.json
Normal file
14
.pi-lens.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"_comment": "pi-lens 仓库级配置。cmd/gui 的 test 排除原因见 docs/zh/ci-cd-runbook.md「诊断噪音」一节。",
|
||||
"_why_cmd_gui_tests_excluded": "cmd/gui 是纯 Electron/Node 目录(0 个 .go 文件、无 go.mod)。pi-lens 的 test runner 在写文件时按仓库主语言(Go)为目录跑测试,而 cmd/gui/*.test.mjs 被识别成测试文件 ⇒ 它对一个纯 Node 目录执行 `go test ./cmd/gui`,必然得到 `no Go files ... [setup failed]`,却被报成「测试失败」。这些 .mjs 的真实运行方式是 `npm test`(CI 的 GUI job 已在跑),pi-lens 无法正确执行它们,故排除触发源。上游缺陷:runner 应先确认目录内有该语言的源文件。",
|
||||
"ignore": [
|
||||
"cmd/gui/node_modules/**",
|
||||
"cmd/gui/**/*.test.mjs",
|
||||
".npm-cache/**",
|
||||
"build/**",
|
||||
"dist/**",
|
||||
"third_party/homeagent-sdk/site_build/**",
|
||||
"third_party/homeagent-sdk/build/**",
|
||||
"third_party/homeagent-sdk/.npm-cache/**"
|
||||
]
|
||||
}
|
||||
109
README.md
109
README.md
@ -26,6 +26,18 @@ homed(内核零 IO) ← PluginSDK → 插件(所有 IO 能力)
|
||||
- **Document 层**:临时记忆,冷数据自动下沉,也支持用户主动提交
|
||||
- **Graph 层**:SQLite 图数据库,持久化实体关系和语义记忆,支持蒸馏管道从原始对话中提取三元组
|
||||
|
||||
**场面识别:声明 + 涌现** — 记忆不按「会话」切,而按**可观察的场所指纹**切:
|
||||
通道(`chan:*`)与对话对象最强(权重 1.0),工具 0.8、话题 0.4、时段最弱 0.2。
|
||||
两条路同时走——**声明**(注入点/工具声明「这是哪个场面」)与**涌现**
|
||||
(指纹同类重复 ≥2 次就自己长出场景,`origin=emergent`,无需标注、无需模型配合)。
|
||||
相似度 ≥0.5 判归属、≥0.35 判唤起(**唤起比归属宽松**:想不起来是损失,多想起一条只是多几行上下文)。
|
||||
场景每次重现强度 +1,久不重现按半衰期衰减。
|
||||
|
||||
> 这条机制有一个值得注意的副作用:**agent 自己的回复也写回记忆**
|
||||
> (`task.go` 的 `context.Append(Source: "agent")`),所以它在同一场面上会**读到自己先前的结论**。
|
||||
> 当输入已过期时,它会据此认出「这事上一轮已经办过」并**主动纠正自己先前的错误判断**——
|
||||
> 表现为自我复盘。这是记忆召回的自然结果,内核里**没有**任何名为「反思」的机制。
|
||||
|
||||
**输入调度:两类别 + 四级中断** — 输入不直接进 LLM,先进调度器。
|
||||
排队(待办工作)与中断(按"有多不能等"分 L1~L4)两类;高级可抢占低级并保存现场
|
||||
(中断栈),同级不抢占。L4 只归内核与内核级插件(如 WebUI 终止按钮)。
|
||||
@ -76,14 +88,22 @@ sequenceDiagram
|
||||
LLM->>LLM: 安全点:中断求值/让位
|
||||
LLM->>LLM: LLM Chat
|
||||
LLM->>ST: StagePostAction 插件可修改/短路
|
||||
alt 无tool call
|
||||
alt 无 tool call
|
||||
LLM-->>EV: 返回response
|
||||
else
|
||||
loop 每个tool
|
||||
ST->>ST: StageBeforeToolcall 插件可拒绝
|
||||
LLM->>LLM: executeToolCall
|
||||
ST->>ST: StageAfterToolcall
|
||||
else 一批 N 个 tool_call
|
||||
Note over EV: batchRunnable 判据:批内 >1 且全部 ParallelSafe<br/>且无同通道重复发送(output_send__「通道」)
|
||||
alt 可并发(三条全满足)
|
||||
par fan-out 并发执行
|
||||
ST->>ST: StageBeforeToolcall ×N 插件可拒绝
|
||||
LLM->>LLM: executeToolCall ×N
|
||||
end
|
||||
else 整批降级串行(任一个未声明 ParallelSafe)
|
||||
loop 每个 tool 依次
|
||||
ST->>ST: StageBeforeToolcall 插件可拒绝
|
||||
LLM->>LLM: executeToolCall
|
||||
end
|
||||
end
|
||||
Note over EV: 顺序收尾:按**声明序** StageAfterToolcall → 落 tool 消息<br/>同通道输出严格保序
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -104,18 +124,26 @@ sequenceDiagram
|
||||
flowchart LR
|
||||
S1[① on_input] --> S2[② pre_action]
|
||||
S2 --> S3[③ post_action]
|
||||
S3 --> Q{有tool?}
|
||||
Q -->|是| S4[④ before_toolcall]
|
||||
S4 --> T[executeToolCall]
|
||||
T --> S5[⑤ after_toolcall]
|
||||
S5 --> S3
|
||||
S3 --> Q{有 tool_call?}
|
||||
Q -->|是,一批 N 个| PB{batchRunnable?<br/>全声明 ParallelSafe<br/>且无同通道重复发送}
|
||||
PB -->|可并发| S45P[④⑤ 并发 ×N<br/>before_toolcall×N → 执行×N<br/>→ 按声明序 after_toolcall]
|
||||
PB -->|整批降级| S45S[④⑤ 依次 ×N<br/>before_toolcall → 执行<br/>→ after_toolcall]
|
||||
S45P --> S3
|
||||
S45S --> S3
|
||||
Q -->|否| S6[⑥ before_output]
|
||||
S6 --> S7[⑦ after_output]
|
||||
style S1 fill:#e1f5fe
|
||||
style S3 fill:#fff3e0
|
||||
style S6 fill:#e8f5e9
|
||||
style S45P fill:#fce4ec
|
||||
style S45S fill:#f5f5f5
|
||||
```
|
||||
|
||||
> ①–⑦ 七个 Stage 均由 `internal/sdk/plugin.go` 公开(`StageOnInput` … `StageAfterOutput`),
|
||||
> 插件可注册挂钩。⚠️ 并行只影响 **④⑤ 的执行时序**:`post_action` 仍在本批工具
|
||||
> 全部收尾后由下一轮触发,`after_toolcall` 也仍按**声明序**回调——
|
||||
> 并发的是 IO 等待,不是插件契约的可见顺序。
|
||||
|
||||
### 三、三层记忆
|
||||
|
||||
```mermaid
|
||||
@ -138,6 +166,17 @@ flowchart TB
|
||||
IDX[Indexer 向量+jieba→BFS depth=2] -->|【记忆索引】| SP
|
||||
MEM[memory_recall/commit/merge/purge/edit]
|
||||
SOC[person_query/set_trait]
|
||||
subgraph SC[场面识别:声明 + 涌现]
|
||||
FE[① 指纹 chan/peer/tool/topic/part<br/>权重 1.0/1.0/0.8/0.4/0.2]
|
||||
EN{② EnterSceneWithHint<br/>相似度 ≥0.5 归属<br/>≥0.35 唤起}
|
||||
FE --> EN
|
||||
EN -->|插件已声明| DEC[origin=declared]
|
||||
EN -->|重现 ≥2 次| EM[origin=emergent<br/>自动长出场景]
|
||||
EM -->|每次重现 strength+1| STR[③ 用进废退<br/>久不重现按半衰期衰减]
|
||||
DEC --> STR
|
||||
end
|
||||
STR -->|Primary| CARRY[本轮命中的场景<br/>挂载的记忆自动唤起]
|
||||
CARRY -->|【场景记忆】| SP
|
||||
end
|
||||
subgraph H[④ 心跳蒸馏]
|
||||
REORG -->|Step3 冷文档| CD
|
||||
@ -151,13 +190,6 @@ flowchart TB
|
||||
|
||||
详细说明见 [`assets/docs/zh/ARCHITECTURE.md`](assets/docs/zh/ARCHITECTURE.md)。
|
||||
|
||||
## 看板娘
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/branding/mascot-xiaozhai.webp" alt="HomeAgent 看板娘 小宅" width="200">
|
||||
<p><strong>小宅</strong> — HomeAgent 看板娘</p>
|
||||
</div>
|
||||
|
||||
## 快速体验
|
||||
|
||||
```bash
|
||||
@ -203,17 +235,33 @@ internal/
|
||||
├── memory/ 三层记忆:Graph(SQLite) / Document(JSON+TF-IDF) / Text(JSONL) + StaticEmbedder(预训练词嵌入/TF-IDF回退) + CleanTemplateText(去模版)
|
||||
├── knowledge/ 知识库(文件系统 + TF-IDF)
|
||||
├── plugin/ 插件注册表 + 子进程加载器(stdio RPC + 共享内存段 + 事件环)
|
||||
├── plugins/ 内置 18 个插件(webui/cli/timer/cmd/mcp/files/cfgmgr/agentcli/healthcheck/pluginmgr/clawhubadapter/multimodal/remotedevice/ai_image/localuse/skillmgr/data 等)
|
||||
├── plugins/ 内置 20 个插件(webui/cli/timer/cmd/mcp/files/cfgmgr/agentcli/healthcheck/pluginmgr/clawhubadapter/multimodal/remotedevice/ai_image/localuse/skillmgr/data/seq/kbtree 等)
|
||||
├── sdk/ PluginSDK(Tool/Stage/Event 三通道)
|
||||
├── config/ SQLite 配置中心
|
||||
├── events/ 事件总线
|
||||
└── internal/lua/adapters/ 10 个 LLM 协议适配器脚本
|
||||
外部插件开发见 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库,使用 `hmapdev` 工具链开发,参考 `example/` 目录下的 Go 和 Lua 示例
|
||||
外部插件开发见 [homeagent-sdk](https://github.com/JianFeeeee/homeagentsdk) 仓库,使用 `hmapdev` 工具链开发,参考 `example/` 目录下的 Go 和 Lua 示例
|
||||
```
|
||||
|
||||
## 项目状态
|
||||
|
||||
**v1.3.x 线**(v1.3.1–v1.3.12,最新已发布)—— **驻留式子 agent** + **输入调度器重做**。
|
||||
**v1.4.x 线**(进行中,`main`)—— **声明式并发安全** + **序列编排(seq)** + **工具结果诚实化**。
|
||||
|
||||
- **同轮工具并行执行**:同一轮里的多个 `tool_call` 默认并发执行,**但以「安全」为前置**——
|
||||
工具必须在自己的 `ToolDef` 里显式声明 `ParallelSafe`(内置工具用 `toolDefOptions`),
|
||||
未声明的一律串行;`Serial` 优先级更高。**批内只要有一个不安全,整批降级为串行**。
|
||||
并发声明写在**工具自身**,内核不做硬编码工具名安全表。同通道输出**严格保序**,
|
||||
结果与文本分离(`toolOutcome.Text` / `Raw`)。
|
||||
- **`seq` 序列编排插件**:AST 解析 + 持久化、具名 group、变量槽、**组内并行 + 组间串行**、
|
||||
条件调用(`seq_when_call`)、跨序列调用图与环检测、错误契约。
|
||||
- **工具结果只统计不裁剪**:结果超出预算时**报告**统计量,不静默截断。
|
||||
- **结构化工具错误契约**:参数按 schema **预校验**(分派前拦下)、
|
||||
「工具不存在」与「执行失败」区分、`Success` 不再恒真。
|
||||
- **设备命令白名单可配置**(waiter `device_cmd_allowlist`)、
|
||||
**站点漂移巡检**、**GUI 增量渲染**(保留全部历史,按 `data-msgkey` 复用节点,
|
||||
50/200/400 条实测 8.2/23.5/38.2ms)。
|
||||
|
||||
**v1.3.x 线**(v1.3.1–v1.3.13)—— **驻留式子 agent** + **输入调度器重做**。
|
||||
|
||||
- **驻留式子 agent**:内核可派驻轻量内核的子 agent(自己的调度器、自己的 temp 图记忆、
|
||||
共享通道登记表)。父经 `resident_agents`(list/create/send/inspect/compress/reclaim/destroy)
|
||||
@ -270,7 +318,7 @@ internal/
|
||||
> inline/small **30.4µs**、frame/small 51.5µs、inline/large 767µs、frame/large 398µs。
|
||||
> 保留原文不修改,以免伪造历史。
|
||||
|
||||
**v1.1.1** — 多模态贯通**插件边界**。v1.1.0 让记忆系统支持了二进制多媒体节点,但那条链路只对内核自己开放;本版打通到插件与模型。公开 SDK 新增媒体字段与三个媒体注入接口(配套 [SDK v1.1.0](https://gitcode.com/JianFeeeee/homeagent-sdk/releases/tag/v1.1.0),整条 1.1.x 线共用),内核实现对应四个 RPC。桥接层此前在**静默裁字段**:插件交进来的 `Confidence`/类型/`SentenceText` 全被丢弃、`Doc` 只留三个字段、`Remove` 不解引用(媒体永久算「被引用」,GC 收不掉)。`processTextInput`/`processMediaInput` 归一成一条 `processInput`,媒体路径由此获得它一直缺的去重、`no_memory`、通道 `Cleaner`、中断语义、`EventRawInput`。修掉三处真实缺陷:**用户发的图从来没出现在 WebUI 聊天记录里**(媒体路径发布 map 而订阅方断言 string)、**`memory_commit` 的 `sentence_text` 从未暴露给模型**(而它是媒体绑定链的必经环节)、**`PluginSDK` 两处并发竞态**(`-race` 实测 11 处,插件重载瞬间偶发 nil 解引用崩溃)。
|
||||
**v1.1.1** — 多模态贯通**插件边界**。v1.1.0 让记忆系统支持了二进制多媒体节点,但那条链路只对内核自己开放;本版打通到插件与模型。公开 SDK 新增媒体字段与三个媒体注入接口(配套 [SDK v1.1.0](https://github.com/JianFeeeee/homeagentsdk/tree/v1.1.0),整条 1.1.x 线共用),内核实现对应四个 RPC。桥接层此前在**静默裁字段**:插件交进来的 `Confidence`/类型/`SentenceText` 全被丢弃、`Doc` 只留三个字段、`Remove` 不解引用(媒体永久算「被引用」,GC 收不掉)。`processTextInput`/`processMediaInput` 归一成一条 `processInput`,媒体路径由此获得它一直缺的去重、`no_memory`、通道 `Cleaner`、中断语义、`EventRawInput`。修掉三处真实缺陷:**用户发的图从来没出现在 WebUI 聊天记录里**(媒体路径发布 map 而订阅方断言 string)、**`memory_commit` 的 `sentence_text` 从未暴露给模型**(而它是媒体绑定链的必经环节)、**`PluginSDK` 两处并发竞态**(`-race` 实测 11 处,插件重载瞬间偶发 nil 解引用崩溃)。
|
||||
|
||||
**v1.1.0** — 记忆系统支持**二进制多媒体节点**。内容寻址媒体存储(CAS + SQLite 元数据 + 磁盘 blob,`Get` always 重校 digest),贯通 L0(上下文事件)/L2(文档)/L3(图谱句子)三层,引用计数式 GC(有引用者绝不删)。视觉模型生成的描述文本是持久语义记忆,blob 只是可被容量 GC 淘汰的缓存。
|
||||
|
||||
@ -278,10 +326,19 @@ internal/
|
||||
|
||||
**v0.9.0** — C ABI v2:外部插件 Stage 回调支持写回(`invoke_stage` 增加 result 输出,插件可在 OnInput/AfterToolcall/PostAction 修改 RawMessage/LLMText/ToolResults 等并同步回内核),ABI 版本随内核 minor 对齐(v0.9.x → ABIVersion=2,`version_min=1` 向后兼容旧插件)。同步修复工具循环 zen 兼容补位误伤首轮 system 上下文的问题。配套 SDK 提供增强版 sanitizer 示例(坏 UTF-8/U+FFFD/ANSI 转义全链路清洗)。**该 ABI 已随 v1.0.0 退场。**
|
||||
|
||||
**v0.8.0** — 核心可用,插件系统增强。内置 20+ 插件,外部插件开发见 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 仓库。新增输入通道 `NoMemory`/`Cleaner`、`ChannelDef`、插件禁用/启用系统(CLI + WebUI),`plugindev` 工具链完成 C ABI `ChannelDef` 传递。
|
||||
**v0.8.0** — 核心可用,插件系统增强。内置 20+ 插件,外部插件开发见 [homeagent-sdk](https://github.com/JianFeeeee/homeagentsdk) 仓库。新增输入通道 `NoMemory`/`Cleaner`、`ChannelDef`、插件禁用/启用系统(CLI + WebUI),`plugindev` 工具链完成 C ABI `ChannelDef` 传递。
|
||||
|
||||
## 文档
|
||||
|
||||
**在线文档**:
|
||||
|
||||
- 介绍站(项目总览):<https://introduce.homeagent.jianfgit.xyz/>
|
||||
- 插件 SDK 文档(快速开始 / API 参考 / 指南 / 示例):<https://sdk.homeagent.jianfgit.xyz/>
|
||||
—— 面向 agent 的纯文本入口:[`llms.txt`](https://sdk.homeagent.jianfgit.xyz/llms.txt) /
|
||||
[`llms-full.txt`](https://sdk.homeagent.jianfgit.xyz/llms-full.txt)
|
||||
|
||||
**仓内文档**(随代码版本走):
|
||||
|
||||
- [项目概览](assets/docs/zh/OVERVIEW.md) | [English](assets/docs/en/OVERVIEW.md)
|
||||
- [技术架构](assets/docs/zh/ARCHITECTURE.md) | [English](assets/docs/en/ARCHITECTURE.md)
|
||||
- [插件开发指南](assets/docs/zh/PLUGIN_DEV.md) | [English](assets/docs/en/PLUGIN_DEV.md)
|
||||
@ -290,7 +347,7 @@ internal/
|
||||
|
||||
## 下载
|
||||
|
||||
[Releases](https://gitcode.com/JianFeeeee/HomeAgent/releases) 提供三种变体:
|
||||
[Releases](https://github.com/JianFeeeee/HomeAgent/releases) 提供三种变体:
|
||||
|
||||
| 变体 | 内容 | 适用 |
|
||||
| --- | --- | --- |
|
||||
@ -328,12 +385,12 @@ make install # 安装到系统
|
||||
**通过网络提供服务时也要向使用者提供源码**(§13 Remote Network Interaction)。
|
||||
即:任何人把改过的 HomeAgent 对外提供网络服务,都必须让该服务的使用者拿到改动后的源码。
|
||||
|
||||
插件与本项目通过公开 [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) 静态链接
|
||||
插件与本项目通过公开 [homeagent-sdk](https://github.com/JianFeeeee/homeagentsdk) 静态链接
|
||||
(SDK 源码会进入插件二进制),但那个仓**以 MIT 发布**——MIT 是宽松许可,拿到授权的代码不继承
|
||||
本项目的 AGPL。因此**外部插件不是本项目的衍生作品**,作者可自行选择许可(含闭源、商业、私有),
|
||||
既不必同许可、也不受 §13 网络条款约束。第三方插件生态的安全与活跃正建立在这条之上。
|
||||
|
||||
边界很清楚:**AGPL 覆盖内核与随包内置插件**(`homed`、`internal/`、`internal/plugins/` 下 18 个内置插件);
|
||||
边界很清楚:**AGPL 覆盖内核与随包内置插件**(`homed`、`internal/`、`internal/plugins/` 下 20 个内置插件);
|
||||
**MIT 覆盖公开 SDK**(`sdk/`,`go.mod` 零外部依赖、只依赖 Go 标准库,不引用内核任何代码)。
|
||||
子进程隔离在这里不重要了——决定许可的是被链接的 SDK 代码本身,而它是 MIT。
|
||||
|
||||
|
||||
110
README_EN.md
110
README_EN.md
@ -87,12 +87,20 @@ sequenceDiagram
|
||||
LLM->>ST: StagePostAction Plugin can modify/short-circuit
|
||||
alt No tool call
|
||||
LLM-->>EV: Returns response
|
||||
else
|
||||
loop Each tool
|
||||
ST->>ST: StageBeforeToolcall Plugin can reject
|
||||
LLM->>LLM: executeToolCall
|
||||
ST->>ST: StageAfterToolcall
|
||||
else a batch of N tool_calls
|
||||
Note over EV: batchRunnable criteria: batch >1 AND all declare ParallelSafe<br/>and no same-channel duplicate send (output_send__«channel»)
|
||||
alt parallel (all three satisfied)
|
||||
par fan-out concurrent execution
|
||||
ST->>ST: StageBeforeToolcall ×N Plugin can reject
|
||||
LLM->>LLM: executeToolCall ×N
|
||||
end
|
||||
else whole batch degrades to serial (any undeclared)
|
||||
loop Each tool in turn
|
||||
ST->>ST: StageBeforeToolcall Plugin can reject
|
||||
LLM->>LLM: executeToolCall
|
||||
end
|
||||
end
|
||||
Note over EV: ordered tail: StageAfterToolcall in **declaration order** → append tool msgs<br/>same-channel output strictly ordered
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -113,18 +121,27 @@ sequenceDiagram
|
||||
flowchart LR
|
||||
S1[① on_input] --> S2[② pre_action]
|
||||
S2 --> S3[③ post_action]
|
||||
S3 --> Q{Has tool?}
|
||||
Q -->|Yes| S4[④ before_toolcall]
|
||||
S4 --> T[executeToolCall]
|
||||
T --> S5[⑤ after_toolcall]
|
||||
S5 --> S3
|
||||
S3 --> Q{Any tool_call?}
|
||||
Q -->|Yes, batch of N| PB{batchRunnable?<br/>all declare ParallelSafe<br/>and no same-channel duplicate send}
|
||||
PB -->|parallel| S45P[④⑤ concurrent ×N<br/>before_toolcall×N → execute×N<br/>→ after_toolcall in declaration order]
|
||||
PB -->|degrade to serial| S45S[④⑤ sequential ×N<br/>before_toolcall → execute<br/>→ after_toolcall]
|
||||
S45P --> S3
|
||||
S45S --> S3
|
||||
Q -->|No| S6[⑥ before_output]
|
||||
S6 --> S7[⑦ after_output]
|
||||
style S1 fill:#e1f5fe
|
||||
style S3 fill:#fff3e0
|
||||
style S6 fill:#e8f5e9
|
||||
style S45P fill:#fce4ec
|
||||
style S45S fill:#f5f5f5
|
||||
```
|
||||
|
||||
> All seven stages ①–⑦ are published by `internal/sdk/plugin.go` (`StageOnInput` …
|
||||
> `StageAfterOutput`); plugins may register hooks. ⚠️ Parallelism affects only the
|
||||
> **execution timing of ④⑤**: `post_action` still fires on the next round once the whole
|
||||
> batch is collected, and `after_toolcall` still runs in **declaration order** —
|
||||
> what runs concurrently is IO waiting, not the plugin-visible contract order.
|
||||
|
||||
### 3. Three-Layer Memory
|
||||
|
||||
```mermaid
|
||||
@ -137,16 +154,27 @@ flowchart TB
|
||||
end
|
||||
subgraph D[② Document File Memory]
|
||||
DS[DocStore JSON+TF-IDF]
|
||||
Q1[Query summary auto-inject] -->|[Related Memory Docs]| SP
|
||||
Q1[Query summary auto-inject] -->|Related Memory Docs| SP
|
||||
Q2[doc_query LLM active recall] -->|Consume+delete source| DS
|
||||
Q2 -->|Original timestamp write to context| RC
|
||||
CD[FindColdDocs 72h] -->|docToTriples| G
|
||||
end
|
||||
subgraph G[③ Graph Database]
|
||||
DB[(SQLite)]
|
||||
IDX[Indexer vector+jieba→BFS depth=2] -->|[Memory Index]| SP
|
||||
IDX[Indexer vector+jieba→BFS depth=2] -->|Memory Index| SP
|
||||
MEM[memory_recall/commit/merge/purge/edit]
|
||||
SOC[person_query/set_trait]
|
||||
subgraph SC[Scene recognition: declared + emergent]
|
||||
FE[① fingerprint chan/peer/tool/topic/part<br/>weights 1.0/1.0/0.8/0.4/0.2]
|
||||
EN{② EnterSceneWithHint<br/>similarity ≥0.5 join<br/>≥0.35 recall}
|
||||
FE --> EN
|
||||
EN -->|plugin declared| DEC[origin=declared]
|
||||
EN -->|seen ≥2 times| EM[origin=emergent<br/>the scene grows itself]
|
||||
EM -->|each recurrence strength+1| STR[③ use-it-or-lose-it<br/>decay by half-life when unused]
|
||||
DEC --> STR
|
||||
end
|
||||
STR -->|Primary| CARRY[scene hit this round<br/>its memories auto-recalled]
|
||||
CARRY -->|Scene Memory| SP
|
||||
end
|
||||
subgraph H[④ Heartbeat Distillation]
|
||||
REORG -->|Step3 Cold docs| CD
|
||||
@ -160,13 +188,6 @@ flowchart TB
|
||||
|
||||
See [`assets/docs/en/ARCHITECTURE.md`](assets/docs/en/ARCHITECTURE.md) for details.
|
||||
|
||||
## Web Mascot
|
||||
|
||||
<div align="center">
|
||||
<img src="assets/branding/mascot-xiaozhai.webp" alt="HomeAgent Web Mascot Xiaozhai" width="200">
|
||||
<p><strong>Xiaozhai</strong> — HomeAgent Web Mascot</p>
|
||||
</div>
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
@ -195,17 +216,35 @@ internal/
|
||||
├── memory/ Three-layer memory: Graph(SQLite) / Document(JSON+TF-IDF) / Text(JSONL) + StaticEmbedder(pretrained word embedding/TF-IDF fallback) + CleanTemplateText(de-template)
|
||||
├── knowledge/ Knowledge base (filesystem + TF-IDF)
|
||||
├── plugin/ Plugin registry + subprocess loader (stdio RPC + shared memory segment + event ring)
|
||||
├── plugins/ 18 built-in plugins (webui/cli/timer/cmd/mcp/files/cfgmgr/agentcli/healthcheck/pluginmgr/clawhubadapter/multimodal/remotedevice/ai_image/localuse/skillmgr/data, ...)
|
||||
├── plugins/ 20 built-in plugins (webui/cli/timer/cmd/mcp/files/cfgmgr/agentcli/healthcheck/pluginmgr/clawhubadapter/multimodal/remotedevice/ai_image/localuse/skillmgr/data/seq/kbtree, ...)
|
||||
├── sdk/ PluginSDK (Tool/Stage/Event three channels)
|
||||
├── config/ SQLite config center
|
||||
├── events/ Event bus
|
||||
└── internal/lua/adapters/ 10 LLM protocol adapter scripts
|
||||
External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo, use `hmapdev` toolchain, refer to Go and Lua examples in `example/`
|
||||
External plugin development: see [homeagent-sdk](https://github.com/JianFeeeee/homeagentsdk) repo, use `hmapdev` toolchain, refer to Go and Lua examples in `example/`
|
||||
```
|
||||
|
||||
## Project Status
|
||||
|
||||
**v1.3.x line** (v1.3.1–v1.3.12, latest released) — **resident sub-agents** + **input scheduler rework**.
|
||||
**v1.4.x line** (in progress, `main`) — **declarative concurrency safety** + **sequence orchestration (seq)** + **honest tool results**.
|
||||
|
||||
- **Same-round parallel tool execution**: multiple `tool_call`s in one round run concurrently,
|
||||
**but safety gates it** — a tool must explicitly declare `ParallelSafe` in its own `ToolDef`
|
||||
(built-ins use `toolDefOptions`); anything undeclared runs serially, and `Serial` wins.
|
||||
**A single unsafe tool degrades the whole batch to serial.** Declarations live **in the tool
|
||||
itself**; the kernel keeps no hardcoded name table. Same-channel output stays **strictly ordered**,
|
||||
and results are separated from text (`toolOutcome.Text` / `Raw`).
|
||||
- **`seq` orchestration plugin**: AST parsing + persistence, named groups, variable slots,
|
||||
**parallel within a group / serial between groups**, conditional calls (`seq_when_call`),
|
||||
cross-sequence call graph with cycle detection, error contract.
|
||||
- **Tool results are reported, never silently truncated** — over budget emits statistics.
|
||||
- **Structured tool error contract**: arguments pre-validated against the schema (rejected before
|
||||
dispatch), "tool not found" distinguished from "execution failed", `Success` no longer always true.
|
||||
- **Configurable device command allowlist** (waiter `device_cmd_allowlist`),
|
||||
**site drift watcher**, **incremental GUI rendering** (keeps all history, reuses nodes by
|
||||
`data-msgkey`; 50/200/400 messages measured at 8.2/23.5/38.2ms).
|
||||
|
||||
**v1.3.x line** (v1.3.1–v1.3.13) — **resident sub-agents** + **input scheduler rework**.
|
||||
|
||||
- **Resident sub-agents**: the kernel can station lightweight-kernel child agents (their own
|
||||
scheduler, their own temp graph memory, sharing the channel registry). The parent dispatches
|
||||
@ -215,11 +254,11 @@ External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/
|
||||
(consistent across all three filter points) lets parent/child deliver to each other;
|
||||
device capabilities became output channels too (one `device/<id>` per device).
|
||||
- **Input scheduler**: two task classes (queued/interrupt) + four interrupt levels (L1–L4)
|
||||
+ preempt/suspend/resume/interrupt-stack; same level never preempts same level, with a
|
||||
- preempt/suspend/resume/interrupt-stack; same level never preempts same level, with a
|
||||
starvation guard and preemption cooldown. L4 belongs only to the kernel and kernel-level
|
||||
plugins (e.g. the WebUI stop button).
|
||||
- **Lightweight kernel profile**: a child's memory surface narrows to "conventional context
|
||||
+ graph memory" (narrow interface; the main graph opens as a query_only handle, writes go
|
||||
- graph memory" (narrow interface; the main graph opens as a query_only handle, writes go
|
||||
to its own temp instance).
|
||||
- **Backlog timely feedback** (later in the line): when the main agent is busy for a long time,
|
||||
the kernel hands queued input to a temporary **triage assistant** — simple items are handled
|
||||
@ -284,7 +323,7 @@ External plugin development: see [homeagent-sdk](https://gitcode.com/JianFeeeee/
|
||||
**v1.1.1** — Multimodal reaches the **plugin boundary**. v1.1.0 gave the memory system binary
|
||||
multimedia nodes, but that path was open only to the kernel itself; this release opens it to
|
||||
plugins and the model. The public SDK gains media fields and three media injection methods
|
||||
(paired with [SDK v1.1.0](https://gitcode.com/JianFeeeee/homeagent-sdk/releases/tag/v1.1.0),
|
||||
(paired with [SDK v1.1.0](https://github.com/JianFeeeee/homeagentsdk/tree/v1.1.0),
|
||||
shared by the whole 1.1.x line), and the kernel implements the four matching RPCs. The bridge
|
||||
layer had been **silently dropping fields**: `Confidence`/types/`SentenceText` handed in by a
|
||||
plugin were discarded, `Doc` kept only three fields, and `Remove` never released references
|
||||
@ -307,10 +346,19 @@ semantic memory; the blob is only a cache that capacity GC may evict.
|
||||
|
||||
**v0.9.0** — C ABI v2: external plugin Stage callbacks can now write back (`invoke_stage` gained a result out-param; plugins may mutate RawMessage/LLMText/ToolResults etc. in OnInput/AfterToolcall/PostAction and have them synced to the core). ABI version now tracks core minor releases (v0.9.x → ABIVersion=2, `version_min=1` keeps old plugins loadable). Also fixes the tool-loop zen-compat placeholder that wrongly fired on first-turn system context tail. The SDK ships an enhanced sanitizer example (bad-UTF-8 / U+FFFD / ANSI-escape scrub across the whole pipeline). **This ABI retired with v1.0.0.**
|
||||
|
||||
**v0.8.0** — Core is functional, plugin system enhanced. 20+ built-in plugins. External plugin development via [homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) repo. Added input channel `NoMemory`/`Cleaner`, `ChannelDef`, plugin disable/enable system (CLI + WebUI), `plugindev` toolchain C ABI `ChannelDef` support.
|
||||
**v0.8.0** — Core is functional, plugin system enhanced. 20+ built-in plugins. External plugin development via [homeagent-sdk](https://github.com/JianFeeeee/homeagentsdk) repo. Added input channel `NoMemory`/`Cleaner`, `ChannelDef`, plugin disable/enable system (CLI + WebUI), `plugindev` toolchain C ABI `ChannelDef` support.
|
||||
|
||||
## Documentation
|
||||
|
||||
**Online docs**:
|
||||
|
||||
- Introduction site (project overview): <https://introduce.homeagent.jianfgit.xyz/>
|
||||
- Plugin SDK docs (getting started / API reference / guides / examples): <https://sdk.homeagent.jianfgit.xyz/>
|
||||
— agent-friendly plain text: [`llms.txt`](https://sdk.homeagent.jianfgit.xyz/llms.txt) /
|
||||
[`llms-full.txt`](https://sdk.homeagent.jianfgit.xyz/llms-full.txt)
|
||||
|
||||
**In-repo docs** (versioned with the code):
|
||||
|
||||
- [Project Overview](assets/docs/en/OVERVIEW.md) | [中文](assets/docs/zh/OVERVIEW.md)
|
||||
- [Technical Architecture](assets/docs/en/ARCHITECTURE.md) | [中文](assets/docs/zh/ARCHITECTURE.md)
|
||||
- [Plugin Development Guide](assets/docs/en/PLUGIN_DEV.md) | [中文](assets/docs/zh/PLUGIN_DEV.md)
|
||||
@ -319,10 +367,10 @@ semantic memory; the blob is only a cache that capacity GC may evict.
|
||||
|
||||
## Downloads
|
||||
|
||||
[Releases](https://gitcode.com/JianFeeeee/HomeAgent/releases) ship three variants:
|
||||
[Releases](https://github.com/JianFeeeee/HomeAgent/releases) ship three variants:
|
||||
|
||||
| Variant | Contents | For |
|
||||
|---|---|---|
|
||||
| --- | --- | --- |
|
||||
| **full** | homed + waiter + desktop GUI + systemd unit | Single-machine, everything |
|
||||
| **server** | homed + waiter + systemd unit | Servers (no desktop environment) |
|
||||
| **client** | waiter + desktop GUI | Connecting to a remote HomeAgent |
|
||||
@ -360,7 +408,7 @@ with it over a network** (§13, Remote Network Interaction). Anyone running a mo
|
||||
as a network service therefore has to make the modified source available to that service's users.
|
||||
|
||||
Plugins are **statically linked** against this project through the public
|
||||
[homeagent-sdk](https://gitcode.com/JianFeeeee/homeagent-sdk) (the SDK source ends up inside the
|
||||
[homeagent-sdk](https://github.com/JianFeeeee/homeagentsdk) (the SDK source ends up inside the
|
||||
plugin binary), but that repository is released under **MIT** — a permissive license, so code
|
||||
received under it does **not** inherit this project's AGPL. External plugins are therefore **not
|
||||
derivative works of this project**: authors pick their own license (closed-source, commercial or
|
||||
@ -368,7 +416,7 @@ private included), with no same-license obligation and no §13 network clause. T
|
||||
vitality of the third-party plugin ecosystem rest on this.
|
||||
|
||||
The boundary is clean: **AGPL covers the kernel and the bundled plugins** (`homed`, `internal/`,
|
||||
the 18 built-in plugins under `internal/plugins/`); **MIT covers the public SDK** (`sdk/`, whose
|
||||
the 20 built-in plugins under `internal/plugins/`); **MIT covers the public SDK** (`sdk/`, whose
|
||||
`go.mod` has zero external dependencies and imports only the Go standard library — it never
|
||||
references any kernel code). Process isolation is beside the point here — what decides the
|
||||
license is the linked SDK code itself, and that code is MIT.
|
||||
@ -376,7 +424,7 @@ license is the linked SDK code itself, and that code is MIT.
|
||||
### Third-party components shipped with the packages
|
||||
|
||||
| Component | License | Location |
|
||||
|---|---|---|
|
||||
| --- | --- | --- |
|
||||
| Chinese-CLIP ViT-B/16 (ONNX artifacts) | Apache-2.0 | `/usr/lib/homeagent/models/chinese-clip-vit-b16-onnx/` |
|
||||
| ONNX Runtime (`libonnxruntime.so`) | MIT | `/usr/lib/homeagent/onnxruntime/` |
|
||||
| jieba dictionary (embedded in the binary) | MIT | `internal/memory/jiebadict/` |
|
||||
|
||||
@ -22,9 +22,11 @@
|
||||
# 结论(打不了包)不变但**理由已变**,别照旧文字理解。
|
||||
#
|
||||
# 用法:
|
||||
# ./deploy-sdk-site.sh # 构建 + 部署 + 验证
|
||||
# ./deploy-sdk-site.sh --check # 只核对线上与本地产物差异,不动线上
|
||||
# ./deploy-sdk-site.sh --rollback <备份目录名> # 回滚
|
||||
# ./deploy-sdk-site.sh # 构建 + 部署 + 验证(两个站)
|
||||
# ./deploy-sdk-site.sh --check # 只核对线上与本地产物差异,不动线上
|
||||
# ./deploy-sdk-site.sh --introduce # 只部署 introduce 站(改 site/ 时用,跳过 SDK 站重建)
|
||||
# ./deploy-sdk-site.sh --rollback <备份目录名> # 回滚 sdk 站
|
||||
# ./deploy-sdk-site.sh --rollback-introduce <备份目录名> # 回滚 introduce 站
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@ -40,6 +42,23 @@ PKG=/tmp/$PKG_NAME
|
||||
say() { printf '\n\033[1m%s\033[0m\n' "$*"; }
|
||||
info() { printf ' %s\n' "$*"; }
|
||||
|
||||
# ── 比对工具:必须是**真能报出差异**的 diff ──
|
||||
#
|
||||
# 2026-09-29 实测踩到:本机 PATH 首位的 diff
|
||||
# (/opt/huawei/harmonyos/ohos-sdk/linux/toolchains/diff)对**任何**输入都返回 0
|
||||
# 且无输出。用它做判据的后果不是「偶尔报错」,而是**永久给出「逐字节一致」的假绿灯**——
|
||||
# --check 永远说「无需部署」,站点改完再也不会被更新,而且看不出来。
|
||||
#
|
||||
# 所以:钉绝对路径 + 启动时自检一次。自检不过就直接退出,
|
||||
# 而不是继续拿一个坏判据去做部署决定(「探针不红先怀疑探针」)。
|
||||
DIFF=/usr/bin/diff
|
||||
[ -x "$DIFF" ] || { printf '找不到可用的 %s\n' "$DIFF" >&2; exit 1; }
|
||||
if "$DIFF" -q <(printf 'A\n') <(printf 'B\n') >/dev/null 2>&1; then
|
||||
printf '★ 判据自检失败:%s 对两个不同输入报告「无差异」。\n' "$DIFF" >&2
|
||||
printf ' 这个 diff 是坏的(PATH 首位那个工具链自带的可能有问题),拒绝据此做部署判断。\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── introduce 站 ──
|
||||
#
|
||||
# 零构建:/home/program/TrueAgent/site/ 里就是成品(site/README.md 自称
|
||||
@ -67,12 +86,12 @@ do_check_introduce() {
|
||||
info "本机源: $n 个文件(已排除 README.md)"
|
||||
files=$($SSH "sudo -n find $SITES/introduce -type f 2>/dev/null | wc -l" 2>/dev/null)
|
||||
info "线上站: $files 个文件"
|
||||
if diff -q <(intro_local_md5) <(intro_remote_md5) >/dev/null 2>&1; then
|
||||
if "$DIFF" -q <(intro_local_md5) <(intro_remote_md5) >/dev/null 2>&1; then
|
||||
info "✓ introduce 线上与本机源逐字节一致"
|
||||
return 0
|
||||
fi
|
||||
info "✗ introduce 有差异:"
|
||||
diff <(intro_local_md5) <(intro_remote_md5) | head -20 || true
|
||||
"$DIFF" <(intro_local_md5) <(intro_remote_md5) | head -20 || true
|
||||
return 1
|
||||
}
|
||||
|
||||
@ -104,10 +123,10 @@ do_deploy_introduce() {
|
||||
local code
|
||||
code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' -k https://introduce.homeagent.jianfgit.xyz/)
|
||||
info "首页 http=$code(introduce 配了 try_files 回落,404 才是异常)"
|
||||
if diff -q <(intro_local_md5) <(intro_remote_md5) >/dev/null 2>&1; then
|
||||
if "$DIFF" -q <(intro_local_md5) <(intro_remote_md5) >/dev/null 2>&1; then
|
||||
info "✓ introduce 线上与本机源逐字节一致"
|
||||
else
|
||||
info "✗ introduce 仍有差异:"; diff <(intro_local_md5) <(intro_remote_md5) | head -20; return 1
|
||||
info "✗ introduce 仍有差异:"; "$DIFF" <(intro_local_md5) <(intro_remote_md5) | head -20; return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@ -132,11 +151,11 @@ do_check() {
|
||||
info "本地产物: $n 个文件"
|
||||
files=$($SSH "sudo -n find $SITES/sdk -type f 2>/dev/null | wc -l" 2>/dev/null)
|
||||
info "线上站: $files 个文件"
|
||||
if diff -q <(local_md5) <(remote_md5) >/dev/null 2>&1; then
|
||||
if "$DIFF" -q <(local_md5) <(remote_md5) >/dev/null 2>&1; then
|
||||
info "✓ 线上与本地产物逐字节一致,无需部署"
|
||||
else
|
||||
info "✗ sdk 有差异,需部署。差异文件:"
|
||||
diff <(local_md5) <(remote_md5) | head -20 || true
|
||||
"$DIFF" <(local_md5) <(remote_md5) | head -20 || true
|
||||
fi
|
||||
|
||||
# introduce 始终检查:两个站是独立的,sdk 一致不代表 introduce 也一致
|
||||
@ -169,8 +188,8 @@ do_deploy() {
|
||||
sudo -n mv $SITES/sdk $SITES/.sdk-old-$TS
|
||||
sudo -n mv $SITES/.sdk-new-$TS $SITES/sdk
|
||||
echo -n '现役站文件数: '; sudo -n find $SITES/sdk -type f | wc -l
|
||||
# .sdk-old 与刚建的 .sdk-bak 内容必然相同(同一份旧站复制两次),
|
||||
# 留一份就够,白占 11M。
|
||||
# 注意:这里的 diff 是**远端**执行的(远端是健康的 /usr/bin/diff),
|
||||
# 不受本机 PATH 首位那个坏 diff 影响。
|
||||
if sudo -n diff -r -q $SITES/.sdk-bak-$TS $SITES/.sdk-old-$TS >/dev/null 2>&1; then
|
||||
sudo -n rm -rf $SITES/.sdk-old-$TS
|
||||
echo '已删重复的 .sdk-old(与 .sdk-bak 内容相同)'
|
||||
@ -190,15 +209,30 @@ do_deploy() {
|
||||
printf ' %-34s ' "$p"
|
||||
curl -s -o /dev/null -m 10 -w 'http=%{http_code}\n' -k "https://sdk.homeagent.jianfgit.xyz/$p"
|
||||
done
|
||||
if diff -q <(local_md5) <(remote_md5) >/dev/null 2>&1; then
|
||||
if "$DIFF" -q <(local_md5) <(remote_md5) >/dev/null 2>&1; then
|
||||
info "✓ 线上与本地产物逐字节一致"
|
||||
else
|
||||
info "✗ 仍有差异:"; diff <(local_md5) <(remote_md5) | head -20; return 1
|
||||
info "✗ 仍有差异:"; "$DIFF" <(local_md5) <(remote_md5) | head -20; return 1
|
||||
fi
|
||||
|
||||
do_deploy_introduce
|
||||
}
|
||||
|
||||
# 只跑 introduce 站:改 site/ 时用。
|
||||
#
|
||||
# 为何需要:do_deploy 会先把 SDK 文档站重建一遍(要跑 apidoc + mkdocs,产物 106 个文件、
|
||||
# 十几 MB),而 site/ 的改动跟 SDK 站毫无关系。只动介绍页却重建整个文档站,
|
||||
# 既慢又把一个本来没必要碰的线上站也卷进变更面。
|
||||
do_deploy_introduce_only() {
|
||||
say "introduce 站:先比对再决定"
|
||||
# 一致就不动线上(越少触碰越好);不一致才走「备份→原子替换→回验」
|
||||
if do_check_introduce; then
|
||||
info "✓ 线上已是最新,无需部署"
|
||||
return 0
|
||||
fi
|
||||
do_deploy_introduce
|
||||
}
|
||||
|
||||
do_rollback() {
|
||||
local bak=${1:?用法: --rollback <备份目录名,如 .sdk-bak-20260927-223242>}
|
||||
say "回滚到 $bak"
|
||||
@ -213,9 +247,25 @@ do_rollback() {
|
||||
info "已回滚,失败版本留在 $SITES/.sdk-failed-$TS"
|
||||
}
|
||||
|
||||
do_rollback_introduce() {
|
||||
local bak=${1:?用法: --rollback-introduce <备份目录名,如 .intro-bak-20260929-170000>}
|
||||
say "回滚 introduce 到 $bak"
|
||||
$SSH "
|
||||
set -e
|
||||
[ -d '$SITES/$bak' ] || { echo '找不到备份 $SITES/$bak'; exit 1; }
|
||||
sudo -n cp -a $SITES/introduce $SITES/.intro-failed-$TS
|
||||
sudo -n rm -rf $SITES/introduce
|
||||
sudo -n cp -a $SITES/$bak $SITES/introduce
|
||||
echo -n '回滚后文件数: '; sudo -n find $SITES/introduce -type f | wc -l
|
||||
" 2>&1 | tail -3
|
||||
info "已回滚,失败版本留在 $SITES/.intro-failed-$TS"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--check) do_check ;;
|
||||
--rollback) do_rollback "${2:?用法: --rollback <备份目录名>}" ;;
|
||||
"") do_deploy ;;
|
||||
*) echo "用法: $0 [--check | --rollback <备份目录名>]"; exit 2 ;;
|
||||
--check) do_check ;;
|
||||
--introduce) do_deploy_introduce_only ;;
|
||||
--rollback) do_rollback "${2:?用法: --rollback <备份目录名>}" ;;
|
||||
--rollback-introduce) do_rollback_introduce "${2:?用法: --rollback-introduce <备份目录名>}" ;;
|
||||
"") do_deploy ;;
|
||||
*) echo "用法: $0 [--check | --introduce | --rollback <备份目录名> | --rollback-introduce <备份目录名>]"; exit 2 ;;
|
||||
esac
|
||||
|
||||
@ -203,18 +203,28 @@ spec = (d.get('devDependencies', {}) or {}).get('electron') or (d.get('dependenc
|
||||
m = re.search(r'(\\d+(?:\\.\\d+)*)', spec)
|
||||
print(m.group(1) if m else '')
|
||||
" 2>/dev/null || true)
|
||||
[ -n "$ever" ] && echo " electron 版本取自 package.json 依赖声明: $ever(非精确)"
|
||||
if [ -n "$ever" ]; then
|
||||
echo " electron 版本取自 package.json 依赖声明: $ever(非精确)"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$gui_out"
|
||||
|
||||
# 优先:缓存里的目标架构 zip(~/.cache/electron/<hash>/electron-v<ver>-linux-<arch>.zip)
|
||||
#
|
||||
# ★ 这里必须 `|| true`:`find` 对**不存在的目录**返回退出码 1,而本脚本是
|
||||
# `set -euo pipefail`,命令替换里的失败会让整个脚本当场退出。
|
||||
# 后果:任何**没有 ~/.cache/electron 的机器**(全新克隆、CI runner、
|
||||
# 其他开发机)跑到这里就死,且只留下一行「electron 版本取自 package.json」
|
||||
# 作为最后的输出,看不出真因。实测(2026-09-29,GitHub runner 与本地
|
||||
# 移走缓存后均复现):build_go 全部成功,然后卡在这里静默退出。
|
||||
# 本机历史上之所以一直「能打包」,只是因为碰巧有那份缓存。
|
||||
local zip=""
|
||||
if [ -n "$ever" ]; then
|
||||
zip=$(find "$HOME/.cache/electron" -name "electron-v${ever}-linux-${ELECTRON_ARCH}.zip" 2>/dev/null | head -1)
|
||||
zip=$(find "$HOME/.cache/electron" -name "electron-v${ever}-linux-${ELECTRON_ARCH}.zip" 2>/dev/null | head -1 || true)
|
||||
fi
|
||||
if [ -z "$zip" ]; then
|
||||
zip=$(find "$HOME/.cache/electron" -name "electron-v*-linux-${ELECTRON_ARCH}.zip" 2>/dev/null | head -1)
|
||||
zip=$(find "$HOME/.cache/electron" -name "electron-v*-linux-${ELECTRON_ARCH}.zip" 2>/dev/null | head -1 || true)
|
||||
fi
|
||||
|
||||
if [ -n "$zip" ]; then
|
||||
@ -620,9 +630,11 @@ build_rpm() {
|
||||
if [ ! -f "$rpmbuild_dir/usr/bin/rpmbuild" ]; then
|
||||
# try to extract from cached deb packages
|
||||
local rpm_deb
|
||||
rpm_deb="$(find /tmp -name "rpm_*.deb" -type f 2>/dev/null | head -1)"
|
||||
# 同 build_gui:`find` 对不存在/无命中会返回 1,`set -euo pipefail` 下
|
||||
# 会让脚本当场退出(`|| true` 是给命令替换兜底,不是忽视错误)。
|
||||
rpm_deb="$(find /tmp -name "rpm_*.deb" -type f 2>/dev/null | head -1 || true)"
|
||||
if [ -z "$rpm_deb" ]; then
|
||||
rpm_deb="$(find "$PROJECT_ROOT" -name "rpm_*.deb" -type f 2>/dev/null | head -1)"
|
||||
rpm_deb="$(find "$PROJECT_ROOT" -name "rpm_*.deb" -type f 2>/dev/null | head -1 || true)"
|
||||
fi
|
||||
if [ -n "$rpm_deb" ]; then
|
||||
mkdir -p "$rpmbuild_dir"
|
||||
|
||||
302
docs/zh/ci-cd-runbook.md
Normal file
302
docs/zh/ci-cd-runbook.md
Normal file
@ -0,0 +1,302 @@
|
||||
# CI/CD 流水线手册(GitHub Actions)
|
||||
|
||||
> 2026-09-29 随仓库迁移 gitcode → GitHub 而建。此前 AtomGit 停止对普通用户
|
||||
> 提供流水线,本仓无任何自动化验证。本文记录两条流水线的用法、机制与边界。
|
||||
> 分支模型见 `docs/git-branching.md`(本文与其 §七「发版产物清单」衔接)。
|
||||
|
||||
## 0. 仓库与流水线总览
|
||||
|
||||
| 仓 | 位置 | 流水线 | 触发 |
|
||||
| --- | --- | --- | --- |
|
||||
| 主仓 HomeAgent | `github.com/JianFeeeee/HomeAgent`(公开) | `ci.yml` + `release.yml` | push / PR、`release/**` push |
|
||||
| SDK 仓 homeagentsdk | `github.com/JianFeeeee/homeagentsdk`(公开) | `release.yml` | `release/**` push |
|
||||
| gitcode 镜像 | 同名仓库 | 无(由主仓 Release 的 sync job 同步) | — |
|
||||
|
||||
**分支保护**(main,公开仓免费):10 项必须检查(Go build/vet/test、Race、
|
||||
cross×5、GUI、C gates、Docs)+ `strict`(必须与 main 同步)+ 禁 force push。
|
||||
⇒ **feature 合入 main 前必须过流水线**(PR 的 checks 全绿才能 merge)。
|
||||
|
||||
**gitcode 的角色**:只读镜像(git push 同步)。Release 附件由 sync job 自动
|
||||
补传,或手工跑 `deploy/scripts/upload_assets.py`(见 §3.4)。
|
||||
|
||||
## 1. CI(ci.yml)—— 每次推送到 main / release/** / PR 都跑
|
||||
|
||||
六个 job,**全部命令均本地实测过**(原则:不写"应该有用"的未验证步骤):
|
||||
|
||||
| job | 内容 | 时长 |
|
||||
| --- | --- | --- |
|
||||
| Go build / vet / test | `go build/vet/test ./...` + `make check-client-versions` | ~12m |
|
||||
| Race detector | `go test -race`(core + waiter) | ~8m |
|
||||
| Cross-compile ×5 | waiter/initconfig/mock-server 五平台(CGO=0) | ~5m |
|
||||
| GUI (node) | `cd cmd/gui && npm test`(纯 Node,零依赖,秒级) | <1m |
|
||||
| C infrastructure gates | `make check-csrc`(告警/ABI/ASan/跨架构) | ~6m |
|
||||
| Docs build | mkdocs.yml 可解析性检查 | <1m |
|
||||
|
||||
**明确不进 CI**(依赖真机/密钥/内网,跑了只会变 flaky 噪音):
|
||||
`deploy-*.sh`、waiter 真机(192.168.2.x)、`npm run test-live`(需真
|
||||
Electron+Xvfb+真后端)、`scripts/kernel-stress/*`、需 `DEEPSEEK_API_KEY` 的
|
||||
真实 LLM 测试(自带 t.Skip)。
|
||||
|
||||
### 已知边界(不是缺陷)
|
||||
|
||||
- **只有 waiter/initconfig/mock-server 能纯交叉编译**。homed/memgc/
|
||||
homed-kb-migrate 依赖 cgo(gojieba/onnx),必须原生构建(见 Makefile)。
|
||||
- **`cmd/gui` 是纯 Electron 目录**(0 个 .go、无 go.mod)。`go test ./...`
|
||||
不会包含它(Go 的 `./...` 语义);只有显式 `go test ./cmd/gui` 才报
|
||||
"no Go files"。前端测试走 `npm test`。
|
||||
- **CGO 必须开**(CI 里 `CGO_ENABLED: 1`):gojieba 需要。
|
||||
|
||||
## 2. 发布(release.yml)—— release/** 推送即发版
|
||||
|
||||
### 2.1 标准发版流程(以 1.3.14 为例)
|
||||
|
||||
```bash
|
||||
# 1. 在发布线的 worktree(干净)里 bump 版本
|
||||
git worktree add /tmp/rel-v1.3.14 release/v1.3.x
|
||||
cd /tmp/rel-v1.3.14
|
||||
sed -i 's/Version = "1.3.13"/Version = "1.3.14"/' internal/meta/meta.go
|
||||
git commit -am "release: 1.3.14"
|
||||
git push origin release/v1.3.x
|
||||
# 2. 流水线自动:读版本 → go build/test 门 → 下载资产 → 打包
|
||||
# → 打 tag v1.3.14 → 建 GitHub release → 传附件 → 回读校验
|
||||
# → (配了 GITCODE_TOKEN 时)同步 gitcode
|
||||
# 3. 在 GitHub Actions 页看进度;全绿即发版完成
|
||||
```
|
||||
|
||||
**版本号唯一事实源是 `internal/meta/meta.go` 的 Version**(SDK 仓是
|
||||
`meta/meta.go`)。改它并推送 = 发版指令。
|
||||
|
||||
### 2.2 幂等闸门:tag 已存在 ⇒ 整轮跳过
|
||||
|
||||
Prepare 先查 `refs/tags/v<version>` 是否存在。已存在(比如改文档的推送、
|
||||
cherry-pick 维护提交)则 Build/Publish/Sync 全部 skipped。**不会重复发版**。
|
||||
|
||||
### 2.3 发版门:go build 硬门 + go test 可显式跳过
|
||||
|
||||
- `go build ./...` —— **硬门**,不可跳过。
|
||||
- `go test ./...` —— 默认跑。历史维护线若存在**既存红测试**(如
|
||||
release/v1.3.x 的 deepsearch 测试,main 上已修),在**改动 meta 的那个
|
||||
提交**的正文里写 `[skip-release-tests]` 即可跳过:
|
||||
|
||||
```bash
|
||||
git commit -am "release: 1.3.14 [skip-release-tests]
|
||||
|
||||
(正文里写明跳过理由——为什么本线的红是既存的、与本次发版无关)"
|
||||
```
|
||||
|
||||
跳过时 CI 输出 `::warning`,决定记录在发版 commit 里可审计。
|
||||
★ 标记查在**改动 meta 的提交**上而非 HEAD:发版提交后常还会跟几个
|
||||
维护提交,只看 HEAD 会让标记被顶掉、静默失效。
|
||||
|
||||
### 2.4 产物与验证
|
||||
|
||||
产物(v1.3.13 实测):
|
||||
|
||||
```text
|
||||
homeagent-client_1.3.13_amd64.deb 83.5MB 客户端(不含模型)
|
||||
homeagent-server_1.3.13_amd64.deb 730.1MB 服务端(含向量模型)
|
||||
homeagent-full_1.3.13_amd64.deb 808.3MB 全量(模型+ORT+GUI)
|
||||
homeagent_1.3.13_linux_amd64.tar.gz 839.2MB 内核+CLI+GUI 打包
|
||||
SHA256SUMS 全量校验和
|
||||
```
|
||||
|
||||
流水线内置的验证(不通过即不发版):
|
||||
|
||||
- deb 元数据(Package/Version/Architecture)逐包核对;
|
||||
- full/server 包内**必须真的含** `TextEncoder.onnx` 与 `libonnxruntime.so`
|
||||
(防"默认启用但装完不能用"的假包);
|
||||
- `sha256sum -c SHA256SUMS`(产物先平铺再验 —— 脚本把校验和写成平铺名);
|
||||
- 发布后**回读**:把附件下载回来再验一遍校验和。
|
||||
|
||||
### 2.5 构建资产(ci-assets-v1 release)
|
||||
|
||||
server/full 的打包需要 719MB 模型 + 24MB ONNX Runtime。它们**内容不随版本
|
||||
变**,故作为 `ci-assets-v1` release 的附件一次性托管,每次发版由流水线下载
|
||||
并 `sha256sum -c` 校验后使用:
|
||||
|
||||
```text
|
||||
chinese-clip-vit-b16-onnx.tar 718.8MB Chinese-CLIP ViT-B/16 ONNX
|
||||
onnxruntime-linux-amd64-1.28.0.tar 23.5MB ORT 1.28.0 + LICENSE + TPN
|
||||
SHA256SUMS
|
||||
```
|
||||
|
||||
模型或 ORT 升级时:改好本地 `CHINESECLIP_BUNDLE_DIR` /
|
||||
`ONNXRUNTIME_ASSET_DIR` 指向的目录 → 重新打包 → 在 GitHub 上新建
|
||||
`ci-assets-v2` release 并上传 → 同步更新 release.yml 里的 `ASSETS_TAG`。
|
||||
|
||||
## 3. 运维要点(都踩过坑)
|
||||
|
||||
### 3.1 workflow 文件必须存在于目标分支
|
||||
|
||||
GitHub 用**被推送 commit 里的** `.github/workflows/*.yml` 决定是否触发。
|
||||
给旧发布线补流水线时,要把 workflow 文件本身 commit 到那条分支
|
||||
(实测:只在 main 有时,推 release/** 什么都不触发)。
|
||||
|
||||
### 3.2 runner 上没有 electron 缓存
|
||||
|
||||
打包脚本从两处找 Electron:`~/.cache/electron` 的 zip,或
|
||||
`cmd/gui/node_modules/electron/dist`。全新 runner 两处都没有 ⇒ release.yml
|
||||
在打包前 `npm ci`(electron 已在 package-lock 锁定;**不能**
|
||||
`npm install --production`,electron 是 devDependency 会被跳过)。
|
||||
|
||||
### 3.3 管道里的 `grep -q` 会因 SIGPIPE 误杀检测
|
||||
|
||||
`dpkg-deb -c <800M 包> | grep -q <目标>`:grep -q 匹配即退出、关闭读端 ⇒
|
||||
tar 写 stdout 收到 EPIPE ⇒ pipefail 判失败。**包越大越易触发**
|
||||
(80M 的 client 没事、800M 的 full 炸)。检测存在性一律
|
||||
`grep <pat> >/dev/null`。
|
||||
|
||||
### 3.4 gh 在非 git 目录要显式 `--repo`
|
||||
|
||||
`gh release download` 在 `/tmp/back` 这类非 git 目录里会报
|
||||
"not a git repository"(gh 从 cwd 的 git 上下文推断仓库)。必须
|
||||
`gh release download "$TAG" --repo "$GITHUB_REPOSITORY"`。
|
||||
|
||||
### 3.5 gitcode 上传的路径语义
|
||||
|
||||
`upload_assets.py` 拼路径是 `os.path.join(ASSET_DIR, name)`:
|
||||
要 `cd` 进资产目录、`ASSET_DIR=.`、传**裸文件名**。SDK 仓的产物多数无
|
||||
扩展名,必须显式列名(自动扫描按后缀识别,会静默一个都不传)。
|
||||
|
||||
### 3.6 gitcode 凭据(`GITCODE_TOKEN`)
|
||||
|
||||
CI 的 sync job 需要仓库 secret `GITCODE_TOKEN`;**未配置时该 job 显式跳过**
|
||||
(不阻断 GitHub 侧发布)。
|
||||
|
||||
**2026-09-29 已配置**:两仓(`JianFeeeee/HomeAgent`、`JianFeeeee/homeagentsdk`)
|
||||
均已设同名 secret,取值自 `~/.git-credentials` 里那条 `https://JianFeeeee:<token>@gitcode.com`。
|
||||
配置方式(经 stdin 传入,避免 token 出现在进程列表):
|
||||
|
||||
```bash
|
||||
printf '%s' "$TOKEN" | gh secret set GITCODE_TOKEN --repo JianFeeeee/HomeAgent
|
||||
printf '%s' "$TOKEN" | gh secret set GITCODE_TOKEN --repo JianFeeeee/homeagentsdk
|
||||
```
|
||||
|
||||
验收:sync job 只在**新版本**发版时运行(`prepare.outputs.exists == 'false'`),
|
||||
历史 tag 触发不了,所以无法用旧版本实跑。等价验证三道:
|
||||
|
||||
```bash
|
||||
# ① secret 存在
|
||||
gh secret list --repo JianFeeeee/HomeAgent | grep GITCODE_TOKEN
|
||||
# ② token 有效
|
||||
curl -s "https://gitcode.com/api/v5/user?access_token=$TOKEN"
|
||||
# ③ job 用的 private-token 头可读 release(两仓都测)
|
||||
curl -s -H "private-token: $TOKEN" \
|
||||
"https://gitcode.com/api/v5/repos/JianFeeeee/HomeAgent/releases/tags/v1.3.13"
|
||||
```
|
||||
|
||||
★ **token 是宽范围的个人令牌**(可读 92 仓/48 私有、有写权限),而 CI 只需要这两个仓。
|
||||
更稳的做法是去 gitcode 建一枚**仅限这两仓**的令牌再替换 —— 这样 CI 泄漏时
|
||||
影响面不扩到其他仓。当前未做(按用户 2026-09-29 的决定)。
|
||||
|
||||
手工补发的完整流程(下载 GitHub 产物 → 建 release 条目 → 上传):
|
||||
|
||||
```bash
|
||||
# token 放 ~/.git-credentials(https://JianFeeeee:<token>@gitcode.com)
|
||||
ASSET_DIR=<产物目录> GITCODE_REPO=JianFeeeee/HomeAgent \
|
||||
python3 deploy/scripts/upload_assets.py <tag> <token>
|
||||
# release 条目必须先存在(脚本向 releases/<tag>/upload_url 取 OBS 签名 URL)
|
||||
```
|
||||
|
||||
## 4. SDK 仓(third_party/homeagent-sdk)
|
||||
|
||||
与主仓同构,差异:
|
||||
|
||||
| | 主仓 | SDK |
|
||||
| --- | --- | --- |
|
||||
| 版本源 | `internal/meta/meta.go` | `meta/meta.go` |
|
||||
| 产物 | 3 deb + 1 tar.gz(2.4GB) | `hmapdev_*` 5 平台 + SHA256SUMS(~140MB) |
|
||||
| CGO | 必须(gojieba) | 不需要(CGO_ENABLED=0 纯交叉) |
|
||||
| 测试 | `go test ./...` | **两处**:根模块 + `tools/hmapdev`(独立 module,根的 ./... 不含它) |
|
||||
| 门 | 同一机制 | 同一机制(`[skip-release-tests]`、幂等闸门) |
|
||||
|
||||
发版:改 `meta/meta.go` 的 Version 推 `release/vX.Y.x`。SDK 版本随核心的
|
||||
中版本走、patch 恒为 `.0`(见 git-branching.md §七.1)。
|
||||
|
||||
## 5. 通知
|
||||
|
||||
Actions 失败会给仓库 owner 发邮件(GitHub 默认)。若嫌吵:
|
||||
github.com/settings/notifications → Actions 关闭,或仓库页 Watch → Custom
|
||||
取消 Actions。注意失败邮件也可能是"验证步骤自身 bug"的假警报 ——
|
||||
先看是哪个 job/step 红了再判断(对照 §3 的坑)。
|
||||
|
||||
## 6. 开发环境的诊断噪音(`cmd/gui [setup failed]`)
|
||||
|
||||
### 6.1 症状
|
||||
|
||||
每轮改完文件,pi-lens 的回合末摘要里会冒一条:
|
||||
|
||||
```text
|
||||
FAIL ./cmd/gui [setup failed]
|
||||
```
|
||||
|
||||
它看着像仓库里有测试红了,**实际是工具缺陷**。真实状态:
|
||||
|
||||
```bash
|
||||
go test ./... # 退出码 0,43 个包全过、0 FAIL
|
||||
find cmd/gui -name '*.go' | wc -l # 0 —— 该目录根本没有 Go 代码
|
||||
go test ./cmd/gui # “no Go files in .../cmd/gui”
|
||||
cd cmd/gui && npm test # 这才是它的测试(node 的 .test.mjs),通过
|
||||
```
|
||||
|
||||
### 6.2 根因(pi-lens 的两个缺陷叠加)
|
||||
|
||||
1. **runner 按仓库根选**,不按被跑的文件选。本仓根有 `go.mod` ⇒ 选中 go runner;
|
||||
而 `cmd/gui/*.test.mjs` 命中通用测试命名(`detectFileRole` 与 runner 无关)
|
||||
⇒ 对 `cmd/gui` 生成 `go test -run . ./cmd/gui` ⇒ 必失败。
|
||||
2. **failed-first 把误报变成永久**:失败项进 `failedTestsByRunner`(进程内 Map),
|
||||
此后**每次**编辑都优先重跑它(与当前编辑的文件无关);而该条目只在测试
|
||||
**通过**时才移除 ⇒ 对这条永远失败的命令,永不自愈。
|
||||
|
||||
日志里的形态(`/root/.pi-lens/sessionstart.log`):
|
||||
|
||||
```text
|
||||
turn_end: README.md → test go cmd/gui/sse-backoff.test.mjs (failed-first)
|
||||
```
|
||||
|
||||
注意触发者是 `README.md` —— 目标是**与本次编辑无关**的陈旧失败项。
|
||||
|
||||
### 6.3 为什么不能用项目级配置关掉
|
||||
|
||||
`.pi-lens.json` 是**项目级**,只认一小排键
|
||||
(`ignore` / `rules` / `maxProjectFiles` / `reviewGraph` / `trivy` + 三个改动开关)。
|
||||
`tests` 是**全局级**键,写进项目文件会被忽略并告警:
|
||||
|
||||
```text
|
||||
"tests" is a global-only pi-lens setting and is not honored in a project .pi-lens.json
|
||||
```
|
||||
|
||||
而全局关掉(`~/.pi-lens/config.json` 的 `{"tests":{"enabled":false}}`)
|
||||
会一起关掉**所有项目**的回合末测试反馈 —— 为一个仓库的误报付全局代价,不值。
|
||||
另:`ignore` 也挡不住,因为它只作用于扫描,不参与测试目标选择(`failed-first`
|
||||
的回退分支根本不看候选文件)。
|
||||
|
||||
### 6.4 修法:本机补丁(已打)
|
||||
|
||||
补丁位置:`~/.pi/agent/npm/node_modules/pi-lens/dist/index.js`。
|
||||
在 `getTestRunTarget` 返回目标前加一道校验:
|
||||
|
||||
> 该 runner 是否**真能跑**这个目标?只有 go 做实质检查 ——
|
||||
> 目标所在目录要有至少一个 `.go` 文件。不能跑就返回 null,
|
||||
> 并顺手把这条不可运行的记录从 `failed-first` 集合里移除。
|
||||
|
||||
原方法体改名为 `selectTestRunTargetRaw`,外面套一层校验(`runTestFileAsync`
|
||||
只有这一个调用点,所以这里是唯一收口)。补丁全文已用 `node --check` 验语法,
|
||||
用 `/usr/bin/diff` 核对为**纯新增、零删除**。
|
||||
|
||||
**立即生效(不必重启会话)**:在会话里执行内置命令 **`/reload`**
|
||||
(重载扩展且不重启 `pi-web-sessiond`);不手动重载则在**下个会话**自然生效。
|
||||
|
||||
**验证**:改一个仓库文件但先不提交,等回合结束,然后
|
||||
`grep 'turn_end: .*→ test' /root/.pi-lens/sessionstart.log | tail -3`
|
||||
—— 应不再出现 `test go cmd/gui/...`;而编辑一个真 Go 测试文件时仍应正常触发。
|
||||
|
||||
**会被覆盖**:pi-lens 升级/重装后补丁消失,误报会回来(不影响仓库,只是噪音)。
|
||||
备份在同目录 `index.js.orig-*`,回退就是拷回去:
|
||||
|
||||
```bash
|
||||
cd ~/.pi/agent/npm/node_modules/pi-lens/dist
|
||||
cp -a index.js.orig-<时间戳> index.js # 然后 /reload
|
||||
```
|
||||
|
||||
> 上游缺陷:runner 选择应先确认目标文件属于该语言(或至少确认目录内有该语言的源文件)。
|
||||
@ -38,6 +38,49 @@
|
||||
|
||||
---
|
||||
|
||||
### 0.1 ★★ 本机 `diff` 是坏的 —— 所有比对判据的硬前置
|
||||
|
||||
**结论:本机一律用 `/usr/bin/diff` 或 `cmp`,不要用裸 `diff`。**
|
||||
|
||||
PATH 首位是 `/opt/huawei/harmonyos/ohos-sdk/linux/toolchains/diff`,
|
||||
它**对任何输入都返回 0 且无输出**。用两个必然不同的小文件验证:
|
||||
|
||||
```bash
|
||||
printf 'A\n' > /tmp/d1; printf 'B\n' > /tmp/d2
|
||||
diff -q /tmp/d1 /tmp/d2; echo $? # ⇒ 0(错!应为 1)
|
||||
/usr/bin/diff -q /tmp/d1 /tmp/d2; echo $? # ⇒ 1(对)
|
||||
cmp -s /tmp/d1 /tmp/d2; echo $? # ⇒ 1(cmp 未被污染)
|
||||
```
|
||||
|
||||
为什么这比“偶尔报错”危险得多:它让**部署判据变成假绿灯**。
|
||||
2026-09-29 实测:`deploy-sdk-site.sh --check` 对着两份**确实不同**的
|
||||
`index.html`(本地 `f8898cfc…` / 线上 `cc615cfa…`)报「✓ 逐字节一致」,
|
||||
于是永远判定「无需部署」——站点改完再也不会被更新,而且看不出来。
|
||||
已在 `547d28d` 修掉(钉绝对路径 + 启动自检)。
|
||||
|
||||
★ 判据自检的通用做法:
|
||||
|
||||
```bash
|
||||
# 启动时用两个必然不同的输入验证判据真的能报差异;不通过就退出,
|
||||
# 而不是继续拿一个坏判据去做决定。
|
||||
if "$DIFF" -q <(printf 'A\n') <(printf 'B\n') >/dev/null 2>&1; then
|
||||
echo '判据自检失败:这个 diff 认为两份不同内容“无差异”' >&2; exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
同类信号(任一出现就立刻怀疑判据本身):
|
||||
|
||||
| 信号 | 含义 |
|
||||
| --- | --- |
|
||||
| `diff` 说无差异,但 `wc -c` / `stat -c %s` 说大小不同 | 判据坏了 |
|
||||
| 部署脚本报「一致」但线上内容明显是旧的 | 判据坏了 |
|
||||
| 测了两个**必然不同**的样本却报「相同」 | 判据坏了 |
|
||||
|
||||
远端(106 / 30)的 `diff` **是健康的**(`/usr/bin/diff`),不受影响;
|
||||
但嵌在 `ssh "…"` 里的命令要分清楚是本地还是远端执行。
|
||||
|
||||
---
|
||||
|
||||
## 1. 部署 homed(本机)
|
||||
|
||||
### 1.1 硬前置:必须 onnxruntime 构建
|
||||
@ -118,6 +161,7 @@ curl -s -H "X-API-Key: $K" http://127.0.0.1:8080/api/v1/status | grep -oE '"comm
|
||||
(86811464),更掩盖了这个问题。
|
||||
|
||||
⇒ 部署前的判据顺序:
|
||||
|
||||
1. `/api/v1/status` 的 `commit` —— 线上在跑什么
|
||||
2. `git log <commit>..HEAD` —— 差哪些提交
|
||||
3. 那些提交里**有无运行时改动**(`internal/`、`cmd/`)—— 只有它才需要部署
|
||||
|
||||
@ -1,211 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 站点漂移巡检:有差异才提醒,绝不自动部署。
|
||||
#
|
||||
# 老大定的档位是「有差异就提醒」而不是「自动推」——文档站发错了是公开可见的,
|
||||
# 宁可等人点一下。所以这个脚本**只读不动**:不构建、不上传、不碰线上任何文件。
|
||||
#
|
||||
# 判三类信号:
|
||||
# 1. 线上漂移:.106 上的站 ≠ 本机产物/site 源(逐字节 md5 清单比对)
|
||||
# 2. 源码漂移:git HEAD 比产物新 ⇒ 提交了但没重新构建部署
|
||||
# 3. 探活失败:.106 或 NapCat 挂了(这类比文档漂移紧急,必须报)
|
||||
#
|
||||
# 为什么不直接调 deploy-sdk-site.sh --check 再 grep 输出:那脚本的输出是给人看的
|
||||
# 彩色文本,拿来当机器判断依据太脆(改个文案就失效)。md5 清单逻辑很短,
|
||||
# 这里复刻一份,注释指向 deploy-sdk-site.sh 保持同步。
|
||||
#
|
||||
# 去重:差异持续存在时,每轮都发会把老大刷屏。所以按「差异指纹」去重——
|
||||
# 差异内容变了才发新的;差异没了发一条「已恢复」;一直没变就闭嘴。
|
||||
#
|
||||
# 定时:cron 7,37 * * * *(错开整点,避开 acme 等已排满 :00 的任务)
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SDK_DIR=/home/program/TrueAgent/third_party/homeagent-sdk
|
||||
BUILD_DIR="$SDK_DIR/site_build"
|
||||
INTRO_SRC=/home/program/TrueAgent/site
|
||||
HOST=192.168.2.106
|
||||
SSH="ssh -n -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=no admin@$HOST"
|
||||
SITES=/vol1/docker/navi-data/sites
|
||||
|
||||
NAPCAT=${NAPCAT_URL:-http://192.168.2.106:25570}
|
||||
QQ_USER=${QQ_USER:-2198972886}
|
||||
|
||||
STATE_DIR=/home/program/TrueAgent/.drift-watch
|
||||
STATE_FILE="$STATE_DIR/state"
|
||||
LOCK="$STATE_DIR/lock"
|
||||
LOG=/var/log/site-drift-watch.log
|
||||
|
||||
# 差异清单最多列这么行,剩下的折叠计数(QQ 消息不宜过长)
|
||||
MAX_LINES=12
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
exec 9>"$LOCK"
|
||||
flock -n 9 || { echo "[$(date '+%F %T')] 上一轮还在跑,跳过" >>"$LOG"; exit 0; }
|
||||
|
||||
say() { printf '\033[1m%s\033[0m\n' "$*"; }
|
||||
info() { printf ' %s\n' "$*"; }
|
||||
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*" >>"$LOG"; }
|
||||
|
||||
# ── 通知(直连 NapCat,cron 不需要唤醒 agent)────────────────────────
|
||||
#
|
||||
# 为什么不走 agent:叫醒 agent 发消息要过 LLM 一趟,慢且烧 token;
|
||||
# NapCat 的 HTTP 接口是现成的,直发即时且零成本。
|
||||
notify() {
|
||||
local msg="$1" payload resp code
|
||||
payload=$(jq -nc --arg m "$msg" --argjson u "$QQ_USER" '{user_id:$u, message:$m}')
|
||||
resp=$(curl -s -m 20 -X POST "$NAPCAT/send_private_msg" \
|
||||
-H 'Content-Type: application/json' -d "$payload" 2>/dev/null)
|
||||
code=$(printf '%s' "$resp" | jq -r '.retcode // -1' 2>/dev/null)
|
||||
if [ "$code" = "0" ]; then
|
||||
info "已通知老大($(printf '%s' "$resp" | jq -r '.data.message_id // "?"'))"
|
||||
return 0
|
||||
fi
|
||||
info "✗ 通知发送失败:$(printf '%s' "$resp" | head -c 200)"
|
||||
log "notify failed: $resp"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── md5 清单(与 deploy-sdk-site.sh 保持一致)─────────────────────────
|
||||
norm_md5() { sed 's| \./| |'; }
|
||||
|
||||
list_md5() { # list_md5 <远端目录> <排除项...>
|
||||
local dir="$1"; shift
|
||||
local excl=()
|
||||
local e
|
||||
for e in "$@"; do excl+=(-not -path "$e"); done
|
||||
$SSH "cd $dir && sudo -n find . -type f -print0 | sort -z | sudo -n xargs -0 md5sum" 2>/dev/null | norm_md5
|
||||
}
|
||||
|
||||
sdk_diff() {
|
||||
diff <( cd "$BUILD_DIR" && find . -type f -print0 | sort -z | xargs -0 md5sum | norm_md5 ) \
|
||||
<( list_md5 "$SITES/sdk" ) 2>/dev/null
|
||||
}
|
||||
|
||||
intro_diff() {
|
||||
# README.md 是仓库说明不是站点资源,故意排除(见 deploy-sdk-site.sh 注释)
|
||||
diff <( cd "$INTRO_SRC" && find index.html assets -type f -print0 | sort -z | xargs -0 md5sum | norm_md5 ) \
|
||||
<( list_md5 "$SITES/introduce" -not -path './README.md' ) 2>/dev/null
|
||||
}
|
||||
|
||||
# ── 探活 ────────────────────────────────────────────────────────────
|
||||
probe() {
|
||||
local url="$1"
|
||||
curl -s -m 15 -X POST "$url" 2>/dev/null | jq -r '.retcode // -1' 2>/dev/null
|
||||
}
|
||||
|
||||
# ── 主流程 ──────────────────────────────────────────────────────────
|
||||
FORCE=0; DRY=0
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--test) FORCE=1 ;;
|
||||
--dry-run) DRY=1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
say "巡检 $HOST 的两个站(只读,不部署)"
|
||||
|
||||
# 1) NapCat 探活(通知通路本身也得是活的,否则有差异也通知不到)
|
||||
nap=off
|
||||
if [ "$(probe "$NAPCAT/get_status")" = "0" ]; then
|
||||
nap=on; info "✓ NapCat 通知通路在线"
|
||||
else
|
||||
info "✗ NapCat 不可达($NAPCAT)——有差异也发不出通知"
|
||||
fi
|
||||
|
||||
# 2) 线上漂移
|
||||
reasons=(); details=""
|
||||
|
||||
# .106 整体可达性:连不上是最高优先级故障
|
||||
if ! $SSH "true" 2>/dev/null; then
|
||||
info "✗ $HOST SSH 不可达(admin@,BatchMode)"
|
||||
reasons+=("🖥 $HOST SSH 连不上,站点状态未知")
|
||||
sdk_diff() { echo "__UNREACHABLE__"; }
|
||||
fi
|
||||
|
||||
sdkout=$(sdk_diff)
|
||||
if [ "$sdkout" = "__UNREACHABLE__" ]; then
|
||||
:
|
||||
elif [ -n "$sdkout" ]; then
|
||||
n=$(printf '%s\n' "$sdkout" | wc -l)
|
||||
reasons+=("📄 sdk 站与本地产物有差异($n 行)")
|
||||
details+="【sdk 站】"$'\n'"$(printf '%s\n' "$sdkout" | head -$MAX_LINES)"
|
||||
[ "$n" -gt "$MAX_LINES" ] && details+=$'\n'"…还有 $((n - MAX_LINES)) 行"
|
||||
details+=$'\n\n'
|
||||
info "✗ sdk 有差异:$n 行"
|
||||
else
|
||||
info "✓ sdk 站与本地产物逐字节一致"
|
||||
fi
|
||||
|
||||
introout=$(intro_diff)
|
||||
if [ "$introout" = "__UNREACHABLE__" ]; then
|
||||
:
|
||||
elif [ -n "$introout" ]; then
|
||||
n=$(printf '%s\n' "$introout" | wc -l)
|
||||
reasons+=("🎨 introduce 站与本机源有差异($n 行)")
|
||||
details+="【introduce 站】"$'\n'"$(printf '%s\n' "$introout" | head -$MAX_LINES)"
|
||||
[ "$n" -gt "$MAX_LINES" ] && details+=$'\n'"…还有 $((n - MAX_LINES)) 行"
|
||||
details+=$'\n\n'
|
||||
info "✗ introduce 有差异:$n 行"
|
||||
else
|
||||
info "✓ introduce 站与本机源逐字节一致"
|
||||
fi
|
||||
|
||||
# 3) 源码漂移:提交了但没重新构建
|
||||
head_ts=$( cd "$SDK_DIR" && git log -1 --format=%ct 2>/dev/null )
|
||||
build_ts=0
|
||||
[ -d "$BUILD_DIR" ] && build_ts=$(stat -c %Y "$BUILD_DIR/index.html" 2>/dev/null || echo 0)
|
||||
if [ -n "$head_ts" ] && [ "$build_ts" -gt 0 ] && [ "$head_ts" -gt "$build_ts" ]; then
|
||||
hs=$(cd "$SDK_DIR" && git log -1 --format='%h %s' 2>/dev/null)
|
||||
reasons+=("🧱 sdk 源码已提交但产物没重新构建($hs)")
|
||||
details+="【源码漂移】"$'\n'"HEAD $head_ts > 产物 $build_ts"$'\n'"$(cd "$SDK_DIR" && git log -1 --format='%h %ad %s' --date=iso 2>/dev/null)"$'\n\n'
|
||||
info "✗ 源码比产物新,需重新构建"
|
||||
else
|
||||
info "✓ 源码与产物无漂移"
|
||||
fi
|
||||
|
||||
# ── 去重与通知 ──────────────────────────────────────────────────────
|
||||
fingerprint=$(printf '%s\n' "${reasons[@]:-}" "$details" | md5sum | cut -c1-16)
|
||||
prev=""; prev_state=""
|
||||
[ -f "$STATE_FILE" ] && { prev_state=$(jq -r '.state // ""' "$STATE_FILE" 2>/dev/null); prev=$(jq -r '.fingerprint // ""' "$STATE_FILE" 2>/dev/null); }
|
||||
|
||||
if [ "$FORCE" = "1" ]; then
|
||||
info "--test:强制通知一次"
|
||||
[ "$DRY" = "1" ] && { say "(dry-run,不实际发送)"; }
|
||||
[ "$DRY" != "1" ] && notify "【站点巡检 · 测试】通知通路自检(当前差异条数:${#reasons[@]})——看到这条说明巡检能及时叫你。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "${#reasons[@]}" -eq 0 ]; then
|
||||
if [ "$prev_state" = "alert" ]; then
|
||||
msg="✅【站点巡检】已恢复正常:sdk 与 introduce 两站都和本机逐字节一致,源码也无漂移。刚才的差异自己好了(或你部署过了)。"
|
||||
[ "$DRY" = "1" ] || notify "$msg"
|
||||
log "recovered (was alert), notified=$([ "$DRY" = "1" ] && echo dry || echo yes)"
|
||||
else
|
||||
info "无差异,按约定保持安静(不打扰老大)"
|
||||
log "clean"
|
||||
fi
|
||||
printf '{"state":"clean","fingerprint":"%s","ts":"%s"}\n' "$fingerprint" "$(date -Is)" >"$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 有差异
|
||||
if [ "$fingerprint" = "$prev" ] && [ "$prev_state" = "alert" ]; then
|
||||
info "差异与上次通知完全相同(指纹 $fingerprint),不重复打扰"
|
||||
log "alert-unchanged fp=$fingerprint"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
say "有差异,准备通知"
|
||||
msg="🚨【站点巡检】发现 ${#reasons[@]} 处漂移(只提醒,未自动部署)"$'\n\n'
|
||||
for r in "${reasons[@]}"; do msg+="· $r"$'\n'; done
|
||||
msg+=$'\n'"$details"
|
||||
msg+=$'\n'"—— 要部署就跑:/home/program/TrueAgent/deploy-sdk-site.sh"
|
||||
msg+=$'\n'" 只看差异:/home/program/TrueAgent/deploy-sdk-site.sh --check"
|
||||
msg+=$'\n'" 回滚:--rollback <备份目录名>"
|
||||
|
||||
if [ "$DRY" = "1" ]; then
|
||||
say "(dry-run,以下内容本该发出)"; printf '%s\n' "$msg"
|
||||
else
|
||||
notify "$msg" && printf '{"state":"alert","fingerprint":"%s","ts":"%s"}\n' "$fingerprint" "$(date -Is)" >"$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
|
||||
fi
|
||||
log "alert fp=$fingerprint reasons=${#reasons[@]}"
|
||||
@ -2018,7 +2018,7 @@
|
||||
</p>
|
||||
<div class="cta rise" style="--d:280ms">
|
||||
<a class="btn btn-primary" href="#quickstart">快速上手 →</a>
|
||||
<a class="btn btn-ghost" href="https://gitcode.com/JianFeeeee/HomeAgent">查看源码</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/JianFeeeee/HomeAgent">查看源码</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -2058,6 +2058,12 @@
|
||||
<h3>记忆不会越用越肿</h3>
|
||||
<p>上下文按相关性取舍,最近 10 条恒受保护。低分下沉成文档,冷文档再蒸馏成三元组。有效工作区间上限 60 万 token。</p>
|
||||
</article>
|
||||
|
||||
<article class="duty rise-zoom">
|
||||
<span class="duty-k">并行</span>
|
||||
<h3>同轮的工具,一起跑</h3>
|
||||
<p>一轮里的多个 tool_call 默认并发,但<strong>安全性前置</strong>:工具必须在自己的定义里声明并发安全,未声明的一律串行。批内只要有一个不安全,<strong>整批降级</strong>——不做收益不抵风险的「部分并发」。同通道输出仍严格保序。</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -2807,6 +2813,9 @@ echo <span class="s">"你好,记住我喜欢喝咖啡"</span> | ./build/waiter
|
||||
<button class="badge" type="button" data-plugin="vanblog" style="--i:17">vanblog</button>
|
||||
<button class="badge" type="button" data-plugin="vikunja" style="--i:18">vikunja</button>
|
||||
<button class="badge" type="button" data-plugin="weather" style="--i:19">weather</button>
|
||||
<button class="badge" type="button" data-plugin="ai_image" style="--i:20">ai_image</button>
|
||||
<button class="badge" type="button" data-plugin="files" style="--i:21">files</button>
|
||||
<button class="badge" type="button" data-plugin="luademo" style="--i:22">luademo</button>
|
||||
<button class="badge muted" type="button" id="expandAll">展开全部</button>
|
||||
</div>
|
||||
<div class="pdetail" id="pdetail">
|
||||
@ -2814,37 +2823,37 @@ echo <span class="s">"你好,记住我喜欢喝咖啡"</span> | ./build/waiter
|
||||
<div class="pcard" id="p-a2a">
|
||||
<div class="pcard-head"><code>a2a</code><span class="ver">v1.3.1</span></div>
|
||||
<p>Agent 间通信。对外起 HTTP 服务暴露 /agent-card 与 /a2a(JSON-RPC)供他人发现与调用;对内提供工具去问别的 Agent。把监听地址设为空即只出站、不开端口。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/a2a" target="_blank" rel="noopener noreferrer" title="SDK example/a2a"><span class="psrc-r">SDK</span><span class="psrc-p">example/a2a</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/a2a" target="_blank" rel="noopener noreferrer" title="SDK example/a2a"><span class="psrc-r">SDK</span><span class="psrc-p">example/a2a</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-acp">
|
||||
<div class="pcard-head"><code>acp</code><span class="ver">v1.2.1</span></div>
|
||||
<p>Agent Client Protocol 通信插件:充当 ACP 服务端接受其他 Agent 的任务请求,同时提供客户端工具向远程 ACP Agent(如 opencode)发起会话并读取回复</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/acp" target="_blank" rel="noopener noreferrer" title="SDK example/acp"><span class="psrc-r">SDK</span><span class="psrc-p">example/acp</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/acp" target="_blank" rel="noopener noreferrer" title="SDK example/acp"><span class="psrc-r">SDK</span><span class="psrc-p">example/acp</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-bili">
|
||||
<div class="pcard-head"><code>bili</code><span class="ver">v1.2.0</span></div>
|
||||
<p>B站视频下载。需系统装 yt-dlp。不指定格式时先返回可用清晰度列表,选定后再下载;输出目录有路径校验,拒绝写成系统目录。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/bili" target="_blank" rel="noopener noreferrer" title="SDK example/bili"><span class="psrc-r">SDK</span><span class="psrc-p">example/bili</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/bili" target="_blank" rel="noopener noreferrer" title="SDK example/bili"><span class="psrc-r">SDK</span><span class="psrc-p">example/bili</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-browser">
|
||||
<div class="pcard-head"><code>browser</code><span class="ver">v2.4.1</span></div>
|
||||
<p>统一浏览器插件:搜索、HTTP抓取(quick)、无头渲染(normal)、交互式浏览器(interactive/CDP)</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/browser" target="_blank" rel="noopener noreferrer" title="SDK example/browser"><span class="psrc-r">SDK</span><span class="psrc-p">example/browser</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/browser" target="_blank" rel="noopener noreferrer" title="SDK example/browser"><span class="psrc-r">SDK</span><span class="psrc-p">example/browser</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-calendar">
|
||||
<div class="pcard-head"><code>calendar</code><span class="ver">v1.1.0</span></div>
|
||||
<p>日历事件管理,支持提醒和重复事件</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/calendar" target="_blank" rel="noopener noreferrer" title="SDK example/calendar"><span class="psrc-r">SDK</span><span class="psrc-p">example/calendar</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/calendar" target="_blank" rel="noopener noreferrer" title="SDK example/calendar"><span class="psrc-r">SDK</span><span class="psrc-p">example/calendar</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-deepsearch">
|
||||
<div class="pcard-head"><code>deepsearch</code><span class="ver">v1.1.2</span></div>
|
||||
<p>为 agent 提供真正的联网信息检索:本地 SearXNG 聚合多引擎(返回标题/URL/摘要/时间),支持新闻、时间范围、指定引擎;并提供网页正文抽取与「搜索+读前K篇」的深检索</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/deepsearch" target="_blank" rel="noopener noreferrer" title="SDK example/deepsearch"><span class="psrc-r">SDK</span><span class="psrc-p">example/deepsearch</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/deepsearch" target="_blank" rel="noopener noreferrer" title="SDK example/deepsearch"><span class="psrc-r">SDK</span><span class="psrc-p">example/deepsearch</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-editdoc">
|
||||
<div class="pcard-head"><code>editdoc</code><span class="ver">v2.0.0</span></div>
|
||||
<p>全能办公插件:新建/读取/编辑/转换 docx·xlsx·pptx·md·csv·txt(基于 python-docx / openpyxl / python-pptx / soffice / pandoc)</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/editdoc" target="_blank" rel="noopener noreferrer" title="SDK example/editdoc"><span class="psrc-r">SDK</span><span class="psrc-p">example/editdoc</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/editdoc" target="_blank" rel="noopener noreferrer" title="SDK example/editdoc"><span class="psrc-r">SDK</span><span class="psrc-p">example/editdoc</span><span class="psrc-out">↗</span></a>
|
||||
<p class="psrc-note">公开仓中的 example 仍是 v1.0.0;v2.0.0 全能版源码尚未公开。</p>
|
||||
</div>
|
||||
<div class="pcard" id="p-homeagent-mail-bridge">
|
||||
@ -2860,57 +2869,72 @@ echo <span class="s">"你好,记住我喜欢喝咖啡"</span> | ./build/waiter
|
||||
<div class="pcard" id="p-memo">
|
||||
<div class="pcard-head"><code>memo</code><span class="ver">v1.1.0</span></div>
|
||||
<p>待办与备忘录刻意分两类:待办每 5 分钟检查未完成数、有则注入提醒;备忘录纯记事不提醒。混成一类要么天天弹、要么被忘掉。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/memo" target="_blank" rel="noopener noreferrer" title="SDK example/memo"><span class="psrc-r">SDK</span><span class="psrc-p">example/memo</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/memo" target="_blank" rel="noopener noreferrer" title="SDK example/memo"><span class="psrc-r">SDK</span><span class="psrc-p">example/memo</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-music">
|
||||
<div class="pcard-head"><code>music</code><span class="ver">v0.1.0</span></div>
|
||||
<p>搜歌与查歌词(网易云公开接口)。只读:不下载音频、不落文件。两段式用法 —— 先搜到歌曲 ID,再按 ID 取词。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/music" target="_blank" rel="noopener noreferrer" title="SDK example/music"><span class="psrc-r">SDK</span><span class="psrc-p">example/music</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/music" target="_blank" rel="noopener noreferrer" title="SDK example/music"><span class="psrc-r">SDK</span><span class="psrc-p">example/music</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-ocr">
|
||||
<div class="pcard-head"><code>ocr</code><span class="ver">v1.0.0</span></div>
|
||||
<p>图片文字识别。需系统装 tesseract(中文另需 chi_sim 语言包)。支持 URL 或 base64 输入,临时文件识别后自动清理。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/ocr" target="_blank" rel="noopener noreferrer" title="SDK example/ocr"><span class="psrc-r">SDK</span><span class="psrc-p">example/ocr</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/ocr" target="_blank" rel="noopener noreferrer" title="SDK example/ocr"><span class="psrc-r">SDK</span><span class="psrc-p">example/ocr</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-plugindev">
|
||||
<div class="pcard-head"><code>plugindev</code><span class="ver">v1.0.0</span></div>
|
||||
<p>把 hmapdev 工具链封装成 Agent 可调用的工具:脚手架生成插件工程、构建打包 .hmap、管理 SDK 版本。配合 plugin_install 即可让 Agent 自己做完「新建插件 → 构建 → 安装」全流程。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/plugindev" target="_blank" rel="noopener noreferrer" title="SDK example/plugindev"><span class="psrc-r">SDK</span><span class="psrc-p">example/plugindev</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/plugindev" target="_blank" rel="noopener noreferrer" title="SDK example/plugindev"><span class="psrc-r">SDK</span><span class="psrc-p">example/plugindev</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-qq">
|
||||
<div class="pcard-head"><code>qq</code><span class="ver">v1.4.1</span></div>
|
||||
<p>经 NapCat 桥接 QQ:20 个工具覆盖消息、群/好友、文件传输。带权限模型 —— 身份绑在「帧」上而非插件全局(中断抢占恢复后不会丢身份),多来源合并取权限交集,敏感工具按前缀一律拒绝非所有者。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/qq" target="_blank" rel="noopener noreferrer" title="SDK example/qq"><span class="psrc-r">SDK</span><span class="psrc-p">example/qq</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/qq" target="_blank" rel="noopener noreferrer" title="SDK example/qq"><span class="psrc-r">SDK</span><span class="psrc-p">example/qq</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-recoverydiag">
|
||||
<div class="pcard-head"><code>recoverydiag</code><span class="ver">v0.2.0</span></div>
|
||||
<p>快速检查/崩溃取证工具集:diag_triage(退出码/信号/存活粗分)、diag_db(config.db 完整性 + LLM 源解析校验)、diag_log_scan(日志签名命中)、diag_delta(last-good 快照 vs 现状 diff)、diag_loc(正交综合定位)。全部返回结论而非原文,确定性、不消耗 LLM token,供 guard / failback 恢复决策使用。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/recoverydiag" target="_blank" rel="noopener noreferrer" title="SDK example/recoverydiag"><span class="psrc-r">SDK</span><span class="psrc-p">example/recoverydiag</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/recoverydiag" target="_blank" rel="noopener noreferrer" title="SDK example/recoverydiag"><span class="psrc-r">SDK</span><span class="psrc-p">example/recoverydiag</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-rss">
|
||||
<div class="pcard-head"><code>rss</code><span class="ver">v1.1.0</span></div>
|
||||
<p>RSS/Atom 订阅监控。按间隔轮询,发现新条目即以中断注入告知 agent;订阅时记下历史条目,所以订一个源不会把旧文章全推一遍。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/rss" target="_blank" rel="noopener noreferrer" title="SDK example/rss"><span class="psrc-r">SDK</span><span class="psrc-p">example/rss</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/rss" target="_blank" rel="noopener noreferrer" title="SDK example/rss"><span class="psrc-r">SDK</span><span class="psrc-p">example/rss</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-sanitizer">
|
||||
<div class="pcard-head"><code>sanitizer</code><span class="ver">v0.1.0</span></div>
|
||||
<p>在三个阶段清洗文本:坏 UTF-8 / U+FFFD / ANSI 转义(会被模型复读)与 LLM 输出里的工具调用残留。不注册工具,只挂钩子。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/sanitizer" target="_blank" rel="noopener noreferrer" title="SDK example/sanitizer"><span class="psrc-r">SDK</span><span class="psrc-p">example/sanitizer</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/sanitizer" target="_blank" rel="noopener noreferrer" title="SDK example/sanitizer"><span class="psrc-r">SDK</span><span class="psrc-p">example/sanitizer</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-vanblog">
|
||||
<div class="pcard-head"><code>vanblog</code><span class="ver">v1.0.0</span></div>
|
||||
<p>管理 VanBlog 开源博客系统:文章的增删改查、分类标签管理、草稿发布、备份导出等</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/vanblog" target="_blank" rel="noopener noreferrer" title="SDK example/vanblog"><span class="psrc-r">SDK</span><span class="psrc-p">example/vanblog</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/vanblog" target="_blank" rel="noopener noreferrer" title="SDK example/vanblog"><span class="psrc-r">SDK</span><span class="psrc-p">example/vanblog</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-vikunja">
|
||||
<div class="pcard-head"><code>vikunja</code><span class="ver">v1.0.1</span></div>
|
||||
<p>Vikunja 待办/任务管理:任务增删改查、项目与看板桶、标签、指派、评论、关联、附件、保存筛选器、团队与分享、通知、订阅、Webhook、时间跟踪、数据导入、实例管理;并附通用 API 直通工具兜底</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/vikunja" target="_blank" rel="noopener noreferrer" title="SDK example/vikunja"><span class="psrc-r">SDK</span><span class="psrc-p">example/vikunja</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/vikunja" target="_blank" rel="noopener noreferrer" title="SDK example/vikunja"><span class="psrc-r">SDK</span><span class="psrc-p">example/vikunja</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-weather">
|
||||
<div class="pcard-head"><code>weather</code><span class="ver">v1.0.0</span></div>
|
||||
<p>查实时天气与预报(wttr.in,无需 API Key)。可设默认城市;结果标记为不进记忆计算 —— 天气是易变数据,反复写进记忆只会挤占预算。</p>
|
||||
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/weather" target="_blank" rel="noopener noreferrer" title="SDK example/weather"><span class="psrc-r">SDK</span><span class="psrc-p">example/weather</span><span class="psrc-out">↗</span></a>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/weather" target="_blank" rel="noopener noreferrer" title="SDK example/weather"><span class="psrc-r">SDK</span><span class="psrc-p">example/weather</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-ai_image">
|
||||
<div class="pcard-head"><code>ai_image</code><span class="ver">v1.3.0</span></div>
|
||||
<p>文生图:按文字提示生成图片,下载到本地并返回<strong>文件路径</strong>(可直接被多模态链路读取)。</p>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/ai_image" target="_blank" rel="noopener noreferrer" title="SDK example/ai_image"><span class="psrc-r">SDK</span><span class="psrc-p">example/ai_image</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-files">
|
||||
<div class="pcard-head"><code>files</code><span class="ver">v1.0.0</span></div>
|
||||
<p>沙箱文件操作:读写与编辑文件,<strong>全部操作限制在沙箱目录内</strong>(读取/写入/编辑/列目录)。</p>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/files" target="_blank" rel="noopener noreferrer" title="SDK example/files"><span class="psrc-r">SDK</span><span class="psrc-p">example/files</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
<div class="pcard" id="p-luademo">
|
||||
<div class="pcard-head"><code>luademo</code><span class="ver">v0.1.0</span></div>
|
||||
<p>Lua 插件全功能示例:工具(no_memory / cleaner)、阶段钩子、通道、数据类 API —— 想用 Lua 写插件,从这个例子抄起。</p>
|
||||
<a class="psrc" href="https://github.com/JianFeeeee/homeagentsdk/blob/main/example/luademo" target="_blank" rel="noopener noreferrer" title="SDK example/luademo"><span class="psrc-r">SDK</span><span class="psrc-p">example/luademo</span><span class="psrc-out">↗</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -2986,35 +3010,35 @@ echo <span class="s">"你好,记住我喜欢喝咖啡"</span> | ./build/waiter
|
||||
<div>
|
||||
<h4>文档</h4>
|
||||
<ul>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/OVERVIEW.md">项目概览</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/ARCHITECTURE.md">技术架构</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/PLUGIN_DEV.md">插件开发</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/ADAPTER.md">Lua 适配器</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/OVERVIEW.md">项目概览</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/ARCHITECTURE.md">技术架构</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/PLUGIN_DEV.md">插件开发</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/ADAPTER.md">Lua 适配器</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>深入</h4>
|
||||
<ul>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/docs/zh/input-scheduler-design.md">输入调度器设计</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/docs/zh/resident-subagent-design.md">驻留式子 Agent</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/docs/zh/multimodal-space.md">统一多模态空间</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/docs/zh/input-scheduler-design.md">输入调度器设计</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/docs/zh/resident-subagent-design.md">驻留式子 Agent</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/docs/zh/multimodal-space.md">统一多模态空间</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>项目</h4>
|
||||
<ul>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent">源码仓库</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/releases">发布下载</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/homeagent-sdk">插件 SDK</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent">源码仓库</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/releases">发布下载</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/homeagentsdk">插件 SDK</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>状态</h4>
|
||||
<ul>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/releases">最新发布:v1.3.x 线</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/internal/meta/meta.go">main 在研:1.4.0</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/LICENSE">内核许可:AGPL-3.0-only</a></li>
|
||||
<li><a href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/LICENSE">插件 SDK:MIT</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/releases">最新发布:v1.3.x 线</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/internal/meta/meta.go">main 在研:1.4.0</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/HomeAgent/blob/main/LICENSE">内核许可:AGPL-3.0-only</a></li>
|
||||
<li><a href="https://github.com/JianFeeeee/homeagentsdk/blob/main/LICENSE">插件 SDK:MIT</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user