agentus 0.3.3 → 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 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`, `tls/`, `settings.json`. It is
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: a second listener, on purpose
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
- ```bash
238
- scripts/make-cert.sh # self-signed, SAN = the name you actually type
239
- # -> $AGENTUS_DATA/tls/{cert.pem,key.pem} (0600, git-ignored) — ~/.agentus/tls by default
240
- # boot log then says: https://0.0.0.0:8443 (self-signed …) ; point a tunnel at THIS port
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
- `npm run tls-smoke` (in CI, real sockets) guards all of it: a verifying client must be
278
- *rejected* (that is what proves the port is actually TLS), the leaf must chain to the root
279
- `/cert.crt` hands out and stay under the 398-day cap, the API login and `wss://…/ws` handshake
280
- must work over TLS (the upgrade handler is bound to both listeners — the easy thing to
281
- forget), and the LAN port must stay plain.
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`、`tls/`、`settings.json`。首次启动时以 0700 创建,启动日志会打印它**以及
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 就会以明文穿过互联网。所以服务端可以自己讲 TLS:
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
- `npm run tls-smoke`(在 CI 里,真 socket)守着这一切:一个做校验的客户端必须被*拒绝*(这才证明该端口
246
- 真的是 TLS),叶子证书必须能链到根证书、且 `/cert.crt` 发出的就是它、并保持在 398 天上限内,API 登录
247
- 和 `wss://…/ws` 握手必须能在 TLS 上工作(升级 handler 绑定在两个监听器上 —— 这是最容易忘的地方),而
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`, mirrored by
18
- * scripts/make-cert.sh). A second implementation here would be a second answer to "where
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, TLS material, settings
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, TLS,
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
@@ -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 AGENTUS_TLS_PORT=0 AGENTUS_DATA=/tmp/<name> npx tsx packages/server/src/index.ts
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
@@ -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
  ```
@@ -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**, the auth state, and the TLS listener:
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
- https://0.0.0.0:8443 (self-signed …) ← present = TLS listener up
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`/`:8443` live, `:8901` dev). A cleanup that leaves the box worse
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`, `tls/` — several of them
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, TLS silently turned off (`tls off: no cert at <QA_DIR>/tls/cert.pem`), and a
125
- running slot was reaped — yet the port answers 200 and `/api/version` looks fine. Cause:
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` · `scripts/make-cert.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
@@ -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.** Both of live's ports live in
31
- `~/.hermes/cache/agentus/ports.env`; `launch.py` pins them into the server's environment, and
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 and the TLS port both drifted before).
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, TLS and the
86
+ - [`README.md`](../../README.md) — install, environment variables, login and the
88
87
  public tunnel.
@@ -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, TLS material, settings)
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, TLS listener, Node version — and exits before anything starts. It is the one
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`, `tls/`, `settings.json`. It is created `0700` on first boot, and the
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, the login and the TLS key
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
- Same handler, same routes, same auth on both listeners; only the socket differs.
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 can speak TLS itself, on a **second** listener:
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
- The listener also serves its own issuer at `/cert.crt`, so a device can install the root once:
224
- `https://<your-name>:<tls-port>/cert.crt`. iOS opens the profile installer directly; Android
225
- downloads it (Settings → Security → CA certificate); iOS also needs *About → Certificate Trust
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
- `make-cert.sh` issues two certs on purpose: a 10-year root (`ca.pem`, what devices install) and a
229
- 390-day leaf (`cert.pem`, what the listener serves). Apple caps server-certificate validity at 398
230
- days, so a 10-year leaf is exactly what iOS refuses *after* the install dance. Rotate the leaf
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, TLS key, settings
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.** That is the TLS listener's self-signed cert (a bare LAN IP
365
- cannot carry a trusted cert). Either use the plain-HTTP `:8788` port on the LAN, or install the
366
- issuer once from `/cert.crt` (see [TLS](#tls)).
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.