Files
HomeAgent/site/index.html
JianFeeeee 9b26db45bc fix(site): 逐条对照源码修正描述(含两处真错误)
上一版有几处表述与源码不符。这轮把页面上每条可核验的说法都对着代码重新查一遍,
改掉 15 处,其中两处是**事实错误**而非措辞问题。

## 事实错误

**① L4 的归属说反了(FAQ)**
原文让读者「用更高级别的中断(如 L4:内核与内核级插件)」插队,暗示插件能用 L4。
源码 `scheduler.go` 的 `clampPluginLevel` 把 **>L3 一律夹到 L3**,注释也写明
「L4 由内核独占(panic、内核事件 selfip)」。照原文写插件会静默拿到 L3。
改为:插件可声明 L1–L3,L4 是内核保留的「立即打断」。

**② 驻留子的父侧动作列错**
组件表写「父可查看/收发/压缩/回收」。"收"不存在 —— 源码的动作集是
`list | create | send | inspect | compress | reclaim | destroy`,
子持有状态面由**父 pull**(resident.go 开篇注释:父持登记表,子持 inputch 处理表,
父 pull 不打断子)。改成「查看/发送/压缩/回收/销毁,子是父拉取而非推送」。

## 措辞不准确(12 处)

- **Context 层**「最近若干条受保护」→ 源码 `pCount := 10` **写死十条**;
  「预训练词向量 → 余弦相似度,TF-IDF 回退」→ 实为优先稠密向量余弦、
  未配置时退到稀疏词向量(TF-IDF / fastText);「自动下沉」→ 归档进 Document 层
- **PluginSDK「四通道」**→ 不是四个"通道",是三面接口(工具/钩子/事件)+ 输出通道声明
- **管道「7 个阶段钩子」**→ 会被读成都在管道内。实际分布是进管道前 1(on_input)、
  轮次中 4、收尾 2(before/after_output),两处都标明
- **sanitizer** 只写了"清工具调用残留",漏了它更常做的是洗坏 UTF-8/U+FFFD/ANSI
  (而这类字节会被模型复读),且不注册工具只挂钩子
- **rss**「推送通知」→ 实际是按间隔轮询 + 中断注入;补上"订阅时记历史条目,
  所以订一个源不会把旧文章全推一遍"
- **memo** 补上可核验的机制:每 5 分钟检查未完成待办
- **ocr / bili** 补外部依赖(tesseract + chi_sim / yt-dlp)—— 不写清楚装完才发现缺
- **mc**「两阶段激活」原样照抄没解释;实为「想连着(意图)」与「确实连着(连接)」
  两个状态分开,所以 bridge 被 kill -9 后能自动重登恢复会话
- **qq** 一句话太单薄,补 20 工具 + 权限模型要点(身份绑帧、取交集、前缀拒绝)
- **a2a / music / weather** 分别补:两个方向与端点、只读无副作用、NoMemory 取舍

## 顺带修掉两个我上一轮引入的 HTML 缺陷

用行替换时失手:Context 卡丢了一个 `</p>`、mc 卡多了一个 `</span>`。
这次写了栈式配对检查才发现(简单的计数对比看不出来)。

## 验证

- 栈式标签配对:p/span/div/button/code/section/h2/h3/details/ul/ol/a/li **全部平衡**
  (修复前 p 差 1、span 差 -1)
- 事实终检 10/10:内置插件 16(all.go 导入数)、LLM 适配器 9 且**逐个名字对上**、
  Go 行 109241→109k、go.mod 1.25.0、L4 归属、resident 动作、Stage 分布、备案号
- 浏览器回归:深浅错误 0、JS 禁用 47/47 可见、reduce 动效停、390/768/1440 溢出 0、
  滚到底未揭示元素 0
- mc 工具数:本写「12 个动作工具」,实测 `tp+"act"` 去重后 activate/deactivate/status
  之外是 **11** 个,已改
2026-09-20 08:48:15 +08:00

2128 lines
94 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HomeAgent — 常驻型个人 Agent 框架</title>
<meta name="description" content="HomeAgent 是一个持续运行的个人智能 Agent 框架:内核零 IO(所有 IO 由插件承载)、三层记忆(Context → Document → Graph)、输入调度(两类别 + 四级中断)与驻留式子 agent。" />
<link rel="icon" type="image/svg+xml" href="assets/logo.svg" />
<meta property="og:title" content="HomeAgent — 常驻型个人 Agent 框架" />
<meta property="og:description" content="内核零 IO · 三层记忆 · 四级中断调度 · 驻留式子 agent" />
<meta property="og:type" content="website" />
<script>
/* 在首次绘制前定主题,避免浅/深闪一下。
优先级:localStorage 手动选择 > 系统偏好。 */
(function () {
try {
var saved = localStorage.getItem("ha-theme");
var sys = window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches
? "light" : "dark";
document.documentElement.setAttribute("data-theme", saved || sys);
} catch (e) {
document.documentElement.setAttribute("data-theme", "dark");
}
})();
</script>
<style>
/* ── 设计令牌 ──
品牌色两主题共用(蓝/青/金 = 三层记忆);语义色按主题覆盖。
默认跟随系统,可用导航栏按钮手动切换(存 localStorage)。 */
:root {
--brand-blue: #3b82f6; /* Context 层 */
--brand-cyan: #06b6d4; /* Document 层 */
--brand-gold: #f59e0b; /* Graph / Kernel */
--brand-violet: #8b5cf6;
--brand-sky: #38bdf8;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-pill: 999px;
--font-sans: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--font-mono: "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
--ease: cubic-bezier(0.22, 1, 0.36, 1);
--ease-back: cubic-bezier(0.34, 1.56, 0.64, 1);
--dur: 300ms;
/* 深色(默认) */
--bg: #070b16;
--bg-soft: rgba(17, 24, 44, 0.72);
--bg-card: rgba(17, 24, 44, 0.58);
--bg-card-hi: rgba(23, 32, 58, 0.72);
--border: rgba(255, 255, 255, 0.09);
--border-hi: rgba(59, 130, 246, 0.38);
--text: #eef1f8;
--text-dim: #a7b0c4;
--text-muted: #77809a;
--code-bg: rgba(6, 10, 22, 0.85);
--code-fg: #cdd3f5;
--nav-bg: rgba(7, 11, 22, 0.72);
--aurora-a: rgba(59, 130, 246, 0.22);
--aurora-b: rgba(6, 182, 212, 0.18);
--aurora-c: rgba(139, 92, 246, 0.16);
--aurora-d: rgba(245, 158, 11, 0.10);
--particle: 148, 190, 255;
--grid-line: rgba(255, 255, 255, 0.035);
--spot: rgba(255, 255, 255, 0.07);
--hover: rgba(255, 255, 255, 0.06);
--sheen: rgba(255, 255, 255, 0.38);
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.28);
--shadow-lg: 0 12px 36px rgba(0, 0, 0, 0.38);
--shadow-glow: 0 0 0 1px rgba(59, 130, 246, 0.22),
0 8px 32px rgba(59, 130, 246, 0.18);
}
/* 浅色:同一套语义变量,另一组取值 */
:root[data-theme="light"] {
--bg: #f4f7fc;
--bg-soft: rgba(255, 255, 255, 0.82);
--bg-card: rgba(255, 255, 255, 0.72);
--bg-card-hi: rgba(255, 255, 255, 0.9);
--border: rgba(15, 23, 42, 0.10);
--border-hi: rgba(37, 99, 235, 0.45);
--text: #0d1526;
--text-dim: #48546c;
--text-muted: #78849a;
--code-bg: #f1f5fb;
--code-fg: #1e293b;
--nav-bg: rgba(244, 247, 252, 0.78);
--aurora-a: rgba(59, 130, 246, 0.22);
--aurora-b: rgba(6, 182, 212, 0.18);
--aurora-c: rgba(139, 92, 246, 0.14);
--aurora-d: rgba(245, 158, 11, 0.14);
--particle: 37, 99, 235;
--grid-line: rgba(15, 23, 42, 0.045);
--spot: rgba(15, 23, 42, 0.05);
--hover: rgba(15, 23, 42, 0.05);
--sheen: rgba(15, 23, 42, 0.16);
--shadow-md: 0 4px 16px rgba(15, 23, 42, 0.08);
--shadow-lg: 0 12px 36px rgba(15, 23, 42, 0.12);
--shadow-glow: 0 0 0 1px rgba(37, 99, 235, 0.16),
0 8px 28px rgba(37, 99, 235, 0.14);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* 防横向溢出:装饰层(极光/粒子)永远不该产生滚动条。
实测 canvas 曾因按 innerWidth 铺开而多出 15px,故这里兜一层。 */
body { overflow-x: clip; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
line-height: 1.75;
-webkit-font-smoothing: antialiased;
}
/* 背景光晕:蓝/青/金,呼应三层记忆 */
body::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
background:
radial-gradient(60rem 40rem at 15% -10%, rgba(59, 130, 246, 0.16), transparent 60%),
radial-gradient(50rem 35rem at 85% 5%, rgba(6, 182, 212, 0.12), transparent 60%),
radial-gradient(45rem 30rem at 50% 100%, rgba(245, 158, 11, 0.08), transparent 60%);
pointer-events: none;
}
.wrap { width: min(1120px, 92vw); margin: 0 auto; }
/* ── 顶部导航 ── */
header {
position: sticky;
top: 0;
z-index: 50;
backdrop-filter: blur(14px);
background: var(--nav-bg);
border-bottom: 1px solid var(--border);
}
nav {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 0.85rem 0;
}
nav .brand {
display: flex;
align-items: center;
gap: 0.6rem;
font-weight: 700;
letter-spacing: 0.01em;
margin-right: auto;
}
nav .brand img { width: 30px; height: 30px; border-radius: 8px; }
nav a.link {
color: var(--text-dim);
text-decoration: none;
font-size: 0.92rem;
padding: 0.35rem 0.2rem;
border-bottom: 2px solid transparent;
transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
nav a.link:hover { color: var(--text); border-bottom-color: var(--brand-blue); }
nav .en { color: var(--text-muted); font-size: 0.85rem; }
@media (max-width: 760px) {
nav { flex-wrap: wrap; gap: 0.6rem; }
nav .hide-sm { display: none; }
}
/* ── Hero ── */
.hero { padding: 6rem 0 4.5rem; }
.hero-grid { display: grid; grid-template-columns: 1fr; }
.hero-copy { max-width: 46rem; }
@media (max-width: 860px) {
.hero { padding: 3rem 0 2rem; }
.hero-grid { gap: 2rem; text-align: center; }
.hero-copy { margin: 0 auto; }
.hero .cta { justify-content: center; }
}
.tagline {
display: inline-block;
font-size: 0.8rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--brand-sky);
border: 1px solid rgba(56, 189, 248, 0.3);
background: rgba(56, 189, 248, 0.08);
padding: 0.3rem 0.7rem;
border-radius: var(--radius-pill);
margin-bottom: 1.1rem;
}
h1 {
font-size: clamp(2.1rem, 5vw, 3.2rem);
line-height: 1.2;
margin: 0 0 1rem;
letter-spacing: -0.02em;
}
h1 .grad {
background: linear-gradient(120deg, var(--brand-blue), var(--brand-cyan) 50%, var(--brand-gold));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.lede {
font-size: 1.08rem;
color: var(--text-dim);
max-width: 40rem;
margin: 0 0 1.6rem;
}
@media (max-width: 860px) { .lede { margin-inline: auto; } }
.cta { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; }
.btn {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.62rem 1.1rem;
border-radius: var(--radius-md);
font-size: 0.94rem;
font-weight: 600;
text-decoration: none;
border: 1px solid transparent;
transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
background var(--dur) var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn-primary {
background: linear-gradient(135deg, var(--brand-blue), var(--brand-cyan));
color: #04121f;
box-shadow: var(--shadow-glow);
}
.btn-ghost {
background: var(--bg-soft);
color: var(--text);
border-color: var(--border);
}
.btn-ghost:hover { background: var(--hover); }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
}
/* ── 通用区块 ── */
section { padding: 3.6rem 0; }
section > .wrap > h2 {
font-size: clamp(1.4rem, 3vw, 1.85rem);
margin: 0 0 0.5rem;
letter-spacing: -0.01em;
}
section > .wrap > .sub {
color: var(--text-dim);
margin: 0 0 2rem;
max-width: 46rem;
}
.card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.4rem;
transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
box-shadow var(--dur) var(--ease);
}
.card:hover {
border-color: rgba(59, 130, 246, 0.35);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.card h3 { margin: 0 0 0.5rem; font-size: 1.05rem; }
.card p { margin: 0; color: var(--text-dim); font-size: 0.93rem; }
.card .icon {
width: 34px; height: 34px;
border-radius: var(--radius-sm);
display: grid; place-items: center;
margin-bottom: 0.8rem;
font-size: 1.05rem;
}
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.1rem; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.1rem; }
@media (max-width: 860px) {
.grid-2, .grid-3 { grid-template-columns: 1fr; }
}
/* ── 三层记忆:用品牌三色 ── */
.layers { display: grid; gap: 0.9rem; }
.layer {
display: grid;
grid-template-columns: 4px 1fr;
gap: 1.1rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
transition: border-color var(--dur) var(--ease);
}
.layer:hover { border-color: var(--border); }
.layer .bar { border-radius: 0; }
.layer .body { padding: 1.05rem 1.2rem 1.05rem 0; }
.layer h3 { margin: 0 0 0.35rem; font-size: 1.02rem; }
.layer h3 .en { color: var(--text-muted); font-weight: 400; font-size: 0.85rem; margin-left: 0.4rem; }
.layer p { margin: 0; color: var(--text-dim); font-size: 0.92rem; }
.l-ctx .bar { background: var(--brand-blue); }
.l-doc .bar { background: var(--brand-cyan); }
.l-gph .bar { background: var(--brand-gold); }
.l-ctx h3 span.mark { color: var(--brand-blue); }
.l-doc h3 span.mark { color: var(--brand-cyan); }
.l-gph h3 span.mark { color: var(--brand-gold); }
/* ── 消息流程图 ──
不用 ASCII:那只能表达"上下关系",工具循环与分支都画不出来。
语义化结构 + CSS,深浅主题跟着走,窄屏也能重排。 */
.flow {
position: relative;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.6rem 1.5rem 1.3rem;
}
/* 注:这里原来有一条贯穿全图的 ::before 轴线。
但节点背景是半透明令牌(--bg-card-hi),轴线会直接透出来——
实测在「处理管道」里穿过整个编号列表,看着像画错了。
连接线 .lnk 本身就是那条主轴,不需要再来一条。 */
/* ── 节点 ── */
.n {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1.9rem auto;
grid-template-areas: "ico t" "ico s";
align-items: center;
gap: 0 0.7rem;
width: 100%;
max-width: 34rem;
margin: 0 auto;
padding: 0.6rem 1.1rem 0.6rem 0.75rem;
background: var(--bg-card-hi);
border: 1px solid var(--border-hi);
border-radius: var(--radius-md);
box-shadow: var(--shadow-md);
}
.n-ico {
grid-area: ico;
display: grid;
place-items: center;
width: 1.9rem; height: 1.9rem;
border-radius: 50%;
font-size: 0.85rem;
background: rgba(59, 130, 246, 0.14);
color: var(--brand-blue);
}
.n-t {
grid-area: t;
font-weight: 700;
font-size: 0.95rem;
color: var(--text);
line-height: 1.35;
}
.n-s {
grid-area: s;
font-size: 0.78rem;
color: var(--text-muted);
font-family: var(--font-mono);
line-height: 1.45;
}
/* 各类节点用颜色区分:与三层记忆的三色一致 */
.n-in .n-ico { background: rgba(119, 128, 154, 0.16); color: var(--text-dim); }
.n-sched .n-ico { background: rgba(56, 189, 248, 0.16); color: var(--brand-sky); }
.n-pipe { border-color: rgba(6, 182, 212, 0.45); }
.n-pipe .n-ico { background: rgba(6, 182, 212, 0.16); color: var(--brand-cyan); }
.n-mem { border-color: rgba(245, 158, 11, 0.45); }
.n-mem .n-ico { background: rgba(245, 158, 11, 0.16); color: var(--brand-gold); }
.n-out .n-ico { background: rgba(59, 130, 246, 0.16); color: var(--brand-blue); }
/* ── 连接线:中轴上的一小段,带流动感 ── */
.lnk {
position: relative;
height: 2.1rem;
width: 2px;
margin: 0 auto;
background: var(--border-hi);
}
.lnk::before {
/* 静态箭头:reduce 下动画停了,方向也要看得出来 */
content: "";
position: absolute;
left: 50%; bottom: -1px;
width: 7px; height: 7px;
margin-left: -3.5px;
border-right: 2px solid var(--border-hi);
border-bottom: 2px solid var(--border-hi);
transform: rotate(45deg);
}
.lnk::after {
/* 下行的光点 */
content: "";
position: absolute;
left: 50%; top: 0;
width: 6px; height: 6px;
margin-left: -3px;
border-radius: 50%;
background: var(--brand-sky);
box-shadow: 0 0 8px var(--brand-sky);
animation: dotDown 2.4s var(--ease) infinite;
animation-delay: var(--d, 0s);
}
@keyframes dotDown {
0% { top: 0; opacity: 0; }
18% { opacity: 1; }
82% { opacity: 1; }
100% { top: 100%; opacity: 0; }
}
/* ── 调度分支 ── */
.br {
position: relative;
z-index: 1;
list-style: none;
max-width: 34rem;
margin: 0.55rem auto 0;
padding: 0;
display: grid;
gap: 0.28rem;
}
.br li {
position: relative;
font-size: 0.78rem;
color: var(--text-dim);
padding: 0.3rem 0.7rem 0.3rem 1.6rem;
border-left: 2px solid var(--border);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
background: rgba(119, 128, 154, 0.06);
}
.br li::before {
content: "└";
position: absolute;
left: 0.42rem;
color: var(--text-muted);
font-family: var(--font-mono);
}
.br li b { color: var(--text); font-weight: 600; }
.br li i { font-style: normal; font-weight: 600; }
.br-que i, .br-que b { color: var(--text-dim); }
.br-pre { border-left-color: rgba(245, 158, 11, 0.55); }
.br-pre i { color: var(--brand-gold); }
.br-off { border-left-color: rgba(139, 92, 246, 0.55); }
.br-off i { color: var(--brand-violet); }
/* ── 工具循环 ── */
.loop {
position: relative;
z-index: 1;
max-width: 34rem;
margin: 0.75rem auto 0;
padding: 2.4rem 1rem 2.6rem;
border: 1px dashed rgba(6, 182, 212, 0.4);
border-radius: var(--radius-md);
background: rgba(6, 182, 212, 0.045);
}
.loop-tag {
position: absolute;
top: -0.68rem; left: 1rem;
padding: 0.12rem 0.55rem;
font-size: 0.7rem;
font-family: var(--font-mono);
color: var(--brand-cyan);
background: var(--bg);
border: 1px solid rgba(6, 182, 212, 0.4);
border-radius: var(--radius-pill);
}
.seq {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.34rem;
}
.seq-item {
display: grid;
grid-template-columns: 1.5rem 1fr;
gap: 0.6rem;
align-items: baseline;
}
.seq-n {
display: grid;
place-items: center;
width: 1.5rem; height: 1.5rem;
border-radius: 50%;
font-size: 0.7rem;
font-family: var(--font-mono);
color: var(--brand-cyan);
background: rgba(6, 182, 212, 0.14);
border: 1px solid rgba(6, 182, 212, 0.3);
}
.seq-b { font-size: 0.82rem; color: var(--text); line-height: 1.5; }
.seq-b b { font-weight: 600; }
.seq-b code { font-size: 0.78rem; }
.seq-b em {
display: block;
font-style: normal;
font-size: 0.74rem;
color: var(--text-muted);
}
/* 循环回边:内嵌徽标。
曾试过从框底绕出去的弧线,但它会压到下一条连接线,看着像画错了。 */
.loop-back {
position: absolute;
right: 0.7rem; bottom: 0.7rem;
display: flex;
align-items: center;
gap: 0.34rem;
padding: 0.2rem 0.6rem;
font-size: 0.7rem;
font-family: var(--font-mono);
color: var(--brand-cyan);
background: rgba(6, 182, 212, 0.12);
border: 1px solid rgba(6, 182, 212, 0.35);
border-radius: var(--radius-pill);
white-space: nowrap;
pointer-events: none;
}
.loop-back::before {
content: "↻";
font-size: 0.85rem;
line-height: 1;
}
.flow-note {
position: relative;
z-index: 1;
margin: 1.1rem auto 0;
max-width: 34rem;
text-align: center;
font-size: 0.76rem;
color: var(--text-muted);
line-height: 1.6;
}
.flow-note code { font-size: 0.74rem; }
/* 窄屏:中轴线收边,节点撑满 */
@media (max-width: 620px) {
.flow { padding: 1.2rem 1rem 1.1rem; }
.flow::before { display: none; }
.n { width: auto; max-width: none; }
.br, .flow-note { max-width: none; }
.loop { max-width: none; padding: 2.2rem 0.8rem 2.4rem; }
.lnk { height: 1.6rem; }
}
/* ── 代码块 ── */
.code {
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 1rem 1.15rem;
font-family: var(--font-mono);
font-size: 0.86rem;
color: var(--code-fg);
overflow-x: auto;
line-height: 1.7;
/* ❗必须显式声明:HTML 源码里的换行默认会被折叠成一个空格,
实测不写这行整段命令会挤成一行(“make build build-cli # 启动…
./build/homed” 全连在一起)。 */
white-space: pre;
}
.code .c { color: var(--text-muted); }
.code .s { color: #7cb342; }
:root:not([data-theme="light"]) .code .s { color: #c3e88d; }
/* ── 数字 ── */
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
@media (max-width: 860px) { .stats { grid-template-columns: repeat(2, 1fr); } }
.stat {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.2rem 1rem;
text-align: center;
}
.stat .n {
font-size: 1.7rem;
font-weight: 700;
letter-spacing: -0.02em;
background: linear-gradient(120deg, var(--brand-blue), var(--brand-cyan));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.stat .l { color: var(--text-muted); font-size: 0.86rem; }
/* ── 插件徽章 ── */
.badges { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.badge {
font-family: var(--font-mono);
font-size: 0.8rem;
padding: 0.32rem 0.65rem;
border-radius: var(--radius-pill);
background: var(--hover);
border: 1px solid var(--border);
color: var(--text-dim);
transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.badge:hover { border-color: rgba(6, 182, 212, 0.45); color: var(--text); }
/* ── 组件角色 ── */
.comp { display: grid; gap: 0.7rem; }
.comp .row {
display: grid;
grid-template-columns: minmax(9rem, auto) 1fr;
gap: 1rem;
padding: 0.75rem 1rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-size: 0.9rem;
align-items: baseline;
}
.comp .row code {
font-family: var(--font-mono);
font-size: 0.85rem;
color: var(--brand-sky);
}
.comp .row span { color: var(--text-dim); }
@media (max-width: 760px) {
.comp .row { grid-template-columns: 1fr; gap: 0.25rem; }
}
/* ── FAQ ── */
details.faq {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 0.95rem 1.15rem;
margin-bottom: 0.7rem;
}
details.faq summary {
cursor: pointer;
font-weight: 600;
font-size: 0.96rem;
list-style: none;
}
details.faq summary::-webkit-details-marker { display: none; }
details.faq summary::before { content: "+ "; color: var(--brand-cyan); }
details.faq[open] summary::before { content: "- "; }
details.faq p {
margin: 0.7rem 0 0;
color: var(--text-dim);
font-size: 0.92rem;
}
/* ── 页脚 ── */
footer {
border-top: 1px solid var(--border);
padding: 2.4rem 0 3rem;
color: var(--text-muted);
font-size: 0.88rem;
}
footer .cols {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.6rem;
margin-bottom: 1.8rem;
}
@media (max-width: 860px) { footer .cols { grid-template-columns: repeat(2, 1fr); } }
footer h4 { color: var(--text); font-size: 0.9rem; margin: 0 0 0.6rem; }
footer ul { list-style: none; padding: 0; margin: 0; }
footer li { margin-bottom: 0.35rem; }
footer a { color: var(--text-dim); text-decoration: none; }
footer a:hover { color: var(--brand-cyan); }
footer .note {
border-top: 1px solid var(--border);
padding-top: 1.3rem;
line-height: 1.7;
}
a.plain { color: var(--brand-sky); text-decoration: none; }
a.plain:hover { text-decoration: underline; }
.mono { font-family: var(--font-mono); }
.muted { color: var(--text-muted); }
/* 滚入淡入:**默认可见**,JS 就绪后才接管。
为什么这样写:若默认 opacity:0,则 JS 未执行/抛错/被禁用时整页内容
永久不可见(实测:30 个 .reveal 里 26 个停在 opacity:0)。
所以动画必须由 JS 主动加 .js-reveal 才启用 —— 渐进增强,不是降级。 */
.js-reveal {
opacity: 0;
transform: translateY(14px);
transition: opacity 600ms var(--ease), transform 600ms var(--ease);
}
.js-reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
.js-reveal { opacity: 1; transform: none; transition: none; }
}
/* ── 插件:徽章可点,下方显示详情 ── */
.badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 1rem;
}
.badge {
/* 重置 button 默认样式 */
appearance: none;
font: inherit;
font-family: var(--font-mono);
font-size: 0.8rem;
padding: 0.34rem 0.7rem;
border-radius: var(--radius-pill);
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text-dim);
cursor: pointer;
transition: border-color 0.22s var(--ease), color 0.22s var(--ease),
background 0.22s var(--ease), transform 0.22s var(--ease-back),
box-shadow 0.22s var(--ease);
}
.badge:hover {
border-color: var(--border-hi);
color: var(--text);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.badge:focus-visible {
outline: 2px solid var(--brand-sky);
outline-offset: 2px;
}
/* 选中态:淡底 + 主色描边(实心渐变太重,20 个齐亮像一堵墙) */
.badge[aria-pressed="true"] {
/* 回退:不支持 color-mix 的浏览器给淡青底 */
background: rgba(6, 182, 212, 0.14);
background: color-mix(in srgb, var(--brand-cyan) 12%, var(--bg-card));
border-color: var(--brand-cyan);
color: var(--text);
}
/* 小圆点提示可点 */
.badge::after {
content: "";
display: inline-block;
width: 4px; height: 4px;
margin-left: 0.4rem;
border-radius: 50%;
background: currentColor;
opacity: 0.45;
vertical-align: middle;
transition: opacity 0.22s var(--ease);
}
.badge[aria-pressed="true"]::after { opacity: 1; }
/* 「展开全部」:虚线边框,与具体插件徽章区分 */
.badge.muted {
font-style: italic;
color: var(--text-muted);
border-style: dashed;
}
.badge.muted::after { display: none; }
.pdetail {
margin-top: 1.2rem;
display: grid;
gap: 0.85rem;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.phint {
grid-column: 1 / -1;
margin: 0;
text-align: center;
color: var(--text-muted);
font-size: 0.88rem;
padding: 0.85rem 0;
border: 1px dashed var(--border);
border-radius: var(--radius-md);
}
.pcard {
display: none;
position: relative;
background: var(--bg-card);
border: 1px solid var(--border-hi);
border-radius: var(--radius-lg);
padding: 1rem 1.15rem;
box-shadow: var(--shadow-md);
}
.pcard.show {
display: block;
animation: pcardIn 0.4s var(--ease-back);
}
@keyframes pcardIn {
from { opacity: 0; transform: translateY(10px) scale(0.98); }
to { opacity: 1; transform: none; }
}
.pcard-head {
display: flex;
align-items: baseline;
gap: 0.6rem;
margin-bottom: 0.5rem;
}
.pcard-head code {
font-size: 0.95rem;
font-weight: 700;
color: var(--brand-cyan);
}
.pcard-head .ver {
font-size: 0.75rem;
color: var(--text-muted);
font-family: var(--font-mono);
margin-left: auto;
}
.pcard p {
margin: 0;
font-size: 0.88rem;
color: var(--text-dim);
line-height: 1.65;
}
/* 源码链接:卡片底部一行,点进对应源码仓路径 */
.psrc {
display: flex;
align-items: center;
gap: 0.4rem;
margin-top: 0.75rem;
padding-top: 0.65rem;
border-top: 1px dashed var(--border);
font-size: 0.76rem;
font-family: var(--font-mono);
color: var(--text-muted);
text-decoration: none;
transition: color 0.2s var(--ease);
}
a.psrc:hover { color: var(--brand-cyan); }
a.psrc:focus-visible {
outline: 2px solid var(--brand-sky);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
.psrc-r {
flex: 0 0 auto;
padding: 0.06rem 0.4rem;
border-radius: var(--radius-sm);
font-size: 0.7rem;
background: rgba(119, 128, 154, 0.16);
color: var(--text-dim);
}
.psrc-p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* editdoc:卡片写 v2.0.0,公开仓只有 v1.0.0 —— 显式标出,不静默指过去 */
.psrc-warn .psrc-r { background: rgba(245, 158, 11, 0.18); color: var(--brand-gold); }
.psrc-note {
margin: 0.4rem 0 0;
font-size: 0.72rem;
line-height: 1.55;
color: var(--brand-gold);
}
.psrc-out {
margin-left: auto;
opacity: 0.55;
transition: transform 0.2s var(--ease), opacity 0.2s var(--ease);
}
a.psrc:hover .psrc-out { opacity: 1; transform: translate(2px, -2px); }
/* 无公开地址:不可点,视觉上也要看得出来 */
.psrc-none {
cursor: default;
opacity: 0.75;
font-style: italic;
}
/* 全部展开时的网格 */
.pdetail.all {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.pdetail.all .pcard { display: block; }
.pdetail.all .phint { display: none; }
/* ── 统计卡:补一行小字,避免数字孤零零 ── */
.stat .sm {
display: block;
margin-top: 0.25rem;
font-size: 0.72rem;
color: var(--text-muted);
letter-spacing: 0.01em;
}
.stat .l { line-height: 1.5; }
/* ── 版面精致化:统一节奏与呼吸 ── */
section { padding: 4.5rem 0; }
@media (max-width: 860px) { section { padding: 3rem 0; } }
h2 { margin: 0 0 0.7rem; letter-spacing: -0.01em; }
.sub { max-width: 44rem; }
/* 卡片内边距与标题间距收敛一档 */
.card { padding: 1.4rem 1.5rem; }
.card h3 { margin: 0 0 0.55rem; }
.card p { font-size: 0.9rem; }
/* 组件表:行高收敛、代码列定宽对齐 */
.comp .row { padding: 0.65rem 1rem; }
.comp .row code { min-width: 7.5rem; }
/* 统计卡内边距 */
.stat { padding: 1.25rem 1rem; }
.stat .n { margin-bottom: 0.15rem; }
/* 三层记忆卡收紧 */
.layer .body { padding: 1rem 1.2rem 1rem 0; }
.layer h3 { margin-bottom: 0.3rem; }
.layer p { margin: 0; }
/* 分隔:章节之间给一条极淡的线,强化节奏 */
section + section > .wrap > h2,
section + section > .wrap > .sub {
/* 占位:保持选择器有效,视觉由间距承担 */
}
/* 备案号:公开站点合规要求,需始终可见 */
.beian {
margin: 1rem 0 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
font-size: 0.78rem;
color: var(--text-muted);
}
.beian a {
color: var(--text-muted);
text-decoration: none;
border-bottom: 1px solid transparent;
transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.beian a:hover {
color: var(--text-dim);
border-bottom-color: var(--border-hi);
}
.beian:focus-within a { outline: none; }
.beian a:focus-visible {
outline: 2px solid var(--brand-sky);
outline-offset: 2px;
border-radius: 2px;
}
.beian-sep { opacity: 0.45; }
/* 打印:不显示装饰与折叠 */
@media print {
.aurora, #fx, #progress, .badges, .pdetail { display: none !important; }
body { background: #fff; color: #000; }
}
/* ═══════════════════════════════════════════════════
动效层。全部遵守 prefers-reduced-motion(见文件末脚本)。
原则:**内容默认可读**,动效只是增强,JS 挂了也不影响阅读。
═══════════════════════════════════════════════════ */
/* ── 极光背景:四团光斑各自漂移 ── */
.aurora {
position: fixed;
inset: -20%;
z-index: -2;
pointer-events: none;
filter: blur(38px);
opacity: 0.9;
}
.aurora span {
position: absolute;
display: block;
border-radius: 50%;
will-change: transform;
}
.aurora .a1 {
width: 46vw; height: 46vw; left: 2%; top: -6%;
background: radial-gradient(circle at 35% 35%, var(--aurora-a), transparent 68%);
animation: drift1 26s ease-in-out infinite;
}
.aurora .a2 {
width: 40vw; height: 40vw; right: -4%; top: 4%;
background: radial-gradient(circle at 60% 40%, var(--aurora-b), transparent 68%);
animation: drift2 32s ease-in-out infinite;
}
.aurora .a3 {
width: 44vw; height: 44vw; left: 22%; bottom: -12%;
background: radial-gradient(circle at 50% 50%, var(--aurora-c), transparent 68%);
animation: drift3 38s ease-in-out infinite;
}
.aurora .a4 {
width: 30vw; height: 30vw; right: 14%; bottom: 6%;
background: radial-gradient(circle at 50% 50%, var(--aurora-d), transparent 68%);
animation: drift4 30s ease-in-out infinite;
}
@keyframes drift1 {
0%,100% { transform: translate3d(0,0,0) scale(1); }
33% { transform: translate3d(6vw,4vh,0) scale(1.12); }
66% { transform: translate3d(-3vw,7vh,0) scale(0.94); }
}
@keyframes drift2 {
0%,100% { transform: translate3d(0,0,0) scale(1); }
40% { transform: translate3d(-7vw,5vh,0) scale(1.1); }
70% { transform: translate3d(-2vw,-4vh,0) scale(0.92); }
}
@keyframes drift3 {
0%,100% { transform: translate3d(0,0,0) scale(1); }
50% { transform: translate3d(5vw,-6vh,0) scale(1.15); }
}
@keyframes drift4 {
0%,100% { transform: translate3d(0,0,0) scale(1); }
45% { transform: translate3d(-5vw,-3vh,0) scale(1.08); }
}
/* ── 细网格:给「工程感」打底 ── */
body::after {
content: "";
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image: linear-gradient(var(--grid-line) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 25%, transparent 78%);
-webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 25%, transparent 78%);
}
/* ── 粒子画布 ── */
#fx {
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
opacity: 0.75;
}
/* ── 顶部滚动进度条(三色渐变) ── */
#progress {
position: fixed;
top: 0; left: 0;
height: 2px;
width: 0%;
z-index: 100;
background: linear-gradient(90deg, var(--brand-blue), var(--brand-cyan) 55%, var(--brand-gold));
box-shadow: 0 0 12px rgba(56, 189, 248, 0.55);
transition: width 90ms linear;
}
/* ── 主题切换按钮 ── */
.theme-btn {
display: inline-grid;
place-items: center;
width: 34px; height: 34px;
border-radius: var(--radius-pill);
border: 1px solid var(--border);
background: var(--bg-soft);
color: var(--text-dim);
cursor: pointer;
padding: 0;
transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease),
transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.theme-btn:hover {
color: var(--text);
border-color: var(--border-hi);
transform: rotate(-18deg) scale(1.06);
}
.theme-btn svg { width: 16px; height: 16px; display: block; }
.theme-btn .i-moon { display: none; }
:root[data-theme="light"] .theme-btn .i-moon { display: block; }
:root[data-theme="light"] .theme-btn .i-sun { display: none; }
/* ── 标题与文字的入场:分块上浮 ── */
.js-fx .rise {
opacity: 0;
transform: translateY(22px);
transition: opacity 0.75s var(--ease), transform 0.75s var(--ease);
transition-delay: var(--d, 0ms);
}
.js-fx .rise.in { opacity: 1; transform: none; }
.js-fx .rise-zoom {
opacity: 0; transform: scale(0.93) translateY(14px);
transition: opacity 0.8s var(--ease), transform 0.8s var(--ease-back);
transition-delay: var(--d, 0ms);
}
.js-fx .rise-zoom.in { opacity: 1; transform: none; }
/* ── Hero 大标题:渐变流动 + 描边光 ── */
h1 .grad {
background: linear-gradient(110deg, var(--brand-blue), var(--brand-cyan) 32%,
var(--brand-violet) 58%, var(--brand-gold) 82%, var(--brand-cyan));
background-size: 280% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: gradFlow 9s ease infinite;
}
@keyframes gradFlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* 光标扫过标题时的光泽 */
h1 {
position: relative;
text-shadow: 0 0 40px rgba(56, 189, 248, 0.10);
}
/* ── 按钮:光泽扫过 + 悬浮抬升 ── */
.btn { position: relative; overflow: hidden; }
.btn::after {
content: "";
position: absolute;
top: 0; left: -120%;
width: 60%; height: 100%;
background: linear-gradient(100deg, transparent, var(--sheen), transparent);
transform: skewX(-18deg);
transition: left 0.75s var(--ease);
pointer-events: none;
}
.btn:hover::after { left: 130%; }
.btn-primary {
background-size: 180% 100%;
animation: gradFlow 7s ease infinite;
}
.btn-primary:hover { box-shadow: 0 10px 30px rgba(6, 182, 212, 0.42); }
/* ── 卡片:跟随光标的聚光 + 3D 微倾 ── */
.card, .layer, .stat, .comp .row, details.faq {
position: relative;
transform-style: preserve-3d;
}
.card::before, .stat::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: radial-gradient(
260px circle at var(--mx, 50%) var(--my, 50%),
var(--spot), transparent 62%);
opacity: 0;
transition: opacity var(--dur) var(--ease);
pointer-events: none;
}
.card:hover::before, .stat:hover::before { opacity: 1; }
.card:hover, .stat:hover {
border-color: var(--border-hi);
box-shadow: var(--shadow-md);
}
/* 图标在悬浮时轻转 */
.card .icon { transition: transform 0.5s var(--ease-back); }
.card:hover .icon { transform: rotate(-8deg) scale(1.12); }
/* ── 三层记忆色条:入场时自上而下「灌注」 ── */
.layer .bar {
transform: scaleY(0);
transform-origin: top center;
transition: transform 0.9s var(--ease) 0.15s;
box-shadow: 0 0 18px currentColor;
}
.layer.in .bar { transform: scaleY(1); }
.layer.l-ctx .bar { color: var(--brand-blue); }
.layer.l-doc .bar { color: var(--brand-cyan); }
.layer.l-gph .bar { color: var(--brand-gold); }
/* ── 流程图的「数据流动」──
光带扫描会盖住节点,改成让节点自己的图标呼吸。 */
.js-fx .flow .n-ico {
animation: nodePulse 4.5s ease-in-out infinite;
}
.js-fx .flow .n:nth-of-type(1) .n-ico { animation-delay: 0s; }
.js-fx .flow .n .n-ico { animation-delay: 0.9s; }
.js-fx .flow .n-mem .n-ico { animation-delay: 1.8s; }
@keyframes nodePulse {
0%, 100% { box-shadow: 0 0 0 0 transparent; }
50% { box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.10); }
}
/* 序号依次点亮,跟着流程走 */
.js-fx .flow .seq-n {
animation: seqPulse 4.5s ease-in-out infinite;
}
.js-fx .flow .seq-item:nth-child(1) .seq-n { animation-delay: 0s; }
.js-fx .flow .seq-item:nth-child(2) .seq-n { animation-delay: 0.35s; }
.js-fx .flow .seq-item:nth-child(3) .seq-n { animation-delay: 0.7s; }
.js-fx .flow .seq-item:nth-child(4) .seq-n { animation-delay: 1.05s; }
.js-fx .flow .seq-item:nth-child(5) .seq-n { animation-delay: 1.4s; }
.js-fx .flow .seq-item:nth-child(6) .seq-n { animation-delay: 1.75s; }
@keyframes seqPulse {
0%, 70%, 100% { border-color: rgba(6, 182, 212, 0.3); color: var(--brand-cyan); }
12% { border-color: var(--brand-cyan); color: var(--text); }
}
/* ── 数字卡片:数值滚动到位 ── */
.stat .n { font-variant-numeric: tabular-nums; }
/* ── 插件徽章:错落浮现 ── */
.js-fx .badge {
opacity: 0;
transform: translateY(10px) scale(0.94);
transition: opacity 0.5s var(--ease), transform 0.5s var(--ease-back);
}
.js-fx .badges.in .badge { opacity: 1; transform: none; }
/* ── 分节标题:渐变下划线展开 ── */
section > .wrap > h2 { position: relative; }
section > .wrap > h2::after {
content: "";
display: block;
width: 62px;
height: 3px;
margin-top: 0.6rem;
border-radius: 3px;
background: linear-gradient(90deg, var(--brand-blue), var(--brand-cyan), var(--brand-gold));
transform: scaleX(0);
transform-origin: left center;
transition: transform 0.9s var(--ease) 0.2s;
}
section > .wrap > h2.in::after { transform: scaleX(1); }
/* ── FAQ 展开:内容淡入 ── */
details.faq[open] p { animation: fadeUp 0.45s var(--ease); }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: none; }
}
/* ── 导航链接:下划线滑入 ── */
nav a.link { position: relative; }
nav a.link::after {
content: "";
position: absolute;
left: 0; bottom: -2px;
width: 100%; height: 2px;
background: linear-gradient(90deg, var(--brand-blue), var(--brand-cyan));
transform: scaleX(0);
transform-origin: right center;
transition: transform var(--dur) var(--ease);
}
nav a.link:hover::after { transform: scaleX(1); transform-origin: left center; }
/* ── 浅色下的微调:光晕别太亮、网格别太淡 ── */
:root[data-theme="light"] .aurora { opacity: 0.62; }
:root[data-theme="light"] #fx { opacity: 0.5; }
:root[data-theme="light"] nav .en,
:root[data-theme="light"] nav a.link { color: var(--text-dim); }
/* 浅色下代码块的光条更淡 */
:root[data-theme="light"] .flow::after {
background: linear-gradient(to bottom, transparent,
rgba(37, 99, 235, 0.07) 45%, rgba(37, 99, 235, 0.12) 50%, transparent);
}
/* ── 统一降级:减少动态效果时全部静止 ── */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
.js-fx .rise, .js-fx .rise-zoom, .js-fx .badge { opacity: 1; transform: none; }
.js-fx .flow .n-ico, .js-fx .flow .seq-n, .lnk::after { animation: none; }
.lnk::after { opacity: 0; }
.layer .bar { transform: scaleY(1); }
section > .wrap > h2::after { transform: scaleX(1); }
#progress { display: none; }
#fx { display: none; }
}
</style>
</head>
<body>
<div id="progress"></div>
<div class="aurora" aria-hidden="true">
<span class="a1"></span><span class="a2"></span>
<span class="a3"></span><span class="a4"></span>
</div>
<canvas id="fx" aria-hidden="true"></canvas>
<header>
<div class="wrap">
<nav>
<span class="brand">
<img src="assets/logo.svg" alt="HomeAgent logo" />
HomeAgent
</span>
<a class="link hide-sm" href="#features">特性</a>
<a class="link hide-sm" href="#memory">记忆</a>
<a class="link hide-sm" href="#architecture">架构</a>
<a class="link hide-sm" href="#quickstart">上手</a>
<a class="link" href="#faq">FAQ</a>
<button class="theme-btn" id="themeToggle" type="button"
aria-label="切换深浅主题" title="切换深浅主题">
<svg class="i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="4.2"/>
<path d="M12 2.5v2.2M12 19.3v2.2M4.2 4.2l1.6 1.6M18.2 18.2l1.6 1.6M2.5 12h2.2M19.3 12h2.2M4.2 19.8l1.6-1.6M18.2 5.8l1.6-1.6"/>
</svg>
<svg class="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
</svg>
</button>
</nav>
</div>
</header>
<main>
<!-- ── Hero ── -->
<section class="hero">
<div class="wrap hero-grid">
<div class="hero-copy">
<span class="tagline rise" style="--d:0ms">常驻型个人 Agent 框架</span>
<h1 class="rise" style="--d:80ms">一个<span class="grad">不会忘记</span><br />也不会被一条长任务卡死的管家</h1>
<p class="lede rise" style="--d:180ms">
HomeAgent 是内核 + 插件的双层 Agent 框架。内核只管
<strong>LLM 编排、记忆与调度</strong>;所有外部能力(消息、文件、网络、设备)
都是可独立安装、崩溃不牵连内核的插件。
</p>
<div class="cta rise" style="--d:280ms">
<a class="btn btn-primary" href="#quickstart">快速上手 →</a>
<a class="btn btn-ghost" href="https://gitcode.com/JianFeeeee/HomeAgent">查看源码</a>
</div>
</div>
</div>
</section>
<!-- ── 特性 ── -->
<section id="features">
<div class="wrap">
<h2 class="reveal rise">四个不那么常见的设计决定</h2>
<p class="sub reveal rise" style="--d:90ms">
先定<strong>边界</strong>与<strong>调度</strong>,再加能力。
</p>
<div class="grid-2">
<div class="card reveal rise" style="--d:0ms">
<div class="icon" style="background: rgba(59,130,246,.14); color: var(--brand-blue)">◇</div>
<h3>内核零 IO</h3>
<p>
内核不碰任何外部世界。消息、文件、网络、硬件全部由插件经
PluginSDK 实现。于是「内核是否可信」与「某个插件是否安全」
可以分开评估。
</p>
</div>
<div class="card reveal rise" style="--d:90ms">
<div class="icon" style="background: rgba(6,182,212,.14); color: var(--brand-cyan)">◈</div>
<h3>插件是独立进程</h3>
<p>
插件是普通 Go 二进制,由内核 spawn 为子进程,经 stdio JSON-RPC
(控制)、共享内存段(数据)、事件环(通知)通信。插件崩溃不牵连内核,
换掉 <span class="mono">plugin.bin</span> 即热重载。
</p>
</div>
<div class="card reveal rise" style="--d:180ms">
<div class="icon" style="background: rgba(245,158,11,.14); color: var(--brand-gold)">◉</div>
<h3>输入是有级别的</h3>
<p>
输入分「排队」与「中断」两类,中断再按紧迫度分 L1–L4。
高级可抢占并保存现场,同级不抢占。L4 归内核独占
—— 所以「停止」真的能立刻停下。
</p>
</div>
<div class="card reveal rise" style="--d:270ms">
<div class="icon" style="background: rgba(139,92,246,.14); color: var(--brand-violet)">◆</div>
<h3>忙时有人顶班</h3>
<p>
主 agent 忙长任务时,积压交给临时<strong>分诊助手</strong>:
能办的直接办,需要主 agent 的立刻回「忙碌中,请稍候」。
不必干等十几分钟。
</p>
</div>
</div>
</div>
</section>
<!-- ── 三层记忆 ── -->
<section id="memory">
<div class="wrap">
<h2 class="reveal rise">三层记忆:从「刚才说了什么」到「三年前说过什么」</h2>
<p class="sub reveal rise" style="--d:90ms">
不是一个大向量库包打天下,而是三层各司其职、逐层下沉。
蓝 / 青 / 金三色对应的就是这三层。
</p>
<div class="layers">
<div class="layer l-ctx reveal">
<div class="bar"></div>
<div class="body">
<h3><span class="mark">①</span> Context<span class="en">工作窗口</span></h3>
<p>
内存中的事件窗口。按与当前输入的相关性打分:优先比统一空间的稠密向量余弦,
未配置时退到稀疏词向量(TF-IDF / fastText)。最近 10 条受保护,
低分事件归档进 Document 层。
</p>
</div>
</div>
<div class="layer l-doc reveal">
<div class="bar"></div>
<div class="body">
<h3><span class="mark">②</span> Document<span class="en">文件记忆</span></h3>
<p>
JSON + TF-IDF 索引的中间层。支持显式提交与隐式归档,
冷数据下沉后蒸馏成三元组进入图谱。
</p>
</div>
</div>
<div class="layer l-gph reveal">
<div class="bar"></div>
<div class="body">
<h3><span class="mark">③</span> Graph<span class="en">图数据库</span></h3>
<p>
SQLite 持久化实体与关系,BFS 遍历召回。三元组从对话中蒸馏而来,
越用越准 —— 「几个月前的事还记得」靠的就是这层。
</p>
</div>
</div>
</div>
<div class="grid-3" style="margin-top: 1.2rem">
<div class="card reveal rise" style="--d:360ms">
<h3>媒体也是一等节点</h3>
<p>
图片、音频不是附件,而是三层里的一类节点:内容寻址(相同字节只存一份)。
媒体不单独记引用账本 —— 删掉持有它的记忆块才删内容,与文本块同一套规则。
</p>
</div>
<div class="card reveal rise" style="--d:450ms">
<h3>图片靠自己的向量被检索</h3>
<p>
图像是真正的记忆块,不是「描述文本 + 可淘汰的 blob」。
旧实现靠视觉模型生成的描述当索引,现已弃用 ——
文本标签只留 <span class="mono">[mime digest]</span>,召回走统一空间的图像向量。
</p>
</div>
<div class="card reveal rise" style="--d:540ms">
<h3>统一多模态空间</h3>
<p>
文本与图像落在同一向量空间(默认 Chinese-CLIP):
可以用一句话搜图,也可以用图找记忆。
</p>
</div>
</div>
</div>
</section>
<!-- ── 架构 ── -->
<section id="architecture">
<div class="wrap">
<h2 class="reveal rise">一条消息进来之后</h2>
<p class="sub reveal rise" style="--d:90ms">
输入先过调度器,再进处理管道。全程 7 个阶段钩子,插件可改写或短路。
</p>
<div class="flow reveal rise">
<!-- 输入 -->
<div class="n n-in">
<span class="n-ico">↘</span>
<span class="n-t">外部输入</span>
<span class="n-s">QQ / WebUI / CLI / 邮件 / 设备</span>
</div>
<div class="lnk" style="--d:0.00s" aria-hidden="true"></div>
<!-- 调度器 -->
<div class="n n-sched">
<span class="n-ico">⇅</span>
<span class="n-t">输入调度器</span>
<span class="n-s">两类别 · 四级中断 L1–L4</span>
</div>
<ul class="br" aria-label="调度器的三种去向">
<li class="br-que"><b>同级不抢占</b> → 入队列排队</li>
<li class="br-pre"><b>更高级别</b> → <i>抢占</i>,现场压入中断栈,稍后恢复</li>
<li class="br-off"><b>忙得太久</b> → <i>转投</i>临时分诊助手</li>
</ul>
<div class="lnk" style="--d:0.55s" aria-hidden="true"></div>
<!-- 处理管道 -->
<div class="n n-pipe">
<span class="n-ico">⇄</span>
<span class="n-t">处理管道</span>
<span class="n-s">记忆召回 + 人格注入 + LLM 调用 + 工具循环</span>
</div>
<!-- 循环体 -->
<div class="loop">
<span class="loop-tag">每轮复用</span>
<ol class="seq">
<li class="seq-item">
<span class="seq-n">1</span>
<div class="seq-b"><code>pre_action</code>
<em>构建消息与工具表 · 插件可在此短路</em></div>
</li>
<li class="seq-item">
<span class="seq-n">2</span>
<div class="seq-b"><b>LLM 调用</b>
<em>provider 回退 + 瞬时错误重试 · 流式累积</em></div>
</li>
<li class="seq-item">
<span class="seq-n">3</span>
<div class="seq-b"><code>post_action</code>
<em>插件可改写最终文本</em></div>
</li>
<li class="seq-item">
<span class="seq-n">4</span>
<div class="seq-b"><code>before_toolcall</code>
<em>逐个工具执行前 · 可拒</em></div>
</li>
<li class="seq-item">
<span class="seq-n">5</span>
<div class="seq-b"><b>执行工具</b>
<em>临界区:副作用不可回滚</em></div>
</li>
<li class="seq-item">
<span class="seq-n">6</span>
<div class="seq-b"><code>after_toolcall</code>
<em>上下文裁剪 · 相关记忆召回 · 结果入消息</em></div>
</li>
</ol>
<div class="loop-back" aria-hidden="true">
<span>有工具调用则回到 ①</span>
</div>
</div>
<div class="lnk" style="--d:1.10s" aria-hidden="true"></div>
<!-- 三层记忆 -->
<div class="n n-mem">
<span class="n-ico">◈</span>
<span class="n-t">三层记忆</span>
<span class="n-s">Context → Document → Graph</span>
</div>
<div class="lnk" style="--d:1.65s" aria-hidden="true"></div>
<!-- 输出 -->
<div class="n n-out">
<span class="n-ico">↗</span>
<span class="n-t">输出通道</span>
<span class="n-s">before_output · after_output · 可寻址到具体 agent</span>
</div>
<p class="flow-note">
<code>on_input</code> 在进管道之前跑;<code>before_output</code> / <code>after_output</code> 在全部轮次结束后跑一次。
</p>
</div>
<h2 class="reveal" style="margin-top: 3rem">核心组件</h2>
<div class="comp reveal rise">
<div class="row"><code>homed</code><span>常驻内核进程:LLM 编排、记忆管理、输入调度、插件生命周期</span></div>
<div class="row"><code>PluginSDK</code><span>面向内核的接口:注册工具 / 挂阶段钩子 / 订阅事件 / 声明输出通道</span></div>
<div class="row"><code>inputch</code><span>最基本的输入路由单位,可划给某个 agent(含驻留子)</span></div>
<div class="row"><code>Stage</code><span>7 个处理阶段钩子(进管道前 1 个、轮次中 4 个、收尾 2 个),插件可改写或短路</span></div>
<div class="row"><code>resident</code><span>驻留子 agent:独立调度器 + temp 图记忆;父可查看/发送/压缩/回收/销毁,子是父拉取而非推送</span></div>
<div class="row"><code>hmapdev</code><span>插件工具链:生成工程、构建 .hmap、安装</span></div>
</div>
<div class="stats reveal" style="margin-top: 2rem">
<div class="stat"><div class="n">36</div><div class="l">在跑插件<br /><span class="sm">16 内置 + 20 外部</span></div></div>
<div class="stat"><div class="n">340</div><div class="l">可用工具<br /><span class="sm">实测注册数</span></div></div>
<div class="stat"><div class="n">109k</div><div class="l">Go 代码行<br /><span class="sm">不含第三方</span></div></div>
<div class="stat"><div class="n">9</div><div class="l">LLM 适配器<br /><span class="sm">另有 1 个网关脚本</span></div></div>
</div>
</div>
</section>
<!-- ── 上手 ── -->
<section id="quickstart">
<div class="wrap">
<h2 class="reveal rise">跑起来</h2>
<p class="sub reveal rise" style="--d:90ms">
依赖 Go 1.25+ 与 CGo(sqlite3)。内核需 Linux;Windows 上
<span class="mono">homed</span> 跑在 WSL2 里,仅需构建 <span class="mono">waiter.exe</span>。
</p>
<div class="code reveal rise"><span class="c"># 构建守护进程 + CLI</span>
make build build-cli
<span class="c"># 启动内核(数据目录自定)</span>
./build/homed -data /tmp/ha
<span class="c"># 另开一个终端:交互模式,或单条消息</span>
./build/waiter
echo <span class="s">"你好,记住我喜欢喝咖啡"</span> | ./build/waiter</div>
<p class="sub reveal rise" style="--d:90ms; margin-top: 1.2rem">
WebUI 与 API 密钥在 <span class="mono">http://localhost:8080</span> 配置,持久化到 SQLite。
</p>
<h2 class="reveal" style="margin-top: 3rem">开箱可用的插件</h2>
<p class="sub reveal rise" style="--d:90ms">外部插件经 <span class="mono">hmapdev</span> 构建为独立二进制,可单独装卸、热重载。<strong>点任意插件看它能做什么。</strong></p>
<div class="badges reveal">
<button class="badge" type="button" data-plugin="a2a" style="--i:0">a2a</button>
<button class="badge" type="button" data-plugin="acp" style="--i:1">acp</button>
<button class="badge" type="button" data-plugin="bili" style="--i:2">bili</button>
<button class="badge" type="button" data-plugin="browser" style="--i:3">browser</button>
<button class="badge" type="button" data-plugin="calendar" style="--i:4">calendar</button>
<button class="badge" type="button" data-plugin="deepsearch" style="--i:5">deepsearch</button>
<button class="badge" type="button" data-plugin="editdoc" style="--i:6">editdoc</button>
<button class="badge" type="button" data-plugin="homeagent-mail-bridge" style="--i:7">homeagent-mail-bridge</button>
<button class="badge" type="button" data-plugin="mc" style="--i:8">mc</button>
<button class="badge" type="button" data-plugin="memo" style="--i:9">memo</button>
<button class="badge" type="button" data-plugin="music" style="--i:10">music</button>
<button class="badge" type="button" data-plugin="ocr" style="--i:11">ocr</button>
<button class="badge" type="button" data-plugin="plugindev" style="--i:12">plugindev</button>
<button class="badge" type="button" data-plugin="qq" style="--i:13">qq</button>
<button class="badge" type="button" data-plugin="recoverydiag" style="--i:14">recoverydiag</button>
<button class="badge" type="button" data-plugin="rss" style="--i:15">rss</button>
<button class="badge" type="button" data-plugin="sanitizer" style="--i:16">sanitizer</button>
<button class="badge" type="button" data-plugin="vanblog" style="--i:17">vanblog</button>
<button class="badge" type="button" data-plugin="vikunja" style="--i:18">vikunja</button>
<button class="badge" type="button" data-plugin="weather" style="--i:19">weather</button>
<button class="badge muted" type="button" id="expandAll">展开全部</button>
</div>
<div class="pdetail" id="pdetail">
<p class="phint" id="phint">点上方任一插件查看用途</p>
<div class="pcard" id="p-a2a">
<div class="pcard-head"><code>a2a</code><span class="ver">v1.3.1</span></div>
<p>Agent 间通信。对外起 HTTP 服务暴露 /agent-card 与 /a2a(JSON-RPC)供他人发现与调用;对内提供工具去问别的 Agent。把监听地址设为空即只出站、不开端口。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/a2a" target="_blank" rel="noopener noreferrer" title="SDK example/a2a"><span class="psrc-r">SDK</span><span class="psrc-p">example/a2a</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-acp">
<div class="pcard-head"><code>acp</code><span class="ver">v1.2.1</span></div>
<p>Agent Client Protocol 通信插件:充当 ACP 服务端接受其他 Agent 的任务请求,同时提供客户端工具向远程 ACP Agent(如 opencode)发起会话并读取回复</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/acp" target="_blank" rel="noopener noreferrer" title="SDK example/acp"><span class="psrc-r">SDK</span><span class="psrc-p">example/acp</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-bili">
<div class="pcard-head"><code>bili</code><span class="ver">v1.2.0</span></div>
<p>B站视频下载。需系统装 yt-dlp。不指定格式时先返回可用清晰度列表,选定后再下载;输出目录有路径校验,拒绝写成系统目录。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/bili" target="_blank" rel="noopener noreferrer" title="SDK example/bili"><span class="psrc-r">SDK</span><span class="psrc-p">example/bili</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-browser">
<div class="pcard-head"><code>browser</code><span class="ver">v2.4.1</span></div>
<p>统一浏览器插件:搜索、HTTP抓取(quick)、无头渲染(normal)、交互式浏览器(interactive/CDP)</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/browser" target="_blank" rel="noopener noreferrer" title="SDK example/browser"><span class="psrc-r">SDK</span><span class="psrc-p">example/browser</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-calendar">
<div class="pcard-head"><code>calendar</code><span class="ver">v1.1.0</span></div>
<p>日历事件管理,支持提醒和重复事件</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/calendar" target="_blank" rel="noopener noreferrer" title="SDK example/calendar"><span class="psrc-r">SDK</span><span class="psrc-p">example/calendar</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-deepsearch">
<div class="pcard-head"><code>deepsearch</code><span class="ver">v1.1.2</span></div>
<p>为 agent 提供真正的联网信息检索:本地 SearXNG 聚合多引擎(返回标题/URL/摘要/时间),支持新闻、时间范围、指定引擎;并提供网页正文抽取与「搜索+读前K篇」的深检索</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/deepsearch" target="_blank" rel="noopener noreferrer" title="SDK example/deepsearch"><span class="psrc-r">SDK</span><span class="psrc-p">example/deepsearch</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-editdoc">
<div class="pcard-head"><code>editdoc</code><span class="ver">v2.0.0</span></div>
<p>全能办公插件:新建/读取/编辑/转换 docx·xlsx·pptx·md·csv·txt(基于 python-docx / openpyxl / python-pptx / soffice / pandoc)</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/editdoc" target="_blank" rel="noopener noreferrer" title="SDK example/editdoc"><span class="psrc-r">SDK</span><span class="psrc-p">example/editdoc</span><span class="psrc-out">↗</span></a>
<p class="psrc-note">公开仓中的 example 仍是 v1.0.0;v2.0.0 全能版源码尚未公开。</p>
</div>
<div class="pcard" id="p-homeagent-mail-bridge">
<div class="pcard-head"><code>homeagent-mail-bridge</code><span class="ver">v0.2.2</span></div>
<p>HomeAgent 接入 AgentMail:邮件驱动的多智能体协作</p>
<a class="psrc" href="https://gitea.jianfgit.xyz/jianf/MailUI4Agents/src/branch/main/plugins/homeagent-mail-bridge" target="_blank" rel="noopener noreferrer" title="agentmail plugins/homeagent-mail-bridge"><span class="psrc-r">agentmail</span><span class="psrc-p">plugins/homeagent-mail-bridge</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-mc">
<div class="pcard-head"><code>mc</code><span class="ver">v0.1.8</span></div>
<p>基于 mineflayer 的虚拟 MC 客户端,作为 chan:mc 通道接入。「激活」分两个状态:想连着(意图)与确实连着(连接),所以 bridge 进程被杀后能自动重登恢复会话,而不是要人再喊一次。带移动/挖掘/合成/攻击等 11 个动作工具。</p>
<span class="psrc psrc-none" title="该插件源码在本地开发仓,尚无公开地址"><span class="psrc-p">本地开发仓(无公开地址)</span></span>
</div>
<div class="pcard" id="p-memo">
<div class="pcard-head"><code>memo</code><span class="ver">v1.1.0</span></div>
<p>待办与备忘录刻意分两类:待办每 5 分钟检查未完成数、有则注入提醒;备忘录纯记事不提醒。混成一类要么天天弹、要么被忘掉。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/memo" target="_blank" rel="noopener noreferrer" title="SDK example/memo"><span class="psrc-r">SDK</span><span class="psrc-p">example/memo</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-music">
<div class="pcard-head"><code>music</code><span class="ver">v0.1.0</span></div>
<p>搜歌与查歌词(网易云公开接口)。只读:不下载音频、不落文件。两段式用法 —— 先搜到歌曲 ID,再按 ID 取词。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/music" target="_blank" rel="noopener noreferrer" title="SDK example/music"><span class="psrc-r">SDK</span><span class="psrc-p">example/music</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-ocr">
<div class="pcard-head"><code>ocr</code><span class="ver">v1.0.0</span></div>
<p>图片文字识别。需系统装 tesseract(中文另需 chi_sim 语言包)。支持 URL 或 base64 输入,临时文件识别后自动清理。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/ocr" target="_blank" rel="noopener noreferrer" title="SDK example/ocr"><span class="psrc-r">SDK</span><span class="psrc-p">example/ocr</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-plugindev">
<div class="pcard-head"><code>plugindev</code><span class="ver">v1.0.0</span></div>
<p>把 hmapdev 工具链封装成 Agent 可调用的工具:脚手架生成插件工程、构建打包 .hmap、管理 SDK 版本。配合 plugin_install 即可让 Agent 自己做完「新建插件 → 构建 → 安装」全流程。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/plugindev" target="_blank" rel="noopener noreferrer" title="SDK example/plugindev"><span class="psrc-r">SDK</span><span class="psrc-p">example/plugindev</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-qq">
<div class="pcard-head"><code>qq</code><span class="ver">v1.4.1</span></div>
<p>经 NapCat 桥接 QQ:20 个工具覆盖消息、群/好友、文件传输。带权限模型 —— 身份绑在「帧」上而非插件全局(中断抢占恢复后不会丢身份),多来源合并取权限交集,敏感工具按前缀一律拒绝非所有者。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/qq" target="_blank" rel="noopener noreferrer" title="SDK example/qq"><span class="psrc-r">SDK</span><span class="psrc-p">example/qq</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-recoverydiag">
<div class="pcard-head"><code>recoverydiag</code><span class="ver">v0.2.0</span></div>
<p>快速检查/崩溃取证工具集:diag_triage(退出码/信号/存活粗分)、diag_db(config.db 完整性 + LLM 源解析校验)、diag_log_scan(日志签名命中)、diag_delta(last-good 快照 vs 现状 diff)、diag_loc(正交综合定位)。全部返回结论而非原文,确定性、不消耗 LLM token,供 guard / failback 恢复决策使用。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/recoverydiag" target="_blank" rel="noopener noreferrer" title="SDK example/recoverydiag"><span class="psrc-r">SDK</span><span class="psrc-p">example/recoverydiag</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-rss">
<div class="pcard-head"><code>rss</code><span class="ver">v1.1.0</span></div>
<p>RSS/Atom 订阅监控。按间隔轮询,发现新条目即以中断注入告知 agent;订阅时记下历史条目,所以订一个源不会把旧文章全推一遍。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/rss" target="_blank" rel="noopener noreferrer" title="SDK example/rss"><span class="psrc-r">SDK</span><span class="psrc-p">example/rss</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-sanitizer">
<div class="pcard-head"><code>sanitizer</code><span class="ver">v0.1.0</span></div>
<p>在三个阶段清洗文本:坏 UTF-8 / U+FFFD / ANSI 转义(会被模型复读)与 LLM 输出里的工具调用残留。不注册工具,只挂钩子。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/sanitizer" target="_blank" rel="noopener noreferrer" title="SDK example/sanitizer"><span class="psrc-r">SDK</span><span class="psrc-p">example/sanitizer</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-vanblog">
<div class="pcard-head"><code>vanblog</code><span class="ver">v1.0.0</span></div>
<p>管理 VanBlog 开源博客系统:文章的增删改查、分类标签管理、草稿发布、备份导出等</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/vanblog" target="_blank" rel="noopener noreferrer" title="SDK example/vanblog"><span class="psrc-r">SDK</span><span class="psrc-p">example/vanblog</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-vikunja">
<div class="pcard-head"><code>vikunja</code><span class="ver">v1.0.1</span></div>
<p>Vikunja 待办/任务管理:任务增删改查、项目与看板桶、标签、指派、评论、关联、附件、保存筛选器、团队与分享、通知、订阅、Webhook、时间跟踪、数据导入、实例管理;并附通用 API 直通工具兜底</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/vikunja" target="_blank" rel="noopener noreferrer" title="SDK example/vikunja"><span class="psrc-r">SDK</span><span class="psrc-p">example/vikunja</span><span class="psrc-out">↗</span></a>
</div>
<div class="pcard" id="p-weather">
<div class="pcard-head"><code>weather</code><span class="ver">v1.0.0</span></div>
<p>查实时天气与预报(wttr.in,无需 API Key)。可设默认城市;结果标记为不进记忆计算 —— 天气是易变数据,反复写进记忆只会挤占预算。</p>
<a class="psrc" href="https://gitcode.com/JianFeeeee/homeagent-sdk/blob/main/example/weather" target="_blank" rel="noopener noreferrer" title="SDK example/weather"><span class="psrc-r">SDK</span><span class="psrc-p">example/weather</span><span class="psrc-out">↗</span></a>
</div>
</div>
</div>
</section>
<!-- ── FAQ ── -->
<section id="faq">
<div class="wrap">
<h2 class="reveal rise">常见问题</h2>
<p class="sub reveal rise" style="--d:90ms">最容易引起误解的六个地方。</p>
<div class="reveal rise">
<details class="faq">
<summary>「内核零 IO」是不是意味着内核很简单?</summary>
<p>
恰恰相反。零 IO 是为了让内核专注做难的部分:输入调度(四级中断、抢占与
现场保存)、上下文预算、三层记忆的蒸馏与召回。IO 挪出去之后,这些才有
清晰的所有权。
</p>
</details>
<details class="faq">
<summary>插件崩了会不会把 Agent 带崩?</summary>
<p>
不会。外部插件是独立子进程,与内核只通过 stdio JSON-RPC、共享内存段、
事件环通信。插件崩溃后,内核摘除它注册的工具、阶段与通道,按退避重启,
其余部分不受影响。
</p>
</details>
<details class="faq">
<summary>为什么消息会「排队」而不是立即处理?</summary>
<p>
因为同一时刻只应有一个任务在改上下文。同级抢占会让两边都做一半。
需要立刻插队时用更高级别的中断:插件可声明 L1–L3,L4 是内核保留的
「立即打断」(panic、内核事件),外部插件声明 L4 会被夹到 L3。
不该让所有消息都变成抢占者。
</p>
</details>
<details class="faq">
<summary>长任务会不会让用户等很久?</summary>
<p>
这正是「分诊助手」的用途。主 agent 忙超阈值且积压够多时,内核拉起临时
驻留子接手:能办的直接办,需要主 agent 的立刻回「忙碌中,请稍候」。
</p>
</details>
<details class="faq">
<summary>记忆会不会无限膨胀?</summary>
<p>
不会。三层是迁移而非复制:上下文事件裁剪后归档成文档,文档冷掉后蒸馏成
图谱三元组。图谱有去重与 UNIQUE 约束;媒体跟着记忆块走,块被永久删除时才回收字节。
</p>
</details>
<details class="faq">
<summary>能换 LLM 吗?</summary>
<p>
可以。内核不绑定厂商:每个 LLM 源对应一个 Lua 脚本负责请求与响应转换。
内置 9 个适配器(OpenAI、Anthropic、Gemini、DeepSeek、Groq、Mistral、
Ollama、GitHub、KimiCode),也可自己写一个接任意兼容 API。
</p>
</details>
</div>
</div>
</section>
</main>
<footer>
<div class="wrap">
<div class="cols">
<div>
<h4>文档</h4>
<ul>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/OVERVIEW.md">项目概览</a></li>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/ARCHITECTURE.md">技术架构</a></li>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/PLUGIN_DEV.md">插件开发</a></li>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/assets/docs/zh/ADAPTER.md">Lua 适配器</a></li>
</ul>
</div>
<div>
<h4>深入</h4>
<ul>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/docs/zh/input-scheduler-design.md">输入调度器设计</a></li>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/docs/zh/resident-subagent-design.md">驻留式子 Agent</a></li>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/blob/main/docs/zh/multimodal-space.md">统一多模态空间</a></li>
</ul>
</div>
<div>
<h4>项目</h4>
<ul>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent">源码仓库</a></li>
<li><a href="https://gitcode.com/JianFeeeee/HomeAgent/releases">发布下载</a></li>
<li><a href="https://gitcode.com/JianFeeeee/homeagent-sdk">插件 SDK</a></li>
</ul>
</div>
<div>
<h4>状态</h4>
<ul>
<li><span class="muted">最新发布 v1.3.x 线</span></li>
<li><span class="muted">main 在研 1.4.0</span></li>
<li><span class="muted">许可 AGPL-3.0-only</span></li>
</ul>
</div>
</div>
<div class="note">
<p style="margin: 0 0 0.4rem">
<strong>AI 辅助声明</strong>:本项目代码、文档与提交历史含 AI 生成或修改的内容。
关键改动经人工审阅,使用前请自行评估验证。
</p>
<p style="margin: 0">
AGPL-3.0-only。通过 HTTP 提供交互式服务时,须向使用者提供完整对应源码(§13)。
</p>
<p class="beian">
<a href="https://beian.miit.gov.cn" target="_blank" rel="noopener noreferrer">豫ICP备2024074105号-1</a>
<span class="beian-sep">·</span>
<a href="https://beian.mps.gov.cn" target="_blank" rel="noopener noreferrer">豫公网安备41070202001579号</a>
</p>
</div>
</div>
</footer>
<script>
/* ═══════════════════════════════════════════════════════════
动效引擎。设计原则(吃过一次亏,见 git 历史):
· **内容默认可读** —— 不用 JS 也完整;只有本脚本真的跑起来
才给 <html> 加 .js-fx 接管初始隐藏,因此 JS 报错/被禁用
时页面不会变成一片空白(早期版本把 opacity:0 写在默认
样式里,结果 30 个元素里 26 个永久不可见)。
· 尊重 prefers-reduced-motion(此时不动、不加 .js-fx)。
· 所有 listener 走 rAF 节流;粒子数按视口面积自适应。
═══════════════════════════════════════════════════════════ */
(function () {
var root = document.documentElement;
var reduce = window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* ── 主题切换 ── */
var toggle = document.getElementById("themeToggle");
function paint(isLight) {
root.setAttribute("data-theme", isLight ? "light" : "dark");
}
if (toggle) {
toggle.addEventListener("click", function () {
var isLight = root.getAttribute("data-theme") === "light";
paint(!isLight);
try { localStorage.setItem("ha-theme", !isLight ? "light" : "dark"); } catch (e) {}
});
}
// 系统主题变化时跟随(用户没手动选过才跟)
try {
var mq = window.matchMedia("(prefers-color-scheme: light)");
if (mq.addEventListener) {
mq.addEventListener("change", function (e) {
if (!localStorage.getItem("ha-theme")) paint(e.matches);
});
}
} catch (e) {}
/* ── 滚动进度条 ── */
var bar = document.getElementById("progress");
var ticking = false;
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(function () {
if (bar) {
var h = document.documentElement.scrollHeight - window.innerHeight;
bar.style.width = (h > 0 ? (window.scrollY / h) * 100 : 0) + "%";
}
ticking = false;
});
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ── 入场动画 ── */
if (!reduce) {
root.classList.add("js-fx");
var els = [].slice.call(document.querySelectorAll(
".rise, .rise-zoom, .reveal, section > .wrap > h2, .badges"));
function reveal(el) {
el.classList.add("in");
if (el.classList.contains("badges")) {
var bs = el.querySelectorAll(".badge");
for (var i = 0; i < bs.length; i++) {
bs[i].style.transitionDelay = (i * 28) + "ms";
}
}
}
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { reveal(e.target); io.unobserve(e.target); }
});
}, { threshold: 0.1, rootMargin: "0px 0px -6% 0px" });
els.forEach(function (el) { io.observe(el); });
} else {
els.forEach(reveal);
}
// 保险:布局因图片/字体迟到而变化时,已在视口内的补上
window.addEventListener("load", function () {
els.forEach(function (el) {
var r = el.getBoundingClientRect();
if (r.top < window.innerHeight && r.bottom > 0) reveal(el);
});
});
// 兜底:3 秒后无论 observer 是否生效,强制全部可见
setTimeout(function () { els.forEach(reveal); }, 3000);
}
/* ── 数字滚动 ── */
var stats = [].slice.call(document.querySelectorAll(".stat .n"));
function runCount(el) {
var raw = el.textContent.trim();
var m = raw.match(/^([\d.]+)(.*)$/);
if (!m) return;
var target = parseFloat(m[1]), suffix = m[2], dur = 1100, t0 = null;
// 小数位按原值保持(如 34.6)
var dec = (m[1].split(".")[1] || "").length;
function step(ts) {
if (!t0) t0 = ts;
var p = Math.min((ts - t0) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = (target * eased).toFixed(dec) + suffix;
if (p < 1) requestAnimationFrame(step);
else el.textContent = raw;
}
requestAnimationFrame(step);
}
if (!reduce && "IntersectionObserver" in window && stats.length) {
var sio = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { runCount(e.target); sio.unobserve(e.target); }
});
}, { threshold: 0.6 });
stats.forEach(function (el) { sio.observe(el); });
}
// 无 IO / reduce 时保留静态文本,不做数字动画
/* ── 卡片聚光 + 3D 微倾(仅精确指针;触屏不做) ── */
var fine = window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches;
if (!reduce && fine) {
var cards = [].slice.call(document.querySelectorAll(".card, .stat"));
cards.forEach(function (c) {
var raf = 0;
c.addEventListener("mousemove", function (ev) {
if (raf) return;
raf = requestAnimationFrame(function () {
var r = c.getBoundingClientRect();
var x = ev.clientX - r.left, y = ev.clientY - r.top;
c.style.setProperty("--mx", x + "px");
c.style.setProperty("--my", y + "px");
// 轻微倾斜:以中心为原点,最多 ±3.2°
var rx = ((y / r.height) - 0.5) * -4.2;
var ry = ((x / r.width) - 0.5) * 4.2;
c.style.transform =
"perspective(760px) rotateX(" + rx.toFixed(2) + "deg) rotateY(" +
ry.toFixed(2) + "deg) translateY(-2px)";
raf = 0;
});
});
c.addEventListener("mouseleave", function () {
if (raf) { cancelAnimationFrame(raf); raf = 0; }
c.style.transform = "";
});
});
}
/* ── 插件徽章:点击展开详情(可多开;「全部」按钮切全展开) ── */
(function () {
var badges = [].slice.call(document.querySelectorAll(".badge[data-plugin]"));
var hint = document.getElementById("phint");
var detail = document.getElementById("pdetail");
if (!badges.length || !detail) return;
function sync(btn) {
var id = "p-" + btn.dataset.plugin;
var card = document.getElementById(id);
var on = btn.getAttribute("aria-pressed") === "true";
if (card) card.classList.toggle("show", on);
}
badges.forEach(function (b) {
b.setAttribute("aria-pressed", "false");
b.addEventListener("click", function () {
var on = b.getAttribute("aria-pressed") === "true";
b.setAttribute("aria-pressed", on ? "false" : "true");
sync(b);
// 有任何一张打开就藏起提示
var any = badges.some(function (x) {
return x.getAttribute("aria-pressed") === "true";
});
if (hint) hint.style.display = any ? "none" : "";
// 首次打开时把详情滚进视口
if (!on) {
var c = document.getElementById("p-" + b.dataset.plugin);
if (c && c.getBoundingClientRect().top > window.innerHeight - 120) {
c.scrollIntoView({ behavior: reduce ? "auto" : "smooth", block: "center" });
}
}
});
});
// 「…共 N 个」按钮:一次全展开
var all = document.querySelector(".badge.muted");
if (all && !all.dataset.bound) {
all.dataset.bound = "1";
var expanded = false;
var total = badges.length;
all.setAttribute("role", "button");
all.setAttribute("tabindex", "0");
all.textContent = "展开全部 " + total + " 个";
function toggleAll() {
expanded = !expanded;
detail.classList.toggle("all", expanded);
badges.forEach(function (b) {
b.setAttribute("aria-pressed", expanded ? "true" : "false");
sync(b);
});
all.textContent = expanded ? "收起" : "展开全部 " + total + " 个";
if (hint) hint.style.display = expanded ? "none" : "";
}
all.addEventListener("click", toggleAll);
all.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggleAll(); }
});
}
})();
/* ── 粒子网络 ── */
var cv = document.getElementById("fx");
if (!reduce && cv && cv.getContext) {
var ctx = cv.getContext("2d");
var dpr = Math.min(window.devicePixelRatio || 1, 2);
var W = 0, H = 0, pts = [], rgb = "148, 190, 255", alive = true;
function themeColor() {
rgb = getComputedStyle(root).getPropertyValue("--particle").trim() ||
"148, 190, 255";
}
function resize() {
/* 用 clientWidth 而不是 innerWidth:后者含滚动条宽度(实测 390 vs 375),
画布按 390 铺开会多出 15px 并把整页撑出横向滚动条。 */
var de = document.documentElement;
W = de.clientWidth; H = de.clientHeight;
cv.width = Math.round(W * dpr); cv.height = Math.round(H * dpr);
cv.style.width = W + "px"; cv.style.height = H + "px";
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
// 密度按面积自适应,上限 72(再多就开始吃 CPU 了)
var n = Math.min(72, Math.round((W * H) / 26000));
pts = [];
for (var i = 0; i < n; i++) {
pts.push({
x: Math.random() * W, y: Math.random() * H,
vx: (Math.random() - 0.5) * 0.24,
vy: (Math.random() - 0.5) * 0.24,
r: Math.random() * 1.5 + 0.6
});
}
}
function frame() {
if (!alive) return;
ctx.clearRect(0, 0, W, H);
for (var i = 0; i < pts.length; i++) {
var p = pts[i];
p.x += p.vx; p.y += p.vy;
if (p.x < -20) p.x = W + 20; else if (p.x > W + 20) p.x = -20;
if (p.y < -20) p.y = H + 20; else if (p.y > H + 20) p.y = -20;
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, 6.2832);
ctx.fillStyle = "rgba(" + rgb + ", 0.5)";
ctx.fill();
}
// 近邻连线:距离 < 130 才连,透明度线性衰减
for (var a = 0; a < pts.length; a++) {
for (var b = a + 1; b < pts.length; b++) {
var dx = pts[a].x - pts[b].x, dy = pts[a].y - pts[b].y;
var d2 = dx * dx + dy * dy;
if (d2 < 16900) {
var o = (1 - Math.sqrt(d2) / 130) * 0.22;
ctx.strokeStyle = "rgba(" + rgb + ", " + o.toFixed(3) + ")";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pts[a].x, pts[a].y);
ctx.lineTo(pts[b].x, pts[b].y);
ctx.stroke();
}
}
}
requestAnimationFrame(frame);
}
themeColor(); resize(); frame();
var rraf = 0;
window.addEventListener("resize", function () {
if (rraf) return;
rraf = requestAnimationFrame(function () { resize(); rraf = 0; });
});
// 切主题时换粒子颜色
if (toggle) toggle.addEventListener("click", function () { setTimeout(themeColor, 30); });
// 标签页不可见时暂停(省电;回来再继续)
document.addEventListener("visibilitychange", function () {
if (document.hidden) { alive = false; }
else if (!alive) { alive = true; frame(); }
});
}
})();
</script>
</body>
</html>