agentus 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -50
- package/README.zh-CN.md +12 -42
- package/bin/agentus.mjs +4 -4
- package/docs/dev/01-dev-loop.md +1 -2
- package/docs/dev/02-verify.md +0 -1
- package/docs/dev/03-release.md +13 -0
- package/docs/dev/04-recover.md +6 -26
- package/docs/dev/05-agent-driven.md +1 -1
- package/docs/dev/README.md +4 -5
- package/docs/installation.md +76 -30
- package/docs/installation.zh-CN.md +68 -29
- package/package.json +10 -9
- package/packages/server/mock/agent.mjs +46 -0
- package/packages/server/src/acp/session-manager.ts +99 -28
- package/packages/server/src/index.ts +55 -63
- package/packages/server/src/notify/center.ts +118 -26
- package/packages/server/src/notify/types.ts +25 -0
- package/packages/server/src/settings.ts +40 -12
- package/packages/shared/src/index.ts +40 -0
- package/packages/web/dist/assets/index-D-RQzauE.css +32 -0
- package/packages/web/dist/assets/index-N0FnNa4-.js +119 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/sw.js +39 -0
- package/packages/web/dist/assets/index-CCvfL56d.css +0 -32
- package/packages/web/dist/assets/index-DPCbsWh9.js +0 -283
- package/scripts/make-cert.sh +0 -101
package/README.md
CHANGED
|
@@ -48,6 +48,11 @@ Hermes on your PATH; `hermes acp --check` self-checks) or `qodercli --acp` (`qod
|
|
|
48
48
|
first, else `newSession` fails with `-32000`). Without one the cockpit still runs and still
|
|
49
49
|
logs in — you just have no backend to start a session on.
|
|
50
50
|
|
|
51
|
+
A plain Hermes install drives sessions; the cockpit's own extras — per-session **thinking
|
|
52
|
+
depth**, a permission mode that survives a restart, the **context-budget** field — come from the
|
|
53
|
+
fork's integration build instead. That is a five-line install:
|
|
54
|
+
[§ Which Hermes to run](docs/installation.md#which-hermes-to-run).
|
|
55
|
+
|
|
51
56
|
The first slot you start will use the Hermes home **your own agent already uses**
|
|
52
57
|
(`~/.hermes`) unless the backend row names another one — see "HERMES_HOME" below.
|
|
53
58
|
|
|
@@ -73,7 +78,7 @@ stale, then serves. Use `AGENTUS_PORT=9000 npm start` to move the port.
|
|
|
73
78
|
### Where your state lives
|
|
74
79
|
|
|
75
80
|
`~/.agentus` by default — `agentus.sqlite` (sessions and transcript), `credentials.json`,
|
|
76
|
-
`auth.token` (the machine credential), `auth.secret`, `
|
|
81
|
+
`auth.token` (the machine credential), `auth.secret`, `settings.json`. It is
|
|
77
82
|
created 0700 on first boot, and the boot log prints it **and why that directory won**:
|
|
78
83
|
|
|
79
84
|
| order | rule |
|
|
@@ -111,8 +116,6 @@ layer never changes.
|
|
|
111
116
|
| `AGENTUS_HISTORY_PAGE` | `500` | transcript page size (also set small in tests to exercise paging) |
|
|
112
117
|
| `AGENTUS_TERM_PTY` | unset | `1` = run the workspace terminal through Python's stdlib `pty` (real tty; needs `python3`) instead of pipes |
|
|
113
118
|
| `AGENTUS_TERM_CMD` | unset | override the terminal command line entirely (e.g. `socat …`), space-separated |
|
|
114
|
-
| `AGENTUS_TLS_PORT` | `8443` when a cert exists, else off | **second listener, TLS** — the one a public tunnel points at (`0` disables it) |
|
|
115
|
-
| `AGENTUS_TLS_CERT` / `AGENTUS_TLS_KEY` | `<AGENTUS_DATA>/tls/{cert,key}.pem` | cert material for that listener (see `scripts/make-cert.sh`) |
|
|
116
119
|
| `AGENTUS_TTS_BASE_URL` | unset | OpenAI-compatible base for **server** speech synthesis (`…/v1`). Unset = browser voices only |
|
|
117
120
|
| `AGENTUS_TTS_API_KEY` / `AGENTUS_TTS_MODEL` / `AGENTUS_TTS_VOICE` | – / `tts-1` / `alloy` | ditto |
|
|
118
121
|
| `AGENTUS_STT_BASE_URL` | unset | OpenAI-compatible base for **server** transcription. Unset = browser recognition only |
|
|
@@ -228,57 +231,23 @@ Two things to know about a tunnel in front of this app:
|
|
|
228
231
|
the session cookie is issued `Secure` automatically (verified through that same tunnel),
|
|
229
232
|
so no config needed. The last hop inside your LAN stays plain HTTP.
|
|
230
233
|
|
|
231
|
-
#### TLS:
|
|
232
|
-
|
|
233
|
-
A plain TCP tunnel (SakuraFrp, safe-nat, `ssh -L`, most frp setups) forwards bytes — it
|
|
234
|
-
does **not** terminate TLS for you. Point one at `:8788` and the operator's password and
|
|
235
|
-
session cookie cross the internet in clear text. So the server can speak TLS itself:
|
|
234
|
+
#### TLS: terminated in front, not in-process
|
|
236
235
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
236
|
+
A plain TCP tunnel (SakuraFrp, safe-nat, `ssh -L`, most frp setups) forwards bytes — it does
|
|
237
|
+
**not** terminate TLS for you. Point one at `:8788` and the operator's password and session
|
|
238
|
+
cookie would cross the internet in clear text. So terminate TLS **in front of Agentus** — a
|
|
239
|
+
reverse proxy (Caddy/nginx) or a tunnel that does its own `public_tls` — and let this process
|
|
240
|
+
speak plain HTTP:
|
|
242
241
|
|
|
243
242
|
| port | speaks | for |
|
|
244
243
|
|---|---|---|
|
|
245
|
-
| `AGENTUS_PORT` (8788) | plain HTTP | LAN, loopback, `curl`, scripts — no cert warning, CI unchanged |
|
|
246
|
-
| `AGENTUS_TLS_PORT` (8443) | HTTPS (self-signed) | **the tunnel** — encrypts the public hop |
|
|
247
|
-
|
|
248
|
-
Same handler, same routes, same auth; only the socket differs. Two listeners beat both
|
|
249
|
-
alternatives: turning the single port into HTTPS would put a cert warning in front of your
|
|
250
|
-
own LAN usage (and break `curl` scripts), while sniffing the first byte on one port to
|
|
251
|
-
serve both hides whether a given visit was really encrypted.
|
|
252
|
-
|
|
253
|
-
**Retiring the warning on your devices.** With TLS on, the listener serves its own
|
|
254
|
-
certificate at `/cert.crt`:
|
|
255
|
-
|
|
256
|
-
```bash
|
|
257
|
-
https://<your-name>:<tls-port>/cert.crt # iOS: opens the profile installer directly
|
|
258
|
-
# Android: downloads it → Settings → Security → CA certificate
|
|
259
|
-
curl --cacert <AGENTUS_DATA>/tls/cert.pem https://<your-name>:<tls-port>/healthz # scripts, no -k
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Installed as a trusted root (iOS also needs *About → Certificate Trust Settings → enable*),
|
|
263
|
-
the browser stops asking and the address bar is clean. It is public material — the private
|
|
264
|
-
key never leaves `<AGENTUS_DATA>/tls/`.
|
|
265
|
-
|
|
266
|
-
The cert is **self-signed on purpose** — no CA issues for an unregistered domain or a bare
|
|
267
|
-
IP, so the browser shows "not private → proceed" once per device, and installing the issuer
|
|
268
|
-
(`/cert.crt`, above) retires even that. Keep the SAN to the name you type (**the DDNS name**,
|
|
269
|
-
not the public IP: a dynamic IP would need re-issuing, and a mismatch adds a second warning).
|
|
270
|
-
|
|
271
|
-
`make-cert.sh` issues **two** certs on purpose: a 10-year root (`ca.pem`, what devices
|
|
272
|
-
install) and a 390-day leaf (`cert.pem`, what the listener serves). Apple caps *server
|
|
273
|
-
certificate* validity at 398 days — a 10-year self-signed leaf is precisely what iOS refuses
|
|
274
|
-
*after* the user has been through the install dance. A root is not a server cert, so it can
|
|
275
|
-
live long; rotating the leaf (`--leaf-only`) never touches an installed device.
|
|
244
|
+
| `AGENTUS_PORT` (8788) | plain HTTP | LAN, loopback, `curl`, scripts — no cert warning, CI unchanged; **the only listener** |
|
|
276
245
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
246
|
+
Certificate lifecycle (issuing, rotation, SAN) stays in that front layer and never touches the
|
|
247
|
+
app, and the LAN path stays warning-free. Nothing is lost on the cookie side either: the handler
|
|
248
|
+
reads `X-Forwarded-Proto` (`packages/server/src/auth.ts`), so a request that arrived over TLS at
|
|
249
|
+
the front layer still gets a `Secure` session cookie — Caddy and nginx both set that header by
|
|
250
|
+
default.
|
|
282
251
|
|
|
283
252
|
Logout revokes the session id server-side, so "sign out" ends the session instead of
|
|
284
253
|
just hiding the UI — the cookie stops working immediately.
|
|
@@ -449,7 +418,6 @@ npm run auth-smoke # 48 assertions: the lock, both creden
|
|
|
449
418
|
npm run backend-smoke # 42: the backend registry, spawn env, health evidence
|
|
450
419
|
npm run workspace-smoke # 85: workspace field, file API, shell, attachments
|
|
451
420
|
npm run voice-smoke # 47: settings/theme guards, voice router, 百炼 shapes
|
|
452
|
-
npm run tls-smoke # the second listener, real sockets + real certs
|
|
453
421
|
npm run package-smoke # packs, installs into a throwaway prefix + HOME, boots
|
|
454
422
|
# that install, drives a mock turn, checks ~/.agentus
|
|
455
423
|
node scripts/smoke.mjs mock "hello" # end-to-end against the mock agent
|
package/README.zh-CN.md
CHANGED
|
@@ -43,6 +43,10 @@ web bundle、配套 APK —— 然后直接退出,不启动任何东西)。
|
|
|
43
43
|
可自检)或 `qodercli --acp`(先 `qodercli login`,否则 `newSession` 会以 `-32000` 失败)。没有它驾驶舱
|
|
44
44
|
照样能跑、照样能登录 —— 只是没有后端可以开会话。
|
|
45
45
|
|
|
46
|
+
裸装的 Hermes 就能跑会话;驾驶舱自己的那几项能力 —— 按会话切换的**思考深度**、跨重启存活的**权限模式**、
|
|
47
|
+
**上下文预算**输入框 —— 来自 fork 的集成构建(tag)。装法就五行:
|
|
48
|
+
[§ 该跑哪份 Hermes](docs/installation.zh-CN.md#该跑哪份-hermes)。
|
|
49
|
+
|
|
46
50
|
你启动的第一个 slot 会使用**你自己的 agent 已经在用的**那个 Hermes home(`~/.hermes`),除非后端那一行
|
|
47
51
|
指定了另一个 —— 见下面的「HERMES_HOME」。
|
|
48
52
|
|
|
@@ -66,7 +70,7 @@ npm run agentus # the same CLI as `npx agentus`, straight from the
|
|
|
66
70
|
### 状态放在哪
|
|
67
71
|
|
|
68
72
|
默认 `~/.agentus` —— 里面有 `agentus.sqlite`(会话与 transcript)、`credentials.json`、`auth.token`
|
|
69
|
-
(机器凭证)、`auth.secret`、`
|
|
73
|
+
(机器凭证)、`auth.secret`、`settings.json`。首次启动时以 0700 创建,启动日志会打印它**以及
|
|
70
74
|
这个目录获胜的原因**:
|
|
71
75
|
|
|
72
76
|
| 顺序 | 规则 |
|
|
@@ -102,8 +106,6 @@ npm run agentus # the same CLI as `npx agentus`, straight from the
|
|
|
102
106
|
| `AGENTUS_HISTORY_PAGE` | `500` | transcript 分页大小(测试里也会调小以覆盖分页路径) |
|
|
103
107
|
| `AGENTUS_TERM_PTY` | 未设置 | `1` = 工作区终端走 Python 标准库 `pty`(真 tty;需要 `python3`)而不是管道 |
|
|
104
108
|
| `AGENTUS_TERM_CMD` | 未设置 | 完全覆盖终端命令行(例如 `socat …`),空格分隔 |
|
|
105
|
-
| `AGENTUS_TLS_PORT` | 存在证书时 `8443`,否则关闭 | **第二个监听器,TLS** —— 公网隧道指向的那个(`0` 关闭它) |
|
|
106
|
-
| `AGENTUS_TLS_CERT` / `AGENTUS_TLS_KEY` | `<AGENTUS_DATA>/tls/{cert,key}.pem` | 该监听器的证书材料(见 `scripts/make-cert.sh`) |
|
|
107
109
|
| `AGENTUS_TTS_BASE_URL` | 未设置 | **服务端**语音合成的 OpenAI 兼容 base(`…/v1`)。未设置 = 只用浏览器语音 |
|
|
108
110
|
| `AGENTUS_TTS_API_KEY` / `AGENTUS_TTS_MODEL` / `AGENTUS_TTS_VOICE` | – / `tts-1` / `alloy` | 同上 |
|
|
109
111
|
| `AGENTUS_STT_BASE_URL` | 未设置 | **服务端**转写的 OpenAI 兼容 base。未设置 = 只用浏览器识别 |
|
|
@@ -202,50 +204,19 @@ cookie 用 `HttpOnly` 而不是 `localStorage` 里的 token,并且它会顺带
|
|
|
202
204
|
- **在边缘终结 TLS。** 当请求带着 `X-Forwarded-Proto: https` 到达时,session cookie 会自动以 `Secure`
|
|
203
205
|
下发(在同一隧道上实测过),所以无需配置。你 LAN 内的最后一跳仍然是纯 HTTP。
|
|
204
206
|
|
|
205
|
-
#### TLS
|
|
207
|
+
#### TLS:在前置层终结,而不是在进程内
|
|
206
208
|
|
|
207
209
|
纯 TCP 隧道(SakuraFrp、safe-nat、`ssh -L`、大多数 frp 配置)只转发字节 —— 它**不会**替你终结 TLS。
|
|
208
|
-
把它指向 `:8788`,运维者的密码和 session cookie
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
scripts/make-cert.sh # self-signed, SAN = the name you actually type
|
|
212
|
-
# -> $AGENTUS_DATA/tls/{cert.pem,key.pem} (0600, git-ignored) — ~/.agentus/tls by default
|
|
213
|
-
# boot log then says: https://0.0.0.0:8443 (self-signed …) ; point a tunnel at THIS port
|
|
214
|
-
```
|
|
210
|
+
把它指向 `:8788`,运维者的密码和 session cookie 就会以明文穿过互联网。所以要在 Agentus **前面**终结
|
|
211
|
+
TLS:放一层反代(Caddy / nginx),或者用自带 `public_tls` 的隧道;本进程自己只讲纯 HTTP:
|
|
215
212
|
|
|
216
213
|
| 端口 | 讲什么 | 给谁 |
|
|
217
214
|
|---|---|---|
|
|
218
|
-
| `AGENTUS_PORT` (8788) | 纯 HTTP | LAN、loopback、`curl`、脚本 —— 无证书警告,CI
|
|
219
|
-
| `AGENTUS_TLS_PORT` (8443) | HTTPS(自签名) | **隧道** —— 加密公网那一跳 |
|
|
220
|
-
|
|
221
|
-
同一套 handler,同一套路由,同一套认证;只有 socket 不同。两个监听器胜过另外两种做法:把单端口变成
|
|
222
|
-
HTTPS 会把证书警告挡在你自己 LAN 的使用前面(并弄坏 `curl` 脚本),而在一个端口上嗅探首字节来同时服
|
|
223
|
-
务两者会掩盖一次访问到底有没有被加密。
|
|
224
|
-
|
|
225
|
-
**在你的设备上退掉那个警告。** 开着 TLS 时,监听器在 `/cert.crt` 提供自己的证书:
|
|
226
|
-
|
|
227
|
-
```bash
|
|
228
|
-
https://<your-name>:<tls-port>/cert.crt # iOS: opens the profile installer directly
|
|
229
|
-
# Android: downloads it → Settings → Security → CA certificate
|
|
230
|
-
curl --cacert <AGENTUS_DATA>/tls/cert.pem https://<your-name>:<tls-port>/healthz # scripts, no -k
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
装成受信任的根证书后(iOS 还需要 *关于 → 证书信任设置 → 启用*),浏览器就不再追问,地址栏也就干净了。
|
|
234
|
-
它是公开材料 —— 私钥永远不离开 `<AGENTUS_DATA>/tls/`。
|
|
235
|
-
|
|
236
|
-
证书是**刻意自签名**的 —— 对一个未注册的域名或裸 IP,没有 CA 会签发,所以浏览器每台设备显示一次「不
|
|
237
|
-
是私密连接 → 继续」,而装上签发者(`/cert.crt`,见上)连这个都能退掉。把 SAN 保持为你实际输入的名字
|
|
238
|
-
(**DDNS 名**,而不是公网 IP:动态 IP 会需要重签,而名字不匹配会多一个警告)。
|
|
239
|
-
|
|
240
|
-
`make-cert.sh` 刻意签发**两张**证书:一张 10 年根证书(`ca.pem`,设备装的就是它)和一张 390 天的叶子
|
|
241
|
-
证书(`cert.pem`,监听器提供的就是它)。Apple 把*服务器证书*有效期上限定在 398 天 —— 一张 10 年的自
|
|
242
|
-
签名叶子证书恰恰是 iOS 在用户走完那套安装流程*之后*会拒绝的东西。根证书不是服务器证书,所以可以活得
|
|
243
|
-
久;轮换叶子证书(`--leaf-only`)永远不碰已经装好的设备。
|
|
215
|
+
| `AGENTUS_PORT` (8788) | 纯 HTTP | LAN、loopback、`curl`、脚本 —— 无证书警告,CI 不变;**唯一的监听** |
|
|
244
216
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
LAN 端口必须保持明文。
|
|
217
|
+
证书的生命周期(签发、轮换、SAN)留在前置层里,永远不碰应用;LAN 这条路也就没有证书警告。cookie 这边
|
|
218
|
+
也没有损失:handler 读 `X-Forwarded-Proto`(`packages/server/src/auth.ts`),所以凡是在前置层走了 TLS
|
|
219
|
+
的请求,照样下发 `Secure` 的 session cookie —— Caddy 和 nginx 默认都会带上这个头。
|
|
249
220
|
|
|
250
221
|
登出会在服务端撤销 session id,所以「登出」是结束会话,而不只是藏起 UI —— cookie 立刻失效。
|
|
251
222
|
|
|
@@ -373,7 +344,6 @@ npm run auth-smoke # 48 assertions: the lock, both creden
|
|
|
373
344
|
npm run backend-smoke # 42: the backend registry, spawn env, health evidence
|
|
374
345
|
npm run workspace-smoke # 85: workspace field, file API, shell, attachments
|
|
375
346
|
npm run voice-smoke # 47: settings/theme guards, voice router, 百炼 shapes
|
|
376
|
-
npm run tls-smoke # the second listener, real sockets + real certs
|
|
377
347
|
npm run package-smoke # packs, installs into a throwaway prefix + HOME, boots
|
|
378
348
|
# that install, drives a mock turn, checks ~/.agentus
|
|
379
349
|
node scripts/smoke.mjs mock "hello" # end-to-end against the mock agent
|
package/bin/agentus.mjs
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* package alike, so `npm i -g agentus` never needs a compiler on the user's machine.
|
|
15
15
|
*
|
|
16
16
|
* WHY `--where` RUNS THE SERVER instead of computing paths here: where the data lives is
|
|
17
|
-
* decided in exactly one place (`packages/server/src/index.ts
|
|
18
|
-
*
|
|
17
|
+
* decided in exactly one place (`packages/server/src/index.ts`). A second
|
|
18
|
+
* implementation here would be a second answer to "where
|
|
19
19
|
* did my sessions go" — the one question in this project that must never have two
|
|
20
20
|
* answers. With AGENTUS_PRINT_PATHS=1 the server prints its own resolution (data dir,
|
|
21
21
|
* web bundle, companion APK, node version) and exits before it opens anything.
|
|
@@ -37,14 +37,14 @@ Usage
|
|
|
37
37
|
|
|
38
38
|
Options
|
|
39
39
|
-p, --port <n> port to listen on (AGENTUS_PORT, default 8788)
|
|
40
|
-
-d, --data <dir> where state lives — sqlite, login,
|
|
40
|
+
-d, --data <dir> where state lives — sqlite, login, settings
|
|
41
41
|
(AGENTUS_DATA, default ~/.agentus)
|
|
42
42
|
-o, --open open the cockpit in your browser once it is up
|
|
43
43
|
--where print the resolved paths and exit (starts nothing)
|
|
44
44
|
-h, --help this text
|
|
45
45
|
-v, --version print the version
|
|
46
46
|
|
|
47
|
-
The server reads more environment variables than these (agent backends, auth,
|
|
47
|
+
The server reads more environment variables than these (agent backends, auth,
|
|
48
48
|
speech). They are listed in the README, and the boot log prints the ones in play.
|
|
49
49
|
|
|
50
50
|
Requirements
|
package/docs/dev/01-dev-loop.md
CHANGED
|
@@ -12,7 +12,6 @@ instances:
|
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| path | `worktrees/agentus` | `worktrees/agentus-dev` |
|
|
14
14
|
| HTTP port | `8788` | `8901` |
|
|
15
|
-
| TLS port | `8443` | disabled (`AGENTUS_TLS_PORT=0`) |
|
|
16
15
|
| data dir | `<tree>/packages/server/.data` | `/tmp/agentus-qa-account` |
|
|
17
16
|
| started by | `~/.hermes/cache/agentus/launch.py` | `bash scripts/dev.sh start` |
|
|
18
17
|
| watcher | no (one process, no reload) | yes (`tsx watch`) |
|
|
@@ -101,7 +100,7 @@ live. Two independent knobs if you need more:
|
|
|
101
100
|
- **The store** — override `AGENTUS_DATA` (as `dev.sh` does). A throwaway instance is one
|
|
102
101
|
line:
|
|
103
102
|
```bash
|
|
104
|
-
AGENTUS_PORT=8907
|
|
103
|
+
AGENTUS_PORT=8907 AGENTUS_DATA=/tmp/<name> npx tsx packages/server/src/index.ts
|
|
105
104
|
```
|
|
106
105
|
Note the server reads `AGENTUS_PORT`, not bare `PORT` — and this box exports an ambient
|
|
107
106
|
`PORT=8648` (Hermes Studio), so a script that reads the ambient value starts a retry loop
|
package/docs/dev/02-verify.md
CHANGED
|
@@ -36,7 +36,6 @@ node scripts/auth-smoke.mjs # the lock, both creden
|
|
|
36
36
|
node scripts/backend-registry-smoke.mjs # backend registry, spawn env, health (~42)
|
|
37
37
|
node scripts/workspace-smoke.mjs # workspace, file API, shell, attachments (~85)
|
|
38
38
|
node scripts/voice-smoke.mjs # settings/theme guards, voice router (~47)
|
|
39
|
-
npm run tls-smoke # the TLS listener, real sockets + real certs
|
|
40
39
|
npm run package-smoke # pack + install into a throwaway HOME + boot
|
|
41
40
|
node scripts/smoke.mjs mock "hello" # end-to-end against the mock agent
|
|
42
41
|
```
|
package/docs/dev/03-release.md
CHANGED
|
@@ -195,6 +195,19 @@ git branch --set-upstream-to=origin/main main
|
|
|
195
195
|
Judge the outcome by identity, not by "the push succeeded": `git ls-remote origin
|
|
196
196
|
refs/heads/main` must equal the commit live is actually serving.
|
|
197
197
|
|
|
198
|
+
> **Do not merge into the live tree before promoting.** `promote` decides whether to restart by
|
|
199
|
+
> diffing **the live tree's old tip** against the commit it is promoting. If the live tree already
|
|
200
|
+
> sits on that commit (e.g. someone ran `git merge --ff-only <dev-branch>` inside
|
|
201
|
+
> `worktrees/agentus`), the diff is empty: promote prints `no server-side change`, ships the new web
|
|
202
|
+
> bundle, and **leaves the old server process running** — a half release that looks green. Restart
|
|
203
|
+
> detection is only as good as the tree it reads, so move `main` through the ref (`git push
|
|
204
|
+
> origin <dev-branch>:main`), never by checking it out in the live worktree.
|
|
205
|
+
>
|
|
206
|
+
> If it already happened: the release is not done. Re-run the restart explicitly
|
|
207
|
+
> (`scripts/relaunch.sh --port <live> --tree <live tree> --launcher <launcher> --launchd <label>`)
|
|
208
|
+
> and then resume the slots promote captured, exactly as promote's step 6 would have. Prove it by
|
|
209
|
+
> the startup log line for the new feature, not by the pid alone.
|
|
210
|
+
|
|
198
211
|
## Rollback
|
|
199
212
|
|
|
200
213
|
The release path has no separate rollback mechanism — you revert the change and promote
|
package/docs/dev/04-recover.md
CHANGED
|
@@ -6,13 +6,12 @@ I fix it without making the box worse?
|
|
|
6
6
|
## Read the boot log first
|
|
7
7
|
|
|
8
8
|
The boot log is the authoritative account of what the server resolved. It prints **which
|
|
9
|
-
data directory won and why
|
|
9
|
+
data directory won and why** and the auth state:
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
data dir: /Users/…/packages/server/.data (AGENTUS_DATA was set) ← the dir + the reason
|
|
13
13
|
auth on: user "admin"
|
|
14
|
-
|
|
15
|
-
tls off: no cert at <DATA_DIR>/tls/cert.pem ← absent/off = no TLS
|
|
14
|
+
http port: 8788 ← the only listener
|
|
16
15
|
```
|
|
17
16
|
|
|
18
17
|
If the directory it names is not the one you expected, stop here — a server serving the
|
|
@@ -24,7 +23,6 @@ empty. Triage the identity, not the liveness (see "Is it serving the wrong store
|
|
|
24
23
|
| port | who |
|
|
25
24
|
|---|---|
|
|
26
25
|
| `8788` | live HTTP |
|
|
27
|
-
| `8443` | live TLS (public tunnel points here) |
|
|
28
26
|
| `8901` | dev / QA instance |
|
|
29
27
|
| `8648` | **Hermes Studio** — not Agentus, do not touch |
|
|
30
28
|
|
|
@@ -82,14 +80,14 @@ file at that same path, and its server was the only way into the machine. Rules:
|
|
|
82
80
|
`spawn(..., { detached: true })` and kill the process group — rather than cleaning up by
|
|
83
81
|
name afterwards.
|
|
84
82
|
5. **Re-check the neighbours afterwards:** one `curl` per resident service
|
|
85
|
-
(`:8648` Studio, `:8788
|
|
83
|
+
(`:8648` Studio, `:8788` live, `:8901` dev). A cleanup that leaves the box worse
|
|
86
84
|
than it found it is the whole failure mode.
|
|
87
85
|
|
|
88
86
|
## The SQLite store, WAL, and the `*.bak` files
|
|
89
87
|
|
|
90
88
|
State lives in `<DATA_DIR>/agentus.sqlite` (the transcript and every other row), accessed
|
|
91
89
|
through Node's built-in `node:sqlite` (hence Node `>= 22.5`). Files beside it: `credentials.json`,
|
|
92
|
-
`auth.token`, `auth.secret`, `sessions.json`, `settings.json
|
|
90
|
+
`auth.token`, `auth.secret`, `sessions.json`, `settings.json` — several of them
|
|
93
91
|
`0600`. The server creates the directory `0700` on first boot.
|
|
94
92
|
|
|
95
93
|
**Back up before you touch the store.** A single write against the operator's real data gets
|
|
@@ -121,8 +119,8 @@ Two cautions that come from real incidents:
|
|
|
121
119
|
## Is it serving the wrong store?
|
|
122
120
|
|
|
123
121
|
Symptoms that look like separate bugs but share one cause: the operator's session list came
|
|
124
|
-
back empty
|
|
125
|
-
|
|
122
|
+
back empty and a running slot was reaped — yet the port answers 200 and `/api/version` looks
|
|
123
|
+
fine. Cause:
|
|
126
124
|
`relaunch → launch.py` inherited the QA environment. The launcher now pins its own
|
|
127
125
|
`AGENTUS_DATA`/`AGENTUS_PORT` and pops the QA/DEV keys, but verify identity directly:
|
|
128
126
|
|
|
@@ -172,24 +170,6 @@ all). Tunables: `AGENTUS_LOGIN_MAX_FAILS` (default 5) / `AGENTUS_LOGIN_LOCK_MS`
|
|
|
172
170
|
every other session; `AGENTUS_AUTH=off` disables the login entirely and the boot log scolds
|
|
173
171
|
you (do not leave it off — the listener is `0.0.0.0` and this cockpit is tunnelled).
|
|
174
172
|
|
|
175
|
-
## TLS certificate regeneration
|
|
176
|
-
|
|
177
|
-
The TLS listener runs on a **second port** (8443 live), pointed at by a plain-TCP tunnel.
|
|
178
|
-
Cert material lives at `<DATA_DIR>/tls/`. Regenerate with:
|
|
179
|
-
|
|
180
|
-
```bash
|
|
181
|
-
scripts/make-cert.sh [name] # default name: the DDNS name; SAN = that name + localhost + 127.0.0.1
|
|
182
|
-
scripts/make-cert.sh --leaf-only # rotate the leaf only; installed devices are untouched
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
`make-cert.sh` issues **two** certs on purpose: a 10-year root (`ca.pem`, what devices install
|
|
186
|
-
once, served at `/cert.crt`) and a 390-day leaf (`cert.pem`/`key.pem`, what the listener
|
|
187
|
-
serves, under Apple's 398-day cap). Keep the SAN to the DDNS name you actually type, not the
|
|
188
|
-
dynamic public IP. It writes into the same directory the server reads from (mirroring the
|
|
189
|
-
server's own resolution in `packages/server/src/index.ts`), and if that resolution and the
|
|
190
|
-
server's ever diverge the listener stays silently off with a cert sitting next to it — after
|
|
191
|
-
regenerating, confirm the boot log still prints `https://…:8443`.
|
|
192
|
-
|
|
193
173
|
## The process-exit safety net for children
|
|
194
174
|
|
|
195
175
|
The server installs `SIGINT`/`SIGTERM`/`SIGHUP` handlers that terminate the child agent
|
|
@@ -38,7 +38,7 @@ agent four things and it can work without guessing.
|
|
|
38
38
|
| auth | `packages/server/src/auth.ts` |
|
|
39
39
|
| web app | `packages/web/src/App.tsx`, `state.ts`, `transcript.ts` |
|
|
40
40
|
| mock agent (dev/QA) | `packages/server/mock/agent.mjs` |
|
|
41
|
-
| dev launcher | `scripts/dev.sh` · release: `scripts/relaunch.sh`
|
|
41
|
+
| dev launcher | `scripts/dev.sh` · release: `scripts/relaunch.sh` |
|
|
42
42
|
|
|
43
43
|
**2. The reference implementation to compare against.** Almost every UI question has a
|
|
44
44
|
sibling project that already answered it, and the operator expects behaviour parity, not
|
package/docs/dev/README.md
CHANGED
|
@@ -21,17 +21,16 @@ variables, and they must never touch each other's code or data:
|
|
|
21
21
|
| tree | `worktrees/agentus` | `worktrees/agentus-dev` |
|
|
22
22
|
| role | the instance the operator actually uses | where changes are made and tested |
|
|
23
23
|
| HTTP port | `8788` | `8901` |
|
|
24
|
-
| TLS port | `8443` | off (`AGENTUS_TLS_PORT=0`) |
|
|
25
24
|
| data dir | `<tree>/packages/server/.data` (pinned by the launcher) | `/tmp/agentus-qa-account` |
|
|
26
25
|
| launcher | `~/.hermes/cache/agentus/launch.py` | `scripts/dev.sh start\|stop\|status\|logs` |
|
|
27
26
|
| supervision | launchd job `ai.hermes.agentus` (`KeepAlive`) | none — a plain detached process |
|
|
28
27
|
| hot reload | **no** — restart is explicit | yes (`tsx watch`) |
|
|
29
28
|
|
|
30
|
-
**Ports are configuration, not literals.**
|
|
31
|
-
`~/.hermes/cache/agentus/ports.env`; `launch.py` pins
|
|
29
|
+
**Ports are configuration, not literals.** Live's port lives in
|
|
30
|
+
`~/.hermes/cache/agentus/ports.env`; `launch.py` pins it into the server's environment, and
|
|
32
31
|
`scripts/dev.sh` / `scripts/relaunch.sh` read the same file instead of hardcoding a number. Move
|
|
33
32
|
live by editing that one line and restarting it — never by hunting a literal through the launchers,
|
|
34
|
-
the scripts and the docs (that is how the port
|
|
33
|
+
the scripts and the docs (that is how the port drifted before).
|
|
35
34
|
|
|
36
35
|
**Live is supervised by launchd.** It used to be a detached child of whatever shell happened to
|
|
37
36
|
start it: nothing brought it back after a crash, and it inherited that shell's entire environment
|
|
@@ -84,5 +83,5 @@ the one exception is a strictly web-only change, and even that is bounded by wha
|
|
|
84
83
|
notification contract.
|
|
85
84
|
- [`docs/refs/`](../refs/) — notes on the reference UIs and ACP rules this project borrows
|
|
86
85
|
behaviour from.
|
|
87
|
-
- [`README.md`](../../README.md) — install, environment variables, login
|
|
86
|
+
- [`README.md`](../../README.md) — install, environment variables, login and the
|
|
88
87
|
public tunnel.
|
package/docs/installation.md
CHANGED
|
@@ -30,6 +30,62 @@ nvm install 22 && nvm use 22 # or
|
|
|
30
30
|
brew install node # or https://nodejs.org/en/download
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
### Which Hermes to run
|
|
34
|
+
|
|
35
|
+
Plain Hermes is enough to drive sessions. The capabilities this cockpit itself leans on are **not**
|
|
36
|
+
in the released build — a per-session **thinking depth** (ACP `reasoning_effort`), a **permission
|
|
37
|
+
mode that survives a restart** and `session/load`, and a free-form **context budget** (with
|
|
38
|
+
256K/512K/768K/1M presets). They live on the ACP branches of the fork
|
|
39
|
+
[`LiangYang666/hermes-agent`](https://github.com/LiangYang666/hermes-agent), and reach the cockpit
|
|
40
|
+
through one **tag per integration build**, in a namespace of its own:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
agentus/<hermes version>/merge-<YYYYMMDD-HHMM>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
A tag and not a branch, on purpose: the branch of the same name (under `yl-dev/`) is the working
|
|
47
|
+
ref and gets rebuilt, while a tag never moves — so pin the tag. The `agentus/` prefix also keeps
|
|
48
|
+
ours apart from upstream's own tags (`v0.21.7`, `v0.21.7+canary.*`), and the timestamp in the
|
|
49
|
+
last segment sorts, so "newest" is a `sort -V`.
|
|
50
|
+
|
|
51
|
+
Install Hermes normally first — its installer brings the Python/Node runtime and puts `hermes` on
|
|
52
|
+
your PATH — then run that build on top of that runtime:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# 1. Hermes itself: the runtime the slots will use
|
|
56
|
+
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
|
|
57
|
+
|
|
58
|
+
# 2. the newest integration build, as a tag (`refs/tags/` spelled out: never ambiguous)
|
|
59
|
+
TAG=$(git ls-remote --tags --refs https://github.com/LiangYang666/hermes-agent.git \
|
|
60
|
+
'refs/tags/agentus/*' | awk '{print $2}' | sed 's#refs/tags/##' | sort -V | tail -1)
|
|
61
|
+
git clone --depth=1 --branch "$TAG" https://github.com/LiangYang666/hermes-agent.git ~/Project/hermes-agent
|
|
62
|
+
|
|
63
|
+
# 3. wrap it, and point the cockpit at the wrapper
|
|
64
|
+
mkdir -p ~/.local/bin
|
|
65
|
+
cat > ~/.local/bin/hermes-acp-src <<'WRAP'
|
|
66
|
+
#!/bin/sh
|
|
67
|
+
exec env PYTHONPATH="$HOME/Project/hermes-agent" hermes "$@"
|
|
68
|
+
WRAP
|
|
69
|
+
chmod +x ~/.local/bin/hermes-acp-src
|
|
70
|
+
AGENTUS_HERMES_CMD=~/.local/bin/hermes-acp-src npm start
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`PYTHONPATH` puts the checkout **ahead of** the installed package, while the installed runtime still
|
|
74
|
+
supplies the interpreter and its dependencies — which is why step 1 is not optional. Verify what a
|
|
75
|
+
slot will actually run (the same check Agentus suggests when a backend is missing):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
PYTHONPATH=~/Project/hermes-agent hermes acp --check # → Hermes ACP check OK
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For a service install, put `AGENTUS_HERMES_CMD` in the environment you hand the server — see
|
|
82
|
+
"Running it as a service". Leaving it at the default (`hermes`) keeps a plain install working, so
|
|
83
|
+
the fork is an upgrade, never a requirement.
|
|
84
|
+
|
|
85
|
+
Moving to a newer build is a fetch + checkout in that same tree — `git fetch --depth=1 origin tag
|
|
86
|
+
<tag> && git checkout <tag>` — not a reinstall; a running slot picks it up the next time it
|
|
87
|
+
restarts.
|
|
88
|
+
|
|
33
89
|
## Install
|
|
34
90
|
|
|
35
91
|
### One command — the published package (public)
|
|
@@ -92,7 +148,7 @@ serves once (no watcher). `AGENTUS_PORT=9000 npm start` moves the port.
|
|
|
92
148
|
agentus [options]
|
|
93
149
|
|
|
94
150
|
-p, --port <n> port to listen on (AGENTUS_PORT, default 8788)
|
|
95
|
-
-d, --data <dir> where state lives (sqlite, login,
|
|
151
|
+
-d, --data <dir> where state lives (sqlite, login, settings)
|
|
96
152
|
(AGENTUS_DATA, default ~/.agentus)
|
|
97
153
|
-o, --open open the cockpit in your browser once it is up
|
|
98
154
|
--where print the resolved paths and exit (starts nothing)
|
|
@@ -101,7 +157,7 @@ agentus [options]
|
|
|
101
157
|
```
|
|
102
158
|
|
|
103
159
|
`--where` is worth knowing: it prints what the server actually resolved — data dir, web bundle,
|
|
104
|
-
companion APK,
|
|
160
|
+
companion APK, Node version — and exits before anything starts. It is the one
|
|
105
161
|
answer to "where did my data go" (see below).
|
|
106
162
|
|
|
107
163
|
## First run
|
|
@@ -137,9 +193,6 @@ unless the backend row names another one — see [HERMES_HOME](#hermes_home).
|
|
|
137
193
|
| `AGENTUS_HERMES_HOME` | `~/.hermes` | default `HERMES_HOME` for a row that does not name one |
|
|
138
194
|
| `AGENTUS_PERM_TIMEOUT_MS` | `300000` (5 min) | how long a permission prompt waits before auto-cancelling |
|
|
139
195
|
| `AGENTUS_HISTORY_PAGE` | `500` | transcript page size |
|
|
140
|
-
| `AGENTUS_TLS_PORT` | `8443` when a cert exists, else off | the TLS listener (`0` disables it) |
|
|
141
|
-
| `AGENTUS_TLS_CERT` / `AGENTUS_TLS_KEY` | `<DATA_DIR>/tls/{cert,key}.pem` | cert material for that listener |
|
|
142
|
-
| `AGENTUS_TLS_CA` | `<DATA_DIR>/tls/ca.pem` | the root a device installs once |
|
|
143
196
|
| `AGENTUS_TTS_BASE_URL` | unset | OpenAI-compatible base for server speech synthesis (`…/v1`); unset = browser voices only |
|
|
144
197
|
| `AGENTUS_TTS_API_KEY` / `AGENTUS_TTS_MODEL` / `AGENTUS_TTS_VOICE` | – / `tts-1` / `alloy` | ditto |
|
|
145
198
|
| `AGENTUS_STT_BASE_URL` | unset | OpenAI-compatible base for server transcription; unset = browser recognition only |
|
|
@@ -167,7 +220,7 @@ wins; the env is what makes a fresh install work before anyone opens it.
|
|
|
167
220
|
### Data directory
|
|
168
221
|
|
|
169
222
|
`~/.agentus` by default. It holds `agentus.sqlite` (sessions + transcript), `credentials.json`,
|
|
170
|
-
`auth.token`, `auth.secret`, `
|
|
223
|
+
`auth.token`, `auth.secret`, `settings.json`. It is created `0700` on first boot, and the
|
|
171
224
|
boot log prints it **and why that directory won**:
|
|
172
225
|
|
|
173
226
|
| order | rule |
|
|
@@ -182,18 +235,17 @@ boot it once with `AGENTUS_DATA=~/.agentus`. `agentus --where` prints what the s
|
|
|
182
235
|
instead of making you guess.
|
|
183
236
|
|
|
184
237
|
Nothing ever writes inside `node_modules`: for an installed package, `<pkg>/packages/server/.data`
|
|
185
|
-
would be deleted by the next `npm i -g agentus`, taking the transcript
|
|
186
|
-
with it.
|
|
238
|
+
would be deleted by the next `npm i -g agentus`, taking the transcript and the login with it.
|
|
187
239
|
|
|
188
240
|
### Ports
|
|
189
241
|
|
|
190
242
|
| port | speaks | for |
|
|
191
243
|
|---|---|---|
|
|
192
|
-
| `AGENTUS_PORT` (8788) | plain HTTP | LAN, loopback, `curl`, scripts — no cert warning, CI unchanged |
|
|
193
|
-
| `AGENTUS_TLS_PORT` (8443) | HTTPS (self-signed) | the tunnel — encrypts the public hop |
|
|
244
|
+
| `AGENTUS_PORT` (8788) | plain HTTP | LAN, loopback, `curl`, scripts — no cert warning, CI unchanged; **the only listener** |
|
|
194
245
|
| 5173 | Vite dev server | front-end hot reload, only during `npm run dev` |
|
|
195
246
|
|
|
196
|
-
|
|
247
|
+
TLS is terminated **in front of** this process (a reverse proxy, or a tunnel that does its own
|
|
248
|
+
`public_tls`) — see [TLS](#tls).
|
|
197
249
|
|
|
198
250
|
### Login
|
|
199
251
|
|
|
@@ -212,23 +264,16 @@ file wins. Delete `credentials.json` to fall back to the environment.
|
|
|
212
264
|
### TLS
|
|
213
265
|
|
|
214
266
|
A plain TCP tunnel (SakuraFrp, `ssh -L`, most frp setups) forwards bytes — it does not terminate
|
|
215
|
-
TLS for you. So the server
|
|
216
|
-
|
|
217
|
-
```bash
|
|
218
|
-
scripts/make-cert.sh # self-signed root + short-lived leaf, SAN = the name you type
|
|
219
|
-
# -> $AGENTUS_DATA/tls/{ca.pem,cert.pem,key.pem} (key 0600, git-ignored)
|
|
220
|
-
# boot log then says: https://0.0.0.0:8443 (self-signed …) ; point a tunnel at THIS port
|
|
221
|
-
```
|
|
267
|
+
TLS for you. So terminate TLS **in front of** the server: a reverse proxy (Caddy, nginx), or a
|
|
268
|
+
tunnel that does its own `public_tls`.
|
|
222
269
|
|
|
223
|
-
|
|
224
|
-
`
|
|
225
|
-
|
|
226
|
-
Settings → enable*. It is public material — the private key never leaves `<DATA_DIR>/tls/`.
|
|
270
|
+
This process listens on plain HTTP only, on purpose: the LAN path then needs no certificate and
|
|
271
|
+
`curl`/scripts stay unchanged, and certificate lifecycle (issuing, rotation, SAN) lives in that
|
|
272
|
+
front layer instead of inside the app.
|
|
227
273
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
(`--leaf-only`) freely; the installed root is untouched.
|
|
274
|
+
The session cookie still comes out `Secure` when the request arrived over TLS: the handler reads
|
|
275
|
+
`X-Forwarded-Proto` (`packages/server/src/auth.ts`), so any front layer that sets it — Caddy and
|
|
276
|
+
nginx both do by default — gets a `Secure` cookie with no configuration here.
|
|
232
277
|
|
|
233
278
|
### Backends
|
|
234
279
|
|
|
@@ -326,7 +371,7 @@ session cookies once instead of logging every device out.
|
|
|
326
371
|
|
|
327
372
|
```bash
|
|
328
373
|
npm rm -g agentus # remove the program (state stays)
|
|
329
|
-
rm -rf ~/.agentus # remove state — SQLite, login,
|
|
374
|
+
rm -rf ~/.agentus # remove state — SQLite, login, settings
|
|
330
375
|
```
|
|
331
376
|
|
|
332
377
|
If you installed into a per-user prefix, `npm rm -g --prefix ~/.local agentus`. Uninstalling never
|
|
@@ -361,6 +406,7 @@ them, e.g. `AGENTUS_DATA=~/.agentus agentus` or `AGENTUS_DATA=<repo>/packages/se
|
|
|
361
406
|
requests. The live stream rides on `/ws`; use a tunnel that passes WebSockets through (SakuraFrp
|
|
362
407
|
TCP+auto-HTTPS tunnels are verified working).
|
|
363
408
|
|
|
364
|
-
**Certificate warning on the LAN.**
|
|
365
|
-
|
|
366
|
-
|
|
409
|
+
**Certificate warning on the LAN.** The server itself speaks plain HTTP on `:8788`, so a warning
|
|
410
|
+
means you are reaching a TLS-terminating front layer directly by IP — its certificate names a
|
|
411
|
+
domain, not an IP. Either use the plain-HTTP `:8788` port on the LAN, or reach the front layer by
|
|
412
|
+
the name its certificate was issued for.
|