agentus 0.3.1 → 0.3.3
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 +5 -5
- package/README.zh-CN.md +5 -5
- package/bin/agentus.mjs +3 -3
- package/docs/dev/01-dev-loop.md +2 -2
- package/docs/dev/03-release.md +18 -5
- package/docs/dev/04-recover.md +14 -8
- package/docs/dev/README.md +24 -1
- package/docs/installation.md +8 -8
- package/docs/installation.zh-CN.md +8 -8
- package/docs/transcript-folding.md +1 -1
- package/package.json +7 -7
- package/packages/server/mock/agent.mjs +8 -0
- package/packages/server/src/acp/session-manager.ts +110 -10
- package/packages/server/src/index.ts +62 -2
- package/packages/server/src/notify/center.ts +15 -0
- package/packages/server/src/pty-helper.py +73 -15
- package/packages/server/src/settings.ts +40 -12
- package/packages/server/src/term.ts +46 -2
- package/packages/web/dist/assets/index-CCvfL56d.css +32 -0
- package/packages/web/dist/assets/index-DCBs8dsw.js +119 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/index-Cqb6QVmU.css +0 -32
- package/packages/web/dist/assets/index-YJcTF0IM.js +0 -283
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ agents: open several sessions in the browser, each one backed by a real
|
|
|
24
24
|
```bash
|
|
25
25
|
# public one-liner — the published package (Node >= 22.5; the store is node:sqlite)
|
|
26
26
|
npx agentus # …or: npm i -g agentus, then `agentus`
|
|
27
|
-
# open http://localhost:
|
|
27
|
+
# open http://localhost:8788 and log in (admin / 123456 — see "Login" below),
|
|
28
28
|
# then "+ new session", pick a backend + a working directory
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -102,7 +102,7 @@ layer never changes.
|
|
|
102
102
|
|
|
103
103
|
| var | default | meaning |
|
|
104
104
|
|---|---|---|
|
|
105
|
-
| `AGENTUS_PORT` | `
|
|
105
|
+
| `AGENTUS_PORT` | `8788` | server port (never reads bare `PORT` — that name is polluted on shared hosts) |
|
|
106
106
|
| `AGENTUS_DATA` | `~/.agentus` | where state lives; see "Where your state lives" for the full order (`<repo>/packages/server/.data` on a checkout that already has one) |
|
|
107
107
|
| `AGENTUS_HERMES_CMD` | `hermes` | binary to spawn for the hermes backend |
|
|
108
108
|
| `AGENTUS_QODER_CMD` | `~/.local/bin/qodercli` | ditto for qoder |
|
|
@@ -196,7 +196,7 @@ Auth environment:
|
|
|
196
196
|
### Exposing it publicly (tunnel / reverse proxy)
|
|
197
197
|
|
|
198
198
|
Put `AGENTUS_BASIC_AUTH=:pass` in the server's environment, then point the tunnel at
|
|
199
|
-
`<lan-ip>:
|
|
199
|
+
`<lan-ip>:8788`. That gives you two independent locks — Basic at the edge of the app, the
|
|
200
200
|
operator login inside it — and scripts can still get in (`curl -u :pass` plus the machine
|
|
201
201
|
token).
|
|
202
202
|
|
|
@@ -231,7 +231,7 @@ Two things to know about a tunnel in front of this app:
|
|
|
231
231
|
#### TLS: a second listener, on purpose
|
|
232
232
|
|
|
233
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 `:
|
|
234
|
+
does **not** terminate TLS for you. Point one at `:8788` and the operator's password and
|
|
235
235
|
session cookie cross the internet in clear text. So the server can speak TLS itself:
|
|
236
236
|
|
|
237
237
|
```bash
|
|
@@ -242,7 +242,7 @@ scripts/make-cert.sh # self-signed, SAN = the name you actual
|
|
|
242
242
|
|
|
243
243
|
| port | speaks | for |
|
|
244
244
|
|---|---|---|
|
|
245
|
-
| `AGENTUS_PORT` (
|
|
245
|
+
| `AGENTUS_PORT` (8788) | plain HTTP | LAN, loopback, `curl`, scripts — no cert warning, CI unchanged |
|
|
246
246
|
| `AGENTUS_TLS_PORT` (8443) | HTTPS (self-signed) | **the tunnel** — encrypts the public hop |
|
|
247
247
|
|
|
248
248
|
Same handler, same routes, same auth; only the socket differs. Two listeners beat both
|
package/README.zh-CN.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
```bash
|
|
23
23
|
# 对外一条命令 —— 用 npm 上发布的包(Node >= 22.5,存储用 node:sqlite)
|
|
24
24
|
npx agentus # 或者 npm i -g agentus 然后直接跑 `agentus`
|
|
25
|
-
# 打开 http://localhost:
|
|
25
|
+
# 打开 http://localhost:8788,用 admin / 123456 登录(见下面「登录」),
|
|
26
26
|
# 然后「+ 新建会话」,选一个后端和一个工作目录
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -93,7 +93,7 @@ npm run agentus # the same CLI as `npx agentus`, straight from the
|
|
|
93
93
|
|
|
94
94
|
| 变量 | 默认值 | 含义 |
|
|
95
95
|
|---|---|---|
|
|
96
|
-
| `AGENTUS_PORT` | `
|
|
96
|
+
| `AGENTUS_PORT` | `8788` | 服务端口(从不读取裸 `PORT` —— 这个名字在共享主机上被污染) |
|
|
97
97
|
| `AGENTUS_DATA` | `~/.agentus` | 状态所在;完整顺序见「状态放在哪」(已有数据的老 checkout 上是 `<repo>/packages/server/.data`) |
|
|
98
98
|
| `AGENTUS_HERMES_CMD` | `hermes` | hermes 后端要拉起的二进制 |
|
|
99
99
|
| `AGENTUS_QODER_CMD` | `~/.local/bin/qodercli` | qoder 同上 |
|
|
@@ -177,7 +177,7 @@ cookie 用 `HttpOnly` 而不是 `localStorage` 里的 token,并且它会顺带
|
|
|
177
177
|
|
|
178
178
|
### 公网暴露(隧道 / 反向代理)
|
|
179
179
|
|
|
180
|
-
把 `AGENTUS_BASIC_AUTH=:pass` 放进服务端环境,然后把隧道指向 `<lan-ip>:
|
|
180
|
+
把 `AGENTUS_BASIC_AUTH=:pass` 放进服务端环境,然后把隧道指向 `<lan-ip>:8788`。这样你就有了两把独立
|
|
181
181
|
的锁 —— 边缘的 Basic,里面的运维者登录 —— 脚本依然能进(`curl -u :pass` 加上机器 token)。
|
|
182
182
|
|
|
183
183
|
这里推荐只用密码的形式(`:pass`,或裸 `pass`):HTTP Basic 总是会让浏览器问用户名(RFC 7617 传
|
|
@@ -205,7 +205,7 @@ cookie 用 `HttpOnly` 而不是 `localStorage` 里的 token,并且它会顺带
|
|
|
205
205
|
#### TLS:刻意做成第二个监听器
|
|
206
206
|
|
|
207
207
|
纯 TCP 隧道(SakuraFrp、safe-nat、`ssh -L`、大多数 frp 配置)只转发字节 —— 它**不会**替你终结 TLS。
|
|
208
|
-
把它指向 `:
|
|
208
|
+
把它指向 `:8788`,运维者的密码和 session cookie 就会以明文穿过互联网。所以服务端可以自己讲 TLS:
|
|
209
209
|
|
|
210
210
|
```bash
|
|
211
211
|
scripts/make-cert.sh # self-signed, SAN = the name you actually type
|
|
@@ -215,7 +215,7 @@ scripts/make-cert.sh # self-signed, SAN = the name you actual
|
|
|
215
215
|
|
|
216
216
|
| 端口 | 讲什么 | 给谁 |
|
|
217
217
|
|---|---|---|
|
|
218
|
-
| `AGENTUS_PORT` (
|
|
218
|
+
| `AGENTUS_PORT` (8788) | 纯 HTTP | LAN、loopback、`curl`、脚本 —— 无证书警告,CI 不变 |
|
|
219
219
|
| `AGENTUS_TLS_PORT` (8443) | HTTPS(自签名) | **隧道** —— 加密公网那一跳 |
|
|
220
220
|
|
|
221
221
|
同一套 handler,同一套路由,同一套认证;只有 socket 不同。两个监听器胜过另外两种做法:把单端口变成
|
package/bin/agentus.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* `agentus` — the command an npm install gives you.
|
|
4
4
|
*
|
|
5
|
-
* npx agentus # start the cockpit on :
|
|
5
|
+
* npx agentus # start the cockpit on :8788
|
|
6
6
|
* npx agentus --port 9000 # …somewhere else
|
|
7
7
|
* npx agentus --data ~/work # …with its state in a directory you chose
|
|
8
8
|
* npx agentus --where # print the resolved paths and exit (nothing starts)
|
|
@@ -36,7 +36,7 @@ Usage
|
|
|
36
36
|
agentus [options]
|
|
37
37
|
|
|
38
38
|
Options
|
|
39
|
-
-p, --port <n> port to listen on (AGENTUS_PORT, default
|
|
39
|
+
-p, --port <n> port to listen on (AGENTUS_PORT, default 8788)
|
|
40
40
|
-d, --data <dir> where state lives — sqlite, login, TLS material, settings
|
|
41
41
|
(AGENTUS_DATA, default ~/.agentus)
|
|
42
42
|
-o, --open open the cockpit in your browser once it is up
|
|
@@ -115,7 +115,7 @@ if (opts.port !== undefined) env.AGENTUS_PORT = String(opts.port);
|
|
|
115
115
|
if (opts.data !== undefined) env.AGENTUS_DATA = opts.data;
|
|
116
116
|
if (opts.where) env.AGENTUS_PRINT_PATHS = "1";
|
|
117
117
|
|
|
118
|
-
const port = env.AGENTUS_PORT ?? "
|
|
118
|
+
const port = env.AGENTUS_PORT ?? "8788";
|
|
119
119
|
if (!opts.where) {
|
|
120
120
|
console.log(`[agentus] starting — open http://localhost:${port} when it is up (Ctrl-C stops it)`);
|
|
121
121
|
}
|
package/docs/dev/01-dev-loop.md
CHANGED
|
@@ -11,7 +11,7 @@ instances:
|
|
|
11
11
|
| | live | dev |
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| path | `worktrees/agentus` | `worktrees/agentus-dev` |
|
|
14
|
-
| HTTP port | `
|
|
14
|
+
| HTTP port | `8788` | `8901` |
|
|
15
15
|
| TLS port | `8443` | disabled (`AGENTUS_TLS_PORT=0`) |
|
|
16
16
|
| data dir | `<tree>/packages/server/.data` | `/tmp/agentus-qa-account` |
|
|
17
17
|
| started by | `~/.hermes/cache/agentus/launch.py` | `bash scripts/dev.sh start` |
|
|
@@ -85,7 +85,7 @@ which one won and why** — a directory change is the thing that would otherwise
|
|
|
85
85
|
For the worktrees, the outcome is fixed by the launchers rather than by the rules:
|
|
86
86
|
|
|
87
87
|
- **live** — `~/.hermes/cache/agentus/launch.py` pins `AGENTUS_DATA=<live>/packages/server/.data`
|
|
88
|
-
and `AGENTUS_PORT=
|
|
88
|
+
and `AGENTUS_PORT=8788`, and pops the inherited QA/DEV keys. The launcher owns its
|
|
89
89
|
identity; it never trusts `os.environ`.
|
|
90
90
|
- **dev** — `scripts/dev.sh` exports `AGENTUS_DATA=/tmp/agentus-qa-account` and
|
|
91
91
|
`AGENTUS_PORT=8901`.
|
package/docs/dev/03-release.md
CHANGED
|
@@ -75,7 +75,7 @@ If `<slug>` is omitted, the branch name is used with `/` → `-`. What it does:
|
|
|
75
75
|
| start shape | `scripts/start.sh`, `package.json` |
|
|
76
76
|
| stale launcher | the live process is still running under `tsx watch` |
|
|
77
77
|
|
|
78
|
-
5. If a restart is needed, calls `scripts/relaunch.sh --port
|
|
78
|
+
5. If a restart is needed, calls `scripts/relaunch.sh --port 8788 --tree <live>
|
|
79
79
|
--launcher <live launcher>`, which TERMs the process group, waits for the port to free,
|
|
80
80
|
starts the launcher, polls `/healthz`, and **asserts the pid changed** (a failure exits
|
|
81
81
|
non-zero, so "built fine, still serving old code" cannot pass).
|
|
@@ -92,7 +92,7 @@ are all required:
|
|
|
92
92
|
- `no server-side change` printed,
|
|
93
93
|
- the **asset hash moved** (`web asset: <before> -> <after>`; `promote` warns if web sources
|
|
94
94
|
changed but the hash did not),
|
|
95
|
-
- the **pid did NOT move** (`lsof -ti:
|
|
95
|
+
- the **pid did NOT move** (`lsof -ti:8788` unchanged — a moved pid means it took the
|
|
96
96
|
restart branch and the release was not the kind it claimed to be).
|
|
97
97
|
|
|
98
98
|
`package.json` is a restart trigger, so **any** edit to it — even one new npm alias for a QA
|
|
@@ -145,15 +145,15 @@ curl -s --noproxy '*' -H "Authorization: Bearer $TOK" http://127.0.0.1:8901/api/
|
|
|
145
145
|
hash. `promote` echoes it; you can also read it directly:
|
|
146
146
|
|
|
147
147
|
```bash
|
|
148
|
-
curl -s --noproxy '*' http://127.0.0.1:
|
|
148
|
+
curl -s --noproxy '*' http://127.0.0.1:8788/api/version
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
**3. Grep the served bundle for the new markers.** Fetch the real vite-named file
|
|
152
152
|
(`assets/index-<hash>.js`) and look for a string only the new code has:
|
|
153
153
|
|
|
154
154
|
```bash
|
|
155
|
-
HASH=$(curl -s --noproxy '*' http://127.0.0.1:
|
|
156
|
-
curl -s --noproxy '*' "http://127.0.0.1:
|
|
155
|
+
HASH=$(curl -s --noproxy '*' http://127.0.0.1:8788/api/version | ...)
|
|
156
|
+
curl -s --noproxy '*' "http://127.0.0.1:8788/assets/$HASH.js" | grep -c '<new-marker>'
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
Two traps: the file is `index-<hash>.js`, so a check that fetches `/assets/<hash>.js`
|
|
@@ -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
|
@@ -23,13 +23,13 @@ empty. Triage the identity, not the liveness (see "Is it serving the wrong store
|
|
|
23
23
|
|
|
24
24
|
| port | who |
|
|
25
25
|
|---|---|
|
|
26
|
-
| `
|
|
26
|
+
| `8788` | live HTTP |
|
|
27
27
|
| `8443` | live TLS (public tunnel points here) |
|
|
28
28
|
| `8901` | dev / QA instance |
|
|
29
29
|
| `8648` | **Hermes Studio** — not Agentus, do not touch |
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
lsof -nP -iTCP:
|
|
32
|
+
lsof -nP -iTCP:8788 -sTCP:LISTEN # who owns the port (returns the CHILD, not the supervisor)
|
|
33
33
|
ps -o pid,ppid,pgid,command -p <pid> # its process group and command line
|
|
34
34
|
```
|
|
35
35
|
|
|
@@ -82,7 +82,7 @@ file at that same path, and its server was the only way into the machine. Rules:
|
|
|
82
82
|
`spawn(..., { detached: true })` and kill the process group — rather than cleaning up by
|
|
83
83
|
name afterwards.
|
|
84
84
|
5. **Re-check the neighbours afterwards:** one `curl` per resident service
|
|
85
|
-
(`:8648` Studio, `:
|
|
85
|
+
(`:8648` Studio, `:8788`/`:8443` live, `:8901` dev). A cleanup that leaves the box worse
|
|
86
86
|
than it found it is the whole failure mode.
|
|
87
87
|
|
|
88
88
|
## The SQLite store, WAL, and the `*.bak` files
|
|
@@ -127,7 +127,7 @@ running slot was reaped — yet the port answers 200 and `/api/version` looks fi
|
|
|
127
127
|
`AGENTUS_DATA`/`AGENTUS_PORT` and pops the QA/DEV keys, but verify identity directly:
|
|
128
128
|
|
|
129
129
|
```bash
|
|
130
|
-
PID=$(lsof -nP -iTCP:
|
|
130
|
+
PID=$(lsof -nP -iTCP:8788 -sTCP:LISTEN -t)
|
|
131
131
|
ps eww -p "$PID" | tr ' ' '\n' | grep AGENTUS # the process's own identity vars
|
|
132
132
|
lsof -p "$PID" | grep sqlite # which store it actually opened
|
|
133
133
|
```
|
|
@@ -137,11 +137,17 @@ Recovery:
|
|
|
137
137
|
```bash
|
|
138
138
|
cd worktrees/agentus
|
|
139
139
|
env -u AGENTUS_DATA -u AGENTUS_BASE -u DEV_LOG_PATH -u DEV_PROMOTE_DETACHED -u DEV_PROMOTE_SELF \
|
|
140
|
-
bash scripts/relaunch.sh --port
|
|
140
|
+
bash scripts/relaunch.sh --port 8788 --launcher ~/.hermes/cache/agentus/launch.py \
|
|
141
|
+
--launchd ai.hermes.agentus
|
|
141
142
|
TOK=$(cat packages/server/.data/auth.token)
|
|
142
|
-
curl -s -X POST -H "Authorization: Bearer $TOK" http://127.0.0.1:
|
|
143
|
+
curl -s -X POST -H "Authorization: Bearer $TOK" http://127.0.0.1:8788/api/sessions/<id>/resume
|
|
143
144
|
```
|
|
144
145
|
|
|
146
|
+
`--launchd <label>` makes relaunch.sh hand the restart to launchd (`kickstart -k`) rather than
|
|
147
|
+
killing the process by hand — once the job is installed, a hand-kill is not a restart, it is a
|
|
148
|
+
respawn race (launchd brings the old process back while our own launcher starts a second one). Pass
|
|
149
|
+
it whenever the label is loaded; relaunch.sh ignores it when it is not.
|
|
150
|
+
|
|
145
151
|
After any restart, put the operator back on the air: `POST /api/sessions/<id>/resume` with the
|
|
146
152
|
machine token → `status: ready` and a fresh pid. **Capture the slot ids to resume before the
|
|
147
153
|
restart** — in the window right after a restart a slot has dropped from `live` into
|
|
@@ -155,8 +161,8 @@ one-click problem. Endpoints behind the login:
|
|
|
155
161
|
|
|
156
162
|
```bash
|
|
157
163
|
TOK=$(cat <DATA_DIR>/auth.token)
|
|
158
|
-
curl -s -H "Authorization: Bearer $TOK" http://127.0.0.1:
|
|
159
|
-
curl -s -X DELETE -H "Authorization: Bearer $TOK" http://127.0.0.1:
|
|
164
|
+
curl -s -H "Authorization: Bearer $TOK" http://127.0.0.1:8788/api/auth/locked-ips
|
|
165
|
+
curl -s -X DELETE -H "Authorization: Bearer $TOK" http://127.0.0.1:8788/api/auth/locked-ips
|
|
160
166
|
```
|
|
161
167
|
|
|
162
168
|
In the UI it is Settings → 登录失败锁定 (which IPs are locked, for how long, unlock / unlock
|
package/docs/dev/README.md
CHANGED
|
@@ -20,12 +20,35 @@ variables, and they must never touch each other's code or data:
|
|
|
20
20
|
|---|---|---|
|
|
21
21
|
| tree | `worktrees/agentus` | `worktrees/agentus-dev` |
|
|
22
22
|
| role | the instance the operator actually uses | where changes are made and tested |
|
|
23
|
-
| HTTP port | `
|
|
23
|
+
| HTTP port | `8788` | `8901` |
|
|
24
24
|
| TLS port | `8443` | off (`AGENTUS_TLS_PORT=0`) |
|
|
25
25
|
| data dir | `<tree>/packages/server/.data` (pinned by the launcher) | `/tmp/agentus-qa-account` |
|
|
26
26
|
| launcher | `~/.hermes/cache/agentus/launch.py` | `scripts/dev.sh start\|stop\|status\|logs` |
|
|
27
|
+
| supervision | launchd job `ai.hermes.agentus` (`KeepAlive`) | none — a plain detached process |
|
|
27
28
|
| hot reload | **no** — restart is explicit | yes (`tsx watch`) |
|
|
28
29
|
|
|
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
|
|
32
|
+
`scripts/dev.sh` / `scripts/relaunch.sh` read the same file instead of hardcoding a number. Move
|
|
33
|
+
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).
|
|
35
|
+
|
|
36
|
+
**Live is supervised by launchd.** It used to be a detached child of whatever shell happened to
|
|
37
|
+
start it: nothing brought it back after a crash, and it inherited that shell's entire environment
|
|
38
|
+
(usually an agent session's, handing `HERMES_SESSION_ID` and `WEIXIN_TOKEN` down to every ACP child
|
|
39
|
+
it spawned). Now:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
launchctl print gui/$(id -u)/ai.hermes.agentus # state, pid, the job's environment
|
|
43
|
+
launchctl kickstart -k gui/$(id -u)/ai.hermes.agentus # the supported restart (stop then start)
|
|
44
|
+
tail -f /tmp/agentus-server.log # the job's stdout/stderr
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`scripts/relaunch.sh --launchd ai.hermes.agentus` is the same thing wrapped with a health check and
|
|
48
|
+
a pid-moved assertion, and that is what `promote` uses. Launchd's `KeepAlive` restarts it on any
|
|
49
|
+
exit, so a crash self-heals; a **manual** `kill` therefore no longer restarts live, it just makes
|
|
50
|
+
launchd respawn it.
|
|
51
|
+
|
|
29
52
|
**Why two trees.** `tsx watch` reloads the whole server on any server-source edit, and a
|
|
30
53
|
reload SIGTERMs every child process — every live `hermes acp` session, including the one
|
|
31
54
|
the operator is typing into. Editing server code in the live tree would therefore kill his
|
package/docs/installation.md
CHANGED
|
@@ -91,7 +91,7 @@ serves once (no watcher). `AGENTUS_PORT=9000 npm start` moves the port.
|
|
|
91
91
|
```
|
|
92
92
|
agentus [options]
|
|
93
93
|
|
|
94
|
-
-p, --port <n> port to listen on (AGENTUS_PORT, default
|
|
94
|
+
-p, --port <n> port to listen on (AGENTUS_PORT, default 8788)
|
|
95
95
|
-d, --data <dir> where state lives (sqlite, login, TLS material, settings)
|
|
96
96
|
(AGENTUS_DATA, default ~/.agentus)
|
|
97
97
|
-o, --open open the cockpit in your browser once it is up
|
|
@@ -107,7 +107,7 @@ answer to "where did my data go" (see below).
|
|
|
107
107
|
## First run
|
|
108
108
|
|
|
109
109
|
1. Start it: `agentus` (or `npx agentus`, or `npm start` from a checkout).
|
|
110
|
-
2. Open **http://localhost:
|
|
110
|
+
2. Open **http://localhost:8788** and log in. Default credentials:
|
|
111
111
|
|
|
112
112
|
```
|
|
113
113
|
username: admin
|
|
@@ -130,7 +130,7 @@ unless the backend row names another one — see [HERMES_HOME](#hermes_home).
|
|
|
130
130
|
|
|
131
131
|
| var | default | meaning |
|
|
132
132
|
|---|---|---|
|
|
133
|
-
| `AGENTUS_PORT` | `
|
|
133
|
+
| `AGENTUS_PORT` | `8788` | server port (never reads bare `PORT`) |
|
|
134
134
|
| `AGENTUS_DATA` | `~/.agentus` | where state lives; see [Data directory](#data-directory) |
|
|
135
135
|
| `AGENTUS_HERMES_CMD` | `hermes` | binary to spawn for the hermes backend |
|
|
136
136
|
| `AGENTUS_QODER_CMD` | `~/.local/bin/qodercli` | ditto for qoder |
|
|
@@ -189,7 +189,7 @@ with it.
|
|
|
189
189
|
|
|
190
190
|
| port | speaks | for |
|
|
191
191
|
|---|---|---|
|
|
192
|
-
| `AGENTUS_PORT` (
|
|
192
|
+
| `AGENTUS_PORT` (8788) | plain HTTP | LAN, loopback, `curl`, scripts — no cert warning, CI unchanged |
|
|
193
193
|
| `AGENTUS_TLS_PORT` (8443) | HTTPS (self-signed) | the tunnel — encrypts the public hop |
|
|
194
194
|
| 5173 | Vite dev server | front-end hot reload, only during `npm run dev` |
|
|
195
195
|
|
|
@@ -268,7 +268,7 @@ a different session list.
|
|
|
268
268
|
<key>Label</key> <string>org.agentus.cockpit</string>
|
|
269
269
|
<key>ProgramArguments</key>
|
|
270
270
|
<array>
|
|
271
|
-
<string>/usr/bin/env</string><string>agentus</string><string>--port</string><string>
|
|
271
|
+
<string>/usr/bin/env</string><string>agentus</string><string>--port</string><string>8788</string>
|
|
272
272
|
</array>
|
|
273
273
|
<key>EnvironmentVariables</key>
|
|
274
274
|
<dict><key>AGENTUS_DATA</key><string>/Users/YOU/.agentus</string></dict>
|
|
@@ -293,7 +293,7 @@ Description=Agentus cockpit
|
|
|
293
293
|
After=network.target
|
|
294
294
|
|
|
295
295
|
[Service]
|
|
296
|
-
ExecStart=/usr/bin/env agentus --port
|
|
296
|
+
ExecStart=/usr/bin/env agentus --port 8788
|
|
297
297
|
Environment=AGENTUS_DATA=%h/.agentus
|
|
298
298
|
Restart=on-failure
|
|
299
299
|
|
|
@@ -335,7 +335,7 @@ touches `~/.hermes` (your agents' own data) or any working directory.
|
|
|
335
335
|
## Troubleshooting
|
|
336
336
|
|
|
337
337
|
**Port already in use.** `agentus --port 9000`, or find and stop the holder:
|
|
338
|
-
`lsof -nP -iTCP:
|
|
338
|
+
`lsof -nP -iTCP:8788 -sTCP:LISTEN`. Agentus never reads bare `PORT` for exactly this reason.
|
|
339
339
|
|
|
340
340
|
**"No backend" / a session won't start.** The agent CLI is missing or not logged in. Run
|
|
341
341
|
`hermes acp --check`, or `qodercli login` (a `-32000` on `newSession` means qoder is not logged
|
|
@@ -362,5 +362,5 @@ requests. The live stream rides on `/ws`; use a tunnel that passes WebSockets th
|
|
|
362
362
|
TCP+auto-HTTPS tunnels are verified working).
|
|
363
363
|
|
|
364
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 `:
|
|
365
|
+
cannot carry a trusted cert). Either use the plain-HTTP `:8788` port on the LAN, or install the
|
|
366
366
|
issuer once from `/cert.crt` (see [TLS](#tls)).
|
|
@@ -88,7 +88,7 @@ npm run agentus # 与 `npx agentus` 同一个 CLI,直接
|
|
|
88
88
|
```
|
|
89
89
|
agentus [options]
|
|
90
90
|
|
|
91
|
-
-p, --port <n> 监听端口 (AGENTUS_PORT,默认
|
|
91
|
+
-p, --port <n> 监听端口 (AGENTUS_PORT,默认 8788)
|
|
92
92
|
-d, --data <dir> 状态目录(sqlite、登录、TLS 材料、设置)
|
|
93
93
|
(AGENTUS_DATA,默认 ~/.agentus)
|
|
94
94
|
-o, --open 就绪后自动在浏览器打开驾驶舱
|
|
@@ -103,7 +103,7 @@ agentus [options]
|
|
|
103
103
|
## 首次运行
|
|
104
104
|
|
|
105
105
|
1. 启动:`agentus`(或 `npx agentus`,或 checkout 里的 `npm start`)。
|
|
106
|
-
2. 打开 **http://localhost:
|
|
106
|
+
2. 打开 **http://localhost:8788** 登录。默认凭据:
|
|
107
107
|
|
|
108
108
|
```
|
|
109
109
|
username: admin
|
|
@@ -125,7 +125,7 @@ agentus [options]
|
|
|
125
125
|
|
|
126
126
|
| 变量 | 默认值 | 含义 |
|
|
127
127
|
|---|---|---|
|
|
128
|
-
| `AGENTUS_PORT` | `
|
|
128
|
+
| `AGENTUS_PORT` | `8788` | 服务端口(从不读裸 `PORT`) |
|
|
129
129
|
| `AGENTUS_DATA` | `~/.agentus` | 状态目录;见[数据目录](#数据目录) |
|
|
130
130
|
| `AGENTUS_HERMES_CMD` | `hermes` | hermes 后端要拉起的可执行文件 |
|
|
131
131
|
| `AGENTUS_QODER_CMD` | `~/.local/bin/qodercli` | qoder 后端同理 |
|
|
@@ -181,7 +181,7 @@ agentus [options]
|
|
|
181
181
|
|
|
182
182
|
| 端口 | 协议 | 用途 |
|
|
183
183
|
|---|---|---|
|
|
184
|
-
| `AGENTUS_PORT`(
|
|
184
|
+
| `AGENTUS_PORT`(8788) | 纯 HTTP | 局域网、本机回环、`curl`、脚本 —— 无证书警告,CI 不变 |
|
|
185
185
|
| `AGENTUS_TLS_PORT`(8443) | HTTPS(自签) | **隧道** —— 加密公网那一跳 |
|
|
186
186
|
| 5173 | Vite 开发服务器 | 前端热更新,仅在 `npm run dev` 期间 |
|
|
187
187
|
|
|
@@ -257,7 +257,7 @@ Agentus 默认在前台跑;一个服务单元能让它跨登录、跨重启常
|
|
|
257
257
|
<key>Label</key> <string>org.agentus.cockpit</string>
|
|
258
258
|
<key>ProgramArguments</key>
|
|
259
259
|
<array>
|
|
260
|
-
<string>/usr/bin/env</string><string>agentus</string><string>--port</string><string>
|
|
260
|
+
<string>/usr/bin/env</string><string>agentus</string><string>--port</string><string>8788</string>
|
|
261
261
|
</array>
|
|
262
262
|
<key>EnvironmentVariables</key>
|
|
263
263
|
<dict><key>AGENTUS_DATA</key><string>/Users/YOU/.agentus</string></dict>
|
|
@@ -282,7 +282,7 @@ Description=Agentus cockpit
|
|
|
282
282
|
After=network.target
|
|
283
283
|
|
|
284
284
|
[Service]
|
|
285
|
-
ExecStart=/usr/bin/env agentus --port
|
|
285
|
+
ExecStart=/usr/bin/env agentus --port 8788
|
|
286
286
|
Environment=AGENTUS_DATA=%h/.agentus
|
|
287
287
|
Restart=on-failure
|
|
288
288
|
|
|
@@ -323,7 +323,7 @@ rm -rf ~/.agentus # 删状态 —— SQLite、登录、TLS 密
|
|
|
323
323
|
## 排错
|
|
324
324
|
|
|
325
325
|
**端口被占用。** 用 `agentus --port 9000`,或找出并停掉占用者:
|
|
326
|
-
`lsof -nP -iTCP:
|
|
326
|
+
`lsof -nP -iTCP:8788 -sTCP:LISTEN`。Agentus 从不读裸 `PORT`,原因正在于此。
|
|
327
327
|
|
|
328
328
|
**「没有后端」/ 会话起不来。** 缺少 agent CLI,或它没登录。跑 `hermes acp --check`,或
|
|
329
329
|
`qodercli login`(`newSession` 报 `-32000` 就是 qoder 没登录)。在设置里检查后端行的命令;启动
|
|
@@ -347,4 +347,4 @@ rm -rf ~/.agentus # 删状态 —— SQLite、登录、TLS 密
|
|
|
347
347
|
`/ws`;请换成能透传 WebSocket 的隧道(SakuraFrp 的 TCP+auto-HTTPS 隧道已验证可用)。
|
|
348
348
|
|
|
349
349
|
**局域网里出现证书警告。** 那是 TLS 监听的自签证书(裸局域网 IP 无法承载可信证书)。要么在
|
|
350
|
-
局域网里用纯 HTTP 的 `:
|
|
350
|
+
局域网里用纯 HTTP 的 `:8788` 端口,要么从 `/cert.crt` 装一次签发证书(见 [TLS](#tls))。
|
|
@@ -131,7 +131,7 @@ And against real data — the one-off used when this landed, on the operator's o
|
|
|
131
131
|
```bash
|
|
132
132
|
# rows → the CLIENT's fold, over the operator API
|
|
133
133
|
curl -s -H "Authorization: Bearer $(cat "${AGENTUS_DATA:-$HOME/.agentus}/auth.token")" \
|
|
134
|
-
'http://127.0.0.1:
|
|
134
|
+
'http://127.0.0.1:8788/api/sessions/<id>/messages?tail=1'
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
Result for `hello`: **3804 rows → 70** (10 agent messages, 15 thought blocks, 37 tool cards, 8 prompts),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentus",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Multi-session web cockpit for ACP-speaking coding agents (Qoder CLI / Hermes). UI has no intelligence — the agent lives in spawned CLI subprocesses.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -64,12 +64,12 @@
|
|
|
64
64
|
"store-fold": "tsx scripts/qa/store-fold.mts",
|
|
65
65
|
"live-stream": "tsx scripts/qa/live-stream.mts",
|
|
66
66
|
"open-target": "tsx scripts/qa/open-target.mts",
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
"rail-recent": "tsx scripts/qa/rail-recent.mts",
|
|
68
|
+
"turn-pulse": "tsx scripts/qa/turn-pulse.mts",
|
|
69
|
+
"resume-tail": "tsx scripts/qa/resume-tail.mts",
|
|
70
|
+
"render-lag-sweep": "node scripts/qa/render-lag-sweep.mjs",
|
|
71
|
+
"replay-window": "node scripts/qa/replay-window.mjs",
|
|
72
|
+
"think-cadence": "node scripts/qa/think-cadence.mjs"
|
|
73
73
|
},
|
|
74
74
|
"engines": {
|
|
75
75
|
"node": ">=22.5"
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
// MOCK_TOOL=1 -> prompt triggers a tool_call + requestPermission
|
|
8
8
|
// MOCK_THINK=1 -> emits agent_thought_chunk before the answer
|
|
9
9
|
// MOCK_SLOW_MS=n -> delay between chunks (default 60)
|
|
10
|
+
// MOCK_SPAWN_MS=n -> boot latency before the handshake answers (default 0). A real agent takes
|
|
11
|
+
// seconds to come up (node + initialize + session/load) and that window is
|
|
12
|
+
// where the cockpit's 「正在唤醒 agent 进程…」 state lives — a browser sweep
|
|
13
|
+
// cannot observe a state that lasts 80 ms.
|
|
10
14
|
// MOCK_SINK=1 -> exit the process mid-turn (crash path for AC5/QA)
|
|
11
15
|
import { randomUUID } from "node:crypto";
|
|
12
16
|
import fs from "node:fs";
|
|
@@ -14,6 +18,10 @@ import { AgentSideConnection, RequestError, ndJsonStream } from "@agentclientpro
|
|
|
14
18
|
import { Readable, Transform, Writable } from "node:stream";
|
|
15
19
|
|
|
16
20
|
const SLOW = Number(process.env.MOCK_SLOW_MS || 60);
|
|
21
|
+
/** Boot latency — deliberately BEFORE the stream is wired, so the slot sits in `starting` for as
|
|
22
|
+
* long as QA asks. Defaults to 0: the mock is instant unless a sweep needs the wake window. */
|
|
23
|
+
const SPAWN_MS = Number(process.env.MOCK_SPAWN_MS || 0);
|
|
24
|
+
if (SPAWN_MS > 0) await new Promise((r) => setTimeout(r, SPAWN_MS));
|
|
17
25
|
|
|
18
26
|
// The file a `[tool-diff]` turn pretends to edit: hermes hands the cockpit the whole file
|
|
19
27
|
// before and after, so the approval surface has a path and a real change to show.
|