add ch 20 (no Web UI) + ch 21 (real deploy LXC case study with 9 gotchas)
This commit is contained in:
334
index.html
334
index.html
@@ -222,6 +222,8 @@
|
||||
<li><a href="#v08">v0.8 重要变化</a></li>
|
||||
<li><a href="#docker">Docker 运行时</a></li>
|
||||
<li><a href="#takeaways">设计洞察</a></li>
|
||||
<li><a href="#no-ui">没有 Web UI 的真相</a></li>
|
||||
<li><a href="#real-deploy">部署实战 · LXC + Docker 双层隔离</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
@@ -943,6 +945,338 @@ metadata:
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ============================== -->
|
||||
<section id="no-ui">
|
||||
<h2><span class="num">20</span>没有 Web UI 的真相 · v0.7 → v0.8 拆分</h2>
|
||||
<p>
|
||||
读到这里你可能发现一件事 —— 本文讲了很多"platform adapter",
|
||||
但始终<strong>没讲一个网页聊天界面</strong>。这不是遗漏,是 v0.8 的事实:
|
||||
<strong>Hermes 上游 v0.8 已经把 Web UI 整个删掉了</strong>。
|
||||
</p>
|
||||
<h3>证据对比</h3>
|
||||
<table>
|
||||
<tr><th>版本</th><th>web/ 子项目</th><th>docker-compose.deploy.yml 有 hermes-web 服务?</th><th>Docker 镜像</th></tr>
|
||||
<tr><td>v0.7.x(2026-04-06 本地快照)</td><td>✅ 有</td><td>✅ 有(ports 4410:80)</td><td>hermes-agent-hermes-agent + hermes-agent-hermes-web(两个容器)</td></tr>
|
||||
<tr><td><strong>v0.8.0(本文分析版本)</strong></td><td>❌ <strong>整个被删</strong></td><td>❌ 连文件都没了</td><td>只剩 hermes-agent(单容器)</td></tr>
|
||||
</table>
|
||||
<p>
|
||||
v0.8.0 发布日:<strong>2026-04-08</strong>(见 <code>RELEASE_v0.8.0.md:3</code>)。
|
||||
</p>
|
||||
|
||||
<h3>那浏览器里能访问什么?</h3>
|
||||
<p>
|
||||
<code>gateway/platforms/api_server.py:1736-1754</code> 列出 v0.8 API Server 的**全部** 17 个路由
|
||||
(实际 grep <code>router\.add_</code> 得到):
|
||||
</p>
|
||||
<pre><code>GET /health
|
||||
GET /v1/health
|
||||
GET /v1/models <span class="g">← OpenAI 兼容 models 列表</span>
|
||||
POST /v1/chat/completions <span class="g">← OpenAI 兼容聊天(支持 stream)</span>
|
||||
POST /v1/responses <span class="g">← OpenAI Responses API 兼容</span>
|
||||
GET /v1/responses/{id}
|
||||
DEL /v1/responses/{id}
|
||||
|
||||
GET /api/jobs <span class="g">← cron 任务管理</span>
|
||||
POST /api/jobs
|
||||
GET /api/jobs/{id}
|
||||
PATCH /api/jobs/{id}
|
||||
DEL /api/jobs/{id}
|
||||
POST /api/jobs/{id}/pause
|
||||
POST /api/jobs/{id}/resume
|
||||
POST /api/jobs/{id}/run
|
||||
|
||||
POST /v1/runs
|
||||
GET /v1/runs/{run_id}/events <span class="g">← 结构化事件 SSE 流</span></code></pre>
|
||||
<p>
|
||||
<strong>0 个 HTML 路由,0 个 static 路由,0 个 template 渲染</strong>。
|
||||
你在浏览器里访问根路径 <code>/</code> 会直接得到 404,因为 API Server 没有配 root handler。
|
||||
</p>
|
||||
|
||||
<h3>仓库里还有静态网页,但 Hermes 不 serve 它们</h3>
|
||||
<p>
|
||||
v0.8 仓库里确实还有两个 HTML 相关目录,但都<strong>不会</strong>被 Hermes 运行时加载:
|
||||
</p>
|
||||
<table>
|
||||
<tr><th>目录</th><th>内容</th><th>用途</th></tr>
|
||||
<tr><td><code>landingpage/</code></td><td>665 行 HTML + 521 JS + 1178 CSS + banner/icons</td><td>Nous 官网营销页(hermes-agent.nousresearch.com)— 拉人下载用,不是聊天 UI</td></tr>
|
||||
<tr><td><code>website/</code></td><td>Docusaurus 项目(docusaurus.config.ts + docs/)</td><td>官方文档站(hermes-agent.nousresearch.com/docs)— 静态生成,独立部署</td></tr>
|
||||
</table>
|
||||
<p>
|
||||
也就是说:<strong>这两个目录是上游自己官网用的源码,不会被 Hermes 自己 serve,更不会跟着 docker compose 起来</strong>。
|
||||
</p>
|
||||
|
||||
<h3>那人们怎么跟 Hermes 聊?</h3>
|
||||
<p>
|
||||
v0.8 把"对话界面"完全交给三类外部入口:
|
||||
</p>
|
||||
<ol>
|
||||
<li><strong>CLI 终端</strong>(<code>hermes</code> 命令 + prompt_toolkit TUI)— 本地最直接</li>
|
||||
<li><strong>22 个消息平台</strong>(Telegram / Discord / Slack / WhatsApp / Signal / Email / Matrix / DingTalk / Feishu / WeCom / WeChat / ...)— Gateway 网关转发</li>
|
||||
<li><strong>REST API</strong>(<code>/v1/*</code> OpenAI 兼容)— 配合任意第三方前端
|
||||
(Open WebUI / LobeChat / 自建前端)</li>
|
||||
</ol>
|
||||
|
||||
<h3>为什么 v0.8 删了 Web UI(推测)</h3>
|
||||
<ul>
|
||||
<li>Web UI 生态已经很成熟(Open WebUI / LobeChat / AnythingLLM / ...),任意一个都比自建强</li>
|
||||
<li>删了之后维护面积减少,可以专心做 agent core + platform adapter + skills 这三件 Hermes 独有的事</li>
|
||||
<li>Hermes 既然已经是 OpenAI 兼容 API,第三方前端塞进去就能用,没必要自己造一个二流前端</li>
|
||||
<li>符合 UNIX 哲学:"做一件事做好",UI 不是 agent 核心能力</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout">
|
||||
<strong>所以</strong>:你直接浏览器打开 <code>hermes.milejoy.com</code>(我们部署的实例)默认是看不到聊天界面的 —
|
||||
看到的是我们自己写的介绍页 + 隔离层 Basic Auth。如果要聊天 UI,方案是<strong>自建一层聊天前端调用 /v1/*</strong>
|
||||
或者<strong>挂一个 Open WebUI 容器</strong>指向 Hermes API。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================== -->
|
||||
<section id="real-deploy">
|
||||
<h2><span class="num">21</span>部署实战 · 公司 VPS LXC + Docker 双层隔离</h2>
|
||||
<p>
|
||||
本文不光是源码解析,也是一次真部署的实录。目标是把 Hermes v0.8 跑到一台已经运行其他服务
|
||||
(LobeChat + PostgreSQL + RustFS 等)的公司 VPS 上,<strong>且不允许 Hermes 意外碰到邻居</strong>。
|
||||
以下是实际方案和踩过的坑,**每一条都来自真实 debug 现场**。
|
||||
</p>
|
||||
|
||||
<h3>最终架构</h3>
|
||||
<div class="ascii-art"> <span class="y">Internet</span>
|
||||
│
|
||||
<span class="b">Nginx + Let's Encrypt (443)</span>
|
||||
│ Basic Auth (boss/mile)
|
||||
│ proxy_set_header Authorization "Bearer …"
|
||||
▼
|
||||
<span class="b">┌──────────────────────────────────────────────────────┐</span>
|
||||
<span class="b">│</span> <span class="g">Incus LXC 容器 hermes-box (Debian 13)</span> <span class="b">│</span>
|
||||
<span class="b">│</span> security.privileged = true <span class="b">│</span>
|
||||
<span class="b">│</span> security.nesting = true <span class="b">│</span>
|
||||
<span class="b">│</span> cpu=4 · mem=6GB · 快照 fresh <span class="b">│</span>
|
||||
<span class="b">│</span> IP: 10.146.223.10 (incusbr0, NAT) <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">┌───────────────────────────────────────┐</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">│ Docker 容器 hermes-agent │</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">│ image: hermes-agent:latest (2.5GB) │</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">│ privileged + seccomp:unconfined │</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">│ apparmor:unconfined │</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">│ 0.0.0.0:8642 (API Server) │</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">│ Hermes v0.8 + Poe (custom provider) │</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">│ 78 bundled skills │</span> <span class="b">│</span>
|
||||
<span class="b">│</span> <span class="y">└───────────────────────────────────────┘</span> <span class="b">│</span>
|
||||
<span class="b">└──────────────────────────────────────────────────────┘</span>
|
||||
|
||||
<span class="g">LobeChat(同一宿主,不同 bridge 网络,完全隔离)</span></div>
|
||||
|
||||
<h3>为什么要 LXC(不只是 Docker)</h3>
|
||||
<p>
|
||||
直接 <code>docker run</code> 的问题是 — Docker 默认 bridge 和宿主网络之间有一定可见性,
|
||||
错误命令可以<strong>访问到邻居容器</strong>(LobeChat 的 PostgreSQL 端口)。我们想要的是:
|
||||
</p>
|
||||
<ol>
|
||||
<li><strong>独立 namespace</strong>:Hermes 看不到宿主进程、宿主文件系统、邻居容器</li>
|
||||
<li><strong>独立网络</strong>:Hermes 的 docker0 和宿主的 docker0 是<em>两条不同的 bridge</em></li>
|
||||
<li><strong>一键回滚</strong>:出事 <code>incus restore hermes-box fresh</code>,整个环境秒回基线</li>
|
||||
<li><strong>一键销毁</strong>:<code>incus delete hermes-box --force</code> 不留任何污染</li>
|
||||
<li><strong>运维栈一致</strong>:跟同一人之前的 HiClaw LXC 部署同栈,少一套技术要学</li>
|
||||
</ol>
|
||||
<p>
|
||||
Incus 选 Debian 13 作为 rootfs,<code>security.nesting=true</code> 允许容器内部再跑 Docker,
|
||||
形成<strong>LXC → Docker → Hermes runtime</strong> 三层。
|
||||
</p>
|
||||
|
||||
<h3>9 条踩坑速查</h3>
|
||||
<p>
|
||||
这不是抱怨清单,是给下一个想做同样事情的人节省 4 小时。
|
||||
</p>
|
||||
|
||||
<h4>坑 1 · 上游 Dockerfile 漏装 git</h4>
|
||||
<p>
|
||||
<code>Dockerfile:14-15</code> 的 apt 包列表:
|
||||
</p>
|
||||
<pre><code>build-essential nodejs npm python3 ripgrep ffmpeg gcc python3-dev libffi-dev procps</code></pre>
|
||||
<p>
|
||||
<strong>没有 git</strong>。但 <code>npm install</code> 阶段需要 git 拉 git 类型依赖,
|
||||
build 在第 17 步失败:<code>npm ERR! syscall spawn git / errno ENOENT</code>。
|
||||
</p>
|
||||
<p>
|
||||
<strong>修法</strong>:sed 补一行
|
||||
</p>
|
||||
<pre><code>sed -i 's/procps/procps git/' Dockerfile</code></pre>
|
||||
|
||||
<h4>坑 2 · docker-compose env_file 不支持行内注释</h4>
|
||||
<p>
|
||||
<code>.env</code> 里写:
|
||||
</p>
|
||||
<pre><code>TELEGRAM_BOT_TOKEN= # 等你从 @BotFather 拿到后填</code></pre>
|
||||
<p>
|
||||
结果 docker compose 把<em>整个 <code># 等你从 @BotFather 拿到后填</code></em> 当作 TOKEN 的值,
|
||||
Hermes 拿去尝试连 Telegram 时报 <code>telegram.error.InvalidToken</code>,容器 crash loop。
|
||||
</p>
|
||||
<p>
|
||||
<strong>修法</strong>:所有行内注释独立成一行。env_file 解析只认行首 <code>#</code>。
|
||||
</p>
|
||||
|
||||
<h4>坑 3 · API_SERVER 绑 0.0.0.0 必须 API_SERVER_KEY</h4>
|
||||
<p>
|
||||
日志:<code>Refusing to start: binding to 0.0.0.0 requires API_SERVER_KEY</code>。
|
||||
这是 Hermes 的硬约束(<code>gateway/platforms/api_server.py</code>),
|
||||
防止无 key 的 API server 对外暴露。
|
||||
</p>
|
||||
<pre><code>API_SERVER_KEY=$(openssl rand -hex 32)</code></pre>
|
||||
|
||||
<h4>坑 4 · docker compose restart 不重读 env_file</h4>
|
||||
<p>
|
||||
改了 <code>.env</code> 之后 <code>docker compose restart</code> 依然用老环境变量。必须 <code>down && up -d</code>
|
||||
才会重读。这是 Docker Compose 的设计,不是 bug,但容易让人白花 10 分钟 debug。
|
||||
</p>
|
||||
|
||||
<h4>坑 5 · Incus 容器 DHCP 拿不到 IPv4</h4>
|
||||
<p>
|
||||
<code>incus list</code> IPv4 列永远是空的,容器 eth0 只有 IPv6。
|
||||
理论上 <code>incusbr0</code> 的 dnsmasq 应该分配 IPv4,实测 DHCPDISCOVER 发出但无 OFFER 回来。
|
||||
可能跟宿主已有的 Docker FORWARD DROP + UFW 规则有关,<code>iptables -I FORWARD</code> 没修好。
|
||||
</p>
|
||||
<p>
|
||||
<strong>修法</strong>:跳过 DHCP,直接写静态 IP 到容器内的 <code>/etc/systemd/network/eth0.network</code>:
|
||||
</p>
|
||||
<pre><code>[Match]
|
||||
Name=eth0
|
||||
|
||||
[Network]
|
||||
Address=10.146.223.10/24
|
||||
Gateway=10.146.223.1
|
||||
DNS=8.8.8.8</code></pre>
|
||||
|
||||
<h4>坑 6 · Docker-in-LXC BuildKit 的 spawn sh EACCES</h4>
|
||||
<p>
|
||||
在 LXC 容器里跑 <code>docker compose build</code>,npm 某些原生包(better-sqlite3)postinstall 报:
|
||||
</p>
|
||||
<pre><code>npm ERR! code EACCES
|
||||
npm ERR! syscall spawn sh
|
||||
npm ERR! path /opt/hermes/node_modules/better-sqlite3</code></pre>
|
||||
<p>
|
||||
<strong>尝试无效的修法</strong>:<code>security.privileged=true</code>、<code>security.nesting=true</code>、
|
||||
<code>security.syscalls.intercept.*</code>、<code>raw.lxc: lxc.apparmor.profile=unconfined</code>、
|
||||
<code>DOCKER_BUILDKIT=0</code>(legacy builder 又被 Dockerfile 的 <code>--chmod</code> 卡住)。
|
||||
</p>
|
||||
<p>
|
||||
<strong>最终有效的修法</strong>:<strong>build 不在 LXC 里做</strong>。改为:
|
||||
</p>
|
||||
<ol>
|
||||
<li>宿主机直接 <code>docker build -t hermes-agent:latest ./source</code>(已知能成功)</li>
|
||||
<li><code>docker save hermes-agent:latest | gzip -1 > /tmp/hermes.tar.gz</code>(~2.5GB)</li>
|
||||
<li><code>incus file push /tmp/hermes.tar.gz hermes-box/tmp/</code></li>
|
||||
<li><code>incus exec hermes-box -- docker load -i /tmp/hermes.tar.gz</code></li>
|
||||
<li>LXC 内的 compose 从 <code>build:</code> 改为 <code>image: hermes-agent:latest</code></li>
|
||||
<li>build 完成后清宿主镜像 <code>docker rmi hermes-agent:latest</code>(保持宿主干净,只让 LXC 里有)</li>
|
||||
</ol>
|
||||
<p>
|
||||
Build 在宿主跑,运行时在 LXC,<strong>隔离边界一点没破</strong>。宿主只多了短暂的 tar 文件。
|
||||
</p>
|
||||
|
||||
<h4>坑 7 · Python socket.socketpair() 在 Hermes 启动时 EACCES</h4>
|
||||
<p>
|
||||
容器启动后立刻 crash,Python traceback:
|
||||
</p>
|
||||
<pre><code>File "/usr/lib/python3.13/asyncio/selector_events.py", line 120, in _make_self_pipe
|
||||
self._ssock, self._csock = socket.socketpair()
|
||||
PermissionError: [Errno 13] Permission denied</code></pre>
|
||||
<p>
|
||||
<strong>原因</strong>:Docker default seccomp 在 LXC 嵌套里拦了 <code>socketpair()</code>。
|
||||
</p>
|
||||
<p>
|
||||
<strong>修法</strong>:<code>docker-compose.yml</code> 给 Hermes 容器加:
|
||||
</p>
|
||||
<pre><code> privileged: true
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
- apparmor:unconfined</code></pre>
|
||||
|
||||
<h4>坑 8 · v0.8 官方 landing/website 路径变化</h4>
|
||||
<p>
|
||||
v0.7 的 <code>docker-compose.deploy.yml</code> 引用 <code>./web</code> 作为单独服务,
|
||||
v0.8 这个目录不存在了(见第 20 章)。如果你还用老 compose 文件 build,第一步就会:
|
||||
</p>
|
||||
<pre><code>unable to prepare context: path "./source/web" not found</code></pre>
|
||||
<p>
|
||||
<strong>修法</strong>:compose 文件只留 <code>hermes-agent</code> 服务,暴露 8642 端口即可。
|
||||
</p>
|
||||
|
||||
<h4>坑 9 · Hermes v0.8 默认拒绝浏览器 CORS</h4>
|
||||
<p>
|
||||
<strong>关键 debug 时刻</strong>。写完聊天 UI 部署上线后,用 curl 测 <code>/v1/chat/completions</code> 返回 200,
|
||||
但浏览器打开 UI 点发送按钮返回 <strong>HTTP 403</strong>。
|
||||
</p>
|
||||
<p>
|
||||
<strong>根因</strong>:<code>gateway/platforms/api_server.py:183-201</code> 的 CORS middleware 对带
|
||||
<code>Origin</code> header 的请求默认拒绝(curl 不带 Origin 能过,浏览器必带 Origin 被拦)。
|
||||
<code>_origin_allowed()</code> 在 <code>api_server.py:393-401</code>,检查 <code>self._cors_origins</code>:
|
||||
</p>
|
||||
<pre><code>def _origin_allowed(self, origin: str) -> bool:
|
||||
if not origin:
|
||||
return True # <span class="g">← curl 这类非浏览器,放行</span>
|
||||
if not self._cors_origins:
|
||||
return False # <span class="r">← 浏览器且未配允许列表,拦</span>
|
||||
return "*" in self._cors_origins or origin in self._cors_origins</code></pre>
|
||||
<p>
|
||||
<strong>修法</strong>:<code>.env</code> 加一行
|
||||
</p>
|
||||
<pre><code>API_SERVER_CORS_ORIGINS=https://hermes.milejoy.com</code></pre>
|
||||
<p>
|
||||
多个 origin 用逗号分隔。代码读 env 在 <code>api_server.py:322-323</code>。改完
|
||||
<code>docker compose down && docker compose up -d</code> 让 env 重读。
|
||||
</p>
|
||||
|
||||
<h3>安全影响分析</h3>
|
||||
<table>
|
||||
<tr><th>维度</th><th>有效的隔离</th><th>失去的保护</th></tr>
|
||||
<tr><td><strong>文件系统</strong></td><td>✅ Hermes 看不到宿主和邻居容器的 /opt</td><td>容器内 root 能 mount / chroot</td></tr>
|
||||
<tr><td><strong>进程</strong></td><td>✅ PID namespace 完全独立</td><td>—</td></tr>
|
||||
<tr><td><strong>网络</strong></td><td>✅ 独立 bridge 10.146.223.0/24,跟 LobeChat docker0 不互通;宿主 Nginx 反代是唯一入口</td><td>—</td></tr>
|
||||
<tr><td><strong>seccomp / AppArmor</strong></td><td>—</td><td>❌ 都 unconfined(为了让 socket.socketpair 能用)</td></tr>
|
||||
<tr><td><strong>kernel 漏洞</strong></td><td>—</td><td>❌ 共享宿主 kernel,kernel exploit 可穿透到宿主</td></tr>
|
||||
<tr><td><strong>销毁</strong></td><td>✅ <code>incus delete --force</code> 秒清整个容器 + docker 层 + volume</td><td>—</td></tr>
|
||||
</table>
|
||||
<p>
|
||||
对单用户内部场景<strong>够用</strong>。如果你要做 Manus 那种"每个用户独立 sandbox 防恶意 prompt"的场景,
|
||||
这套方案的 kernel 共享就是痛点 — 那时应该上 <strong>Firecracker / Kata Containers / gVisor</strong>,
|
||||
不是 LXC。
|
||||
</p>
|
||||
|
||||
<h3>跟 Manus(Firecracker)对比</h3>
|
||||
<table>
|
||||
<tr><th>维度</th><th>Manus 公共服务</th><th>Hermes 本次部署</th></tr>
|
||||
<tr><td>隔离技术</td><td>每 session 一个 Firecracker microVM</td><td>共用 LXC 容器(长命)</td></tr>
|
||||
<tr><td>启动时间</td><td>~1-2s(microVM 冷启)</td><td>~0.5s(LXC 容器本就启着)</td></tr>
|
||||
<tr><td>kernel 隔离</td><td>✅(独立 kernel)</td><td>❌(共享宿主 kernel)</td></tr>
|
||||
<tr><td>用户模型</td><td>多租户陌生人</td><td>单用户 / 内部团队</td></tr>
|
||||
<tr><td>成本模型</td><td>按 session 付费</td><td>固定资源预留</td></tr>
|
||||
<tr><td>运维复杂度</td><td>需要 orchestrator + session 生命周期管理</td><td><code>incus</code> 命令 + 快照</td></tr>
|
||||
</table>
|
||||
<p>
|
||||
结论:<strong>"威胁模型决定隔离技术"</strong>。不要看到 Manus 用 Firecracker 就觉得自己也得用 —
|
||||
先问自己这三个问题:
|
||||
</p>
|
||||
<ol>
|
||||
<li>会不会有陌生人用你的 agent?(不会 → LXC 就够)</li>
|
||||
<li>agent 会跑用户提供的代码吗?(不会 → LXC 就够)</li>
|
||||
<li>kernel exploit 是不是你的现实威胁?(不是 → LXC 就够)</li>
|
||||
</ol>
|
||||
|
||||
<h3>最终交付</h3>
|
||||
<p>
|
||||
部署好之后的公司实例:
|
||||
</p>
|
||||
<ul>
|
||||
<li>URL:<a href="https://hermes.milejoy.com" target="_blank" rel="noopener">hermes.milejoy.com</a></li>
|
||||
<li>访问:HTTP Basic Auth(两个账号:<code>boss</code> / <code>mile</code>)</li>
|
||||
<li>UI:自建<strong>玻璃拟态聊天前端</strong>(Cormorant Garamond 古金色 + 流式 SSE + Markdown + 工具进度 chip + localStorage 历史)</li>
|
||||
<li>API:<code>/v1/chat/completions</code> OpenAI 兼容,Nginx 自动注入 Bearer,浏览器零密钥</li>
|
||||
<li>后端:Poe GPT-5(主) + OpenRouter(备用)</li>
|
||||
<li>Skills:78 bundled + Hermes 自己的 agent 记忆 / 学习循环</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
· Hermes Agent v0.8.0 源码解析 · 2026-04-13 ·<br>
|
||||
源码取证:<code>~/Projects/research/20260413-hermes-source-analysis/source/</code>(git commit <code>d6785dc</code>)·<br>
|
||||
|
||||
Reference in New Issue
Block a user