Merge commit 'refs/tmp/gmain' into ci/boot-graph-tests
127
.github/workflows/ci.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
# HomeAgent 主仓 CI。
|
# HologramAgent 主仓 CI。
|
||||||
#
|
#
|
||||||
# 设计原则:**CI 里跑的每一条命令,都是本地已实测通过的命令**。
|
# 设计原则:**CI 里跑的每一条命令,都是本地已实测通过的命令**。
|
||||||
# 不写「应该有用来试试」的步骤 —— 未验证的 CI 步骤会把假红灯变成常态,
|
# 不写「应该有用来试试」的步骤 —— 未验证的 CI 步骤会把假红灯变成常态,
|
||||||
@ -521,19 +521,124 @@ jobs:
|
|||||||
csrc:
|
csrc:
|
||||||
name: C infrastructure gates
|
name: C infrastructure gates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
# 门禁本身实测 0~1 秒(csrc-lint/abi/headers/sanitize/cross 各自 <1s)。
|
||||||
|
# 这 15 分钟全部是给 apt-get 的余量 —— 卡死历来只发生在装包阶段。
|
||||||
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
# clang 供双编译器告警对照;gcc-aarch64 供跨架构编译门禁。
|
# ── 安装 C 工具链 ─────────────────────────────────────────────
|
||||||
# 门禁在缺工具时是显式 SKIP 而不是假通过,这里装齐以免静默降级。
|
#
|
||||||
- name: 安装 C 工具链
|
# ★ 加固点(2026-10-22):实测两次该 job 卡死 20 分钟并超时,
|
||||||
|
# 而**本地同一门禁 0~1 秒全通过**(make check-csrc)。卡死点全在
|
||||||
|
# apt-get:ubuntu-latest runner 上 `apt-get update` 偶发挂住不返回,
|
||||||
|
# 既没有失败也没有进展,只能等 job 级 timeout。
|
||||||
|
#
|
||||||
|
# 三重加固:
|
||||||
|
# 1. 逐包检查是否已安装 —— runner 镜像通常自带 cmake/clang,
|
||||||
|
# 已装就跳过 apt-get(多数情况直接走这条,完全不碰网络)
|
||||||
|
# 2. 需要装时给 apt-get 加自身的超时与重试,让它**自己失败**,
|
||||||
|
# 而不是把整个 job 拖到 job 级 timeout(后者报错信息毫无指向性)
|
||||||
|
# 3. 装完立即 `command -v` 复核,缺工具就红 —— 见下方"缺工具即失败"
|
||||||
|
- name: 安装 C 工具链(已装则跳过)
|
||||||
|
# 120s(update) + 180s(install) = 300s,给 5 分钟正好;
|
||||||
|
# 超出说明 timeout 命令本身没生效,此时应当红而不是继续等。
|
||||||
|
timeout-minutes: 5
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -qq
|
set -uo pipefail
|
||||||
sudo apt-get install -y -qq cmake clang gcc-aarch64-linux-gnu
|
# ★ 不再需要 gcc-aarch64-linux-gnu(2026-10-22)。
|
||||||
|
#
|
||||||
|
# 实测:装它要从源拉 10MB+ 的 cpp-13 等依赖,在 runner 上卡住超时
|
||||||
|
# (15:47:39 开始下 cpp-13-aarch64,180s 后仍未完成)。
|
||||||
|
# 而 csrc-cross 只需要 `-fsyntax-only`,clang 自带交叉前端即可:
|
||||||
|
# `clang --target=aarch64-linux-gnu` 会真的切到 aarch64 目标
|
||||||
|
# (__aarch64__ 宏生效、指针宽度按该架构算),足以暴露本门禁要抓的
|
||||||
|
# 架构问题。Makefile 的 csrc-cross 已按
|
||||||
|
# CC_ARM64 > clang --target > aarch64-gcc > SKIP 的优先级挑选。
|
||||||
|
need=""
|
||||||
|
command -v cmake >/dev/null || need="$need cmake"
|
||||||
|
command -v clang >/dev/null || need="$need clang"
|
||||||
|
# aarch64 的 libc 头(1.7MB,仅头文件,**不含编译器**)。
|
||||||
|
# 用它 + 已预装的 clang 交叉前端,替代 10MB+ 的完整交叉 GCC
|
||||||
|
# (实测那个会卡在下载 cpp-13-aarch64,180s 超时)。
|
||||||
|
[ -d /usr/aarch64-linux-gnu/include ] || need="$need libc6-dev-arm64-cross"
|
||||||
|
|
||||||
|
if [ -z "$need" ]; then
|
||||||
|
echo "✅ 依赖已存在,跳过 apt-get:$(command -v cmake clang | tr '\n' ' ')"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "需要安装:$need"
|
||||||
|
|
||||||
|
# ★ 2026-10-22 实测:加 Acquire::*Timeout=60 + Retries=3 之后仍然卡满
|
||||||
|
# 6 分钟 step 超时,且**日志里一个 apt 输出都没有** —— 因为 `-qq`
|
||||||
|
# 把一切静默了。卡死不可观测正是它拖满超时的原因:既看不到卡在哪个
|
||||||
|
# 源,也看不到到底在下载什么。
|
||||||
|
#
|
||||||
|
# 所以:
|
||||||
|
# · 去掉 `-qq`,保留可读进度(否则下次卡死仍然无从诊断)
|
||||||
|
# · 用 `timeout` 命令做外层硬超时 —— Acquire::Timeout 只管单个连接,
|
||||||
|
# 不管 apt 整体(源重试、DPkg 锁等待都不受它约束)
|
||||||
|
# · 失败后**不重试整段**:宁可红一次让人看见,也不要为偶发网络
|
||||||
|
# 抖动掩盖真实问题
|
||||||
|
APT_OPTS="-o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 -o Acquire::Retries=2"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if ! timeout 120 sudo apt-get $APT_OPTS update; then
|
||||||
|
echo "::error::apt-get update 超时/失败(120s)—— runner 网络或源不可用"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if ! timeout 180 sudo apt-get $APT_OPTS install -y --no-install-recommends $need; then
|
||||||
|
echo "::error::apt-get install 超时/失败(180s)需要安装:$need"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 缺工具即失败(★ 这条是本次加固的核心)────────────────────
|
||||||
|
#
|
||||||
|
# 此前门禁在缺工具时打 `[SKIP] ... exit 0` —— **CI 照样变绿**。
|
||||||
|
# 那意味着:apt-get 悄悄失败(或将来有人删掉安装步骤)⇒ 五道门禁
|
||||||
|
# 全部跳过 ⇒ job 绿 ⇒ 让人以为 C 侧有门禁,实际一个都没跑。
|
||||||
|
#
|
||||||
|
# 静默降级比红更危险:红会有人修,绿没人看。
|
||||||
|
# 所以 CI 里显式要求工具必须在场,SKIP 视为失败。
|
||||||
|
- name: 确认 C 工具链齐备(缺则失败,不接受 SKIP)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
miss=""
|
||||||
|
for t in cmake clang; do
|
||||||
|
if command -v "$t" >/dev/null 2>&1; then
|
||||||
|
echo " ✅ $t: $(command -v "$t")"
|
||||||
|
else
|
||||||
|
echo " ❌ $t 缺失" >&2
|
||||||
|
miss="$miss $t"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -z "$miss" ] || {
|
||||||
|
echo "::error::C 工具链缺失:$miss —— 缺工具时门禁会 SKIP 并 exit 0,等于假装通过"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── 跑门禁 ──────────────────────────────────────────────────
|
||||||
|
# 门禁自身 0~1 秒,给它 5 分钟绰绰有余;超出说明是异常(不是慢)。
|
||||||
- name: make check-csrc
|
- name: make check-csrc
|
||||||
run: make check-csrc
|
timeout-minutes: 5
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
make check-csrc 2>&1 | tee /tmp/csrc.log
|
||||||
|
|
||||||
|
# ★ 二次确认:任何 [SKIP] 都视为失败。
|
||||||
|
#
|
||||||
|
# Makefile 里每道门禁在缺依赖时 `echo "[SKIP] ..."; exit 0`,
|
||||||
|
# 这在本机是合理的(开发者未必装了交叉工具链),但在 CI 里
|
||||||
|
# 就等于把"没验证"伪装成"通过"。上面那步已保证工具齐备,
|
||||||
|
# 这里再拦一次是为了覆盖将来新增门禁时漏配依赖的情况。
|
||||||
|
if grep -q '\[SKIP\]' /tmp/csrc.log; then
|
||||||
|
echo "::error::门禁出现 [SKIP],说明有检查没真正执行:"
|
||||||
|
grep '\[SKIP\]' /tmp/csrc.log >&2
|
||||||
|
echo "CI 里不接受 SKIP —— 要么装上依赖,要么明确从 check-csrc 摘掉这条。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ 全部门禁实际执行,无 SKIP"
|
||||||
|
|
||||||
# ── 文档站构建(mkdocs,纯 Python,无外部依赖)──
|
# ── 文档站构建(mkdocs,纯 Python,无外部依赖)──
|
||||||
docs:
|
docs:
|
||||||
@ -589,11 +694,11 @@ jobs:
|
|||||||
#
|
#
|
||||||
# 取值依据(实测 2026-10-21,直连不带代理,非推测):
|
# 取值依据(实测 2026-10-21,直连不带代理,非推测):
|
||||||
# https://gitcode.com/JianFeeeee/HologramAgent → 200 ← 当前用这个
|
# https://gitcode.com/JianFeeeee/HologramAgent → 200 ← 当前用这个
|
||||||
# https://gitcode.com/JianFeeeee/HomeAgent → 301 ← 旧名,跳转到上面
|
# https://gitcode.com/JianFeeeee/HologramAgent → 301 ← 旧名,跳转到上面
|
||||||
# https://gitcode.com/JianFeeeee/hologramagentsdk → 200(SDK 仓,另一对象)
|
# https://gitcode.com/JianFeeeee/hologramagentsdk → 200(SDK 仓,另一对象)
|
||||||
# https://gitcode.com/JianFeeeee/homeagentsdk → 404
|
# https://gitcode.com/JianFeeeee/homeagentsdk → 404
|
||||||
#
|
#
|
||||||
# 背景:gitcode 侧镜像仓已由 HomeAgent 改名为 HologramAgent(与 GitHub
|
# 背景:gitcode 侧镜像仓已由 HologramAgent 改名为 HologramAgent(与 GitHub
|
||||||
# 主仓同名)。旧地址仍 301 跳转,跟随后也能到达,但走重定向 ⇒ 别再用它。
|
# 主仓同名)。旧地址仍 301 跳转,跟随后也能到达,但走重定向 ⇒ 别再用它。
|
||||||
# 判定两仓同一对象:两地址 git ls-remote 的 refs 列表 md5 相同。
|
# 判定两仓同一对象:两地址 git ls-remote 的 refs 列表 md5 相同。
|
||||||
#
|
#
|
||||||
@ -617,7 +722,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
# 必须取完整历史:镜像可能在任意旧提交上,快进需要共同祖先。
|
# 必须取完整历史:镜像可能在任意旧提交上,快进需要共同祖先。
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
# ★ 改名(HomeAgent → HologramAgent)后的必然过渡态:gitcode 上的镜像仓
|
# ★ 改名(HologramAgent → HologramAgent)后的必然过渡态:gitcode 上的镜像仓
|
||||||
# 是**另一个账号下的独立仓库**,改 GitHub 仓名不会自动建出它。
|
# 是**另一个账号下的独立仓库**,改 GitHub 仓名不会自动建出它。
|
||||||
# 若不在这里显式探测,缺少目标仓时只会得到 git 的原始报错
|
# 若不在这里显式探测,缺少目标仓时只会得到 git 的原始报错
|
||||||
# ("repository not found" 混在 push 输出里),看不出是「仓没建」。
|
# ("repository not found" 混在 push 输出里),看不出是「仓没建」。
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
@ -308,7 +308,7 @@ jobs:
|
|||||||
[ "$PRE" = "true" ] && FLAGS+=(--prerelease)
|
[ "$PRE" = "true" ] && FLAGS+=(--prerelease)
|
||||||
gh release create "$TAG" \
|
gh release create "$TAG" \
|
||||||
--title "$TAG" \
|
--title "$TAG" \
|
||||||
--notes "HomeAgent $VERSION
|
--notes "HologramAgent $VERSION
|
||||||
|
|
||||||
产物清单与校验见 SHA256SUMS。
|
产物清单与校验见 SHA256SUMS。
|
||||||
- \`hologramagent_${VERSION}_linux_amd64.tar.gz\` — 内核 + CLI + GUI 打包
|
- \`hologramagent_${VERSION}_linux_amd64.tar.gz\` — 内核 + CLI + GUI 打包
|
||||||
@ -361,11 +361,11 @@ jobs:
|
|||||||
#
|
#
|
||||||
# 取值依据(实测 2026-10-21,直连不带代理,非推测):
|
# 取值依据(实测 2026-10-21,直连不带代理,非推测):
|
||||||
# https://gitcode.com/JianFeeeee/HologramAgent → 200 ← 当前用这个
|
# https://gitcode.com/JianFeeeee/HologramAgent → 200 ← 当前用这个
|
||||||
# https://gitcode.com/JianFeeeee/HomeAgent → 301 ← 旧名,跳转到上面
|
# https://gitcode.com/JianFeeeee/HologramAgent → 301 ← 旧名,跳转到上面
|
||||||
# https://gitcode.com/JianFeeeee/hologramagentsdk → 200(SDK 仓,另一对象)
|
# https://gitcode.com/JianFeeeee/hologramagentsdk → 200(SDK 仓,另一对象)
|
||||||
# https://gitcode.com/JianFeeeee/homeagentsdk → 404
|
# https://gitcode.com/JianFeeeee/homeagentsdk → 404
|
||||||
#
|
#
|
||||||
# 背景:gitcode 侧镜像仓已由 HomeAgent 改名为 HologramAgent(与 GitHub
|
# 背景:gitcode 侧镜像仓已由 HologramAgent 改名为 HologramAgent(与 GitHub
|
||||||
# 主仓同名)。旧地址仍 301 跳转,跟随后也能到达,但走重定向 ⇒ 别再用它。
|
# 主仓同名)。旧地址仍 301 跳转,跟随后也能到达,但走重定向 ⇒ 别再用它。
|
||||||
# 判定两仓同一对象:两地址 git ls-remote 的 refs 列表 md5 相同。
|
# 判定两仓同一对象:两地址 git ls-remote 的 refs 列表 md5 相同。
|
||||||
#
|
#
|
||||||
@ -390,7 +390,7 @@ jobs:
|
|||||||
name: linux-amd64
|
name: linux-amd64
|
||||||
path: dist
|
path: dist
|
||||||
# ★ 前置检查:gitcode 镜像仓是否存在。
|
# ★ 前置检查:gitcode 镜像仓是否存在。
|
||||||
# 改名(HomeAgent → HologramAgent)后 gitcode 目标仓需要**人工新建**,
|
# 改名(HologramAgent → HologramAgent)后 gitcode 目标仓需要**人工新建**,
|
||||||
# 不探的话失败会埋在 push/curl 的原始输出里,看不出是「仓没建」。
|
# 不探的话失败会埋在 push/curl 的原始输出里,看不出是「仓没建」。
|
||||||
# 语义:token 未配 = 可选项跳过(上一步);token 有但仓不存在 = 硬失败。
|
# 语义:token 未配 = 可选项跳过(上一步);token 有但仓不存在 = 硬失败。
|
||||||
#
|
#
|
||||||
|
|||||||
72
Makefile
@ -1,3 +1,7 @@
|
|||||||
|
# 门禁脚本需要 bash 的 PIPESTATUS(/bin/sh=dash 不支持)。
|
||||||
|
# 没有它就无法区分"编译器失败"与"head 成功" —— 实测会让门禁假通过。
|
||||||
|
SHELL := /bin/bash
|
||||||
|
|
||||||
.PHONY: test-gui all build build-plain build-cli build-gui clean install test run build-static build-linux-arm64 lint fmt sync-client-versions check-client-versions check-starmap-shared check-client-protocol csrc csrc-test csrc-lint csrc-abi csrc-headers csrc-sanitize csrc-cross csrc-fuzz check-csrc check-csrc-full
|
.PHONY: test-gui all build build-plain build-cli build-gui clean install test run build-static build-linux-arm64 lint fmt sync-client-versions check-client-versions check-starmap-shared check-client-protocol csrc csrc-test csrc-lint csrc-abi csrc-headers csrc-sanitize csrc-cross csrc-fuzz check-csrc check-csrc-full
|
||||||
# HOMED_TAGS 默认带 onnxruntime:发行版**默认启用**本地向量空间(与
|
# HOMED_TAGS 默认带 onnxruntime:发行版**默认启用**本地向量空间(与
|
||||||
# deploy/packaging/build.sh 保持一致)。
|
# deploy/packaging/build.sh 保持一致)。
|
||||||
@ -255,21 +259,71 @@ csrc-fuzz:
|
|||||||
# C 侧的架构相关问题(endianness 假设、指针宽度、size_t vs int 宽度、
|
# C 侧的架构相关问题(endianness 假设、指针宽度、size_t vs int 宽度、
|
||||||
# -fsanitize 不可用)只有真的用目标编译器编一遍才会暴露。
|
# -fsanitize 不可用)只有真的用目标编译器编一遍才会暴露。
|
||||||
# 与 deploy/packaging/build.sh 的 arm64 目标共用同一套 CC 变量。
|
# 与 deploy/packaging/build.sh 的 arm64 目标共用同一套 CC 变量。
|
||||||
|
#
|
||||||
|
# ## 为什么首选 clang --target,而不是 aarch64-linux-gnu-gcc(2026-10-22)
|
||||||
|
#
|
||||||
|
# 这道门禁只做 `-fsyntax-only`(不链接、不运行),而 clang 自带交叉前端:
|
||||||
|
# `clang --target=aarch64-linux-gnu` 会真的切到 aarch64 目标(`__aarch64__`
|
||||||
|
# 宏生效、指针宽度按该架构计算),足以暴露本门禁要抓的那类问题
|
||||||
|
# (endianness 假设、指针/size_t 宽度、不支持的 C 扩展)。
|
||||||
|
#
|
||||||
|
# 而装 `gcc-aarch64-linux-gnu` 需要从源拉 10MB+ 的 cpp-13 等依赖 ——
|
||||||
|
# 实测在 CI runner 上会卡住超时(见 .github/workflows/ci.yml 的加固说明)。
|
||||||
|
# 既然只需要语法层验证,就不该为它引入一个会卡死的下载依赖。
|
||||||
|
#
|
||||||
|
# 优先级:CC_ARM64(显式指定) > clang --target > aarch64-linux-gnu-gcc > SKIP
|
||||||
|
#
|
||||||
|
# ★ 宿主头泄漏陷阱(CI 实测):只加 -isystem 指向 aarch64 头还不够 ——
|
||||||
|
# 宿主 /usr/include/stdint.h 会先被找到,它再 include <bits/libc-header-start.h>
|
||||||
|
# (amd64 布局),aarch64 目标下必然找不到:
|
||||||
|
# /usr/include/stdint.h:26: fatal error: 'bits/libc-header-start.h' file not found
|
||||||
|
# 所以 clang-target 模式必须 -nostdinc 剃掉全部默认路径,再按
|
||||||
|
# 「clang 内建头 → aarch64 目标头」顺序加回(见下方 ARM64_INC)。
|
||||||
|
# explicit / cross-gcc 模式用编译器自带搜索路径,不传这些。
|
||||||
|
#
|
||||||
|
# ★ 两个假通过陷阱(实测踩到,勿删):
|
||||||
|
# 1. 显式 CC_ARM64 指向不存在的命令时,`cmd | head` 的退出码是 head 的(恒 0),
|
||||||
|
# command-not-found(127) 被吞 ⇒ 门禁显示"通过"。所以循环里改用 PIPESTATUS[0],
|
||||||
|
# 且对显式指定的编译器先做 command -v 校验。
|
||||||
|
# 2. 上述 PIPESTATUS 需要 bash;Make 默认 /bin/sh=dash 不支持(见文件顶 SHELL)。
|
||||||
.PHONY: csrc-cross
|
.PHONY: csrc-cross
|
||||||
csrc-cross:
|
csrc-cross:
|
||||||
@echo "== C 侧交叉编译(linux/arm64)=="
|
@echo "== C 侧交叉编译(linux/arm64)=="
|
||||||
@CC_ARM64=$${CC_ARM64:-aarch64-linux-gnu-gcc}; \
|
@ok=1; \
|
||||||
if ! command -v $$CC_ARM64 >/dev/null 2>&1; then \
|
if [ -n "$${CC_ARM64:-}" ]; then \
|
||||||
echo " [SKIP] $$CC_ARM64 不存在(未装交叉工具链)"; exit 0; \
|
if ! command -v "$$CC_ARM64" >/dev/null 2>&1; then \
|
||||||
fi; \
|
echo " [FAIL] CC_ARM64=$$CC_ARM64 不存在"; exit 1; \
|
||||||
ok=1; \
|
|
||||||
for src in $(CSRC_SRCS); do \
|
|
||||||
if ! $$CC_ARM64 $(CSRC_CFLAGS) -Werror -fsyntax-only $$src 2>&1 | head -20; then \
|
|
||||||
ok=0; \
|
|
||||||
fi; \
|
fi; \
|
||||||
|
CC="$$CC_ARM64"; MODE="explicit"; \
|
||||||
|
elif command -v clang >/dev/null 2>&1; then \
|
||||||
|
CC="clang --target=aarch64-linux-gnu"; MODE="clang-target"; \
|
||||||
|
elif command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \
|
||||||
|
CC="aarch64-linux-gnu-gcc"; MODE="cross-gcc"; \
|
||||||
|
else \
|
||||||
|
echo " [SKIP] 无 clang、无 aarch64-linux-gnu-gcc(未装任何交叉前端)"; exit 0; \
|
||||||
|
fi; \
|
||||||
|
\
|
||||||
|
ARM64_INC="-nostdinc -isystem $$(clang -print-resource-dir)/include"; \
|
||||||
|
for d in /usr/aarch64-linux-gnu/include /usr/include/aarch64-linux-gnu; do \
|
||||||
|
[ -d "$$d" ] && ARM64_INC="$$ARM64_INC -isystem $$d"; \
|
||||||
|
done; \
|
||||||
|
\
|
||||||
|
if [ "$$MODE" = "clang-target" ]; then \
|
||||||
|
printf '#include <string.h>\nint main(void){return 0;}\n' > /tmp/_archchk.c; \
|
||||||
|
if ! clang --target=aarch64-linux-gnu $$ARM64_INC -fsyntax-only /tmp/_archchk.c >/dev/null 2>&1; then \
|
||||||
|
echo " [SKIP] clang 交叉前端不可用(缺 aarch64 libc 头,装 libc6-dev-arm64-cross 即可)"; exit 0; \
|
||||||
|
fi; \
|
||||||
|
fi; \
|
||||||
|
\
|
||||||
|
EXTRA_INC=""; \
|
||||||
|
[ "$$MODE" = "clang-target" ] && EXTRA_INC="$$ARM64_INC"; \
|
||||||
|
for src in $(CSRC_SRCS); do \
|
||||||
|
$$CC $$EXTRA_INC $(CSRC_CFLAGS) -Werror -fsyntax-only $$src 2>&1 | head -20; \
|
||||||
|
rc=$${PIPESTATUS[0]}; \
|
||||||
|
if [ "$$rc" != "0" ]; then ok=0; fi; \
|
||||||
done; \
|
done; \
|
||||||
if [ "$$ok" = "1" ]; then \
|
if [ "$$ok" = "1" ]; then \
|
||||||
echo " $$CC_ARM64: 0 告警、编译通过 ✓($(words $(CSRC_SRCS)) 个源文件)"; \
|
echo " $$CC [$$MODE]: 0 告警、编译通过 ✓($(words $(CSRC_SRCS)) 个源文件)"; \
|
||||||
else \
|
else \
|
||||||
echo " [FAIL] arm64 交叉编译失败"; exit 1; \
|
echo " [FAIL] arm64 交叉编译失败"; exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -240,7 +240,7 @@ internal/
|
|||||||
├── config/ SQLite 配置中心
|
├── config/ SQLite 配置中心
|
||||||
├── events/ 事件总线
|
├── events/ 事件总线
|
||||||
└── internal/lua/adapters/ 10 个 LLM 协议适配器脚本
|
└── internal/lua/adapters/ 10 个 LLM 协议适配器脚本
|
||||||
外部插件开发见 [homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) 仓库,使用 `hmapdev` 工具链开发,参考 `example/` 目录下的 Go 和 Lua 示例
|
外部插件开发见 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 仓库,使用 `hmapdev` 工具链开发,参考 `example/` 目录下的 Go 和 Lua 示例
|
||||||
```
|
```
|
||||||
|
|
||||||
## 项目状态
|
## 项目状态
|
||||||
@ -318,7 +318,7 @@ internal/
|
|||||||
> inline/small **30.4µs**、frame/small 51.5µs、inline/large 767µs、frame/large 398µs。
|
> 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://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.1** — 多模态贯通**插件边界**。v1.1.0 让记忆系统支持了二进制多媒体节点,但那条链路只对内核自己开放;本版打通到插件与模型。公开 SDK 新增媒体字段与三个媒体注入接口(配套 [SDK v1.1.0](https://github.com/JianFeeeee/hologramagentsdk/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 淘汰的缓存。
|
**v1.1.0** — 记忆系统支持**二进制多媒体节点**。内容寻址媒体存储(CAS + SQLite 元数据 + 磁盘 blob,`Get` always 重校 digest),贯通 L0(上下文事件)/L2(文档)/L3(图谱句子)三层,引用计数式 GC(有引用者绝不删)。视觉模型生成的描述文本是持久语义记忆,blob 只是可被容量 GC 淘汰的缓存。
|
||||||
|
|
||||||
@ -326,7 +326,7 @@ 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.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+ 插件,外部插件开发见 [homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) 仓库。新增输入通道 `NoMemory`/`Cleaner`、`ChannelDef`、插件禁用/启用系统(CLI + WebUI),`plugindev` 工具链完成 C ABI `ChannelDef` 传递。
|
**v0.8.0** — 核心可用,插件系统增强。内置 20+ 插件,外部插件开发见 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 仓库。新增输入通道 `NoMemory`/`Cleaner`、`ChannelDef`、插件禁用/启用系统(CLI + WebUI),`plugindev` 工具链完成 C ABI `ChannelDef` 传递。
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
@ -385,7 +385,7 @@ make install # 安装到系统
|
|||||||
**通过网络提供服务时也要向使用者提供源码**(§13 Remote Network Interaction)。
|
**通过网络提供服务时也要向使用者提供源码**(§13 Remote Network Interaction)。
|
||||||
即:任何人把改过的 HologramAgent 对外提供网络服务,都必须让该服务的使用者拿到改动后的源码。
|
即:任何人把改过的 HologramAgent 对外提供网络服务,都必须让该服务的使用者拿到改动后的源码。
|
||||||
|
|
||||||
插件与本项目通过公开 [homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) 静态链接
|
插件与本项目通过公开 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 静态链接
|
||||||
(SDK 源码会进入插件二进制),但那个仓**以 MIT 发布**——MIT 是宽松许可,拿到授权的代码不继承
|
(SDK 源码会进入插件二进制),但那个仓**以 MIT 发布**——MIT 是宽松许可,拿到授权的代码不继承
|
||||||
本项目的 AGPL。因此**外部插件不是本项目的衍生作品**,作者可自行选择许可(含闭源、商业、私有),
|
本项目的 AGPL。因此**外部插件不是本项目的衍生作品**,作者可自行选择许可(含闭源、商业、私有),
|
||||||
既不必同许可、也不受 §13 网络条款约束。第三方插件生态的安全与活跃正建立在这条之上。
|
既不必同许可、也不受 §13 网络条款约束。第三方插件生态的安全与活跃正建立在这条之上。
|
||||||
|
|||||||
@ -221,7 +221,7 @@ internal/
|
|||||||
├── config/ SQLite config center
|
├── config/ SQLite config center
|
||||||
├── events/ Event bus
|
├── events/ Event bus
|
||||||
└── internal/lua/adapters/ 10 LLM protocol adapter scripts
|
└── internal/lua/adapters/ 10 LLM protocol adapter scripts
|
||||||
External plugin development: see [homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) repo, use `hmapdev` toolchain, refer to Go and Lua examples in `example/`
|
External plugin development: see [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) repo, use `hmapdev` toolchain, refer to Go and Lua examples in `example/`
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
@ -323,7 +323,7 @@ External plugin development: see [homeagentsdk](https://github.com/JianFeeeee/ho
|
|||||||
**v1.1.1** — Multimodal reaches the **plugin boundary**. v1.1.0 gave the memory system binary
|
**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
|
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
|
plugins and the model. The public SDK gains media fields and three media injection methods
|
||||||
(paired with [SDK v1.1.0](https://github.com/JianFeeeee/homeagentsdk/tree/v1.1.0),
|
(paired with [SDK v1.1.0](https://github.com/JianFeeeee/hologramagentsdk/tree/v1.1.0),
|
||||||
shared by the whole 1.1.x line), and the kernel implements the four matching RPCs. The bridge
|
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
|
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
|
plugin were discarded, `Doc` kept only three fields, and `Remove` never released references
|
||||||
@ -346,7 +346,7 @@ 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.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 [homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) 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 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) repo. Added input channel `NoMemory`/`Cleaner`, `ChannelDef`, plugin disable/enable system (CLI + WebUI), `plugindev` toolchain C ABI `ChannelDef` support.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@ -408,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.
|
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
|
Plugins are **statically linked** against this project through the public
|
||||||
[homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) (the SDK source ends up inside the
|
[hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) (the SDK source ends up inside the
|
||||||
plugin binary), but that repository is released under **MIT** — a permissive license, so code
|
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
|
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
|
derivative works of this project**: authors pick their own license (closed-source, commercial or
|
||||||
|
|||||||
@ -111,5 +111,5 @@ Core functionality is operational. Plugin system and SDK are ready for independe
|
|||||||
|
|
||||||
- Built-in plugins (13): webui / cli / agentcli / cfgmgr / healthcheck / kbtree / multimodal / pluginmgr / clawhubadapter / remotedevice / skillmgr / seemedia / seq
|
- Built-in plugins (13): webui / cli / agentcli / cfgmgr / healthcheck / kbtree / multimodal / pluginmgr / clawhubadapter / remotedevice / skillmgr / seemedia / seq
|
||||||
- Criterion: **a plugin stays built-in only if it requires internal-SDK-only capabilities**; everything else ships as an .hmap external plugin (see ARCHITECTURE)
|
- Criterion: **a plugin stays built-in only if it requires internal-SDK-only capabilities**; everything else ships as an .hmap external plugin (see ARCHITECTURE)
|
||||||
- External plugin examples ([homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) repo `example/`, both Go and Lua types): qq / files / a2a / ai_image / bili / browser / calendar / editdoc / memo / music / ocr / rss / sanitizer / weather / luademo
|
- External plugin examples ([hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) repo `example/`, both Go and Lua types): qq / files / a2a / ai_image / bili / browser / calendar / editdoc / memo / music / ocr / rss / sanitizer / weather / luademo
|
||||||
- Distribution: `.hmap` plugin package format, installable via WebUI
|
- Distribution: `.hmap` plugin package format, installable via WebUI
|
||||||
|
|||||||
@ -8,13 +8,20 @@
|
|||||||
|
|
||||||
All external interaction capabilities of HologramAgent comes from plugins. Plugins interact with the kernel through `PluginSDK` (Go API).
|
All external interaction capabilities of HologramAgent comes from plugins. Plugins interact with the kernel through `PluginSDK` (Go API).
|
||||||
|
|
||||||
**SDK Repository**: Plugin development tools, template code, and example plugins are hosted in the [homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) repository.
|
**SDK Repository**: Plugin development tools, template code, and example plugins are hosted in the [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) repository.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/JianFeeeee/homeagentsdk.git
|
git clone https://github.com/JianFeeeee/hologramagentsdk.git
|
||||||
cd homeagentsdk
|
cd hologramagentsdk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **The repo name and the module path are two different things.** The repository
|
||||||
|
> was renamed to `hologramagentsdk` in Oct 2026, but the `go.mod` of every
|
||||||
|
> **published tag** (v1.3.0 and earlier) still declares
|
||||||
|
> `module github.com/JianFeeeee/homeagentsdk`, so `import` / `go.mod` keep using
|
||||||
|
> the old module path (as in the examples below). Once the SDK ships a tag with
|
||||||
|
> the new module name, the core repo and new plugins can migrate together.
|
||||||
|
|
||||||
Each plugin implements a three-method interface:
|
Each plugin implements a three-method interface:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -48,7 +55,7 @@ plugin types, and producing `.hmap` plugin bundles (the tool is named after that
|
|||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd homeagentsdk/tools/hmapdev
|
cd hologramagentsdk/tools/hmapdev
|
||||||
go build -o hmapdev
|
go build -o hmapdev
|
||||||
# Add hmapdev to PATH or use directly
|
# Add hmapdev to PATH or use directly
|
||||||
# Prebuilt binaries also ship as release assets (hmapdev_linux_amd64, ...)
|
# Prebuilt binaries also ship as release assets (hmapdev_linux_amd64, ...)
|
||||||
@ -844,31 +851,31 @@ Internal: records are stored in SQLite `disabled_plugins` table (`name`, `disabl
|
|||||||
|
|
||||||
## 7. Example Plugin Reference
|
## 7. Example Plugin Reference
|
||||||
|
|
||||||
### SDK Repository Examples (`homeagentsdk/example/`)
|
### SDK Repository Examples (`hologramagentsdk/example/`)
|
||||||
|
|
||||||
| Example | Type | Features |
|
| Example | Type | Features |
|
||||||
|---------|------|----------|
|
|---------|------|----------|
|
||||||
| [weather](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/weather) | Go | Weather queries (wttr.in); demonstrates NoMemory/Cleaner/stage hooks/channels/text memory |
|
| [weather](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/weather) | Go | Weather queries (wttr.in); demonstrates NoMemory/Cleaner/stage hooks/channels/text memory |
|
||||||
| [luademo](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/luademo) | Lua | Full-featured Lua example covering the whole v0.8.0 Lua SDK surface |
|
| [luademo](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/luademo) | Lua | Full-featured Lua example covering the whole v0.8.0 Lua SDK surface |
|
||||||
| [qq](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/qq) | Go | NapCat OneBot integration, 24 tools, full input/output channel wiring |
|
| [qq](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/qq) | Go | NapCat OneBot integration, 24 tools, full input/output channel wiring |
|
||||||
| [memo](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/memo) | Go | Memo management, PreAction injection + timed interrupt dual reminder |
|
| [memo](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/memo) | Go | Memo management, PreAction injection + timed interrupt dual reminder |
|
||||||
| [files](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/files) | Go | File system operations, 4 write modes, sandbox isolation |
|
| [files](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/files) | Go | File system operations, 4 write modes, sandbox isolation |
|
||||||
| [browser](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/browser) | Go | Web search + HTTP fetch (SSRF) + Chromium render (merged from web/webfetch) |
|
| [browser](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/browser) | Go | Web search + HTTP fetch (SSRF) + Chromium render (merged from web/webfetch) |
|
||||||
| [bili](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/bili) | Go | Bilibili video download (yt-dlp) |
|
| [bili](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/bili) | Go | Bilibili video download (yt-dlp) |
|
||||||
| [editdoc](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/editdoc) | Go | Office document editing and format conversion |
|
| [editdoc](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/editdoc) | Go | Office document editing and format conversion |
|
||||||
| [a2a](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/a2a) | Go | Agent-to-Agent protocol |
|
| [a2a](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/a2a) | Go | Agent-to-Agent protocol |
|
||||||
| [ocr](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/ocr) | Go | Offline text recognition (Tesseract) |
|
| [ocr](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/ocr) | Go | Offline text recognition (Tesseract) |
|
||||||
| [sanitizer](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/sanitizer) | Go | Output sanitizer filter |
|
| [sanitizer](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/sanitizer) | Go | Output sanitizer filter |
|
||||||
| [calendar](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/calendar) | Go | Calendar management |
|
| [calendar](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/calendar) | Go | Calendar management |
|
||||||
| [rss](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/rss) | Go | RSS subscriptions |
|
| [rss](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/rss) | Go | RSS subscriptions |
|
||||||
| [ai_image](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/ai_image) | Go | AI image generation |
|
| [ai_image](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/ai_image) | Go | AI image generation |
|
||||||
| [music](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/music) | Go | Music playback |
|
| [music](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/music) | Go | Music playback |
|
||||||
| [vikunja](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/vikunja) | Go | Vikunja task management (projects/tasks/labels CRUD) |
|
| [vikunja](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/vikunja) | Go | Vikunja task management (projects/tasks/labels CRUD) |
|
||||||
| [vanblog](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/vanblog) | Go | VanBlog publishing and management |
|
| [vanblog](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/vanblog) | Go | VanBlog publishing and management |
|
||||||
| [deepsearch](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/deepsearch) | Go | Multi-round deep search (progressive focus + cited summary) |
|
| [deepsearch](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/deepsearch) | Go | Multi-round deep search (progressive focus + cited summary) |
|
||||||
| [acp](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/acp) | Go | Agent Client Protocol (external editors/IDEs drive this agent) |
|
| [acp](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/acp) | Go | Agent Client Protocol (external editors/IDEs drive this agent) |
|
||||||
| [recoverydiag](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/recoverydiag) | Go | Five-part fault diagnosis (triage / sqlite check / log signatures / diff / ranked conclusions); core plugin of failback mode |
|
| [recoverydiag](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/recoverydiag) | Go | Five-part fault diagnosis (triage / sqlite check / log signatures / diff / ranked conclusions); core plugin of failback mode |
|
||||||
| [plugindev](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/plugindev) | Go | Plugin scaffolding: generate, build, install — lets the agent develop plugins itself |
|
| [plugindev](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/plugindev) | Go | Plugin scaffolding: generate, build, install — lets the agent develop plugins itself |
|
||||||
|
|
||||||
### Built-in Plugins
|
### Built-in Plugins
|
||||||
|
|
||||||
@ -903,4 +910,4 @@ Internal: records are stored in SQLite `disabled_plugins` table (`name`, `disabl
|
|||||||
|
|
||||||
*Want to understand the project goals? See [OVERVIEW.md](OVERVIEW.md).*
|
*Want to understand the project goals? See [OVERVIEW.md](OVERVIEW.md).*
|
||||||
*Want to understand the architecture? See [ARCHITECTURE.md](ARCHITECTURE.md).*
|
*Want to understand the architecture? See [ARCHITECTURE.md](ARCHITECTURE.md).*
|
||||||
*SDK repository and development tools? See [homeagentsdk](https://github.com/JianFeeeee/homeagentsdk).*
|
*SDK repository and development tools? See [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk).*
|
||||||
|
|||||||
@ -10,7 +10,17 @@ HologramAgent 的认知架构由三个核心子系统构成:事件循环(eve
|
|||||||
|
|
||||||
**阶段管道(StageHost)** 管理两类注册:工具定义(ToolDef)与阶段处理器(StageHandler)。ToolDef 包含 `NoMemory bool` 和 `Cleaner func(string) string` 两个可选的记忆控制字段:`NoMemory=true` 时工具输出不参与向量化/jieba/蒸馏计算(原文保留);`Cleaner` 在输出进入计算层前执行过滤(如提取 JSON 的 `content` 字段)。两者均不修改原文,只影响计算层输入。`RegisterTool` 拒绝同名注册,推断工具所属插件名,并维护工具到插件的映射表 `toolPlugins`。`RegisterStage` 将处理器追加至对应阶段的处理器列表。触发阶段执行时(`RunStage`),**所有已注册处理器通过 goroutine 并行执行**,共享同一 `*StageContext` 实例(通过 `sync.RWMutex` 保护并发访问)。单个处理器的 panic 被独立恢复,不影响其他处理器。短路语义通过检查 `ctx.Response != nil` 实现——任一阶段处理器可设置此值提前终止当前链路。工具执行 `ExecuteTool` 内置 panic 恢复与栈追踪记录。`UnregisterPluginTools` 在插件热重载时移除对应工具集。
|
**阶段管道(StageHost)** 管理两类注册:工具定义(ToolDef)与阶段处理器(StageHandler)。ToolDef 包含 `NoMemory bool` 和 `Cleaner func(string) string` 两个可选的记忆控制字段:`NoMemory=true` 时工具输出不参与向量化/jieba/蒸馏计算(原文保留);`Cleaner` 在输出进入计算层前执行过滤(如提取 JSON 的 `content` 字段)。两者均不修改原文,只影响计算层输入。`RegisterTool` 拒绝同名注册,推断工具所属插件名,并维护工具到插件的映射表 `toolPlugins`。`RegisterStage` 将处理器追加至对应阶段的处理器列表。触发阶段执行时(`RunStage`),**所有已注册处理器通过 goroutine 并行执行**,共享同一 `*StageContext` 实例(通过 `sync.RWMutex` 保护并发访问)。单个处理器的 panic 被独立恢复,不影响其他处理器。短路语义通过检查 `ctx.Response != nil` 实现——任一阶段处理器可设置此值提前终止当前链路。工具执行 `ExecuteTool` 内置 panic 恢复与栈追踪记录。`UnregisterPluginTools` 在插件热重载时移除对应工具集。
|
||||||
|
|
||||||
**上下文窗口(RelevanceContext)** 维护一个按时间排序的事件列表。`Append` 在录入前经 `textForVector` 聚合工具输出:按 `NoMemory` 跳过、`Cleaner` 过滤(插件为各自工具注册的清洗函数,如 QQ 外置插件剥离工具调用模板),最后经 `CleanText` 做基本空白规范化,再通过三分支向量策略(agent 事件用 Response,用户事件用 Input,cold_storage 用 Input+Response)计算嵌入向量。`Prune` 在事件数超过 `topK` 时触发,**无条件保护最近 10 条事件不被裁剪**(recency bias),对剩余候选事件计算与当前输入的 CosineSimilarity,按评分降序保留 `topK - 10` 条(下限为 0),之后按时间戳重排序。裁剪出的事件中,过滤掉 `agentcli` 和 `terminal` 来源后,其余通过 `docStore.ContextToDoc` 归档至 Document 层,保留原始时间戳。持久化采用 5 秒防抖写入磁盘 JSON 文件。
|
**上下文窗口(RelevanceContext)** 维护一个按时间排序的事件列表。`Append` 在录入前经 `textForVector` 聚合工具输出:按 `NoMemory` 跳过、`Cleaner` 过滤(插件为各自工具注册的清洗函数),最后经 `CleanText` 做基本空白规范化,再通过三分支向量策略(agent 事件用 Response,用户事件用 Input,cold_storage 用 Input+Response)计算嵌入向量。`Prune` 在事件数超过 `topK` 时触发,**无条件保护最近 10 条事件不被裁剪**(recency bias),对剩余候选事件计算与当前输入的 CosineSimilarity,按评分降序保留 `topK - 10` 条(下限为 0),之后按时间戳重排序。裁剪出的事件通过 `docStore.ContextToDoc` **迁移**至 Document 层:事件连同它持有的块一起下沉,保留原始时间戳与完整正文。持久化采用 5 秒防抖写入磁盘 JSON 文件。
|
||||||
|
|
||||||
|
> ★ **哪些事件不该进 L2,由插件自己声明,内核不认插件名**。
|
||||||
|
>
|
||||||
|
> 旧版这里写的是「过滤掉 `agentcli` 和 `terminal` 来源后归档」——那是在内核里
|
||||||
|
> 硬编码插件行为,已废除。正确姿势是插件在注册时就声明:
|
||||||
|
> - `ToolDef.NoMemory` / `ToolDef.Cleaner`:工具输出不进计算层
|
||||||
|
> - `ChannelDef.NoMemory` / `ChannelDef.Cleaner`:该通道的输入不进计算层
|
||||||
|
> - `InjectOptions.NoMemory`:本次注入不参与记忆
|
||||||
|
>
|
||||||
|
> 内核只消费这些**声明**(已由 `textForVector` 实现),不认识任何具体插件。
|
||||||
|
|
||||||
**工具定义聚合自五个来源**:IOManager 注册的插件工具;StageHost 注册的 SDK 插件工具;Indexer 提供的记忆索引工具;内置条件工具(依据 memory / knowledge / docStore / social / pluginReg / providerManager 等模块的非空状态选择性添加,包括记忆操作、知识检索、文档查询、社交网络、插件重载、子代理生成、输出通道工具、LLM 源切换等);以及按 `pendingMedia` 状态添加的媒体处理工具。`buildToolDefs()` 在每次 process 周期中重新聚合所有这些来源。
|
**工具定义聚合自五个来源**:IOManager 注册的插件工具;StageHost 注册的 SDK 插件工具;Indexer 提供的记忆索引工具;内置条件工具(依据 memory / knowledge / docStore / social / pluginReg / providerManager 等模块的非空状态选择性添加,包括记忆操作、知识检索、文档查询、社交网络、插件重载、子代理生成、输出通道工具、LLM 源切换等);以及按 `pendingMedia` 状态添加的媒体处理工具。`buildToolDefs()` 在每次 process 周期中重新聚合所有这些来源。
|
||||||
|
|
||||||
@ -83,35 +93,64 @@ eventLoop() → processTextInput()
|
|||||||
|
|
||||||
## 三层记忆
|
## 三层记忆
|
||||||
|
|
||||||
|
### 记忆块是唯一载体,层是持有关系
|
||||||
|
|
||||||
|
三层不是三种数据结构,而是**同一批记忆块的三层持有关系**:
|
||||||
|
|
||||||
|
```
|
||||||
|
记忆块 MemoryBlock(union)
|
||||||
|
├─ 文本块:负载在 Text
|
||||||
|
└─ 媒体块:负载在 Data(配 MIME/宽高/尺寸)
|
||||||
|
↑ 同一个块 ID 在 context → doc → graph 全程不变
|
||||||
|
↑ 块自带 vector + fingerprint:向量只算一次,跨层复用
|
||||||
|
```
|
||||||
|
|
||||||
|
| 层 | 承载单元 | 与模型的关系 | 容量 | 流动性 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **L1 Context** | 事件(内联正文 + 块) | **直接投喂**(进 system prompt 时序) | 最近 n 条 / 上限 m 条 | 超限时按相似度迁出 |
|
||||||
|
| **L2 Document** | 事件(内联正文 + 块) | **不直接投喂**(需召回才进 L1) | 无上限 | 冷到阈值后蒸馏进 L3 |
|
||||||
|
| **L3 Graph** | 三元组(节点 + 关系边),节点即记忆块 | 不投喂(经召回进入 L1) | 持久 | 不流出 |
|
||||||
|
|
||||||
|
★ **L1 与 L2 结构完全同构**,唯一区别是「是否被直接投喂给模型」。
|
||||||
|
L2 不是 L1 的摘要层,也不是另一种数据形态——它就是同样的事件 + 同样的块,只是不直接进模型视野。
|
||||||
|
|
||||||
|
★ **正文(含多媒体正文)内联在块里**:文本块的 `Text` 就是正文,媒体块的 `Data` 就是字节。
|
||||||
|
不存在「块 + 外部正文」的分离形态,也不存在靠渲染字符串承载原文的路径。
|
||||||
|
事件是块的容器,不是块的替代品。
|
||||||
|
|
||||||
|
★ **任何一层都不压缩**。视图(检索摘要、给模型看的渲染)可以是压缩的,但视图不是记忆。
|
||||||
|
|
||||||
### 记忆流转
|
### 记忆流转
|
||||||
|
|
||||||
```
|
```
|
||||||
① Context (工作窗口)
|
① Context (工作窗口 — 直接投喂)
|
||||||
RelevanceContext — 内存 events[] + JSON持久化
|
RelevanceContext — 内存 events[] + JSON持久化
|
||||||
|
每条事件:Timestamp / Source / Input / Response(完整) / ToolsUsed / ToolResults / Blocks
|
||||||
Append: 每次输入, CleanText → 三分支向量(textForVector)
|
Append: 每次输入, CleanText → 三分支向量(textForVector)
|
||||||
agent事件→Response, 用户事件→Input, cold_storage→Input+Response
|
agent事件→Response, 用户事件→Input, cold_storage→Input+Response
|
||||||
向量层级:统一多模态空间(主,带 fingerprint)→ StaticEmbedder 词嵌入 → TF-IDF(回退)
|
向量层级:统一多模态空间(主,带 fingerprint)→ StaticEmbedder 词嵌入 → TF-IDF(回退)
|
||||||
Prune: DenseCosine(仅同指纹才比较)→ 退化 StaticEmbedder CosineSimilarity;保留 topK + 最近10条
|
Prune: DenseCosine(仅同指纹才比较)→ 退化 StaticEmbedder CosineSimilarity;保留 topK + 最近10条
|
||||||
├── 保留 → timeline → 按时间排序 → system prompt
|
├── 保留 → timeline → 按时间排序 → system prompt
|
||||||
└── 低分 → Document 层归档 (原始时间戳)
|
└── 低分 → Document 层归档(迁移:事件连同块一起下沉,时间戳与正文不变)
|
||||||
Save: 5s debounce 写盘
|
Save: 5s debounce 写盘
|
||||||
|
|
||||||
↓ Prune 归档 ↑ LLM 主动召回
|
↓ Prune 归档(迁移) ↑ 召回(迁回)
|
||||||
|
|
||||||
② Document (文件记忆)
|
② Document (文件记忆 — 不直接投喂,结构与 L1 同构)
|
||||||
DocStore — JSON文件 + 稠密向量(统一多模态空间;dense_fp 须与当前空间同指纹,不符即重算;兜底: StaticEmbedder / TF-IDF InvertedIndex)
|
DocStore — 每篇文档 = 检索元数据 + 事件列表(内联正文 + 块)
|
||||||
写入: Prune归档 / doc_commit / Graph快照(syncGraphToDocs)
|
写入: Prune归档 / doc_commit / Graph快照(syncGraphToDocs)
|
||||||
读取:
|
读取:
|
||||||
├── 自动注入: Query(input, top3) → 同一向量空间下相似度摘要 → 【相关记忆文档】→ system prompt (只读)
|
├── 自动预填充: Query(input, top3) → 同一向量空间下相似度摘要 → 【相关记忆文档】→ system prompt (只读,不动层)
|
||||||
└── LLM主动: doc_query → Consume(读取并删除)
|
└── LLM主动: doc_query → Consume(取走)
|
||||||
→ 逐条 context.Append{Timestamp: d.CreatedAt, Source: "cold_storage"}
|
→ 逐条按各自原始时间戳把事件插回 L1 时序
|
||||||
→ 文档以原始时间戳写入 context 时间线, 从 docStore 删除
|
→ 块随事件迁回(同一块 ID,向量不重算),L2 不再持有
|
||||||
冷化: FindColdDocs(72h, ≤2次访问) → docToTriples → Graph
|
冷化: FindColdDocs(72h, ≤2次访问) → docToTriples → Graph
|
||||||
|
|
||||||
↓ 冷文档蒸馏 ↑ 自动召回
|
↓ 冷文档蒸馏 ↑ 自动召回
|
||||||
|
|
||||||
③ Graph (图数据库)
|
③ Graph (图数据库 — 顶层仍是三元组,节点即记忆块)
|
||||||
SQLite — entities + relations 表
|
SQLite — memory_blocks(节点) + memory_block_edges(边)
|
||||||
|
三元组 = 【节点】【关系边】【节点】;putTripleBlocksTx 落为 src --edge_type--> dst
|
||||||
写入: memory_commit / 冷文档蒸馏 / Pipeline 规则蒸馏 / memory_merge
|
写入: memory_commit / 冷文档蒸馏 / Pipeline 规则蒸馏 / memory_merge
|
||||||
读取:
|
读取:
|
||||||
├── 自动召回: Indexer.BuildContext(input)
|
├── 自动召回: Indexer.BuildContext(input)
|
||||||
@ -174,21 +213,33 @@ eventLoop() → processTextInput()
|
|||||||
- 向量化前统一经 `CleanText` 去模版噪声
|
- 向量化前统一经 `CleanText` 去模版噪声
|
||||||
- 三分支 `textForVector`:agent 事件用 Response、用户事件用 Input、cold_storage 用 Input+Response
|
- 三分支 `textForVector`:agent 事件用 Response、用户事件用 Input、cold_storage 用 Input+Response
|
||||||
- 预训练词嵌入 `StaticEmbedder` → CosineSimilarity,模型不可用时自动回退 TF-IDF
|
- 预训练词嵌入 `StaticEmbedder` → CosineSimilarity,模型不可用时自动回退 TF-IDF
|
||||||
- 保护最近 10 条记录免于淘汰,超出部分按相关性排序归档到文档记忆
|
- 保护最近 10 条记录免于淘汰,超出部分按相关性排序**迁移**到文档记忆
|
||||||
- 归档事件以原始时间戳写入文档记忆,后续 `doc_query` 召回时按原始时间戳插回时序
|
- 迁移是事件**连同它持有的块**一起下沉,时间戳与正文不变;源事件清空块(同一块不同时留两层)
|
||||||
|
|
||||||
### Document 层
|
### Document 层
|
||||||
|
|
||||||
`internal/memory/document/document.go` — `Store`
|
`internal/memory/document/document.go` — `Store`
|
||||||
- 消费即删模式:`doc_query` 检索到后删除
|
|
||||||
- 双路召回:与 Context 共享的 StaticEmbedder 语义向量搜索 + jieba 关键词提取(模型未加载时回退 char-bigram TF-IDF)
|
**与 Context 同构,只是不直接投喂给模型**:
|
||||||
|
- 每篇文档 = 检索元数据(`ID`/`Summary`/`Vector`/`DenseVec`/`DenseFP`/`AccessCount`)
|
||||||
|
+ **事件列表**(内联正文 + 块)
|
||||||
|
- 正文内联在块里:文本块的 `Text` 是正文,媒体块的 `Data` 是字节
|
||||||
|
- `Doc.Content` 只是**渲染视图**(供摘要/人工阅读/SDK 兼容),不是原文载体
|
||||||
|
- 自动预填充:与 Context 共享的向量空间做相似度检索 → 摘要进 system prompt(**只读**,不动层)
|
||||||
|
- 主动召回:`doc_query` 取走后,**逐条按各自原始时间戳**把事件插回 L1 时序,块随事件迁回
|
||||||
|
- 冷化:`FindColdDocs(72h, ≤2次访问)` → `docToTriples` → Graph
|
||||||
|
|
||||||
### Graph 层
|
### Graph 层
|
||||||
|
|
||||||
`internal/memory/graph.go` — `GraphDB`
|
`internal/memory/graph.go` — `GraphDB`
|
||||||
- SQLite WAL 模式,两张表(驱动:mattn/go-sqlite3,CGo)
|
|
||||||
- `Commit(triples)` — UPSERT entities + INSERT relations
|
**顶层结构仍是三元组,变的是节点的物理表示**:
|
||||||
- `Recall(keywords, depth)` — 关键词 LIKE 搜索 + BFS 遍历
|
- SQLite WAL 模式(驱动:mattn/go-sqlite3,CGo)
|
||||||
|
- `memory_blocks`(节点,带 vector + fingerprint + modality)+ `memory_block_edges`(边)
|
||||||
|
- 三元组 = 【节点】【关系边】【节点】:`putTripleBlocksTx` 落为 `src --edge_type--> dst`
|
||||||
|
- `entities` / `relations` 旧表是**存量过渡路径**,逐步退场(退场的是节点存储,不是三元组概念)
|
||||||
|
- `Commit(triples)` — 写入时直接块化为节点 + 边
|
||||||
|
- `Recall(keywords, depth)` — 关键词检索 + BFS 遍历
|
||||||
|
|
||||||
### 记忆工具(LLM 可直接调用)
|
### 记忆工具(LLM 可直接调用)
|
||||||
|
|
||||||
|
|||||||
@ -24,12 +24,20 @@ CLI 需要终端面板时,**订阅事件总线**(`EventTerminalOutput` + `Ev
|
|||||||
自己攒视图,而不是去读内核里的某个状态组件。内核只提供事件总线与插件注册面——
|
自己攒视图,而不是去读内核里的某个状态组件。内核只提供事件总线与插件注册面——
|
||||||
新增一类能力不需要改内核,也不会在核心里堆出第二套业务真相。
|
新增一类能力不需要改内核,也不会在核心里堆出第二套业务真相。
|
||||||
|
|
||||||
**三层记忆架构** — 通过分级存储策略管理 Agent 长周期运行中的信息留存:
|
**三层记忆架构** — 三层不是三种数据结构,而是**同一批记忆块的三层持有关系**:
|
||||||
- **Context 层**:内存中预训练词嵌入评分的事件窗口(StaticEmbedder 词向量 → CosineSimilarity,TF-IDF 回退),实时维护最近上下文,低相关性事件自动下沉到下一层
|
- **Context 层(L1,直接投喂)**:内存中预训练词嵌入评分的事件窗口(StaticEmbedder 词向量 → CosineSimilarity,TF-IDF 回退),实时维护最近上下文,低相关性事件自动下沉到下一层
|
||||||
- **Document 层**:JSON 文件 + TF-IDF 向量索引的临时记忆,支持显式提交和隐式归档,冷数据蒸馏到 Graph
|
- **Document 层(L2,不直接投喂)**:与 L1 **结构同构**——同样的事件 + 同样的块,只是不直接进模型视野;支持显式提交和隐式归档,冷数据蒸馏到 Graph
|
||||||
- **Graph 层**:SQLite 图数据库,持久化实体(entities)和关系(relations),BFS 遍历召回,蒸馏管道从对话中提取三元组
|
- **Graph 层(L3,持久)**:SQLite 图数据库,**顶层仍是三元组**(节点 + 关系边),但节点从 `entities` 表升级为 `memory_blocks`;BFS 遍历召回,蒸馏管道从对话中提取三元组
|
||||||
|
|
||||||
三层递进:上下文 → 冷归档 → 长期图记忆,构成从短期到持久的信息衰减与整合管道。
|
★ **L1 与 L2 唯一区别是「是否被直接投喂给模型」**:L2 不是 L1 的摘要层,也不是
|
||||||
|
另一种数据形态。召回 = 把事件(连同它持有的块)从“不投喂”的持有层**迁回**“投喂”的持有层。
|
||||||
|
|
||||||
|
★ **正文(含多媒体正文)内联在块里**:文本块的 `Text` 是正文,媒体块的 `Data` 是字节。
|
||||||
|
不存在「块 + 外部正文」的分离形态。**任何一层都不压缩** —— 视图(检索摘要、渲染结果)
|
||||||
|
可以是压缩的,但视图不是记忆。
|
||||||
|
|
||||||
|
三层流动:上下文 → 冷归档 → 长期图记忆,构成从短期到持久的信息衰减与整合管道;
|
||||||
|
每一次都是**迁移**(块 ID 与向量不变,只换持有层),不是复制。
|
||||||
|
|
||||||
**媒体记忆(v1.2.0 起:一等记忆块)** — 图片/音频不是附属物,而是记忆的一等节点:
|
**媒体记忆(v1.2.0 起:一等记忆块)** — 图片/音频不是附属物,而是记忆的一等节点:
|
||||||
- **内容寻址存储(CAS)**:digest 寻址,元数据在 SQLite、blob 在磁盘(两级分桶),相同字节只存一份,
|
- **内容寻址存储(CAS)**:digest 寻址,元数据在 SQLite、blob 在磁盘(两级分桶),相同字节只存一份,
|
||||||
@ -101,5 +109,5 @@ CLI 需要终端面板时,**订阅事件总线**(`EventTerminalOutput` + `Ev
|
|||||||
|
|
||||||
- 内置插件(13 个):webui / cli / agentcli / cfgmgr / healthcheck / kbtree / multimodal / pluginmgr / clawhubadapter / remotedevice / skillmgr / seemedia / seq
|
- 内置插件(13 个):webui / cli / agentcli / cfgmgr / healthcheck / kbtree / multimodal / pluginmgr / clawhubadapter / remotedevice / skillmgr / seemedia / seq
|
||||||
- 判定标准:**只有必须使用内部 SDK 能力(公开 SDK 没有的接口面)的插件才内置**,其余一律外置为 .hmap 插件(详见 ARCHITECTURE「内置插件的判定标准」)
|
- 判定标准:**只有必须使用内部 SDK 能力(公开 SDK 没有的接口面)的插件才内置**,其余一律外置为 .hmap 插件(详见 ARCHITECTURE「内置插件的判定标准」)
|
||||||
- 外部插件示例([homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) 仓库 `example/`,含 Go 和 Lua 两种类型):qq / files / a2a / ai_image / bili / browser / calendar / editdoc / memo / music / ocr / rss / sanitizer / weather / luademo
|
- 外部插件示例([hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 仓库 `example/`,含 Go 和 Lua 两种类型):qq / files / a2a / ai_image / bili / browser / calendar / editdoc / memo / music / ocr / rss / sanitizer / weather / luademo
|
||||||
- 打包分发:`.hmap` 插件包格式,通过 WebUI 安装
|
- 打包分发:`.hmap` 插件包格式,通过 WebUI 安装
|
||||||
|
|||||||
@ -9,13 +9,19 @@
|
|||||||
HologramAgent 的所有外部交互能力都来自插件。插件通过 `PluginSDK`(Go API)与内核交互。
|
HologramAgent 的所有外部交互能力都来自插件。插件通过 `PluginSDK`(Go API)与内核交互。
|
||||||
|
|
||||||
**SDK 仓库**:插件开发工具、模板代码和示例插件统一托管在
|
**SDK 仓库**:插件开发工具、模板代码和示例插件统一托管在
|
||||||
[homeagentsdk](https://github.com/JianFeeeee/homeagentsdk) 仓库。
|
[hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk) 仓库。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/JianFeeeee/homeagentsdk.git
|
git clone https://github.com/JianFeeeee/hologramagentsdk.git
|
||||||
cd homeagentsdk
|
cd hologramagentsdk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **仓名与 module 路径不是一回事**:仓 2026-10 已改名为 `hologramagentsdk`,
|
||||||
|
> 但**已发布 tag(v1.3.0 及更早)的 go.mod 里 module 仍是**
|
||||||
|
> `github.com/JianFeeeee/homeagentsdk`,所以 `import` 与 `go.mod` 里继续写旧
|
||||||
|
> module 路径(下面示例即如此)。等 SDK 发新 tag 把 module 改成新名后,
|
||||||
|
> 主仓与新插件再一起改 import。
|
||||||
|
|
||||||
每个插件实现一个三方法接口:
|
每个插件实现一个三方法接口:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@ -49,7 +55,7 @@ type Plugin interface {
|
|||||||
### 安装
|
### 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd homeagentsdk/tools/hmapdev
|
cd hologramagentsdk/tools/hmapdev
|
||||||
go build -o hmapdev
|
go build -o hmapdev
|
||||||
# 将 hmapdev 加入 PATH 或直接使用
|
# 将 hmapdev 加入 PATH 或直接使用
|
||||||
# 也可从 SDK 的 release 附件下载预编译二进制(hmapdev_linux_amd64 等)
|
# 也可从 SDK 的 release 附件下载预编译二进制(hmapdev_linux_amd64 等)
|
||||||
@ -830,31 +836,31 @@ pmgr.ReloadPlugins() // 重载所有插件
|
|||||||
|
|
||||||
## 七、示例插件参考
|
## 七、示例插件参考
|
||||||
|
|
||||||
### SDK 仓库示例(`homeagentsdk/example/`)
|
### SDK 仓库示例(`hologramagentsdk/example/`)
|
||||||
|
|
||||||
| 示例 | 类型 | 特点 |
|
| 示例 | 类型 | 特点 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| [weather](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/weather) | Go | 天气查询(wttr.in),演示 NoMemory/Cleaner/阶段钩子/通道/文本记忆 |
|
| [weather](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/weather) | Go | 天气查询(wttr.in),演示 NoMemory/Cleaner/阶段钩子/通道/文本记忆 |
|
||||||
| [luademo](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/luademo) | Lua | Lua 全功能示例,覆盖 v0.8.0 Lua SDK 全部 API 面 |
|
| [luademo](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/luademo) | Lua | Lua 全功能示例,覆盖 v0.8.0 Lua SDK 全部 API 面 |
|
||||||
| [qq](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/qq) | Go | NapCat OneBot 对接,24 个工具,输入/输出通道完整对接 |
|
| [qq](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/qq) | Go | NapCat OneBot 对接,24 个工具,输入/输出通道完整对接 |
|
||||||
| [memo](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/memo) | Go | 备忘管理,PreAction 注入 + 定时打断双提醒 |
|
| [memo](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/memo) | Go | 备忘管理,PreAction 注入 + 定时打断双提醒 |
|
||||||
| [files](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/files) | Go | 文件系统操作,4 种写入模式,沙箱隔离 |
|
| [files](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/files) | Go | 文件系统操作,4 种写入模式,沙箱隔离 |
|
||||||
| [browser](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/browser) | Go | 网络搜索、网页抓取(SSRF)、浏览器渲染(合并自 web/webfetch) |
|
| [browser](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/browser) | Go | 网络搜索、网页抓取(SSRF)、浏览器渲染(合并自 web/webfetch) |
|
||||||
| [bili](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/bili) | Go | B 站视频下载(yt-dlp) |
|
| [bili](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/bili) | Go | B 站视频下载(yt-dlp) |
|
||||||
| [editdoc](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/editdoc) | Go | Office 文档编辑与格式转换 |
|
| [editdoc](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/editdoc) | Go | Office 文档编辑与格式转换 |
|
||||||
| [a2a](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/a2a) | Go | Agent-to-Agent 协议 |
|
| [a2a](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/a2a) | Go | Agent-to-Agent 协议 |
|
||||||
| [ocr](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/ocr) | Go | 离线文字识别(Tesseract) |
|
| [ocr](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/ocr) | Go | 离线文字识别(Tesseract) |
|
||||||
| [sanitizer](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/sanitizer) | Go | 输出清洗过滤器 |
|
| [sanitizer](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/sanitizer) | Go | 输出清洗过滤器 |
|
||||||
| [calendar](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/calendar) | Go | 日历管理 |
|
| [calendar](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/calendar) | Go | 日历管理 |
|
||||||
| [rss](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/rss) | Go | RSS 订阅 |
|
| [rss](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/rss) | Go | RSS 订阅 |
|
||||||
| [ai_image](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/ai_image) | Go | AI 图片生成 |
|
| [ai_image](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/ai_image) | Go | AI 图片生成 |
|
||||||
| [music](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/music) | Go | 音乐播放 |
|
| [music](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/music) | Go | 音乐播放 |
|
||||||
| [vikunja](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/vikunja) | Go | Vikunja 任务管理对接(项目/任务/标签 CRUD) |
|
| [vikunja](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/vikunja) | Go | Vikunja 任务管理对接(项目/任务/标签 CRUD) |
|
||||||
| [vanblog](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/vanblog) | Go | VanBlog 博客发布与管理 |
|
| [vanblog](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/vanblog) | Go | VanBlog 博客发布与管理 |
|
||||||
| [deepsearch](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/deepsearch) | Go | 多轮深度检索(逐层聚焦 + 引用汇总) |
|
| [deepsearch](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/deepsearch) | Go | 多轮深度检索(逐层聚焦 + 引用汇总) |
|
||||||
| [acp](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/acp) | Go | Agent Client Protocol 对接(外部编辑器/IDE 驱动本 agent) |
|
| [acp](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/acp) | Go | Agent Client Protocol 对接(外部编辑器/IDE 驱动本 agent) |
|
||||||
| [recoverydiag](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/recoverydiag) | Go | 故障诊断五件套(分诊/sqlite 校验/日志签名/diff/结论排序),failback 模式的核心插件 |
|
| [recoverydiag](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/recoverydiag) | Go | 故障诊断五件套(分诊/sqlite 校验/日志签名/diff/结论排序),failback 模式的核心插件 |
|
||||||
| [plugindev](https://github.com/JianFeeeee/homeagentsdk/tree/main/example/plugindev) | Go | 插件脚手架:生成工程、构建、安装,供 agent 自助开发插件 |
|
| [plugindev](https://github.com/JianFeeeee/hologramagentsdk/tree/main/example/plugindev) | Go | 插件脚手架:生成工程、构建、安装,供 agent 自助开发插件 |
|
||||||
|
|
||||||
### 内置插件
|
### 内置插件
|
||||||
|
|
||||||
@ -884,4 +890,4 @@ pmgr.ReloadPlugins() // 重载所有插件
|
|||||||
|
|
||||||
*了解项目整体目标?查看 [OVERVIEW.md](OVERVIEW.md)。*
|
*了解项目整体目标?查看 [OVERVIEW.md](OVERVIEW.md)。*
|
||||||
*了解技术架构?查看 [ARCHITECTURE.md](ARCHITECTURE.md)。*
|
*了解技术架构?查看 [ARCHITECTURE.md](ARCHITECTURE.md)。*
|
||||||
*SDK 仓库与开发工具?查看 [homeagentsdk](https://github.com/JianFeeeee/homeagentsdk)。*
|
*SDK 仓库与开发工具?查看 [hologramagentsdk](https://github.com/JianFeeeee/hologramagentsdk)。*
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
HomeAgent 是完全独立自研的新一代 Agent 框架。
|
HologramAgent 是完全独立自研的新一代 Agent 框架。
|
||||||
|
|
||||||
## 架构总览
|
## 架构总览
|
||||||
|
|
||||||
HomeAgent 采用内核 + 插件双层架构:
|
HologramAgent 采用内核 + 插件双层架构:
|
||||||
|
|
||||||
```
|
```
|
||||||
外部输入(QQ/Web/CLI)
|
外部输入(QQ/Web/CLI)
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
name: commondev-discipline
|
name: commondev-discipline
|
||||||
description: commondev 主管式多仓库开发流水线的使用与开发纪律。当你要为某个工程开一条分支、把工程派给一个下属 agent、或用 pi 拉起执行 agent 干活时必读。核心动作:为每个工程对话新建驻留式子 agent 并把该工程的一对通道划给它(否则所有工程都挤在根 agent 上)。**git 托管平台的操作(Gitea/GitHub/GitLab)不在插件里,而在本文** —— 用 enc_shell + 保存的凭据调 curl/CLI 完成。也含 hmap 插件开发与部署纪律。
|
description: commondev 主管式多仓库开发流水线的使用与开发纪律。当你要为某个工程开一条分支、把工程派给一个下属 agent、或用 pi 拉起执行 agent 干活时必读。核心动作:为每个工程对话新建驻留式子 agent 并把该工程的一对通道划给它(否则所有工程都挤在根 agent 上)。**git 托管平台的操作(Gitea/GitHub/GitLab)不在插件里,而在本文** —— 用 enc_shell + 保存的凭据调 curl/CLI 完成。也含 hmap 插件开发与部署纪律。
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
author: HomeAgent
|
author: HologramAgent
|
||||||
---
|
---
|
||||||
|
|
||||||
# commondev 纪律(主管模式 · 工程对话 · 平台操作)
|
# commondev 纪律(主管模式 · 工程对话 · 平台操作)
|
||||||
@ -176,7 +176,7 @@ enc_shell(command="curl -s -X POST -H \"Authorization: token @{t}\" -H 'Content-
|
|||||||
| 角色 | 是谁 | 干什么 |
|
| 角色 | 是谁 | 干什么 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **人** | 老板 | 只与主管对接 |
|
| **人** | 老板 | 只与主管对接 |
|
||||||
| **主管** | 你(长期驻留的 HomeAgent 主 agent) | 拆任务、**定义判据**、复审、决定合入 |
|
| **主管** | 你(长期驻留的 HologramAgent 主 agent) | 拆任务、**定义判据**、复审、决定合入 |
|
||||||
| **执行 agent** | 按工程拉起的一个 pi / dsh 实例 | 在某棵工作树里干活 |
|
| **执行 agent** | 按工程拉起的一个 pi / dsh 实例 | 在某棵工作树里干活 |
|
||||||
| **commondev 插件** | 外部独立插件 | 仓库/凭据/分支/PR/测试,全部机械化操作 |
|
| **commondev 插件** | 外部独立插件 | 仓库/凭据/分支/PR/测试,全部机械化操作 |
|
||||||
|
|
||||||
|
|||||||
@ -2,5 +2,5 @@
|
|||||||
"name": "commondev-discipline",
|
"name": "commondev-discipline",
|
||||||
"description": "commondev 主管式多仓库开发流水线的使用与开发纪律:为每个工程对话新建驻留子并划入其通道、投递必须落同一 session、复核必须含变异阶段;以及 hmap 插件开发纪律(plg.json、SDK 版本、产物形态、Serial/ParallelSafe、外部插件能力边界、凭据与孤儿清理)",
|
"description": "commondev 主管式多仓库开发流水线的使用与开发纪律:为每个工程对话新建驻留子并划入其通道、投递必须落同一 session、复核必须含变异阶段;以及 hmap 插件开发纪律(plg.json、SDK 版本、产物形态、Serial/ParallelSafe、外部插件能力边界、凭据与孤儿清理)",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "HomeAgent"
|
"author": "HologramAgent"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
---
|
---
|
||||||
name: knowledge-base
|
name: knowledge-base
|
||||||
description: 按分类树检索 HomeAgent 知识库。当需要查阅项目知识、架构约定、历史决策,或用户提到"知识库/knowledge/知识库有哪些/这个项目的约定是什么"时使用。支持树形导航、分类内检索、以图搜知识。
|
description: 按分类树检索 HologramAgent 知识库。当需要查阅项目知识、架构约定、历史决策,或用户提到"知识库/knowledge/知识库有哪些/这个项目的约定是什么"时使用。支持树形导航、分类内检索、以图搜知识。
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
author: HomeAgent
|
author: HologramAgent
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
先看树定位分类,再做定向检索。接口由 HomeAgent 的 `kbtree` 插件提供,
|
先看树定位分类,再做定向检索。接口由 HologramAgent 的 `kbtree` 插件提供,
|
||||||
只读、需 token。详见下方"接入"。
|
只读、需 token。详见下方"接入"。
|
||||||
|
|
||||||
HomeAgent 知识库按**分类树**组织(`tech/go/并发`、`life/sleep` …)。
|
HologramAgent 知识库按**分类树**组织(`tech/go/并发`、`life/sleep` …)。
|
||||||
|
|
||||||
### 何时用
|
### 何时用
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ curl -s -H "$AUTH" "$BASE/search?q=goroutine&limit=5"
|
|||||||
若知识条目挂了图片,它在**多模态统一空间**里有向量,能被图片本身检索到。
|
若知识条目挂了图片,它在**多模态统一空间**里有向量,能被图片本身检索到。
|
||||||
前提是宿主已接入多模态向量 provider(否则只是记录了媒体,不参与召回)。
|
前提是宿主已接入多模态向量 provider(否则只是记录了媒体,不参与召回)。
|
||||||
|
|
||||||
判断是否就绪:HomeAgent 自身的知识库接口会返回稠密路状态
|
判断是否就绪:HologramAgent 自身的知识库接口会返回稠密路状态
|
||||||
(`dense.enabled` / `dense.ready` / `dense.total`)。若为未启用,
|
(`dense.enabled` / `dense.ready` / `dense.total`)。若为未启用,
|
||||||
**不要承诺"能以图搜"**。
|
**不要承诺"能以图搜"**。
|
||||||
|
|
||||||
@ -132,13 +132,13 @@ curl -s -H "$AUTH" "$BASE/search?q=goroutine&limit=5"
|
|||||||
- **分类不存在返回 404**,并在 `categories` 字段里附上现有分类 —— 用它自查
|
- **分类不存在返回 404**,并在 `categories` 字段里附上现有分类 —— 用它自查
|
||||||
拼写。
|
拼写。
|
||||||
- **`items=0` 只是不要正文**,`total_count` 仍准确,可用于判断规模。
|
- **`items=0` 只是不要正文**,`total_count` 仍准确,可用于判断规模。
|
||||||
- **本服务只读**。写方法返回 405。要写知识请用 HomeAgent 主 agent 的
|
- **本服务只读**。写方法返回 405。要写知识请用 HologramAgent 主 agent 的
|
||||||
`knowledge_create`(或 WebUI 界面),不要试图绕过它直接写这个接口。
|
`knowledge_create`(或 WebUI 界面),不要试图绕过它直接写这个接口。
|
||||||
- 知识名里不能有 `..`、空格(会被规范成 `_`)、点开头的段。
|
- 知识名里不能有 `..`、空格(会被规范成 `_`)、点开头的段。
|
||||||
|
|
||||||
### 写入
|
### 写入
|
||||||
|
|
||||||
本服务不提供写入。若你在 HomeAgent 主 agent 内部,写入用内核工具:
|
本服务不提供写入。若你在 HologramAgent 主 agent 内部,写入用内核工具:
|
||||||
|
|
||||||
```
|
```
|
||||||
knowledge_create name="tech/go/调度" content="正文..."
|
knowledge_create name="tech/go/调度" content="正文..."
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# kbtree 客户端:按分类树检索 HomeAgent 知识库。
|
# kbtree 客户端:按分类树检索 HologramAgent 知识库。
|
||||||
#
|
#
|
||||||
# 由 knowledge-base skill 调用(agent 读 SKILL.md 后按需调本脚本)。
|
# 由 knowledge-base skill 调用(agent 读 SKILL.md 后按需调本脚本)。
|
||||||
# 也可人工使用:
|
# 也可人工使用:
|
||||||
@ -88,7 +88,7 @@ if ! (exec 3<>"/dev/tcp/${HOSTPORT%%:*}/${HOSTPORT##*:}") 2>/dev/null; then
|
|||||||
cat >&2 <<EOF
|
cat >&2 <<EOF
|
||||||
kbtree: 连不上 $BASE(端口未监听)。
|
kbtree: 连不上 $BASE(端口未监听)。
|
||||||
|
|
||||||
最常见原因——**服务还没上线**。kbtree 是 HomeAgent 的内置插件,
|
最常见原因——**服务还没上线**。kbtree 是 HologramAgent 的内置插件,
|
||||||
需要二进制里含它才会启动。若 homed 是在合入该插件之前构建/部署的,
|
需要二进制里含它才会启动。若 homed 是在合入该插件之前构建/部署的,
|
||||||
就一直没有这个服务。上线后验证:
|
就一直没有这个服务。上线后验证:
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "knowledge-base",
|
"name": "knowledge-base",
|
||||||
"description": "按分类树检索 HomeAgent 知识库:树形导航、分类内检索、以图搜知识",
|
"description": "按分类树检索 HologramAgent 知识库:树形导航、分类内检索、以图搜知识",
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"author": "HomeAgent"
|
"author": "HologramAgent"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# HomeAgent GUI 设计说明与外部借鉴
|
# HologramAgent GUI 设计说明与外部借鉴
|
||||||
|
|
||||||
本文件记录 GUI(`cmd/gui`)在设计过程中**明确借鉴的外部实现**,以及本仓自行做出的设计决策。
|
本文件记录 GUI(`cmd/gui`)在设计过程中**明确借鉴的外部实现**,以及本仓自行做出的设计决策。
|
||||||
|
|
||||||
@ -12,11 +12,11 @@
|
|||||||
### PiDeck(`C:\Users\21989\AppData\Local\Programs\PiDeck`)
|
### PiDeck(`C:\Users\21989\AppData\Local\Programs\PiDeck`)
|
||||||
|
|
||||||
**PiDeck 是本机安装的一个第三方 Electron 应用**(其前端依赖
|
**PiDeck 是本机安装的一个第三方 Electron 应用**(其前端依赖
|
||||||
`@deepseek-ai/dsh-web-frontend`)。HomeAgent GUI 在交互设计上参考了它的以下
|
`@deepseek-ai/dsh-web-frontend`)。HologramAgent GUI 在交互设计上参考了它的以下
|
||||||
做法。参考方式为**阅读其打包产物 `app.asar` 中的 CSS 实现**,理解其设计意图后
|
做法。参考方式为**阅读其打包产物 `app.asar` 中的 CSS 实现**,理解其设计意图后
|
||||||
在 HomeAgent 的样式体系中独立重写 —— 未复制其代码,也未使用其资源文件。
|
在 HologramAgent 的样式体系中独立重写 —— 未复制其代码,也未使用其资源文件。
|
||||||
|
|
||||||
| 借鉴项 | PiDeck 的实现 | HomeAgent 的落地 | 动机 |
|
| 借鉴项 | PiDeck 的实现 | HologramAgent 的落地 | 动机 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| **加载 / 思考动画** | `@keyframes _dsh-state-dot-chase`(三点透明度递减 `1 → .6 → .35 → .15`)与 `tool-activity-dot`(上浮 + 缩放) | `renderer/style.css` 的 `haDots` / `haDotFloat` | 旧实现是 `border + rotate` 的经典 spinner。旋转语义上暗示"在加载某个确定的东西",而 agent 思考本身没有进度可转;且在 15px 的消息气泡里转圈糊成一团 |
|
| **加载 / 思考动画** | `@keyframes _dsh-state-dot-chase`(三点透明度递减 `1 → .6 → .35 → .15`)与 `tool-activity-dot`(上浮 + 缩放) | `renderer/style.css` 的 `haDots` / `haDotFloat` | 旧实现是 `border + rotate` 的经典 spinner。旋转语义上暗示"在加载某个确定的东西",而 agent 思考本身没有进度可转;且在 15px 的消息气泡里转圈糊成一团 |
|
||||||
| **运行态呼吸光晕** | `.turn-row--running:before` —— 一层几乎看不见的 accent 底色(`color-mix(... 3%)`)以 `2.2s` 周期做 `opacity: 0 → 1 → 0` 呼吸 | `.msg-bubble.msg-running::before` | 单看气泡里的几个点,视线要缩到一小块;光晕铺在整条消息上,余光就能感知"agent 正在回" |
|
| **运行态呼吸光晕** | `.turn-row--running:before` —— 一层几乎看不见的 accent 底色(`color-mix(... 3%)`)以 `2.2s` 周期做 `opacity: 0 → 1 → 0` 呼吸 | `.msg-bubble.msg-running::before` | 单看气泡里的几个点,视线要缩到一小块;光晕铺在整条消息上,余光就能感知"agent 正在回" |
|
||||||
@ -24,20 +24,20 @@
|
|||||||
| **组合键处理** | ——(PiDeck 为 Web 应用,不涉及系统级输入注入) | —— | —— |
|
| **组合键处理** | ——(PiDeck 为 Web 应用,不涉及系统级输入注入) | —— | —— |
|
||||||
|
|
||||||
**关于分组卡的规则差异**:PiDeck 的分组策略我们理解为"折叠成组",
|
**关于分组卡的规则差异**:PiDeck 的分组策略我们理解为"折叠成组",
|
||||||
HomeAgent 额外加了一条规则 —— **只有 1 个工具时保持单卡**(组卡没意义,
|
HologramAgent 额外加了一条规则 —— **只有 1 个工具时保持单卡**(组卡没意义,
|
||||||
反而多一层点击)。这是本地决策,不是 PiDeck 的做法。
|
反而多一层点击)。这是本地决策,不是 PiDeck 的做法。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 本仓自行做出的设计(未借鉴)
|
## 本仓自行做出的设计(未借鉴)
|
||||||
|
|
||||||
以下几项与 PiDeck 无关,是为 HomeAgent 自身场景做的决定:
|
以下几项与 PiDeck 无关,是为 HologramAgent 自身场景做的决定:
|
||||||
|
|
||||||
- **SSE 取代 EventSource**:需要自定义 `X-API-Key` 与 `Last-Event-ID` 请求头,
|
- **SSE 取代 EventSource**:需要自定义 `X-API-Key` 与 `Last-Event-ID` 请求头,
|
||||||
浏览器原生 `EventSource` 不支持。
|
浏览器原生 `EventSource` 不支持。
|
||||||
- **增量聊天渲染**(`applyIncrementalChatRender`):按 `data-msgkey` 复用 DOM
|
- **增量聊天渲染**(`applyIncrementalChatRender`):按 `data-msgkey` 复用 DOM
|
||||||
节点。性能问题由本仓实测定位(旧实现 200 条消息单次全量重建 235ms)。
|
节点。性能问题由本仓实测定位(旧实现 200 条消息单次全量重建 235ms)。
|
||||||
- **设备令牌注入**(`isDeviceTokenPath` / `deviceApiKey`):HomeAgent 服务端
|
- **设备令牌注入**(`isDeviceTokenPath` / `deviceApiKey`):HologramAgent 服务端
|
||||||
`/api/v1/device/` 命名空间下并存两套鉴权(`requireAPI` 认 cookie、
|
`/api/v1/device/` 命名空间下并存两套鉴权(`requireAPI` 认 cookie、
|
||||||
`requireToken` 只认 `X-API-Key`),需要客户端主动带令牌。
|
`requireToken` 只认 `X-API-Key`),需要客户端主动带令牌。
|
||||||
- **静默启动的"创建后隐藏"**:不创建窗口会导致渲染进程不启动(SSE、设备桥
|
- **静默启动的"创建后隐藏"**:不创建窗口会导致渲染进程不启动(SSE、设备桥
|
||||||
|
|||||||
@ -166,7 +166,7 @@ check(
|
|||||||
// ── 5) 页面不得再引用公网 CDN ───────────────────────────────────────
|
// ── 5) 页面不得再引用公网 CDN ───────────────────────────────────────
|
||||||
//
|
//
|
||||||
// 与服务端 starmap_vendor_test.go 同一判据(那里钉 WebUI,这里钉 GUI)。
|
// 与服务端 starmap_vendor_test.go 同一判据(那里钉 WebUI,这里钉 GUI)。
|
||||||
// HomeAgent 支持离线/内网部署,而前端有 4 个硬依赖在公网上时,
|
// HologramAgent 支持离线/内网部署,而前端有 4 个硬依赖在公网上时,
|
||||||
// 断网/出口受限环境下星图必坏、且用户无从修复。
|
// 断网/出口受限环境下星图必坏、且用户无从修复。
|
||||||
|
|
||||||
const htmlSrc = readFileSync(join(here, "renderer", "index.html"), "utf8");
|
const htmlSrc = readFileSync(join(here, "renderer", "index.html"), "utf8");
|
||||||
|
|||||||
@ -185,7 +185,7 @@ async function adoptSlSession(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 带 Electron cookie jar 的 fetch(跟随外部网关 302 → login 域 → 回跳,最多 6 跳),
|
// 带 Electron cookie jar 的 fetch(跟随外部网关 302 → login 域 → 回跳,最多 6 跳),
|
||||||
// 使 POST /api/v1/login 真正触达内层 HomeAgent 并取回 homeagent_session。
|
// 使 POST /api/v1/login 真正触达内层 HologramAgent 并取回 homeagent_session。
|
||||||
async function sessionFetch(initUrl, opts) {
|
async function sessionFetch(initUrl, opts) {
|
||||||
const { session } = require("electron");
|
const { session } = require("electron");
|
||||||
let url = initUrl;
|
let url = initUrl;
|
||||||
@ -430,7 +430,7 @@ function createWindow(show = true) {
|
|||||||
height: 860,
|
height: 860,
|
||||||
minWidth: 900,
|
minWidth: 900,
|
||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
title: "HomeAgent",
|
title: "HologramAgent",
|
||||||
frame: false,
|
frame: false,
|
||||||
show: false, // 一律先不显示,由 ready-to-show 决定
|
show: false, // 一律先不显示,由 ready-to-show 决定
|
||||||
icon: path.join(__dirname, "icon.ico"),
|
icon: path.join(__dirname, "icon.ico"),
|
||||||
@ -806,7 +806,7 @@ ipcMain.handle("webui:openLogin", (_, { url, username, password }) => {
|
|||||||
parent: mainWindow,
|
parent: mainWindow,
|
||||||
modal: true,
|
modal: true,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
title: "HomeAgent - 网关联机登录",
|
title: "HologramAgent - 网关联机登录",
|
||||||
webPreferences: { contextIsolation: true, nodeIntegration: false },
|
webPreferences: { contextIsolation: true, nodeIntegration: false },
|
||||||
});
|
});
|
||||||
const u = encodeURIComponent(username || "");
|
const u = encodeURIComponent(username || "");
|
||||||
@ -1456,7 +1456,7 @@ async function connectDeviceBridgeOnce(cfg) {
|
|||||||
op: "hello",
|
op: "hello",
|
||||||
device: {
|
device: {
|
||||||
device_id: deviceBridgeId,
|
device_id: deviceBridgeId,
|
||||||
name: "HomeAgent GUI",
|
name: "HologramAgent GUI",
|
||||||
kind: "computer",
|
kind: "computer",
|
||||||
authorized: authorized,
|
authorized: authorized,
|
||||||
caps: [
|
caps: [
|
||||||
@ -1986,8 +1986,8 @@ function executeHomeagentCmd(capability, reqId) {
|
|||||||
const h = Math.min(parseInt(area.height || 600, 10) - 40, 560);
|
const h = Math.min(parseInt(area.height || 600, 10) - 40, 560);
|
||||||
const html = /<\/?[a-z][\s\S]*>/i.test(raw)
|
const html = /<\/?[a-z][\s\S]*>/i.test(raw)
|
||||||
? raw
|
? raw
|
||||||
: "<div style='font-family:sans-serif;display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:24px;box-sizing:border-box'><h1 style='margin:0 0 16px;color:#ff7fac'>HomeAgent</h1><p style='font-size:16px;line-height:1.6;white-space:pre-wrap;word-break:break-all'>" +
|
: "<div style='font-family:sans-serif;display:flex;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:24px;box-sizing:border-box'><h1 style='margin:0 0 16px;color:#ff7fac'>HologramAgent</h1><p style='font-size:16px;line-height:1.6;white-space:pre-wrap;word-break:break-all'>" +
|
||||||
String(raw || "HomeAgent 远程屏幕提示").replace(/</g, "<") +
|
String(raw || "HologramAgent 远程屏幕提示").replace(/</g, "<") +
|
||||||
"</p></div>";
|
"</p></div>";
|
||||||
try {
|
try {
|
||||||
if (screensueWin && !screensueWin.isDestroyed()) screensueWin.destroy();
|
if (screensueWin && !screensueWin.isDestroyed()) screensueWin.destroy();
|
||||||
@ -1999,7 +1999,7 @@ function executeHomeagentCmd(capability, reqId) {
|
|||||||
alwaysOnTop: true,
|
alwaysOnTop: true,
|
||||||
frame: false,
|
frame: false,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
title: "HomeAgent · screensue",
|
title: "HologramAgent · screensue",
|
||||||
webPreferences: { nodeIntegration: false, contextIsolation: true },
|
webPreferences: { nodeIntegration: false, contextIsolation: true },
|
||||||
});
|
});
|
||||||
screensueWin.setAlwaysOnTop(true, "screen-saver");
|
screensueWin.setAlwaysOnTop(true, "screen-saver");
|
||||||
@ -3223,7 +3223,7 @@ async function setDeviceAuthorized(authorized) {
|
|||||||
op: "hello",
|
op: "hello",
|
||||||
device: {
|
device: {
|
||||||
device_id: deviceBridgeId,
|
device_id: deviceBridgeId,
|
||||||
name: "HomeAgent GUI",
|
name: "HologramAgent GUI",
|
||||||
kind: "computer",
|
kind: "computer",
|
||||||
authorized: !!authorized,
|
authorized: !!authorized,
|
||||||
caps: [
|
caps: [
|
||||||
@ -3291,7 +3291,7 @@ function buildTrayMenuTemplate() {
|
|||||||
const electron = require("electron");
|
const electron = require("electron");
|
||||||
const TMenu = electron.Menu;
|
const TMenu = electron.Menu;
|
||||||
const tpl = [];
|
const tpl = [];
|
||||||
tpl.push({ label: "HomeAgent", enabled: true });
|
tpl.push({ label: "HologramAgent", enabled: true });
|
||||||
tpl.push({ type: "separator" });
|
tpl.push({ type: "separator" });
|
||||||
// 远程连接状态(缓存避免每次读文件)
|
// 远程连接状态(缓存避免每次读文件)
|
||||||
const connLabel = trayConnCache.connLabel || "未连接";
|
const connLabel = trayConnCache.connLabel || "未连接";
|
||||||
@ -3404,7 +3404,7 @@ function initTray() {
|
|||||||
img = nImg.createEmpty();
|
img = nImg.createEmpty();
|
||||||
}
|
}
|
||||||
tray = new Tray(img);
|
tray = new Tray(img);
|
||||||
tray.setToolTip("HomeAgent - 个人智能管家");
|
tray.setToolTip("HologramAgent - 个人智能管家");
|
||||||
rebuildTrayMenu();
|
rebuildTrayMenu();
|
||||||
tray.on("double-click", () => showMainWindow());
|
tray.on("double-click", () => showMainWindow());
|
||||||
console.log("[tray] READY: " + (tray ? "tray-created" : "null"));
|
console.log("[tray] READY: " + (tray ? "tray-created" : "null"));
|
||||||
@ -3525,7 +3525,7 @@ app.whenReady().then(async () => {
|
|||||||
// 「启动时不显示主窗口,驻留托盘后台运行」,但主进程从来没读过它 ——
|
// 「启动时不显示主窗口,驻留托盘后台运行」,但主进程从来没读过它 ——
|
||||||
// whenReady 无条件 createWindow(),于是必然弹窗。
|
// whenReady 无条件 createWindow(),于是必然弹窗。
|
||||||
//
|
//
|
||||||
// 实测(本机,prefs.silentStart=true):窗口标题 HomeAgent 照样出现。
|
// 实测(本机,prefs.silentStart=true):窗口标题 HologramAgent 照样出现。
|
||||||
//
|
//
|
||||||
// 与 applyAutoLaunch 里的 openAsHidden 是两回事:那个是告诉 OS「开机自启
|
// 与 applyAutoLaunch 里的 openAsHidden 是两回事:那个是告诉 OS「开机自启
|
||||||
// 时如何启动」(仅 Windows/macOS 生效),这里是本进程自己决定要不要显示,
|
// 时如何启动」(仅 Windows/macOS 生效),这里是本进程自己决定要不要显示,
|
||||||
@ -3605,7 +3605,7 @@ let notifSeq = 0;
|
|||||||
ipcMain.handle("notify:show", (_, payload) => {
|
ipcMain.handle("notify:show", (_, payload) => {
|
||||||
try {
|
try {
|
||||||
const p = payload || {};
|
const p = payload || {};
|
||||||
const title = String(p.title || "HomeAgent");
|
const title = String(p.title || "HologramAgent");
|
||||||
const body = String(p.body || "");
|
const body = String(p.body || "");
|
||||||
if (!body) return { ok: false, error: "empty body" };
|
if (!body) return { ok: false, error: "empty body" };
|
||||||
if (!Notification.isSupported()) return { ok: false, error: "unsupported" };
|
if (!Notification.isSupported()) return { ok: false, error: "unsupported" };
|
||||||
@ -3721,10 +3721,10 @@ function saveGuiPrefs(p) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Electron LoginItem 写入的注册表项名是「electron.app.<exe 名>」。
|
// Electron LoginItem 写入的注册表项名是「electron.app.<exe 名>」。
|
||||||
// 产物改名过(HomeAgent.exe → homeagent-gui.exe)就会多出一条旧项,
|
// 产物改名过(HologramAgent.exe → homeagent-gui.exe)就会多出一条旧项,
|
||||||
// 且 setLoginItemSettings 不会清理它们 ⇒ 开机启动两次同一个 exe。
|
// 且 setLoginItemSettings 不会清理它们 ⇒ 开机启动两次同一个 exe。
|
||||||
// 实测本机 Run 键里确实同时存在:
|
// 实测本机 Run 键里确实同时存在:
|
||||||
// electron.app.HomeAgent = "E:\program\homeagent\...\homeagent-gui.exe"
|
// electron.app.HologramAgent = "E:\program\homeagent\...\homeagent-gui.exe"
|
||||||
// electron.app.homeagent-gui = E:\program\homeagent\...\homeagent-gui.exe
|
// electron.app.homeagent-gui = E:\program\homeagent\...\homeagent-gui.exe
|
||||||
// 两条指向同一 exe,所以表面看是两个自启动,实际是同一份的重复。
|
// 两条指向同一 exe,所以表面看是两个自启动,实际是同一份的重复。
|
||||||
//
|
//
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"author": "JianFeeeee <jianfeeeee@homeagent.local>",
|
"author": "JianFeeeee <jianfeeeee@homeagent.local>",
|
||||||
"homepage": "https://github.com/JianFeeeee/HologramAgent",
|
"homepage": "https://github.com/JianFeeeee/HologramAgent",
|
||||||
"description": "HomeAgent Desktop GUI - Multi-connection management dashboard",
|
"description": "HologramAgent Desktop GUI - Multi-connection management dashboard",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron . --no-sandbox",
|
"start": "electron . --no-sandbox",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.homeagent.gui",
|
"appId": "com.homeagent.gui",
|
||||||
"productName": "HomeAgent",
|
"productName": "HologramAgent",
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": [
|
"target": [
|
||||||
"deb",
|
"deb",
|
||||||
|
|||||||
@ -741,7 +741,7 @@ async function api(p, o) {
|
|||||||
|
|
||||||
// 从服务端发现设备网关地址(自动链接的权威来源)。
|
// 从服务端发现设备网关地址(自动链接的权威来源)。
|
||||||
//
|
//
|
||||||
// 失败不报错:老版本 HomeAgent 没有这个端点,回退到本地推导即可
|
// 失败不报错:老版本 HologramAgent 没有这个端点,回退到本地推导即可
|
||||||
// (见 renderDeviceChannel 里的 state.discoveredGateway || 旧口径)。
|
// (见 renderDeviceChannel 里的 state.discoveredGateway || 旧口径)。
|
||||||
//
|
//
|
||||||
// 优先 url_portal(门户同源形态):GUI 主进程连 WS 走系统解析器,
|
// 优先 url_portal(门户同源形态):GUI 主进程连 WS 走系统解析器,
|
||||||
@ -1083,7 +1083,7 @@ async function refreshAll() {
|
|||||||
// 服务端列表未含本机(可能离线),仍展示本地状态
|
// 服务端列表未含本机(可能离线),仍展示本地状态
|
||||||
state.devices.push({
|
state.devices.push({
|
||||||
device_id: dbinfo.deviceId,
|
device_id: dbinfo.deviceId,
|
||||||
name: "HomeAgent GUI",
|
name: "HologramAgent GUI",
|
||||||
kind: "computer",
|
kind: "computer",
|
||||||
authorized: dbinfo.authorized,
|
authorized: dbinfo.authorized,
|
||||||
online: false,
|
online: false,
|
||||||
@ -1555,7 +1555,7 @@ function renderOverview() {
|
|||||||
b.commit && b.commit !== "unknown" ? String(b.commit).slice(0, 7) : "";
|
b.commit && b.commit !== "unknown" ? String(b.commit).slice(0, 7) : "";
|
||||||
return (
|
return (
|
||||||
"v" + escHtml(v) +
|
"v" + escHtml(v) +
|
||||||
'<div class="stat-sub">' + escHtml(b.kernel_name || "HomeAgent") +
|
'<div class="stat-sub">' + escHtml(b.kernel_name || "HologramAgent") +
|
||||||
(sha ? " · " + escHtml(sha) : "") + "</div>"
|
(sha ? " · " + escHtml(sha) : "") + "</div>"
|
||||||
);
|
);
|
||||||
})(),
|
})(),
|
||||||
@ -1819,8 +1819,8 @@ function buildChatLayout() {
|
|||||||
"</h2>" +
|
"</h2>" +
|
||||||
"<p>" +
|
"<p>" +
|
||||||
__(
|
__(
|
||||||
"连接 HomeAgent 服务端后即可开始对话。请在设置中添加后端连接。",
|
"连接 HologramAgent 服务端后即可开始对话。请在设置中添加后端连接。",
|
||||||
"Connect to a HomeAgent server to start chatting. Add a backend connection in Settings.",
|
"Connect to a HologramAgent server to start chatting. Add a backend connection in Settings.",
|
||||||
) +
|
) +
|
||||||
"</p>" +
|
"</p>" +
|
||||||
'<button class="btn btn-primary" onclick="goSettingsConn()">' +
|
'<button class="btn btn-primary" onclick="goSettingsConn()">' +
|
||||||
@ -1853,7 +1853,7 @@ function buildChatLayout() {
|
|||||||
if (state.messages.length === 0) {
|
if (state.messages.length === 0) {
|
||||||
html +=
|
html +=
|
||||||
'<div class="empty-state" style="flex:1;display:flex;align-items:center;justify-content:center"><p>' +
|
'<div class="empty-state" style="flex:1;display:flex;align-items:center;justify-content:center"><p>' +
|
||||||
__("开始与您的 HomeAgent 聊天吧", "Start chatting with your HomeAgent") +
|
__("开始与您的 HologramAgent 聊天吧", "Start chatting with your HologramAgent") +
|
||||||
"</p></div>";
|
"</p></div>";
|
||||||
}
|
}
|
||||||
html +=
|
html +=
|
||||||
@ -2229,7 +2229,7 @@ function renderChat() {
|
|||||||
if (msgs.length === 0) {
|
if (msgs.length === 0) {
|
||||||
html =
|
html =
|
||||||
'<div class="empty-state" style="flex:1;display:flex;align-items:center;justify-content:center"><p>' +
|
'<div class="empty-state" style="flex:1;display:flex;align-items:center;justify-content:center"><p>' +
|
||||||
__("开始与您的 HomeAgent 聊天吧", "Start chatting with your HomeAgent") +
|
__("开始与您的 HologramAgent 聊天吧", "Start chatting with your HologramAgent") +
|
||||||
"</p></div>";
|
"</p></div>";
|
||||||
} else {
|
} else {
|
||||||
msgs.forEach((m, i) => {
|
msgs.forEach((m, i) => {
|
||||||
@ -5963,7 +5963,7 @@ function renderGuiPrefs() {
|
|||||||
sw(
|
sw(
|
||||||
"autoLaunch",
|
"autoLaunch",
|
||||||
__("开机自启", "Auto launch on start"),
|
__("开机自启", "Auto launch on start"),
|
||||||
__("登录后自动启动 HomeAgent(静默)", "Start silently at login"),
|
__("登录后自动启动 HologramAgent(静默)", "Start silently at login"),
|
||||||
"saveGuiPrefs('autoLaunch', this.checked)",
|
"saveGuiPrefs('autoLaunch', this.checked)",
|
||||||
) +
|
) +
|
||||||
sw(
|
sw(
|
||||||
@ -6041,7 +6041,7 @@ function renderConnSection() {
|
|||||||
"</h3>" +
|
"</h3>" +
|
||||||
"<label>" +
|
"<label>" +
|
||||||
__("名称", "Name") +
|
__("名称", "Name") +
|
||||||
'</label><input id="conn-name" placeholder="My HomeAgent">' +
|
'</label><input id="conn-name" placeholder="My HologramAgent">' +
|
||||||
"<label>" +
|
"<label>" +
|
||||||
__("连接类型", "Type") +
|
__("连接类型", "Type") +
|
||||||
'</label><select id="conn-type" onchange="toggleConnType()">' +
|
'</label><select id="conn-type" onchange="toggleConnType()">' +
|
||||||
@ -6734,7 +6734,7 @@ async function connectFetchSSE(url) {
|
|||||||
String(p.content || "").slice(0, 40);
|
String(p.content || "").slice(0, 40);
|
||||||
notifyTurnOnce(
|
notifyTurnOnce(
|
||||||
_sig,
|
_sig,
|
||||||
"HomeAgent",
|
"HologramAgent",
|
||||||
String(p.content || "").slice(0, 160) || __("(空回复)", "(empty reply)"),
|
String(p.content || "").slice(0, 160) || __("(空回复)", "(empty reply)"),
|
||||||
);
|
);
|
||||||
endChatTurn();
|
endChatTurn();
|
||||||
@ -7329,8 +7329,8 @@ function renderDevices() {
|
|||||||
html +=
|
html +=
|
||||||
'<p style="color:var(--text-muted)">' +
|
'<p style="color:var(--text-muted)">' +
|
||||||
__(
|
__(
|
||||||
"暂无设备接入。设备通过 WebSocket 连接到设备网关(默认经 HomeAgent 反代到 devices.<基域名>,或直连 127.0.0.1:9890/api/v1/device/ws),携带 token 后 hello 登记、bind 授权。",
|
"暂无设备接入。设备通过 WebSocket 连接到设备网关(默认经 HologramAgent 反代到 devices.<基域名>,或直连 127.0.0.1:9890/api/v1/device/ws),携带 token 后 hello 登记、bind 授权。",
|
||||||
"No devices yet. Devices connect via WebSocket (proxied by HomeAgent at devices.<base-domain>, or directly 127.0.0.1:9890/api/v1/device/ws), hello to register, bind to authorize.",
|
"No devices yet. Devices connect via WebSocket (proxied by HologramAgent at devices.<base-domain>, or directly 127.0.0.1:9890/api/v1/device/ws), hello to register, bind to authorize.",
|
||||||
) +
|
) +
|
||||||
"</p>";
|
"</p>";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>HomeAgent</title>
|
<title>HologramAgent</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="icon.svg" />
|
<link rel="icon" type="image/svg+xml" href="icon.svg" />
|
||||||
<link rel="stylesheet" href="style.css" />
|
<link rel="stylesheet" href="style.css" />
|
||||||
<!--
|
<!--
|
||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
为什么(与服务端 webui 插件同一条理由,见
|
为什么(与服务端 webui 插件同一条理由,见
|
||||||
internal/plugins/webui/starmap_vendor_test.go 的注释):
|
internal/plugins/webui/starmap_vendor_test.go 的注释):
|
||||||
HomeAgent 明确支持离线/内网部署,而前端原本有 4 个硬依赖在公网上
|
HologramAgent 明确支持离线/内网部署,而前端原本有 4 个硬依赖在公网上
|
||||||
(cdnjs/jsdelivr)。真实现象是「服务端 curl 同一批 URL 返回 200,
|
(cdnjs/jsdelivr)。真实现象是「服务端 curl 同一批 URL 返回 200,
|
||||||
但用户点星图看到『3D 星图不可用(CDN 加载失败)』」——不是服务端的问题,
|
但用户点星图看到『3D 星图不可用(CDN 加载失败)』」——不是服务端的问题,
|
||||||
是客户端访问不到公共 CDN(内网/出口受限/断网)。
|
是客户端访问不到公共 CDN(内网/出口受限/断网)。
|
||||||
@ -108,7 +108,7 @@
|
|||||||
<div id="app">
|
<div id="app">
|
||||||
<!-- 左侧图标栏:主页=对话,其余为二级页面 -->
|
<!-- 左侧图标栏:主页=对话,其余为二级页面 -->
|
||||||
<aside class="rail" id="rail">
|
<aside class="rail" id="rail">
|
||||||
<div class="rail-logo"><img src="icon.svg" alt="HomeAgent" /></div>
|
<div class="rail-logo"><img src="icon.svg" alt="HologramAgent" /></div>
|
||||||
<button
|
<button
|
||||||
class="rail-btn active"
|
class="rail-btn active"
|
||||||
id="rail-chat"
|
id="rail-chat"
|
||||||
@ -318,7 +318,7 @@
|
|||||||
|
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<h1>HomeAgent</h1>
|
<h1>HologramAgent</h1>
|
||||||
<span
|
<span
|
||||||
class="conn-indicator"
|
class="conn-indicator"
|
||||||
id="conn-indicator"
|
id="conn-indicator"
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
// 文案写着「启动时不显示主窗口,驻留托盘后台运行」,但主进程从来没读过它
|
// 文案写着「启动时不显示主窗口,驻留托盘后台运行」,但主进程从来没读过它
|
||||||
// —— whenReady 无条件 createWindow(),于是必然弹窗。
|
// —— whenReady 无条件 createWindow(),于是必然弹窗。
|
||||||
//
|
//
|
||||||
// 实测(本机,prefs.silentStart=true):窗口标题 HomeAgent 照样出现。
|
// 实测(本机,prefs.silentStart=true):窗口标题 HologramAgent 照样出现。
|
||||||
//
|
//
|
||||||
// 本判据钉住三件事:
|
// 本判据钉住三件事:
|
||||||
// 1. whenReady 真的读 silentStart 并据此决定 show
|
// 1. whenReady 真的读 silentStart 并据此决定 show
|
||||||
|
|||||||
@ -296,7 +296,7 @@ func (s *MockServer) handlePlugins(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (s *MockServer) handleChatHistory(w http.ResponseWriter, r *http.Request) {
|
func (s *MockServer) handleChatHistory(w http.ResponseWriter, r *http.Request) {
|
||||||
writeJSON(w, []ChatMessage{
|
writeJSON(w, []ChatMessage{
|
||||||
{Role: "user", Content: "你好"},
|
{Role: "user", Content: "你好"},
|
||||||
{Role: "assistant", Content: "你好!我是 HomeAgent,有什么可以帮你的?"},
|
{Role: "assistant", Content: "你好!我是 HologramAgent,有什么可以帮你的?"},
|
||||||
{Role: "user", Content: "测试消息"},
|
{Role: "user", Content: "测试消息"},
|
||||||
{Role: "assistant", Content: "这是模拟后端的测试回复,GUI 连接正常 ✅"},
|
{Role: "assistant", Content: "这是模拟后端的测试回复,GUI 连接正常 ✅"},
|
||||||
})
|
})
|
||||||
@ -361,7 +361,7 @@ func (s *MockServer) handleChatEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (s *MockServer) handleMemoryGraph(w http.ResponseWriter, r *http.Request) {
|
func (s *MockServer) handleMemoryGraph(w http.ResponseWriter, r *http.Request) {
|
||||||
writeJSON(w, map[string]interface{}{
|
writeJSON(w, map[string]interface{}{
|
||||||
"nodes": []map[string]interface{}{
|
"nodes": []map[string]interface{}{
|
||||||
{"id": "1", "label": "HomeAgent", "group": "system"},
|
{"id": "1", "label": "HologramAgent", "group": "system"},
|
||||||
{"id": "2", "label": "GUI 测试", "group": "user"},
|
{"id": "2", "label": "GUI 测试", "group": "user"},
|
||||||
},
|
},
|
||||||
"edges": []map[string]interface{}{
|
"edges": []map[string]interface{}{
|
||||||
@ -511,7 +511,7 @@ func main() {
|
|||||||
server.registerRoutes(mux)
|
server.registerRoutes(mux)
|
||||||
|
|
||||||
addr := ":9099"
|
addr := ":9099"
|
||||||
log.Printf("=== HomeAgent Mock Server ====")
|
log.Printf("=== HologramAgent Mock Server ====")
|
||||||
log.Printf("监听地址: http://0.0.0.0%s", addr)
|
log.Printf("监听地址: http://0.0.0.0%s", addr)
|
||||||
log.Printf("API 基础路径: http://0.0.0.0%s/api/v1/", addr)
|
log.Printf("API 基础路径: http://0.0.0.0%s/api/v1/", addr)
|
||||||
log.Printf("SSE 端点: http://0.0.0.0%s/api/v1/chat/events", addr)
|
log.Printf("SSE 端点: http://0.0.0.0%s/api/v1/chat/events", addr)
|
||||||
|
|||||||
@ -349,7 +349,7 @@ func (s *MockServer) handleDeviceWS(w http.ResponseWriter, r *http.Request) {
|
|||||||
ws.WriteJSON(client.CmdMsg{
|
ws.WriteJSON(client.CmdMsg{
|
||||||
Op: "cmd",
|
Op: "cmd",
|
||||||
ReqID: "test_2_clipboardsue_" + fmt.Sprintf("%d", time.Now().UnixMilli()),
|
ReqID: "test_2_clipboardsue_" + fmt.Sprintf("%d", time.Now().UnixMilli()),
|
||||||
Command: "homeagent-clipboardsue HomeAgent远程测试_" + fmt.Sprintf("%d", time.Now().UnixMilli()),
|
Command: "homeagent-clipboardsue HologramAgent远程测试_" + fmt.Sprintf("%d", time.Now().UnixMilli()),
|
||||||
CmdType: "homeagent",
|
CmdType: "homeagent",
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -404,7 +404,7 @@ func (s *MockServer) handleDeviceWS(w http.ResponseWriter, r *http.Request) {
|
|||||||
ws.WriteJSON(client.CmdMsg{
|
ws.WriteJSON(client.CmdMsg{
|
||||||
Op: "cmd",
|
Op: "cmd",
|
||||||
ReqID: "test_6_screensue_" + fmt.Sprintf("%d", time.Now().UnixMilli()),
|
ReqID: "test_6_screensue_" + fmt.Sprintf("%d", time.Now().UnixMilli()),
|
||||||
Command: `homeagent-screensue 10 <!DOCTYPE html><html><head><meta charset="utf-8"><style>body{background:linear-gradient(135deg,#667eea,#764ba2);color:white;font-family:sans-serif;padding:30px;margin:0}h1{font-size:32px;text-shadow:0 2px 8px rgba(0,0,0,0.3)}.card{background:rgba(255,255,255,0.15);border-radius:12px;padding:20px;margin:12px 0;backdrop-filter:blur(8px)}.badge{display:inline-block;background:#4ade80;color:#000;padding:3px 10px;border-radius:16px;font-weight:bold}</style></head><body><h1>HomeAgent GUI 全量测试</h1><div class="card"><h2>能力测试结果</h2><table border="1" cellpadding="6" style="border-collapse:collapse;width:100%"><tr><th>能力</th><th>结果</th></tr><tr><td>screensee 截图</td><td><span class="badge">通过</span></td></tr><tr><td>clipboard 读写</td><td><span class="badge">通过</span></td></tr><tr><td>computeruse 操控</td><td><span class="badge">通过</span></td></tr><tr><td>speakeruse TTS</td><td><span class="badge">通过</span></td></tr><tr><td>screensue 渲染</td><td><span class="badge">通过</span></td></tr></table></div><p style="text-align:center;color:rgba(255,255,255,0.7)">2026-08-23 19:50</p></body></html>`,
|
Command: `homeagent-screensue 10 <!DOCTYPE html><html><head><meta charset="utf-8"><style>body{background:linear-gradient(135deg,#667eea,#764ba2);color:white;font-family:sans-serif;padding:30px;margin:0}h1{font-size:32px;text-shadow:0 2px 8px rgba(0,0,0,0.3)}.card{background:rgba(255,255,255,0.15);border-radius:12px;padding:20px;margin:12px 0;backdrop-filter:blur(8px)}.badge{display:inline-block;background:#4ade80;color:#000;padding:3px 10px;border-radius:16px;font-weight:bold}</style></head><body><h1>HologramAgent GUI 全量测试</h1><div class="card"><h2>能力测试结果</h2><table border="1" cellpadding="6" style="border-collapse:collapse;width:100%"><tr><th>能力</th><th>结果</th></tr><tr><td>screensee 截图</td><td><span class="badge">通过</span></td></tr><tr><td>clipboard 读写</td><td><span class="badge">通过</span></td></tr><tr><td>computeruse 操控</td><td><span class="badge">通过</span></td></tr><tr><td>speakeruse TTS</td><td><span class="badge">通过</span></td></tr><tr><td>screensue 渲染</td><td><span class="badge">通过</span></td></tr></table></div><p style="text-align:center;color:rgba(255,255,255,0.7)">2026-08-23 19:50</p></body></html>`,
|
||||||
CmdType: "homeagent",
|
CmdType: "homeagent",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"app": {
|
"app": {
|
||||||
"bundleName": "com.example.homeagent",
|
"bundleName": "com.example.homeagent",
|
||||||
"vendor": "HomeAgent",
|
"vendor": "HologramAgent",
|
||||||
"versionCode": 1004000,
|
"versionCode": 1004000,
|
||||||
"versionName": "1.4.0",
|
"versionName": "1.4.0",
|
||||||
// 分层图标:前景是字形,背景(沉淀色)在 base/ 与 dark/ 各一份,随系统主题切换。
|
// 分层图标:前景是字形,背景(沉淀色)在 base/ 与 dark/ 各一份,随系统主题切换。
|
||||||
@ -2,7 +2,7 @@
|
|||||||
"string": [
|
"string": [
|
||||||
{
|
{
|
||||||
"name": "app_name",
|
"name": "app_name",
|
||||||
"value": "HomeAgent"
|
"value": "HologramAgent"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 989 B After Width: | Height: | Size: 989 B |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 988 B After Width: | Height: | Size: 988 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "entry",
|
"name": "entry",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "HomeAgent HarmonyOS client entry module",
|
"description": "HologramAgent HarmonyOS client entry module",
|
||||||
"main": "",
|
"main": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@ -45,7 +45,7 @@ export function applyChatSse(ev: SseEvent, sink: ChatStreamSink): void {
|
|||||||
const frameType: string = frame['type'] as string ?? '';
|
const frameType: string = frame['type'] as string ?? '';
|
||||||
const type: string = ev.event.length > 0 ? ev.event : frameType;
|
const type: string = ev.event.length > 0 ? ev.event : frameType;
|
||||||
// 诊断只记事件类型(内容可能含隐私,不落盘)
|
// 诊断只记事件类型(内容可能含隐私,不落盘)
|
||||||
hilog.debug(0x0000, 'HomeAgent', 'sse %{public}s', type);
|
hilog.debug(0x0000, 'HologramAgent', 'sse %{public}s', type);
|
||||||
|
|
||||||
if (type === 'agent_output') {
|
if (type === 'agent_output') {
|
||||||
const content: string = payload['content'] as string ?? '';
|
const content: string = payload['content'] as string ?? '';
|
||||||
@ -10,7 +10,7 @@ export class DeviceBridgeClient {
|
|||||||
private url: string = '';
|
private url: string = '';
|
||||||
private token: string = '';
|
private token: string = '';
|
||||||
private deviceId: string = '';
|
private deviceId: string = '';
|
||||||
private name: string = 'HomeAgent OHOS';
|
private name: string = 'HologramAgent OHOS';
|
||||||
private kind: string = 'phone';
|
private kind: string = 'phone';
|
||||||
private caps: string[] = [];
|
private caps: string[] = [];
|
||||||
private hostname: string = 'ohos';
|
private hostname: string = 'ohos';
|
||||||
@ -237,7 +237,7 @@ export class DeviceBridgeClient {
|
|||||||
// 为何不报错回去:这些 op 不要求回执(服务端 pushToDevice 不等
|
// 为何不报错回去:这些 op 不要求回执(服务端 pushToDevice 不等
|
||||||
// device→server 的应答),回一个错误帧反而会被当成未知帧处理。
|
// device→server 的应答),回一个错误帧反而会被当成未知帧处理。
|
||||||
if (op.length > 0) {
|
if (op.length > 0) {
|
||||||
hilog.debug(0x0000, 'HomeAgent', 'device bridge: 忽略不支持的 op %{public}s', op);
|
hilog.debug(0x0000, 'HologramAgent', 'device bridge: 忽略不支持的 op %{public}s', op);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ function ensureBridgeStateTracking(): void {
|
|||||||
*
|
*
|
||||||
* 网关改造为子域反代后位于 devices.<基域名>,而基域名与子域标签都是**服务端
|
* 网关改造为子域反代后位于 devices.<基域名>,而基域名与子域标签都是**服务端
|
||||||
* 配置**,客户端拼不出来。正常路径是先调 discoverGateway() 问服务端;
|
* 配置**,客户端拼不出来。正常路径是先调 discoverGateway() 问服务端;
|
||||||
* 本函数只在「服务端没有发现端点」(老版本 HomeAgent)时兜底,
|
* 本函数只在「服务端没有发现端点」(老版本 HologramAgent)时兜底,
|
||||||
* 保留旧部署的可用性。
|
* 保留旧部署的可用性。
|
||||||
*/
|
*/
|
||||||
export function deviceGatewayUrl(base: string): string {
|
export function deviceGatewayUrl(base: string): string {
|
||||||
@ -216,7 +216,7 @@ class StatusStore {
|
|||||||
if (build !== undefined && build !== null) {
|
if (build !== undefined && build !== null) {
|
||||||
const bVer: string = build['version'] as string ?? '';
|
const bVer: string = build['version'] as string ?? '';
|
||||||
const bCommit: string = build['commit'] as string ?? '';
|
const bCommit: string = build['commit'] as string ?? '';
|
||||||
const bName: string = build['kernel_name'] as string ?? 'HomeAgent';
|
const bName: string = build['kernel_name'] as string ?? 'HologramAgent';
|
||||||
const bSdk: string = build['sdk_compatible'] as string ?? '';
|
const bSdk: string = build['sdk_compatible'] as string ?? '';
|
||||||
const bTime: string = build['build_time'] as string ?? '';
|
const bTime: string = build['build_time'] as string ?? '';
|
||||||
lic = build['license'] as string ?? '';
|
lic = build['license'] as string ?? '';
|
||||||
@ -11,11 +11,11 @@ import { ApiError } from './ApiClient';
|
|||||||
* 约定:
|
* 约定:
|
||||||
* - UI 只显示 userMessage() 返回的短句(人话、可行动、不含技术细节);
|
* - UI 只显示 userMessage() 返回的短句(人话、可行动、不含技术细节);
|
||||||
* - 技术细节(原始 message / HTTP 状态码 / 响应体)只写进 hilog,
|
* - 技术细节(原始 message / HTTP 状态码 / 响应体)只写进 hilog,
|
||||||
* 通过 `hdc shell hilog | grep HomeAgent` 排查,不进 UI。
|
* 通过 `hdc shell hilog | grep HologramAgent` 排查,不进 UI。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const DOMAIN: number = 0xA0A0;
|
const DOMAIN: number = 0xA0A0;
|
||||||
const TAG: string = 'HomeAgent';
|
const TAG: string = 'HologramAgent';
|
||||||
|
|
||||||
/** 网络类错误的统一提示:不暴露主机名、端口、curl 错误码。 */
|
/** 网络类错误的统一提示:不暴露主机名、端口、curl 错误码。 */
|
||||||
const MSG_UNREACHABLE: string = '连接不上后端服务,请检查网络与服务地址';
|
const MSG_UNREACHABLE: string = '连接不上后端服务,请检查网络与服务地址';
|
||||||
@ -208,7 +208,7 @@ export struct ConnectionsPane {
|
|||||||
.fontSize(12)
|
.fontSize(12)
|
||||||
.fontColor(this.palette().textSecondary)
|
.fontColor(this.palette().textSecondary)
|
||||||
.margin({ bottom: 10 })
|
.margin({ bottom: 10 })
|
||||||
TextInput({ placeholder: '名称 (如 HomeAgent)', text: this.editName })
|
TextInput({ placeholder: '名称 (如 HologramAgent)', text: this.editName })
|
||||||
.height(36).fontSize(13).fontColor(this.palette().textPrimary)
|
.height(36).fontSize(13).fontColor(this.palette().textPrimary)
|
||||||
.placeholderColor(this.palette().textMuted).backgroundColor(this.palette().bgInput)
|
.placeholderColor(this.palette().textMuted).backgroundColor(this.palette().bgInput)
|
||||||
.borderRadius(RADIUS_SM).border({ width: 1, color: this.palette().border })
|
.borderRadius(RADIUS_SM).border({ width: 1, color: this.palette().border })
|
||||||
@ -69,7 +69,7 @@ export struct DeviceLocalPane {
|
|||||||
}) {
|
}) {
|
||||||
PlainCard({ caption: '基本信息' }) {
|
PlainCard({ caption: '基本信息' }) {
|
||||||
DeviceKvRow({ label: '设备 ID', value: this.deviceId.length > 0 ? this.deviceId : '未注册' })
|
DeviceKvRow({ label: '设备 ID', value: this.deviceId.length > 0 ? this.deviceId : '未注册' })
|
||||||
DeviceKvRow({ label: '名称', value: 'HomeAgent OHOS' })
|
DeviceKvRow({ label: '名称', value: 'HologramAgent OHOS' })
|
||||||
DeviceKvRow({ label: '类型', value: 'phone' })
|
DeviceKvRow({ label: '类型', value: 'phone' })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ export struct GradientBackground {
|
|||||||
// 只处理本地沙箱文件;http(s) 背景图不是本应用的场景(设置页只给图库选择)
|
// 只处理本地沙箱文件;http(s) 背景图不是本应用的场景(设置页只给图库选择)
|
||||||
const path: string = src.startsWith('file://') ? src.substring(7) : src;
|
const path: string = src.startsWith('file://') ? src.substring(7) : src;
|
||||||
if (!path.startsWith('/')) {
|
if (!path.startsWith('/')) {
|
||||||
hilog.error(0x0000, 'HomeAgent', 'bg: unsupported source');
|
hilog.error(0x0000, 'HologramAgent', 'bg: unsupported source');
|
||||||
this.bgPixel = undefined;
|
this.bgPixel = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -59,11 +59,11 @@ export struct GradientBackground {
|
|||||||
await srcObj.release();
|
await srcObj.release();
|
||||||
fileIo.closeSync(f);
|
fileIo.closeSync(f);
|
||||||
this.bgPixel = pm;
|
this.bgPixel = pm;
|
||||||
hilog.info(0x0000, 'HomeAgent', 'bg decoded ok');
|
hilog.info(0x0000, 'HologramAgent', 'bg decoded ok');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 失败必须留痕:否则表现就是"设置了但没生效",无从定位
|
// 失败必须留痕:否则表现就是"设置了但没生效",无从定位
|
||||||
const err = e as BusinessError;
|
const err = e as BusinessError;
|
||||||
hilog.error(0x0000, 'HomeAgent', 'bg decode failed code=%{public}d', err.code as number);
|
hilog.error(0x0000, 'HologramAgent', 'bg decode failed code=%{public}d', err.code as number);
|
||||||
this.bgPixel = undefined;
|
this.bgPixel = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,7 +21,7 @@ function storedThemeMode(): string {
|
|||||||
|
|
||||||
export default class EntryAbility extends UIAbility {
|
export default class EntryAbility extends UIAbility {
|
||||||
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
||||||
console.info('[HomeAgent] ability onCreate');
|
console.info('[HologramAgent] ability onCreate');
|
||||||
const sysDark: boolean =
|
const sysDark: boolean =
|
||||||
this.context.config?.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
|
this.context.config?.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK;
|
||||||
// Seed the OS color state first, then resolve the persisted mode.
|
// Seed the OS color state first, then resolve the persisted mode.
|
||||||
@ -33,7 +33,7 @@ export default class EntryAbility extends UIAbility {
|
|||||||
|
|
||||||
onDestroy(): void {
|
onDestroy(): void {
|
||||||
stopForegroundBridge();
|
stopForegroundBridge();
|
||||||
console.info('[HomeAgent] ability onDestroy');
|
console.info('[HologramAgent] ability onDestroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** System dark/light switch — re-resolve when mode is 'system'. */
|
/** System dark/light switch — re-resolve when mode is 'system'. */
|
||||||
@ -75,7 +75,7 @@ export default class EntryAbility extends UIAbility {
|
|||||||
onWindowStageCreate(windowStage: window.WindowStage): void {
|
onWindowStageCreate(windowStage: window.WindowStage): void {
|
||||||
const startUI = (): void => {
|
const startUI = (): void => {
|
||||||
windowStage.loadContent('pages/Index', () => {
|
windowStage.loadContent('pages/Index', () => {
|
||||||
console.info('[HomeAgent] main page loaded');
|
console.info('[HologramAgent] main page loaded');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -99,21 +99,21 @@ export default class EntryAbility extends UIAbility {
|
|||||||
// 全屏沉浸:内容延伸到状态栏和导航栏区域
|
// 全屏沉浸:内容延伸到状态栏和导航栏区域
|
||||||
win.setWindowLayoutFullScreen(true);
|
win.setWindowLayoutFullScreen(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[HomeAgent] setWindowLayoutFullScreen failed: ' + (e as Error).message);
|
console.warn('[HologramAgent] setWindowLayoutFullScreen failed: ' + (e as Error).message);
|
||||||
}
|
}
|
||||||
connStore.init(this.context).then(async () => {
|
connStore.init(this.context).then(async () => {
|
||||||
const cur = connStore.getCurrentConnection();
|
const cur = connStore.getCurrentConnection();
|
||||||
if (cur !== null) {
|
if (cur !== null) {
|
||||||
apiClient.setConnection(cur);
|
apiClient.setConnection(cur);
|
||||||
}
|
}
|
||||||
console.info('[HomeAgent] connStore initialized');
|
console.info('[HologramAgent] connStore initialized');
|
||||||
// init 之后持久化的主题模式才可读,这里按存量设置重新解析并刷新系统栏
|
// init 之后持久化的主题模式才可读,这里按存量设置重新解析并刷新系统栏
|
||||||
this.reapplyStoredTheme();
|
this.reapplyStoredTheme();
|
||||||
this.applySystemBar();
|
this.applySystemBar();
|
||||||
startForegroundBridge(this.context);
|
startForegroundBridge(this.context);
|
||||||
startUI();
|
startUI();
|
||||||
}).catch((e: Error) => {
|
}).catch((e: Error) => {
|
||||||
console.error('[HomeAgent] connStore init failed: ' + e.message);
|
console.error('[HologramAgent] connStore init failed: ' + e.message);
|
||||||
startUI();
|
startUI();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -129,16 +129,16 @@ export default class EntryAbility extends UIAbility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onWindowStageDestroy(): void {
|
onWindowStageDestroy(): void {
|
||||||
console.info('[HomeAgent] ability onWindowStageDestroy');
|
console.info('[HologramAgent] ability onWindowStageDestroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
onForeground(): void {
|
onForeground(): void {
|
||||||
startForegroundBridge(this.context);
|
startForegroundBridge(this.context);
|
||||||
console.info('[HomeAgent] ability onForeground');
|
console.info('[HologramAgent] ability onForeground');
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackground(): void {
|
onBackground(): void {
|
||||||
stopForegroundBridge();
|
stopForegroundBridge();
|
||||||
console.info('[HomeAgent] ability onBackground');
|
console.info('[HologramAgent] ability onBackground');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +207,7 @@ export function normalizeBaseUrl(raw: string): string {
|
|||||||
export function defaultConnection(): ConnectionConfig {
|
export function defaultConnection(): ConnectionConfig {
|
||||||
const c: ConnectionConfig = {
|
const c: ConnectionConfig = {
|
||||||
id: '',
|
id: '',
|
||||||
name: 'HomeAgent',
|
name: 'HologramAgent',
|
||||||
url: '',
|
url: '',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
type: 'webui',
|
type: 'webui',
|
||||||