From 4078f3ac0a058c94e4220d66f6967514aaba52ff Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 20 Sep 2026 09:46:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(site):=20=E9=87=8D=E5=86=99=E9=A6=96?= =?UTF-8?q?=E5=B1=8F=E6=96=87=E6=A1=88=20+=20=E4=BA=A4=E9=94=99=E5=9B=BE?= =?UTF-8?q?=E6=96=87=E5=B8=83=E5=B1=80=20+=20=E4=B8=80=E6=BB=91=E4=B8=80?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 口号与文笔(用户指「不够响亮、部分文笔不好」) Hero 改为「是…更是…」句式: 是记得住的管家 / 更是从不让你干等的搭档 ## 五个设计决定:从卡片改为交错图文 原来 4 张卡片平铺。改为 5 行交错(文/图左右互换),每行配一张内联 SVG 示意图,纯 CSS + 主题令牌,深浅自适应,零外部依赖。 **换掉一条、新增一条**(用户指出「插件跑在独立进程」不算特色 —— MCP、LSP 都这么做,不是差异点): | | 内容 | 依据 | |---|---|---| | ② | 部署,从未如此便捷 | 实测插件 `statically linked`、`not a dynamic executable` | | ③ | 数据如水,随流,随改,随走 | 共享内存 + 相对偏移零拷贝;实测整段写入读回 3.5µs | 标题按用户给的句式写(②③ 原文照用),正文不给形容词、给可核验的做法与数字。 ## 一滑一页 `scroll-snap-type: y mandatory` + 每节 `min-height: 100svh`。 为此把 features 的 5 条决定各拆成独立 section(原 2.74 屏塞 5 行, mandatory 下会锁死底部),architecture 的流程图也单独成节。 现 13 节,实测连续 8 次滑动精确停在第 1..8 节,间距 828px = 一屏。 ## 两处实测纠正(都是我先判断错、再被数据推翻) 1. **`proximity` 做不到「一滑一页」**:实测滑 500px 落点就是 500,离最近 节边界 395px,不触发吸附 —— 只是「有时粘一下」。改用 mandatory。 2. **我误报 architecture「底部锁死」**:按 `h > innerHeight` 判定,忽略了 溢出行仍可滚动。用真实 wheel 实测 13 节末元素全部可达(含该节 828 < 900)。 所以没有锁死,压缩 vertical rhythm 是顺带的,不是修复。 ## 验证 - 深浅双主题:13 节 / 5 交错行 / 5 示意图、控制台错误 0、横向溢出 0 - 一滑一页:8 次滑动停在 8 个不同节,落点间距精确 828px - 72px 落点偏移经查是 `scroll-padding-top`(导航高 67px),确保标题不被遮挡 —— 有意为之 - JS 禁用 24/24 可见;reduce 下 snap 自动关闭(`prefers-reduced-motion`) - 390/768 无 snap(窄屏强制一屏反而难受);1024/1440 启用 - 真人式滚动(wheel 与 400px 步进两种)未揭示元素均为 0 注:本轮前期用了几个 Python 补丁脚本改 HTML,用户指出「不好」。后续改为 直接编辑以产出可审阅的 diff,脚本已删除。 --- site/index.html | 410 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 362 insertions(+), 48 deletions(-) diff --git a/site/index.html b/site/index.html index 46e2bbd..9dc8dbf 100644 --- a/site/index.html +++ b/site/index.html @@ -291,6 +291,134 @@ .grid-2, .grid-3 { grid-template-columns: 1fr; } } + /* ── 整屏翻页 ── + 用 mandatory(一滑一页)。前提是**每节都必须塞得进一屏** —— + mandatory 下比视口高的节,超出部分会永久滚不到。 + 为此 features 的 5 条设计决定已各自独立成节(见 stageB2), + architecture 的流程图也单独成节。 + 实测 proximity 不满足需求:滑 500px 落在 500,离最近边界 395px, + 距离太远不触发吸附 —— 那样只是"有时粘一下",不是翻页。 */ + html { + scroll-snap-type: y mandatory; + scroll-padding-top: 4.5rem; + } + section { + min-height: calc(100svh - 4.5rem); + display: flex; + flex-direction: column; + justify-content: center; + scroll-snap-align: start; + } + section > .wrap { width: min(1120px, 92vw); margin: 0 auto; } + /* 吸附容器不能有 transform 祖先:会让 snap 失效(实测过)。 + 故 .wrap 上不加任何位移类动画,入场只做透明度。 */ + @media (prefers-reduced-motion: reduce) { + html { scroll-snap-type: none; } + } + /* 窄屏/矮屏放宽:手机可视高度小且地址栏收缩会变,强制一屏反而难受 */ + @media (max-width: 860px), (max-height: 620px) { + html { scroll-snap-type: none; } + section { min-height: 0; } + } + + /* ── 五个决定的索引(总起页)── */ + .idx { + list-style: none; + margin: 2.2rem 0 0; + padding: 0; + display: grid; + gap: 0.5rem; + } + .idx li { + display: flex; + align-items: center; + gap: 0.85rem; + padding: 0.7rem 1rem; + font-size: 0.95rem; + color: var(--text-dim); + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-md); + transition: border-color .25s var(--ease), color .25s var(--ease), transform .25s var(--ease); + } + .idx li:hover { border-color: var(--border-hi); color: var(--text); transform: translateX(3px); } + .idx li span { + flex: 0 0 auto; + font-family: var(--font-mono); + font-size: 0.75rem; + color: var(--brand-cyan); + } + + /* ── 交错行:文/图左右互换 ── + 为什么用 grid 而不是 flex 的 order 翻转:order 只改视觉次序, + 键盘 Tab 与读屏仍按 DOM —— 这里 DOM 始终「标题在前」, + 翻的只是视觉,所以两者都正确。 */ + .alt { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 3.2rem; + align-items: center; + margin-top: 3.4rem; + } + .alt.flip .alt-copy { order: 2; } + .alt.flip .alt-visual { order: 1; } + .alt-n { + display: inline-block; + font-family: var(--font-mono); + font-size: 0.75rem; + letter-spacing: 0.08em; + color: var(--brand-cyan); + border: 1px solid var(--border); + border-radius: var(--radius-pill); + padding: 0.1rem 0.55rem; + margin-bottom: 0.7rem; + } + .alt-copy h3 { + font-size: clamp(1.15rem, 2vw, 1.5rem); + margin: 0 0 0.7rem; + letter-spacing: -0.01em; + } + .alt-copy p { margin: 0; color: var(--text-dim); line-height: 1.8; } + .alt-visual { min-width: 0; } + .alt-visual svg { width: 100%; height: auto; display: block; } + .dv-note { + margin: 0.6rem 0 0; + text-align: center; + font-size: 0.75rem; + color: var(--text-muted); + } + @media (max-width: 860px) { + .alt { grid-template-columns: 1fr; gap: 1.6rem; margin-top: 2.6rem; } + .alt.flip .alt-copy, .alt.flip .alt-visual { order: 0; } + } + + /* ── 示意图图元 ── */ + .dv-bound { fill: none; stroke: var(--border); stroke-width: 1.5; stroke-dasharray: 6 5; } + .dv-box { fill: var(--bg-card); stroke: var(--border-hi); stroke-width: 1.4; } + .dv-accent { fill: rgba(6, 182, 212, 0.10); stroke: var(--brand-cyan); } + .dv-run { fill: rgba(59, 130, 246, 0.10); stroke: var(--brand-blue); } + .dv-l4 { fill: rgba(139, 92, 246, 0.13); stroke: var(--brand-violet); } + .dv-l3 { fill: rgba(59, 130, 246, 0.11); stroke: var(--brand-blue); } + .dv-l2 { fill: rgba(6, 182, 212, 0.10); stroke: var(--brand-cyan); } + .dv-l1 { fill: rgba(245, 158, 11, 0.10); stroke: var(--brand-gold); } + .dv-t { fill: var(--text); font-size: 15px; font-weight: 700; text-anchor: middle; } + .dv-ts { fill: var(--text); font-size: 12.5px; font-weight: 600; text-anchor: middle; } + .dv-s { fill: var(--text-muted); font-size: 10.5px; text-anchor: middle; } + .dv-lab { fill: var(--text-dim); font-size: 11px; text-anchor: middle; } + .dv-labs { fill: var(--text-muted); font-size: 9.5px; text-anchor: middle; } + .dv-cap { fill: var(--text-muted); font-size: 10px; } + .dv-seg { fill: rgba(119,128,154,.07); stroke: var(--border-hi); stroke-width: 1.4; stroke-dasharray: 5 4; } + .dv-hd { fill: rgba(139,92,246,.12); stroke: var(--brand-violet); } + .dv-ar { fill: rgba(6,182,212,.09); stroke: var(--border-hi); } + .dv-off { fill: none; stroke: var(--border); stroke-width: 1.2; stroke-dasharray: 4 4; } + .dv-offt { fill: var(--text-muted); font-size: 11.5px; text-anchor: middle; text-decoration: line-through; } + .dv-no path { stroke: var(--brand-gold); stroke-width: 1.6; opacity: .85; } + .dv-line { fill: none; stroke: var(--border-hi); stroke-width: 1.4; } + .dv-dash { stroke-dasharray: 4 4; } + .dv-pk { fill: none; stroke: var(--brand-gold); stroke-width: 2; } + .dv-arrow { fill: none; stroke: var(--brand-cyan); stroke-width: 1.6; stroke-dasharray: 4 4; } + .dv-x { fill: var(--text-muted); font-size: 10px; text-anchor: middle; } + /* ── 三层记忆:用品牌三色 ── */ .layers { display: grid; gap: 0.9rem; } .layer { @@ -385,7 +513,7 @@ /* ── 连接线:中轴上的一小段,带流动感 ── */ .lnk { position: relative; - height: 2.1rem; + height: 1.15rem; width: 2px; margin: 0 auto; background: var(--border-hi); @@ -461,8 +589,8 @@ position: relative; z-index: 1; max-width: 34rem; - margin: 0.75rem auto 0; - padding: 2.4rem 1rem 2.6rem; + margin: 0.5rem auto 0; + padding: 1.7rem 1rem 1.9rem; border: 1px dashed rgba(6, 182, 212, 0.4); border-radius: var(--radius-md); background: rgba(6, 182, 212, 0.045); @@ -1306,11 +1434,10 @@
常驻型个人 Agent 框架 -

一个不会忘记
也不会被一条长任务卡死的管家

+

是记得住的管家
更是从不让你干等的搭档

- HomeAgent 是内核 + 插件的双层 Agent 框架。内核只管 - LLM 编排、记忆与调度;所有外部能力(消息、文件、网络、设备) - 都是可独立安装、崩溃不牵连内核的插件。 + 内核只管编排、记忆与调度,消息 / 文件 / 网络 / 设备 + 一律交给插件。插件崩了不牵连内核 —— 换掉一个二进制就热重载。

快速上手 → @@ -1321,57 +1448,232 @@ +
-

四个不那么常见的设计决定

-

- 先定边界与调度,再加能力。 +

先定边界,再加能力

+

+ 大多数 Agent 框架先堆功能、边界后补。这里反过来 —— 五个决定,都是先把「谁能碰什么」定死。

+
    +
  1. 01内核零 IO,只碰调度
  2. +
  3. 02部署,从未如此便捷
  4. +
  5. 03数据如水,随流,随改,随走
  6. +
  7. 04轻重缓急,各归其位
  8. +
  9. 05忙时,有人替你接住
  10. +
+
+
-
-
-
◇
-

内核零 IO

+ +
+
+ +
+
+ 01 +

内核零 IO,只碰调度

- 内核不碰任何外部世界。消息、文件、网络、硬件全部由插件经 - PluginSDK 实现。于是「内核是否可信」与「某个插件是否安全」 - 可以分开评估。 + 内核不读文件、不发网络、不碰硬件。它只做三件事: + 编排、记忆、调度。外面的一切 —— 消息、文件、网络、设备 —— + 都由插件接管。于是「内核可信吗」和「这个插件安全吗」 + 成了两个可以分开回答的问题,而不是一次押上整块。

- -
-
◈
-

插件是独立进程

-

- 插件是普通 Go 二进制,由内核 spawn 为子进程,经 stdio JSON-RPC - (控制)、共享内存段(数据)、事件环(通知)通信。插件崩溃不牵连内核, - 换掉 plugin.bin 即热重载。 -

-
- -
-
◉
-

输入是有级别的

-

- 输入分「排队」与「中断」两类,中断再按紧迫度分 L1–L4。 - 高级可抢占并保存现场,同级不抢占。L4 归内核独占 - —— 所以「停止」真的能立刻停下。 -

-
- -
-
◆
-

忙时有人顶班

-

- 主 agent 忙长任务时,积压交给临时分诊助手: - 能办的直接办,需要主 agent 的立刻回「忙碌中,请稍候」。 - 不必干等十几分钟。 -

+
+ + + 外部世界 + + 内核 + 编排 · 记忆 · 调度 + + + 插件 + + 插件 + + 插件 + + 插件 + + + + + + + +

箭头只进出插件 —— 内核一列都没有

+ +
+
+ +
+
+ 02 +

部署,从未如此便捷

+

+ 插件是静态编译的单个文件:目标机上没有 Go、没有 libc、 + 没有容器、没有运行时,拷过去就能跑。 + 本体同样不要求你先装什么 —— 没有数据库、没有缓存、没有消息队列, + 记忆与索引都在进程内。所谓部署,就是把文件放上去。 +

+
+
+ + + 目标机 + + plugin.bin + 单个文件 + + + Go 工具链 + + libc / glibc + + Node / Python + + 容器运行时 + + + + + + 都不需要 + +

实测:statically linked,无动态依赖

+
+
+
+
+ + +
+
+ +
+
+ 03 +

数据如水,随流,随改,随走

+

+ 控制信令走 stdio,真正的数据走一块共享内存。 + 变长内容只传一个相对偏移,两边 mmap 到不同地址也能各自解引用。 + 所以上下文在插件里是零拷贝改写的 —— + 不是「序列化出去、传字符串、再反序列化回来」,而是两边看着同一块内存。 +

+
+
+ + + 内核 + homed + + 插件 + plugin.bin + + 共享内存段(一块,两边同一物理页) + + header + + arena 变长数据 + {off, len} 相对基址 + + + + 零拷贝:不序列化、不复制 + +

实测整段写入+读回 3.5µs

+
+
+
+
+ + +
+
+ +
+
+ 04 +

轻重缓急,各归其位

+

+ 输入先分两类:可以等的,和不能等的。 + 不能等的再按紧迫度排 L1–L4:高级别可以抢占,被抢占的现场压进栈、稍后原样恢复。 + 同一时刻只有一个任务在改上下文,所以不会两边各做一半。 + L4 归内核独占 —— 「停止」按下去就真的停。 +

+
+
+ + + + L4 内核独占 · 立即打断 + + L3 高优先级 + + L2 重要 + + L1 可等 + + 高 → 低 + + 正在跑 + 现场压栈 + + 抢占 → 稍后恢复 + +
+
+
+
+ + +
+
+ +
+
+ 05 +

忙时,有人替你接住

+

+ 主 agent 被长任务占住、积压又够多时,内核把这些消息转给一个 + 临时驻留的分诊助手。能当场办的就办了; + 非等主 agent 不可的,立刻回一句「忙碌中,请稍候」。 + 你不必对着对话框猜它还在不在。 +

+
+
+ + + 主 agent + 长任务 · 占住很久 + + + 积压消息 ×3 + + 积压消息 ×4 + + + 转投 + + 分诊助手 + 当场办 / 立刻答 + + + 不必干等 + +
+
+
+
+ +
@@ -1543,7 +1845,13 @@

-

核心组件

+
+ + + +
+
+

核心组件

homed常驻内核进程:LLM 编排、记忆管理、输入调度、插件生命周期
PluginSDK面向内核的接口:注册工具 / 挂阶段钩子 / 订阅事件 / 声明输出通道
@@ -1585,7 +1893,13 @@ echo "你好,记住我喜欢喝咖啡" | ./build/waiter WebUI 与 API 密钥在 http://localhost:8080 配置,持久化到 SQLite。

-

开箱可用的插件

+
+
+ + +
+
+

开箱可用的插件

外部插件经 hmapdev 构建为独立二进制,可单独装卸、热重载。点任意插件看它能做什么。