agentus 0.1.0 → 0.3.0
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 +19 -2
- package/README.zh-CN.md +396 -0
- package/docs/dev/01-dev-loop.md +186 -0
- package/docs/dev/02-verify.md +237 -0
- package/docs/dev/03-release.md +218 -0
- package/docs/dev/04-recover.md +199 -0
- package/docs/dev/05-agent-driven.md +114 -0
- package/docs/dev/README.md +65 -0
- package/docs/installation.md +366 -0
- package/docs/installation.zh-CN.md +350 -0
- package/package.json +5 -4
- package/packages/server/mock/agent.mjs +35 -0
- package/packages/server/src/acp/registry.ts +51 -2
- package/packages/server/src/acp/session-manager.ts +200 -39
- package/packages/server/src/fs.ts +216 -2
- package/packages/server/src/index.ts +172 -15
- package/packages/server/src/notify/center.ts +12 -1
- package/packages/server/src/pty-helper.py +135 -0
- package/packages/server/src/settings.ts +64 -1
- package/packages/server/src/store/store.ts +35 -3
- package/packages/server/src/term.ts +69 -28
- package/packages/shared/src/index.ts +11 -1
- package/packages/web/dist/assets/index-C5XlgEcj.js +283 -0
- package/packages/web/dist/assets/index-D667PX2c.css +32 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/index-B52l55_4.css +0 -1
- package/packages/web/dist/assets/index-BRgC7oYt.js +0 -246
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Developing Agentus with a coding agent in the loop
|
|
2
|
+
|
|
3
|
+
This doc answers: how do I hand a task on this repository to a coding agent, and how do I
|
|
4
|
+
review what it produces?
|
|
5
|
+
|
|
6
|
+
## AGENTS.md — the file an agent reads first
|
|
7
|
+
|
|
8
|
+
`AGENTS.md` is a plain-markdown conventions file that coding agents (Hermes, Qoder CLI and
|
|
9
|
+
others) read automatically at the start of a session. It is where a repo states the things
|
|
10
|
+
that are true of every change: where things live, the invariants, the git discipline, the
|
|
11
|
+
commands to run. It is not documentation for humans — it is the brief an agent gets before
|
|
12
|
+
it touches anything.
|
|
13
|
+
|
|
14
|
+
**The Agentus repository itself carries no `AGENTS.md` as of this writing** — the
|
|
15
|
+
conventions that govern work on it live in this `docs/dev/` set plus the operator's own
|
|
16
|
+
working notes. `TODO(verify)`: a `docs/dev/`-aware root `AGENTS.md` may be added; if one
|
|
17
|
+
appears it becomes the entry point an agent reads first, and this manual is what it links to.
|
|
18
|
+
|
|
19
|
+
Where `AGENTS.md` **is** load-bearing in this workspace: the operator's spoken-reply
|
|
20
|
+
workspace (`~/Workspace/voice-chat/AGENTS.md`) is read by both `hermes` and `qodercli`, so
|
|
21
|
+
the same style rules apply whichever backend a session runs on. A running session keeps the
|
|
22
|
+
prompt it booted with, so an edit to that file only shows up in a **new** session. When a
|
|
23
|
+
task is about how Agentus-backed sessions should *speak*, that file — not the code — is the
|
|
24
|
+
lever.
|
|
25
|
+
|
|
26
|
+
## Handing a task to an agent
|
|
27
|
+
|
|
28
|
+
Agentus is a good target for agent work because every claim is checkable in-repo. Give the
|
|
29
|
+
agent four things and it can work without guessing.
|
|
30
|
+
|
|
31
|
+
**1. The real entry points.** Name the files, do not describe them:
|
|
32
|
+
|
|
33
|
+
| area | file |
|
|
34
|
+
|---|---|
|
|
35
|
+
| server entry | `packages/server/src/index.ts` (boots, resolves data dir, wires routes/WS) |
|
|
36
|
+
| spawn + ACP relay | `packages/server/src/acp/registry.ts`, `acp/backends.ts` |
|
|
37
|
+
| store | `packages/server/src/store/store.ts` |
|
|
38
|
+
| auth | `packages/server/src/auth.ts` |
|
|
39
|
+
| web app | `packages/web/src/App.tsx`, `state.ts`, `transcript.ts` |
|
|
40
|
+
| mock agent (dev/QA) | `packages/server/mock/agent.mjs` |
|
|
41
|
+
| dev launcher | `scripts/dev.sh` · release: `scripts/relaunch.sh` · `scripts/make-cert.sh` |
|
|
42
|
+
|
|
43
|
+
**2. The reference implementation to compare against.** Almost every UI question has a
|
|
44
|
+
sibling project that already answered it, and the operator expects behaviour parity, not
|
|
45
|
+
copied markup. Point the agent at the right checkout (below) and ask for the *contract* —
|
|
46
|
+
where the control lives, what it reads, how it behaves — never the markup.
|
|
47
|
+
|
|
48
|
+
**3. The verification the change must run.** State it up front so the agent does not declare
|
|
49
|
+
victory on a build:
|
|
50
|
+
|
|
51
|
+
- `npm run typecheck` (0 errors), then `npm run build`;
|
|
52
|
+
- the server smokes for the area (`node scripts/*.mjs`);
|
|
53
|
+
- for a UI change, a CDP sweep **and** `npm run build` before it (the server serves the built
|
|
54
|
+
bundle). If no sweep covers the surface, the round writes one — see [02-verify.md](./02-verify.md);
|
|
55
|
+
- for a release, the deployed-bundle checks in [03-release.md](./03-release.md).
|
|
56
|
+
|
|
57
|
+
**4. The boundary.** The live instance may be restarted only by an explicit release, and
|
|
58
|
+
only on the operator's word unless the change is strictly web-only. Say this in the brief;
|
|
59
|
+
an unprompted `promote` kills the session the agent is running in.
|
|
60
|
+
|
|
61
|
+
## Reference implementations, and where they live
|
|
62
|
+
|
|
63
|
+
The operator asks to align a surface with these two products ("体验对齐" = behaviour parity).
|
|
64
|
+
Read the checkouts before designing; take the contract, not the code — a genuinely derived
|
|
65
|
+
block needs its license/NOTICE line, an idea needs nothing.
|
|
66
|
+
|
|
67
|
+
| product | checkout | what it is good for |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| AionUi | `~/Project/AionUi` (`packages/desktop/src/renderer`) | layout mechanics: resizable panes, grouped history, local-file preview, table zoom, markdown utils; its `docs/prds/**` give per-surface intent *and* a "已知局限" appendix (the admitted flaws are what NOT to copy) |
|
|
70
|
+
| hermes-studio | `~/.hermes/cache/studio-ref` (client) | the session menu, tool-run grouping (`ToolRunSummary.vue`, `tool-run-grouping.ts`), the message queue, `appendedTextDelta` streaming |
|
|
71
|
+
|
|
72
|
+
Repo-side notes on both are under [`docs/refs/`](../refs/) (`aionui-acp-rules.md`,
|
|
73
|
+
`hermes-studio-ui-ideas.md`).
|
|
74
|
+
|
|
75
|
+
Two rules that come out of using them:
|
|
76
|
+
|
|
77
|
+
- **Code, not screenshots.** A reference's source gives the decisions (e.g. "running tools
|
|
78
|
+
are not grouped"; the exact `useResizableSplit` clamping) that a screenshot hides.
|
|
79
|
+
- **Check the form before borrowing.** AionUi is an Electron app; a mechanism that hangs off
|
|
80
|
+
its Titlebar has no equivalent in a pure web page + Android WebView shell. Ask "does it
|
|
81
|
+
attach to a layer we have?" before copying it.
|
|
82
|
+
|
|
83
|
+
## Review discipline
|
|
84
|
+
|
|
85
|
+
These are the habits that keep agent-produced changes safe on a two-instance, live-traffic
|
|
86
|
+
codebase.
|
|
87
|
+
|
|
88
|
+
- **Diagnose before editing.** For a reported defect or a "why did it do that" question,
|
|
89
|
+
produce the mechanism (store query, log line, code path) and the numbers **first**, and
|
|
90
|
+
stop there. A diff in the same turn as a question is answering a question with
|
|
91
|
+
unrequested work. When a spec is restated, write your reading back — the shape, one
|
|
92
|
+
example, and every point where your reading could differ — before touching code.
|
|
93
|
+
- **Root cause over symptom, and fix the class.** The operator reports the symptom
|
|
94
|
+
accurately and the cause never. Find the general condition, not just the instance the
|
|
95
|
+
report names, and say which class of defect it belongs to.
|
|
96
|
+
- **One logical change per commit.** Write the message to a file and use `-F` (inline
|
|
97
|
+
backticks / `$` / `!` are eaten by the shell). If a file carries someone else's in-flight
|
|
98
|
+
work, commit only your hunk (`git diff -U3 -- <file>` → `git apply --cached <patch>`),
|
|
99
|
+
never a plain `git add <file>`.
|
|
100
|
+
- **Reproduce before you fix; make the new check prove the bug.** Run the assertion against
|
|
101
|
+
the current (broken) build, quote the failing count, then fix, then re-run. A check that
|
|
102
|
+
never failed on the broken build proves nothing.
|
|
103
|
+
- **Measure before and after, and put the numbers in the reply.** "Looks the same to me" is
|
|
104
|
+
not evidence, and a light/dark screenshot pair can be two copies of one theme — compare
|
|
105
|
+
real values.
|
|
106
|
+
- **Re-verify the checks that watched the thing you changed.** A behaviour change
|
|
107
|
+
invalidates its own observation point; a harness edit invalidates every green it produced.
|
|
108
|
+
- **Leave the cockpit as you found it.** Purge the sessions, workspaces and probe files the
|
|
109
|
+
round created; keep the operator's; report the final counts.
|
|
110
|
+
- **`main` and the live instance move only on their own paths** — merge/release per
|
|
111
|
+
[03-release.md](./03-release.md), never as a side effect of a "fix".
|
|
112
|
+
|
|
113
|
+
Cross-links: [01-dev-loop.md](./01-dev-loop.md) · [02-verify.md](./02-verify.md) ·
|
|
114
|
+
[03-release.md](./03-release.md) · [04-recover.md](./04-recover.md).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Developing Agentus
|
|
2
|
+
|
|
3
|
+
This is the in-repo manual for working on Agentus: how to run a development instance,
|
|
4
|
+
how to prove a change works, how to release it to the live instance, and how to recover
|
|
5
|
+
when it breaks.
|
|
6
|
+
|
|
7
|
+
**What Agentus is.** A multi-session web cockpit for ACP-speaking coding agents
|
|
8
|
+
(`hermes acp` / `qodercli --acp`). The server spawns one child process per session and
|
|
9
|
+
relays ACP JSON-RPC between the browser and that agent. The UI holds no intelligence:
|
|
10
|
+
gate access → spawn → relay → stream → persist → surface permission cards. State lives in
|
|
11
|
+
`<DATA_DIR>` (default `~/.agentus`; see [01-dev-loop.md](./01-dev-loop.md) for how the
|
|
12
|
+
directory resolves). Node `>= 22.5` is required — the store is `node:sqlite`.
|
|
13
|
+
|
|
14
|
+
## The mental model
|
|
15
|
+
|
|
16
|
+
There are **two instances of the same codebase**, distinguished only by environment
|
|
17
|
+
variables, and they must never touch each other's code or data:
|
|
18
|
+
|
|
19
|
+
| | live | dev |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| tree | `worktrees/agentus` | `worktrees/agentus-dev` |
|
|
22
|
+
| role | the instance the operator actually uses | where changes are made and tested |
|
|
23
|
+
| HTTP port | `8787` | `8901` |
|
|
24
|
+
| TLS port | `8443` | off (`AGENTUS_TLS_PORT=0`) |
|
|
25
|
+
| data dir | `<tree>/packages/server/.data` (pinned by the launcher) | `/tmp/agentus-qa-account` |
|
|
26
|
+
| launcher | `~/.hermes/cache/agentus/launch.py` | `scripts/dev.sh start\|stop\|status\|logs` |
|
|
27
|
+
| hot reload | **no** — restart is explicit | yes (`tsx watch`) |
|
|
28
|
+
|
|
29
|
+
**Why two trees.** `tsx watch` reloads the whole server on any server-source edit, and a
|
|
30
|
+
reload SIGTERMs every child process — every live `hermes acp` session, including the one
|
|
31
|
+
the operator is typing into. Editing server code in the live tree would therefore kill his
|
|
32
|
+
conversation mid-turn (measured: page went white mid-turn, the turn died). The live tree
|
|
33
|
+
runs **without** a watcher and is only moved by an explicit release
|
|
34
|
+
([03-release.md](./03-release.md)). All edits happen in the dev tree.
|
|
35
|
+
|
|
36
|
+
**The loop you repeat every round:**
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
change (dev tree) → verify (sweeps + suites) → promote (dev→live) → record (track + lessons)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Nothing merges to `main` or reaches the live instance without the operator saying so —
|
|
43
|
+
the one exception is a strictly web-only change, and even that is bounded by what
|
|
44
|
+
`promote` prints (see [03-release.md](./03-release.md)).
|
|
45
|
+
|
|
46
|
+
## The five documents
|
|
47
|
+
|
|
48
|
+
| doc | the question it answers |
|
|
49
|
+
|---|---|
|
|
50
|
+
| [01-dev-loop.md](./01-dev-loop.md) | How do I run a development instance, and how is its data kept separate from live? |
|
|
51
|
+
| [02-verify.md](./02-verify.md) | How do I prove a change actually works — unit suites and real-browser sweeps? |
|
|
52
|
+
| [03-release.md](./03-release.md) | How do I get a verified change onto the live instance, and how do I roll it back? |
|
|
53
|
+
| [04-recover.md](./04-recover.md) | The instance is broken — where do I look, and how do I fix it safely? |
|
|
54
|
+
| [05-agent-driven.md](./05-agent-driven.md) | How do I hand a task on this repo to a coding agent, and review what it does? |
|
|
55
|
+
|
|
56
|
+
## Related in-repo docs
|
|
57
|
+
|
|
58
|
+
- [`docs/transcript-folding.md`](../transcript-folding.md) — how one reply becomes rows,
|
|
59
|
+
pages and bubbles (the store's row semantics).
|
|
60
|
+
- [`docs/android-notify-contract.md`](../android-notify-contract.md) — the server↔phone
|
|
61
|
+
notification contract.
|
|
62
|
+
- [`docs/refs/`](../refs/) — notes on the reference UIs and ACP rules this project borrows
|
|
63
|
+
behaviour from.
|
|
64
|
+
- [`README.md`](../../README.md) — install, environment variables, login, TLS and the
|
|
65
|
+
public tunnel.
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Installing Agentus
|
|
2
|
+
|
|
3
|
+
[中文](./installation.zh-CN.md) · [README](../README.md)
|
|
4
|
+
|
|
5
|
+
Agentus is a multi-session web cockpit for ACP-speaking coding agents. One npm package
|
|
6
|
+
(`agentus`) runs a Node server that serves a built React SPA and spawns agent CLIs
|
|
7
|
+
(`hermes acp` / `qodercli --acp`) as child processes. The browser drives the sessions; the
|
|
8
|
+
reasoning stays inside the CLI subprocesses.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
Two things, both required:
|
|
13
|
+
|
|
14
|
+
- **Node >= 22.5.** The store is `node:sqlite`, which landed in 22.5. Agentus refuses to start
|
|
15
|
+
on anything older, with a clear message.
|
|
16
|
+
- **An ACP agent CLI on your PATH** — `hermes acp` or `qodercli --acp`. This is the one
|
|
17
|
+
prerequisite npm cannot install for you. Without one the cockpit still runs and you can still
|
|
18
|
+
log in; you just have no backend to open a session on.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# check the agent side (Hermes self-checks; qoder needs a login first)
|
|
22
|
+
hermes acp --check
|
|
23
|
+
qodercli login # else newSession fails with -32000
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Node itself is easy to get:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
nvm install 22 && nvm use 22 # or
|
|
30
|
+
brew install node # or https://nodejs.org/en/download
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
### One command — the published package (public)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx agentus # run once, nothing installed globally
|
|
39
|
+
npm i -g agentus # …or install it, then run `agentus`
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`npx agentus` runs the cockpit in the foreground — Ctrl-C stops it, and it takes its agent
|
|
43
|
+
children with it.
|
|
44
|
+
|
|
45
|
+
### One command — the installer script
|
|
46
|
+
|
|
47
|
+
The same install from the repository's own script, with the checks and the fallbacks spelled out
|
|
48
|
+
(no `sudo`, ever):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
curl -fsSL https://raw.githubusercontent.com/agentus-org/agentus/main/scripts/install.sh | bash
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The installer (`scripts/install.sh`) checks Node, installs the published `agentus` package with
|
|
55
|
+
npm (globally), and falls back to a per-user prefix (`~/.local`) if the global tree is not
|
|
56
|
+
writable — it never reaches for `sudo`. It prints the start command and the URL when it is done.
|
|
57
|
+
It refuses to run as root unless you set `AGENTUS_ALLOW_ROOT=1`.
|
|
58
|
+
|
|
59
|
+
Overrides, passed as environment variables:
|
|
60
|
+
|
|
61
|
+
| var | default | meaning |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `AGENTUS_VERSION` | `latest` | pin a package version, e.g. `0.1.0` |
|
|
64
|
+
| `AGENTUS_INSTALL_DIR` | unset | install into this `npm --prefix` instead of the global one |
|
|
65
|
+
| `AGENTUS_REGISTRY` | npm's own | registry to fetch from |
|
|
66
|
+
| `AGENTUS_ALLOW_ROOT` | `0` | `1` permits running as root |
|
|
67
|
+
| `AGENTUS_NODE_MIN` | `22.5` | override the Node floor check |
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# pin a version and a prefix
|
|
71
|
+
AGENTUS_VERSION=0.1.0 AGENTUS_INSTALL_DIR="$HOME/.local" \
|
|
72
|
+
bash scripts/install.sh
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### From source (a checkout)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/agentus-org/agentus.git
|
|
79
|
+
cd agentus
|
|
80
|
+
npm start # installs if needed, builds, serves (scripts/start.sh)
|
|
81
|
+
npm run dev -w @agentus/web # front-end hot reload (Vite :5173, reachable over LAN)
|
|
82
|
+
npm run agentus # the same CLI as `npx agentus`, straight from the checkout
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`npm start` (→ `scripts/start.sh`) checks the Node version, installs dependencies with
|
|
86
|
+
`NODE_ENV=development` when `node_modules` is missing, builds when the web bundle is stale, then
|
|
87
|
+
serves once (no watcher). `AGENTUS_PORT=9000 npm start` moves the port.
|
|
88
|
+
|
|
89
|
+
## CLI
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
agentus [options]
|
|
93
|
+
|
|
94
|
+
-p, --port <n> port to listen on (AGENTUS_PORT, default 8787)
|
|
95
|
+
-d, --data <dir> where state lives (sqlite, login, TLS material, settings)
|
|
96
|
+
(AGENTUS_DATA, default ~/.agentus)
|
|
97
|
+
-o, --open open the cockpit in your browser once it is up
|
|
98
|
+
--where print the resolved paths and exit (starts nothing)
|
|
99
|
+
-h, --help this text
|
|
100
|
+
-v, --version print the version
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--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
|
|
105
|
+
answer to "where did my data go" (see below).
|
|
106
|
+
|
|
107
|
+
## First run
|
|
108
|
+
|
|
109
|
+
1. Start it: `agentus` (or `npx agentus`, or `npm start` from a checkout).
|
|
110
|
+
2. Open **http://localhost:8787** and log in. Default credentials:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
username: admin
|
|
114
|
+
password: 123456 ← change it
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Change it in the app: **Settings → 账号** (the first card). It applies immediately and writes
|
|
118
|
+
`<DATA_DIR>/credentials.json` (0600, scrypt hash).
|
|
119
|
+
3. Add a backend. The registry is seeded on first boot from the builtin rows (see
|
|
120
|
+
[Backends](#backends)); check that `hermes` or `qoder` resolves, or point a row at your own
|
|
121
|
+
command.
|
|
122
|
+
4. **+ new session** → pick a backend and a working directory → start it.
|
|
123
|
+
|
|
124
|
+
The first session you start will use the Hermes home your own agent already uses (`~/.hermes`)
|
|
125
|
+
unless the backend row names another one — see [HERMES_HOME](#hermes_home).
|
|
126
|
+
|
|
127
|
+
## Configuration reference
|
|
128
|
+
|
|
129
|
+
### Environment variables
|
|
130
|
+
|
|
131
|
+
| var | default | meaning |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `AGENTUS_PORT` | `8787` | server port (never reads bare `PORT`) |
|
|
134
|
+
| `AGENTUS_DATA` | `~/.agentus` | where state lives; see [Data directory](#data-directory) |
|
|
135
|
+
| `AGENTUS_HERMES_CMD` | `hermes` | binary to spawn for the hermes backend |
|
|
136
|
+
| `AGENTUS_QODER_CMD` | `~/.local/bin/qodercli` | ditto for qoder |
|
|
137
|
+
| `AGENTUS_HERMES_HOME` | `~/.hermes` | default `HERMES_HOME` for a row that does not name one |
|
|
138
|
+
| `AGENTUS_PERM_TIMEOUT_MS` | `300000` (5 min) | how long a permission prompt waits before auto-cancelling |
|
|
139
|
+
| `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
|
+
| `AGENTUS_TTS_BASE_URL` | unset | OpenAI-compatible base for server speech synthesis (`…/v1`); unset = browser voices only |
|
|
144
|
+
| `AGENTUS_TTS_API_KEY` / `AGENTUS_TTS_MODEL` / `AGENTUS_TTS_VOICE` | – / `tts-1` / `alloy` | ditto |
|
|
145
|
+
| `AGENTUS_STT_BASE_URL` | unset | OpenAI-compatible base for server transcription; unset = browser recognition only |
|
|
146
|
+
| `AGENTUS_STT_API_KEY` / `AGENTUS_STT_MODEL` / `AGENTUS_STT_LANGUAGE` | – / `whisper-1` / – | ditto |
|
|
147
|
+
| `DASHSCOPE_API_KEY` / `DASHSCOPE_BASE_URL` | – | 百炼 (DashScope) credentials; read from the process env **and** `~/.hermes/.env` |
|
|
148
|
+
|
|
149
|
+
Auth variables:
|
|
150
|
+
|
|
151
|
+
| var | default | meaning |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `AGENTUS_AUTH` | `on` | `off` = no login at all (local hacking only) |
|
|
154
|
+
| `AGENTUS_USERNAME` | `admin` | operator name |
|
|
155
|
+
| `AGENTUS_PASSWORD` | `123456` | operator password (plaintext in env) |
|
|
156
|
+
| `AGENTUS_PASSWORD_HASH` | — | `scrypt:<salt>:<hex>`; wins over `AGENTUS_PASSWORD` |
|
|
157
|
+
| `AGENTUS_SESSION_TTL_MS` | 604800000 (7d) | session lifetime |
|
|
158
|
+
| `AGENTUS_AUTH_SECRET` | `<DATA_DIR>/auth.secret` | cookie signing key (0600, auto-minted) |
|
|
159
|
+
| `AGENTUS_AUTH_TOKEN` | `<DATA_DIR>/auth.token` | machine token for scripts (0600, auto-minted) |
|
|
160
|
+
| `AGENTUS_LOGIN_MAX_FAILS` / `AGENTUS_LOGIN_LOCK_MS` | `5` / `30000` | per-IP brute-force lockout |
|
|
161
|
+
| `AGENTUS_BASIC_AUTH` | — | HTTP Basic in front of **everything** (`:pass` checks only the password) |
|
|
162
|
+
|
|
163
|
+
The voice/theme environment variables are a **bootstrap only**: the settings page (⚙ in the rail)
|
|
164
|
+
owns them afterwards and stores them under `<DATA_DIR>/settings.json` (0600). What the page says
|
|
165
|
+
wins; the env is what makes a fresh install work before anyone opens it.
|
|
166
|
+
|
|
167
|
+
### Data directory
|
|
168
|
+
|
|
169
|
+
`~/.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
|
|
171
|
+
boot log prints it **and why that directory won**:
|
|
172
|
+
|
|
173
|
+
| order | rule |
|
|
174
|
+
|---|---|
|
|
175
|
+
| 1 | `AGENTUS_DATA` when it is set — every launcher and dev/QA instance pins this |
|
|
176
|
+
| 2 | `~/.agentus` once it holds something, or when there is no repo-side data yet |
|
|
177
|
+
| 3 | `<repo>/packages/server/.data` — an existing checkout that predates this layout |
|
|
178
|
+
|
|
179
|
+
Rule 3 exists so an install that quietly switched directories cannot come up with an empty
|
|
180
|
+
session list (which reads exactly like "my sessions are gone"). To move such a checkout over,
|
|
181
|
+
boot it once with `AGENTUS_DATA=~/.agentus`. `agentus --where` prints what the server resolved
|
|
182
|
+
instead of making you guess.
|
|
183
|
+
|
|
184
|
+
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.
|
|
187
|
+
|
|
188
|
+
### Ports
|
|
189
|
+
|
|
190
|
+
| port | speaks | for |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `AGENTUS_PORT` (8787) | 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 |
|
|
194
|
+
| 5173 | Vite dev server | front-end hot reload, only during `npm run dev` |
|
|
195
|
+
|
|
196
|
+
Same handler, same routes, same auth on both listeners; only the socket differs.
|
|
197
|
+
|
|
198
|
+
### Login
|
|
199
|
+
|
|
200
|
+
The cockpit is behind a login by default. Two credentials exist:
|
|
201
|
+
|
|
202
|
+
| credential | how it travels | who uses it |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| session cookie | `HttpOnly` + `SameSite=Lax`, HMAC-SHA256 signed, 7d | the browser (`POST /api/auth/login`) |
|
|
205
|
+
| machine token | `Authorization: Bearer <t>` or `?token=<t>` | scripts, CI, launchers — read it from `<DATA_DIR>/auth.token` (0600) |
|
|
206
|
+
|
|
207
|
+
Sessions are stateless cookies with a server-side index at `<DATA_DIR>/sessions.json` (0600), so
|
|
208
|
+
revocation and "log out everywhere" are real and survive a restart. `AGENTUS_USERNAME` /
|
|
209
|
+
`AGENTUS_PASSWORD` / `AGENTUS_PASSWORD_HASH` are the bootstrap; once you save in the 账号 card the
|
|
210
|
+
file wins. Delete `credentials.json` to fall back to the environment.
|
|
211
|
+
|
|
212
|
+
### TLS
|
|
213
|
+
|
|
214
|
+
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
|
+
```
|
|
222
|
+
|
|
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/`.
|
|
227
|
+
|
|
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.
|
|
232
|
+
|
|
233
|
+
### Backends
|
|
234
|
+
|
|
235
|
+
| id | command | notes |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| `hermes` | `hermes acp` | needs a Hermes install on PATH (`hermes acp --check`) |
|
|
238
|
+
| `qoder` | `qodercli --acp` | needs `qodercli login` first, else `newSession` fails `-32000` |
|
|
239
|
+
| `mock` | `node packages/server/mock/agent.mjs` | offline dev/QA: fake streaming, permission prompts, crash/plan/tool triggers |
|
|
240
|
+
|
|
241
|
+
Adding a backend is one row in `packages/server/src/acp/backends.ts`; the protocol layer never
|
|
242
|
+
changes.
|
|
243
|
+
|
|
244
|
+
## HERMES_HOME
|
|
245
|
+
|
|
246
|
+
A spawned agent CLI inherits the server's environment, so the server **always sets `HERMES_HOME`
|
|
247
|
+
explicitly**: the row's own home if it names one, otherwise the default — **your real `~/.hermes`**,
|
|
248
|
+
the same `state.db` your gateway and Studio have open. That is the intended production setting: a
|
|
249
|
+
session drives your actual agent, with your config, credentials, memory and session list.
|
|
250
|
+
|
|
251
|
+
An *inherited* `HERMES_HOME` is ignored, so where a session writes is always something you chose —
|
|
252
|
+
on the backend row, or via `AGENTUS_HERMES_HOME`. To keep one session's data separate, name a
|
|
253
|
+
directory in that row's HERMES_HOME; no permission is needed.
|
|
254
|
+
|
|
255
|
+
## Running it as a service
|
|
256
|
+
|
|
257
|
+
Agentus runs in the foreground by default; a service unit keeps it up across logins and reboots.
|
|
258
|
+
Point the unit at the same `AGENTUS_DATA` you use interactively, or the cockpit will come up with
|
|
259
|
+
a different session list.
|
|
260
|
+
|
|
261
|
+
**macOS (launchd)** — `~/Library/LaunchAgents/org.agentus.cockpit.plist`:
|
|
262
|
+
|
|
263
|
+
```xml
|
|
264
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
265
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
266
|
+
<plist version="1.0">
|
|
267
|
+
<dict>
|
|
268
|
+
<key>Label</key> <string>org.agentus.cockpit</string>
|
|
269
|
+
<key>ProgramArguments</key>
|
|
270
|
+
<array>
|
|
271
|
+
<string>/usr/bin/env</string><string>agentus</string><string>--port</string><string>8787</string>
|
|
272
|
+
</array>
|
|
273
|
+
<key>EnvironmentVariables</key>
|
|
274
|
+
<dict><key>AGENTUS_DATA</key><string>/Users/YOU/.agentus</string></dict>
|
|
275
|
+
<key>RunAtLoad</key> <true/>
|
|
276
|
+
<key>KeepAlive</key> <true/>
|
|
277
|
+
<key>StandardOutPath</key> <string>/tmp/agentus.out.log</string>
|
|
278
|
+
<key>StandardErrorPath</key><string>/tmp/agentus.err.log</string>
|
|
279
|
+
</dict>
|
|
280
|
+
</plist>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
launchctl load ~/Library/LaunchAgents/org.agentus.cockpit.plist
|
|
285
|
+
launchctl kickstart -k gui/$(id -u)/org.agentus.cockpit # restart
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Linux (systemd, user unit)** — `~/.config/systemd/user/agentus.service`:
|
|
289
|
+
|
|
290
|
+
```ini
|
|
291
|
+
[Unit]
|
|
292
|
+
Description=Agentus cockpit
|
|
293
|
+
After=network.target
|
|
294
|
+
|
|
295
|
+
[Service]
|
|
296
|
+
ExecStart=/usr/bin/env agentus --port 8787
|
|
297
|
+
Environment=AGENTUS_DATA=%h/.agentus
|
|
298
|
+
Restart=on-failure
|
|
299
|
+
|
|
300
|
+
[Install]
|
|
301
|
+
WantedBy=default.target
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
systemctl --user daemon-reload
|
|
306
|
+
systemctl --user enable --now agentus
|
|
307
|
+
journalctl --user -u agentus -f
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
TODO(verify): the exact `env` resolution of `agentus` inside a launchd/systemd unit depends on
|
|
311
|
+
where npm put the bin (a per-user prefix is not on a service's PATH by default) — use an absolute
|
|
312
|
+
path to the `agentus` bin if the unit fails to start.
|
|
313
|
+
|
|
314
|
+
## Upgrade
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
npm i -g agentus@latest # or: npx agentus@latest … for a one-off
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Your state is untouched — `~/.agentus` (or wherever `AGENTUS_DATA` points) is not inside the
|
|
321
|
+
package, so an upgrade cannot delete it. If you installed into a prefix with
|
|
322
|
+
`AGENTUS_INSTALL_DIR=~/.local`, upgrade with the same prefix. On startup the server adopts existing
|
|
323
|
+
session cookies once instead of logging every device out.
|
|
324
|
+
|
|
325
|
+
## Uninstall
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npm rm -g agentus # remove the program (state stays)
|
|
329
|
+
rm -rf ~/.agentus # remove state — SQLite, login, TLS key, settings
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
If you installed into a per-user prefix, `npm rm -g --prefix ~/.local agentus`. Uninstalling never
|
|
333
|
+
touches `~/.hermes` (your agents' own data) or any working directory.
|
|
334
|
+
|
|
335
|
+
## Troubleshooting
|
|
336
|
+
|
|
337
|
+
**Port already in use.** `agentus --port 9000`, or find and stop the holder:
|
|
338
|
+
`lsof -nP -iTCP:8787 -sTCP:LISTEN`. Agentus never reads bare `PORT` for exactly this reason.
|
|
339
|
+
|
|
340
|
+
**"No backend" / a session won't start.** The agent CLI is missing or not logged in. Run
|
|
341
|
+
`hermes acp --check`, or `qodercli login` (a `-32000` on `newSession` means qoder is not logged
|
|
342
|
+
in). Check the backend row's command in Settings; the boot log and a 探测 (probe) say whether the
|
|
343
|
+
command resolves.
|
|
344
|
+
|
|
345
|
+
**Login problems.**
|
|
346
|
+
|
|
347
|
+
- Forgot the password: delete `<DATA_DIR>/credentials.json` and restart — the bootstrap from
|
|
348
|
+
`AGENTUS_USERNAME` / `AGENTUS_PASSWORD` applies again (or the defaults).
|
|
349
|
+
- Locked out by the brute-force limiter: **Settings → 登录失败锁定** lists the locked IPs with
|
|
350
|
+
unlock / unlock all.
|
|
351
|
+
- Locked out entirely (can't log in to reach Settings): delete `<DATA_DIR>/sessions.json` to drop
|
|
352
|
+
the session index, or restart the server — the in-memory lockout clears on boot.
|
|
353
|
+
|
|
354
|
+
**"My sessions are gone."** The server is reading a different data directory. Run `agentus --where`
|
|
355
|
+
— it prints the data dir **and which rule chose it** (top of this page). The usual cause is an
|
|
356
|
+
existing checkout whose sessions live in `<repo>/packages/server/.data` while the install now
|
|
357
|
+
defaults to `~/.agentus`. Boot once with `AGENTUS_DATA` set to the directory that actually holds
|
|
358
|
+
them, e.g. `AGENTUS_DATA=~/.agentus agentus` or `AGENTUS_DATA=<repo>/packages/server/.data agentus`.
|
|
359
|
+
|
|
360
|
+
**The page loads but says "reconnecting" forever.** A tunnel is dropping WebSocket `Upgrade`
|
|
361
|
+
requests. The live stream rides on `/ws`; use a tunnel that passes WebSockets through (SakuraFrp
|
|
362
|
+
TCP+auto-HTTPS tunnels are verified working).
|
|
363
|
+
|
|
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 `:8787` port on the LAN, or install the
|
|
366
|
+
issuer once from `/cert.crt` (see [TLS](#tls)).
|