@monster-orange/den 0.4.0 → 0.4.1
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/CHANGELOG.md +409 -0
- package/docs/distributions.md +204 -14
- package/package.json +3 -1
- package/src/shared/actor-token.ts +275 -0
- package/src/shared/agent-actor.ts +12 -2
- package/src/shared/agents/den-claude.ts +99 -1
- package/src/shared/agents/runtime.ts +7 -0
- package/src/shared/agents/runtimes.ts +9 -2
- package/src/shared/environment-manager/scope.ts +22 -0
- package/src/shared/environment-manager/service-runner.ts +94 -2
- package/src/shared/profile.ts +15 -4
- package/src/shared/sessions/client.ts +9 -2
- package/src/shared/sessions/env.ts +13 -3
- package/src/shared/sessions/host.ts +23 -0
- package/src/shared/web-secret.ts +181 -0
- package/src/shared/web-session.ts +491 -0
- package/src/ui/cli/agent-ops.ts +843 -0
- package/src/ui/cli/agents-command.ts +96 -614
- package/src/ui/cli/flag-parser.ts +21 -0
- package/src/ui/cli/index.ts +94 -10
- package/src/ui/cli/sessions-command.ts +32 -0
- package/src/ui/cli/target-workspace.ts +105 -0
- package/src/ui/cli/web-command.ts +536 -14
- package/src/ui/mcp/api.ts +7 -0
- package/src/ui/mcp/server.ts +74 -6
- package/src/ui/server/agent-routes.ts +323 -0
- package/src/ui/server/audit.ts +81 -27
- package/src/ui/server/http-errors.ts +21 -3
- package/src/ui/server/index.ts +814 -66
- package/src/ui/server/public/agents-page.js +648 -0
- package/src/ui/server/public/agents-section.js +299 -0
- package/src/ui/server/public/agents.html +439 -0
- package/src/ui/server/public/appearance.js +3 -0
- package/src/ui/server/public/error-page.js +103 -0
- package/src/ui/server/public/header.js +45 -11
- package/src/ui/server/public/i18n/es.js +90 -1
- package/src/ui/server/public/i18n/ja.js +86 -1
- package/src/ui/server/public/local-dev.html +53 -0
- package/src/ui/server/public/logs.html +3 -0
- package/src/ui/server/public/panel.js +1 -1
- package/src/ui/server/public/release.html +8 -0
- package/src/ui/server/public/settings-page.js +5 -0
- package/src/ui/server/public/settings-registry.js +33 -0
- package/src/ui/server/public/shell.js +248 -5
- package/src/ui/server/public/topbar.js +51 -2
- package/src/ui/server/request-guard.ts +670 -0
- package/src/ui/server/route-classes.ts +317 -0
- package/src/ui/server/session-routes.ts +445 -0
- package/src/ui/server/shutdown-guard.ts +91 -0
- package/src/ui/server/sudo-store.ts +29 -3
- package/src/ui/server/ui-assets.ts +10 -1
- package/src/ui/server/websocket.ts +326 -0
- package/src/ui/server/workspace-routes.ts +13 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,415 @@ All notable changes to Den are documented here. Each release lists the
|
|
|
4
4
|
config and manifest changes a distribution must make to move to it under
|
|
5
5
|
**Migrations**.
|
|
6
6
|
|
|
7
|
+
## [0.4.1] - 2026-11-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **The Agents tab** (0.4.1-dev.5, AG-4a-term, AG spec §5d): "Agents" in
|
|
12
|
+
the rail under Develop, after Graph, at `/agents`. The list holds every
|
|
13
|
+
session and agent of the workspaces in view (the scope bar, or `?ws=`),
|
|
14
|
+
grouped by workspace: state, persona and runtime, and a waiting badge
|
|
15
|
+
(shown once the host can tell, AG-6a);
|
|
16
|
+
a plain session (`sessions start`) is labelled "session", an agent whose
|
|
17
|
+
token `web logout --all` revoked says "token revoked". Click a row to
|
|
18
|
+
open its terminal (xterm.js, served from Den's own node_modules: no CDN):
|
|
19
|
+
the scrollback, then the live output, and your keys go to the session.
|
|
20
|
+
Up to four panes tile (a fifth closes the oldest), and the URL holds them
|
|
21
|
+
(`/agents?open=<id>,<id>`), so a reload or Back reattaches each with its
|
|
22
|
+
scrollback. A pane's bar has its name, workspace and runtime, Stop (an
|
|
23
|
+
agent, behind the lock) and Close, which detaches: the session keeps
|
|
24
|
+
running, as it does when you leave the page. "+ Start agent" (or
|
|
25
|
+
`/agents?start=1`) asks for the workspace under All, starts it and opens
|
|
26
|
+
its pane. ⌘K offers "Agents › <ws> / <name>" for each and "Start agent…".
|
|
27
|
+
The Workspaces page's Agents section links to it ("Open in Agents →",
|
|
28
|
+
and Open on a row with a session). In English, Japanese and Spanish;
|
|
29
|
+
light and dark; one column on a phone. Without the optional PTY module
|
|
30
|
+
the list says terminals need it, and nothing else changes.
|
|
31
|
+
- **`GET /api/sessions` and the `/api/sessions/<id>/io` WebSocket**
|
|
32
|
+
(0.4.1-dev.5). The list: the sessions and agents of the active
|
|
33
|
+
workspaces, this distribution's only (asking never starts a session
|
|
34
|
+
host). The io socket is the session host's attach: binary frames carry
|
|
35
|
+
output and input, a text frame `{"type":"resize","cols":c,"rows":r}`
|
|
36
|
+
resizes, and the server says `{"type":"session",…}` first and
|
|
37
|
+
`{"type":"exit","exitCode":n}` at the end. Closing it detaches and never
|
|
38
|
+
signals the session; several browsers and the CLI can attach at once
|
|
39
|
+
(the size is the last one's). It is shell-grade: this server's Host, the
|
|
40
|
+
page's Origin, the CSRF token as the `den.csrf.<token>` subprotocol (a
|
|
41
|
+
browser's WebSocket can't send a header), a sign-in beyond loopback, and
|
|
42
|
+
an agent token reaches its own session's io only. Every attach and
|
|
43
|
+
detach is in the audit trail, with the actor from the credential. A
|
|
44
|
+
plain GET of it is 426.
|
|
45
|
+
|
|
46
|
+
- **`web logout --all` revokes every agent's token** (0.4.1-dev.4, SEC-1
|
|
47
|
+
§6c.2): it is the "something leaked" button, so it ends everything. A
|
|
48
|
+
running agent's next call gets 401 `token_revoked`: "This agent's token
|
|
49
|
+
was revoked (`web logout --all`): restart or resume the agent to get a
|
|
50
|
+
new one." (its stdio MCP server refuses to start with one). The output
|
|
51
|
+
says how many it revoked. Plain `web logout` (one browser, or every
|
|
52
|
+
browser) leaves agent tokens alone.
|
|
53
|
+
- **den-claude denies Den's state dir to the agent** (0.4.1-dev.4, SEC-1
|
|
54
|
+
§6c.1): `--settings <file>` (0600) with `permissions.deny` for `Read` and
|
|
55
|
+
`Bash` of `<state>/web.secret` and `<state>/sessions/**`. It binds
|
|
56
|
+
cooperating agents only: an agent runs as the same OS user, and the
|
|
57
|
+
server's token rules are the boundary.
|
|
58
|
+
- **`setup`, `up`, `down` and `restart` across active workspaces**
|
|
59
|
+
(0.4.1-dev.4). `-w <workspace>` names the workspace (`setup` takes one
|
|
60
|
+
now; several still go with `--plan`). With no `-w`, a named service
|
|
61
|
+
resolves in the focus first, then across the other active workspaces, as
|
|
62
|
+
the web API's `&workspace=` does: in exactly one, it runs there (the CLI
|
|
63
|
+
says which); in several, an error lists them with the `-w` command for
|
|
64
|
+
each; in none, or names in different workspaces, an error. Each exits 1
|
|
65
|
+
and runs nothing. Before, a service in an active workspace that wasn't
|
|
66
|
+
the focus made `setup` print nothing and exit 0, `restart` say "not
|
|
67
|
+
found" and `up` say "no derived dependencies".
|
|
68
|
+
|
|
69
|
+
- **Agent actor tokens** (0.4.1-dev.3, SEC-1c, SEC-1 spec §1, §5, §7).
|
|
70
|
+
- **`agents start`** (the CLI and `POST /api/workspaces/<ws>/agents`)
|
|
71
|
+
mints a random `den_at_…` token for the agent and hands it to the
|
|
72
|
+
session as `DEN_ACTOR_TOKEN`, through the §5a-env allowlist's Den
|
|
73
|
+
markers (a caller's, a runtime's or a `--keep-env` value of it is
|
|
74
|
+
dropped). Only its sha256 is stored, in `actor-tokens.json` (0600) in
|
|
75
|
+
the state dir, with the agent, its workspace, its grants and its
|
|
76
|
+
session. One live token per agent: **resume** mints a fresh one and the
|
|
77
|
+
old one stops working; **stop** revokes it, and so does the session's
|
|
78
|
+
end (an exit or a crash), checked when the token is presented.
|
|
79
|
+
- **The scrub always strips a person's credential** from every session's
|
|
80
|
+
env, whatever put it there: `DEN_PERSON_TOKEN`, any value carrying a
|
|
81
|
+
person token, a path to `web.secret`. **`sessions start --keep-env`**
|
|
82
|
+
of one (or of `DEN_ACTOR_TOKEN`) is refused, exit 1, and the session
|
|
83
|
+
host's log records the refusal and who asked (the name, never a value).
|
|
84
|
+
- **The server accepts the agent's bearer.** The CLI inside an agent
|
|
85
|
+
sends its `DEN_ACTOR_TOKEN`, never the person token (an agent without
|
|
86
|
+
one sends none). The audit actor comes from the credential: an entry
|
|
87
|
+
reads `actor: "agent"` only for a verified agent token. Agent headers
|
|
88
|
+
(`X-Den-Agent`, …) next to another credential, or alone, are recorded
|
|
89
|
+
as `claimed` with `unverified: true`, and the Activity panel shows
|
|
90
|
+
them as "(unverified)".
|
|
91
|
+
- **What an agent token can't do**: open another session's routes
|
|
92
|
+
(`/api/sessions/<id>/…`: only its own session; the routes themselves
|
|
93
|
+
come with the terminal), any other shell-grade route (start, resume,
|
|
94
|
+
the session admin routes, so it mints no tokens), `/api/web/*`,
|
|
95
|
+
`/auth`, `/signin`, sign out or rotate; and it never passes the lock
|
|
96
|
+
(R8): no unlock, no sudo password, no prewarmed sudo, and the lock reads
|
|
97
|
+
closed to it. Each answers 403 `agent_forbidden`. A workspace check
|
|
98
|
+
(WS-3a) has its seam in the request guard, allowing everything for now.
|
|
99
|
+
- **The stdio MCP server Den starts for an agent** carries that agent's
|
|
100
|
+
token: its calls are audited as the agent, and `--actor` /
|
|
101
|
+
`--workspace` that don't match the token's record refuse to start.
|
|
102
|
+
- **Signing in the browser** (0.4.1-dev.2, SEC-1b, SEC-1 spec §2, §4). No
|
|
103
|
+
password: reading `web.secret` proves you are this computer's user, and
|
|
104
|
+
the CLI does it.
|
|
105
|
+
- **`<cli> web open`** starts the server if needed and opens a fresh tab
|
|
106
|
+
on a sign-in link; **`<cli> web up`** opens one too when it doesn't find
|
|
107
|
+
an existing tab. **`<cli> web login [--next <path>] [--origin <url>]`**
|
|
108
|
+
prints a fresh link, for another browser.
|
|
109
|
+
The link is `http://localhost:<port>/auth?c=<code>&next=<path>`: the
|
|
110
|
+
code works once, within 60 seconds, and is an HMAC of a random nonce
|
|
111
|
+
and its expiry under `web.secret` (the server records the nonce as
|
|
112
|
+
used). It is printed only on that one line and never logged. `next`
|
|
113
|
+
must be a same-origin path (`//evil`, `http://evil`, `/\evil` go to
|
|
114
|
+
`/`).
|
|
115
|
+
- **`GET /auth?c=…`** swaps the code for a session and answers 303 to
|
|
116
|
+
`next`. A bad, expired or used code gets the signed-out page: "That
|
|
117
|
+
sign-in link expired or was used. Run `<cli> web login` for a new one."
|
|
118
|
+
- **The cookie** `den_session_<port>` (an opaque random id; per port, so
|
|
119
|
+
two distributions on one machine keep their own): HttpOnly,
|
|
120
|
+
SameSite=Strict, Path=/, and Secure when the request came over HTTPS
|
|
121
|
+
(the edge's `X-Forwarded-Proto` counts only on the edge hostname from
|
|
122
|
+
a loopback or Docker peer). The record is server-side, in
|
|
123
|
+
`web-sessions.json` (0600) in the state dir, keyed by an HMAC of the id
|
|
124
|
+
under the secret: created, last seen, the user agent (truncated), the
|
|
125
|
+
host it was issued on and its own CSRF token. It never expires
|
|
126
|
+
(0.4.1-dev.4, above).
|
|
127
|
+
- **`GET /api/session`** answers `{ csrf, actor: "person-browser",
|
|
128
|
+
signedIn: true }` for a signed-in browser, with the session's own CSRF
|
|
129
|
+
token (a signed-in browser's mutations must carry that one). Signed out
|
|
130
|
+
on loopback it answers as before.
|
|
131
|
+
- **The route classes (§4).** On loopback no route needs a sign-in
|
|
132
|
+
(0.4.1-dev.4): read-only is open; mutating and **shell-grade** (an
|
|
133
|
+
agent's start and resume, the session admin routes, later the terminal)
|
|
134
|
+
need Origin and CSRF (as in SEC-1a), or the person's bearer (the CLI and
|
|
135
|
+
MCP need no setup). Beyond loopback (the edge hostname, a LAN name, or
|
|
136
|
+
a peer that isn't loopback): every route needs a session or a bearer,
|
|
137
|
+
so a LAN or edge Host is usable again once signed in. Only `/auth`,
|
|
138
|
+
`/signin`, `/api/session` and the UI's static files need none.
|
|
139
|
+
- **The signed-out page** (beyond loopback), in the shell frame: "Sign in
|
|
140
|
+
to use <name>", "Run `<cli> web login` and open the link." with a copy
|
|
141
|
+
button (through the edge the command names its origin), and on
|
|
142
|
+
`127.0.0.1` or `[::1]` a link to the same page on `localhost`, where the
|
|
143
|
+
cookie is. It never offers a form for a secret or a token. `/api`
|
|
144
|
+
answers 401 `signin_required` (R26's shape). In en, ja and es.
|
|
145
|
+
- **localhost is canonical**: the links Den prints use it, and following
|
|
146
|
+
the signed-out page's link from 127.0.0.1 lands signed in.
|
|
147
|
+
- **`<cli> web sessions [--json]`** lists the signed-in browsers (id
|
|
148
|
+
prefix, created, last seen, host, user agent); **`<cli> web logout
|
|
149
|
+
[<id prefix>]`** signs out one, or every browser; **`--all`** also
|
|
150
|
+
rotates `web.secret`, which ends every session and every person token
|
|
151
|
+
(the CLI re-reads it and keeps working), and (0.4.1-dev.4) revokes every
|
|
152
|
+
agent's token. They ask the running server
|
|
153
|
+
with the person token, or edit the state files when it isn't running.
|
|
154
|
+
- **Settings → Command line → Browser sign-in** says whether this
|
|
155
|
+
browser is signed in, with **Sign out** (`POST /api/session/logout`).
|
|
156
|
+
- **A non-loopback bind refuses to start** (`DEN_BIND_HOST=0.0.0.0` or a
|
|
157
|
+
LAN address) without HTTPS in front (the edge, with `edge.tls`), unless
|
|
158
|
+
`web.allow_plain_http_remote: true` is in `config/local/user.yml`: the
|
|
159
|
+
error says a session cookie would cross the network in clear text.
|
|
160
|
+
With it, the machine's own addresses and host name are accepted with
|
|
161
|
+
the port.
|
|
162
|
+
- **Request checks on every request** (0.4.1-dev.1, SEC-1a, SEC-1 spec §3).
|
|
163
|
+
Hardening for everyone; no sign-in yet (that is SEC-1b).
|
|
164
|
+
- **Host allowlist** (DNS rebinding): `localhost`, `127.0.0.1` and
|
|
165
|
+
`[::1]` with the server's port, plus the edge hostnames that route to
|
|
166
|
+
Den. Anything else answers 421 `misdirected` (R26's JSON for `/api`, the
|
|
167
|
+
shell-frame error page for a page, which links to the same path on
|
|
168
|
+
`localhost`). The product's own name is accepted when `/etc/hosts` maps
|
|
169
|
+
it to 127.0.0.1 (the URL `web up` opens then), and so is a specific
|
|
170
|
+
address `DEN_BIND_HOST` binds.
|
|
171
|
+
- **Origin** on POST, PUT, PATCH, DELETE and every WebSocket upgrade: an
|
|
172
|
+
allowed host, http or https. A missing Origin needs a valid
|
|
173
|
+
`Authorization`. Otherwise 403 `origin_rejected`.
|
|
174
|
+
- **CSRF**: the shell sends `X-Den-CSRF` on every same-origin mutation,
|
|
175
|
+
through `denShell.api()` or a page's own `fetch()`, with the token the
|
|
176
|
+
new `GET /api/session` hands out (`{ csrf, actor }`, same-origin callers
|
|
177
|
+
only). The token is per server start; after a restart the shell fetches
|
|
178
|
+
a fresh one and retries once. A browser mutation without it answers 403
|
|
179
|
+
`csrf`.
|
|
180
|
+
- **GETs that run something** (they stream over GET, for EventSource):
|
|
181
|
+
`/api/service/exec`, `/api/run`, `/api/workspace/exec`,
|
|
182
|
+
`/api/workspace/sync` and `/api/aws/login`. They answer this server's
|
|
183
|
+
own pages only (`Sec-Fetch-Site: same-origin`, or `none` for a typed
|
|
184
|
+
URL), or a caller with neither header and a valid bearer (the CLI,
|
|
185
|
+
MCP, curl). Another site's `<img src>` or no-cors fetch answers 403
|
|
186
|
+
`cross_site` (or `origin_rejected`), and nothing runs.
|
|
187
|
+
- **One route table** (`route-classes.ts`): every route the server
|
|
188
|
+
dispatches is `readonly`, `mutating` or `shell-grade` (§4), by method
|
|
189
|
+
and path; the checks read it, and SEC-1b will too. A test scans the
|
|
190
|
+
dispatchers and fails on a route the table doesn't classify.
|
|
191
|
+
- Other GETs, pages and SSE streams are unchanged on loopback (§6).
|
|
192
|
+
- **`web.secret` and the CLI's person token** (SEC-1a, §5). The server
|
|
193
|
+
creates `web.secret` (32 random bytes, mode 0600) in the state dir at
|
|
194
|
+
start. The CLI and the stdio MCP server send `Authorization: Bearer
|
|
195
|
+
den_pt_…`, derived from it, on every call: nothing to set up. The server
|
|
196
|
+
verifies it in constant time; an invalid bearer answers 401
|
|
197
|
+
`unauthorized`. The audit entry of a call made with it gains `principal:
|
|
198
|
+
"person:<os user>"`, from the credential, never from a header.
|
|
199
|
+
`<cli> web token --print` prints it for curl (stdout only, with a
|
|
200
|
+
one-line note on stderr that it grants your access).
|
|
201
|
+
- **Agents on the Workspaces page** (0.4.1-dev.0, AG-4a, AG spec §5c).
|
|
202
|
+
Under the selected workspace's repo table, one row per agent: id and
|
|
203
|
+
persona, runtime, state (`running`, `stopping`, `stopped`, `ended`,
|
|
204
|
+
`exited (<code>)`), directory and when its session started. **Stop**, and
|
|
205
|
+
**Resume** where the record allows it (the runtime declares resume and the
|
|
206
|
+
agent isn't running). **+ Start agent** picks a persona and a runtime. The
|
|
207
|
+
workspace header counts them ("n agents"). In en, ja and es.
|
|
208
|
+
- Routes: `GET /api/workspaces/<ws>/agents`, `POST
|
|
209
|
+
/api/workspaces/<ws>/agents` (start), `POST
|
|
210
|
+
/api/workspaces/<ws>/agents/<id>/stop` and `…/resume`, `GET
|
|
211
|
+
/api/personas`. They call the CLI's own operations (the new
|
|
212
|
+
`agent-ops.ts`, which `<cli> agents` is now a front for): neither keeps
|
|
213
|
+
state of its own, and a workspace lists its own agents only.
|
|
214
|
+
- Guards: the lock (R8) gates Start, Stop and Resume (423 `locked`;
|
|
215
|
+
the page opens the lock first, as for a service action),
|
|
216
|
+
`agents.max_concurrent` caps starts (429 `cap`), and an agent actor is
|
|
217
|
+
refused (403). Errors are R26's shape and reach the page as error
|
|
218
|
+
toasts. The agent's env is the CLI's allowlist (§5a-env), and every
|
|
219
|
+
action is audited with the person as its actor.
|
|
220
|
+
- Listing never starts a session host: with none running, every agent
|
|
221
|
+
reads `ended`.
|
|
222
|
+
- No terminal and no Open in dev.0 to dev.4: dev.5 adds them (the Agents
|
|
223
|
+
tab, above).
|
|
224
|
+
|
|
225
|
+
### Changed
|
|
226
|
+
|
|
227
|
+
- **No browser sign-in on loopback, for any route** (0.4.1-dev.4; Ward,
|
|
228
|
+
2026-11-06; SEC-1 Q-SEC-1). On localhost the agents (start, resume), the
|
|
229
|
+
browser-session routes, `/signin` and, later, the terminal need only what
|
|
230
|
+
any change needs: this server's Host, the page's Origin and its CSRF
|
|
231
|
+
token. The lock (R8) stays the only prompt. A sign-in is still required
|
|
232
|
+
for every route when the server is reached beyond loopback: the edge
|
|
233
|
+
hostname, a LAN name or address, or a peer that isn't loopback (a
|
|
234
|
+
`DEN_BIND_HOST` bind). The Host allowlist, the Origin check,
|
|
235
|
+
`X-Den-CSRF`, `web.secret`, the person token and agent tokens (and what
|
|
236
|
+
an agent token can't do) are unchanged.
|
|
237
|
+
- **A browser session never expires** (0.4.1-dev.4; SEC-1 Q-SEC-2): it ends
|
|
238
|
+
at sign-out, `web logout` or `web logout --all`. The cookie asks to be
|
|
239
|
+
kept 400 days, a browser's most. `web.session.idle` and
|
|
240
|
+
`web.session.max` are gone: if either is still in a config file the
|
|
241
|
+
server logs that it is ignored, naming the key and the file, and starts.
|
|
242
|
+
- **Health covers every active workspace, whatever is in view**
|
|
243
|
+
(0.4.1-dev.4, WS-6.4). `/api/health` and its stream check every active
|
|
244
|
+
workspace's services; each row carries `inView` (one of its workspaces is
|
|
245
|
+
in the view scope). The top bar's chip and drawer count only the rows in
|
|
246
|
+
view; the pages get every row, so the Workspaces page shows a workspace's
|
|
247
|
+
real health when it is outside the view (it read "unknown (not
|
|
248
|
+
checked)").
|
|
249
|
+
- **The stdio MCP server's startup line says `unverified`** (0.4.1-dev.4,
|
|
250
|
+
SEC-1 §6c.3) when no `DEN_ACTOR_TOKEN` verified its `--actor`: "scoped
|
|
251
|
+
to north as agent:planner (unverified: no DEN_ACTOR_TOKEN)".
|
|
252
|
+
- **`/signin` is read-only** (0.4.1-dev.3). Signed in, or on loopback, it
|
|
253
|
+
answers 303 to its (same-origin) `next`; signed out beyond loopback, the
|
|
254
|
+
signed-out page. It was shell-grade, so the route's own check never ran
|
|
255
|
+
beyond loopback.
|
|
256
|
+
- **`web up` and `web restart` print the server's refusal** (0.4.1-dev.3):
|
|
257
|
+
when the server exits at start (a non-loopback bind without HTTPS, a
|
|
258
|
+
wrong `web.allow_plain_http_remote` value) the CLI prints "<name> web refused to start:
|
|
259
|
+
<the reason line>" and then where the log is, instead of waiting out
|
|
260
|
+
"did not become ready". Secrets in the line are redacted.
|
|
261
|
+
- **`web sessions --help`** says that LAST SEEN is written at most once a
|
|
262
|
+
minute, so it equals CREATED right after a sign-in, and that a session
|
|
263
|
+
never expires.
|
|
264
|
+
- **`agents list` shows `stopping`** for an agent that was stopped while its
|
|
265
|
+
session is still on its way out (it was `running`), and its `--json` rows
|
|
266
|
+
gain `startedAt` and `resumable`.
|
|
267
|
+
|
|
268
|
+
### Fixed
|
|
269
|
+
|
|
270
|
+
- **A non-loopback bind needs `web.allow_plain_http_remote`, edge or no
|
|
271
|
+
edge** (0.4.1-dev.5, a dev.4 dogfood finding). With the edge configured,
|
|
272
|
+
`DEN_BIND_HOST=0.0.0.0` started, but the server's own port still answered
|
|
273
|
+
plain HTTP on every interface. It is refused now unless
|
|
274
|
+
`web.allow_plain_http_remote: true` is in `config/local/user.yml`; the
|
|
275
|
+
one exception is the Docker bridge address (`172.16.0.0/12`, as
|
|
276
|
+
`DEN_BIND_HOST=172.17.0.1`) with the edge configured, for an edge that
|
|
277
|
+
reaches the host there (Linux). The edge on macOS reaches Den on
|
|
278
|
+
loopback and needs neither.
|
|
279
|
+
- **The edge hostname is believed only from the edge** (0.4.1-dev.5): a
|
|
280
|
+
request whose `Host` is the edge hostname from a peer the edge can't be
|
|
281
|
+
(not loopback, the Docker bridge or Docker Desktop's network) is 421
|
|
282
|
+
`misdirected`. A LAN client could send the edge's name straight to a
|
|
283
|
+
non-loopback bind and be treated as coming through the edge.
|
|
284
|
+
- **`web.allow_plain_http_remote` in the distribution config is said to be
|
|
285
|
+
ignored** (0.4.1-dev.5): it is read only from `config/local/user.yml` (a
|
|
286
|
+
choice for one machine); the server logs a warning naming the file it
|
|
287
|
+
found it in, and the refusal names the file to set it in.
|
|
288
|
+
- **den-claude's Bash denies match** (0.4.1-dev.5): Claude Code 2.1.x asked
|
|
289
|
+
for approval of `cat <state>/web.secret` instead of denying it; the
|
|
290
|
+
legacy `Bash(cat <path>:*)` shape didn't match. The rules are Claude
|
|
291
|
+
Code's wildcard form now (`Bash(*<path>*)`, and `cat`, `head`, `tail`,
|
|
292
|
+
`less`, `more`, `xxd`, `od`, `strings`, `base64` and `cp` of it), with
|
|
293
|
+
the path under `~` and `$HOME` too, and a path with spaces escaped. The
|
|
294
|
+
`Read` rules are unchanged.
|
|
295
|
+
- **`restart <repo>/<svc>` of a single-service repo restarts it** after
|
|
296
|
+
`up <repo>` (0.4.1-dev.5, a dev.4 dogfood finding). The record was kept
|
|
297
|
+
under `<repo>`, so the restart found none, adopted the running process
|
|
298
|
+
and printed ✔ without restarting anything, leaving two records on one
|
|
299
|
+
process group. For a repo with one service, `<repo>` and `<repo>/<svc>`
|
|
300
|
+
are one daemon now; a duplicate left by dev.4 is merged at the next
|
|
301
|
+
command; and a restart that adopts a process restarts it.
|
|
302
|
+
- **`up`, `down`, `restart` and `setup --help` mention `-w <workspace>`**
|
|
303
|
+
(0.4.1-dev.5).
|
|
304
|
+
- **A subcommand's `--help` is its own** (0.4.1-dev.4): `web sessions`,
|
|
305
|
+
`web login`, `web logout` and `web token --help` (or `-h`) printed the
|
|
306
|
+
general web help, and `mcp connect|disconnect|clients|config --help`
|
|
307
|
+
printed `mcp`'s, because the dispatcher answered `<command> --help`
|
|
308
|
+
first. `web login`, `web logout` and `web token` have a help of their own
|
|
309
|
+
now (`web token --help` exited 2).
|
|
310
|
+
- **A relative `product.state_dir` is relative to den.yml's directory**
|
|
311
|
+
(0.4.1-dev.4), the distribution root. It was resolved against the cwd, so
|
|
312
|
+
an agent's CLI or MCP server started in its workspace's directory read
|
|
313
|
+
and wrote another state dir (another `web.secret`, other tokens).
|
|
314
|
+
- **Den's test servers are all tracked** (0.4.1-dev.4): every suite and
|
|
315
|
+
helper that starts a web or MCP server starts it in a process group of
|
|
316
|
+
its own and stops the group (tests/child-servers.ts), so none outlives a
|
|
317
|
+
run.
|
|
318
|
+
- **A command name that isn't declared never runs** (0.4.1-dev.3, SEC-1
|
|
319
|
+
§6b.2). `/api/service/exec?cmd=constructor` (or `toString`, any name an
|
|
320
|
+
object inherits) ran `undefined` as a shell command; the service and
|
|
321
|
+
workspace command lookups now read the declared commands only. `/api/run`
|
|
322
|
+
refuses a `module` that isn't a module name (a `..` segment, a slash at
|
|
323
|
+
the start), 400.
|
|
324
|
+
- **A web server whose output's reader is gone no longer spins**
|
|
325
|
+
(0.4.1-dev.3). When the terminal, script or test runner that started the
|
|
326
|
+
server was killed, the next log line failed with EPIPE and the "continue"
|
|
327
|
+
guard for uncaught errors logged that to the equally closed stderr, which
|
|
328
|
+
failed again: a loop at a full core that stopped the server answering,
|
|
329
|
+
for as long as the orphan lived. The server now writes nothing once its
|
|
330
|
+
output is gone, and shuts down (as for SIGTERM: services it started stay
|
|
331
|
+
up). A server `<cli> web up` starts logs to a file and is not affected.
|
|
332
|
+
- **The server's exit is bounded** (0.4.1-dev.3). A SIGTERM or SIGINT with
|
|
333
|
+
no prompt waiting on the person forces the exit after 10 s
|
|
334
|
+
(`DEN_SHUTDOWN_DEADLINE_MS`) if the shutdown hasn't finished, and a
|
|
335
|
+
second signal exits at once (it was ignored, so a stuck shutdown left the
|
|
336
|
+
server deaf to every later SIGTERM).
|
|
337
|
+
- **Den's own test servers never outlive a test run** (0.4.1-dev.3): they
|
|
338
|
+
start in a process group of their own, a test stops the whole group, and
|
|
339
|
+
the run's global teardown (or the next run's setup) stops any group a
|
|
340
|
+
killed or timed-out test left behind.
|
|
341
|
+
- **The Release page with no deploy environment** (`environments: [local]`
|
|
342
|
+
only) no longer asks `/api/release/status?env=undefined` (a 400 on every
|
|
343
|
+
load); it says no deploy environment is configured.
|
|
344
|
+
- **An agent's persona shows on every row** (0.4.1-dev.1), an id that is
|
|
345
|
+
its persona too ("planner persona planner").
|
|
346
|
+
- **The unlock popover names agents** (0.4.1-dev.1): "Unlock to start, stop
|
|
347
|
+
and restart services and agents", in en, ja and es.
|
|
348
|
+
- **Another workspace's agent is a 404 before the lock** (0.4.1-dev.1):
|
|
349
|
+
Stop or Resume of an agent id that belongs to another workspace answers
|
|
350
|
+
"no agent here" even while the lock is closed (it answered 423), and
|
|
351
|
+
still never names the other workspace.
|
|
352
|
+
- **The 421 page and the signed-out page start no polls** (0.4.1-dev.2, a
|
|
353
|
+
consumer's SEC-1a dogfood). Every `/api` call from them would answer the
|
|
354
|
+
same refusal, so the server marks them and the shell, as for an invalid
|
|
355
|
+
config, starts none of its polls and streams (health, the lock, the env
|
|
356
|
+
list, themes, the changelog, the stale-server check); the status chip
|
|
357
|
+
reads "Status unknown" instead of "Checking…" forever.
|
|
358
|
+
- **No config polls under an invalid config.** The server marks every page
|
|
359
|
+
it serves while the config fails validation, and the shell also learns it
|
|
360
|
+
from the first `422 config_invalid` answer. From then on it starts none of
|
|
361
|
+
the polls and streams that read the config (health and its stream, edge,
|
|
362
|
+
workspaces, applications, the org config, the sudo vault status, Logs'
|
|
363
|
+
services, Settings' workspace settings), so the browser console no longer
|
|
364
|
+
logs a "Failed to load resource … 422" line for each of them. A reload
|
|
365
|
+
once the config is fixed starts them again.
|
|
366
|
+
|
|
367
|
+
### Migrations
|
|
368
|
+
|
|
369
|
+
- **Scripts that POST to `/api` with curl** need `Authorization: Bearer
|
|
370
|
+
$(<cli> web token --print)` or an `Origin` (SEC-1a), and so do curl GETs
|
|
371
|
+
of the routes that run something (`/api/service/exec`, `/api/run`,
|
|
372
|
+
`/api/workspace/exec`, `/api/workspace/sync`, `/api/aws/login`). Other
|
|
373
|
+
GETs work unchanged.
|
|
374
|
+
- **A Host other than `localhost`, `127.0.0.1`, `[::1]` or Den's edge
|
|
375
|
+
hostname** answers 421 (SEC-1a). A server exposed with
|
|
376
|
+
`DEN_BIND_HOST=0.0.0.0` goes through the edge, or sets
|
|
377
|
+
`web.allow_plain_http_remote: true` in `config/local/user.yml`: then the
|
|
378
|
+
machine's own addresses and host name work again, signed in (SEC-1b).
|
|
379
|
+
Without either, a non-loopback bind now refuses to start, saying why.
|
|
380
|
+
- **SEC-1: nothing asks for a sign-in on localhost.** Pages, read-only
|
|
381
|
+
calls, the shell's changes, agents and (later) the terminal work as
|
|
382
|
+
before; the lock (R8) is the only prompt. A sign-in is needed only when
|
|
383
|
+
the server is reached beyond loopback (below).
|
|
384
|
+
- **`web.session.idle` and `web.session.max` are ignored** (a session never
|
|
385
|
+
expires). Remove them; until then the server logs a warning naming each,
|
|
386
|
+
and starts.
|
|
387
|
+
- **Supported browsers for signing in**: Chromium, Firefox and Safari 16.4
|
|
388
|
+
or later. A browser that sends neither `Sec-Fetch-Site` nor `Origin`
|
|
389
|
+
(Safari before 16.4) can't sign in (it can still use localhost, where no
|
|
390
|
+
sign-in is asked).
|
|
391
|
+
- **Edge-fronted or remote users** see the signed-out page once; `<cli> web
|
|
392
|
+
login --origin <the edge's URL>` (the page shows the exact command) signs
|
|
393
|
+
that browser in.
|
|
394
|
+
- **Scripts that call `/api` with curl from loopback**: GETs work
|
|
395
|
+
unchanged; mutations need `Authorization: Bearer $(<cli> web token
|
|
396
|
+
--print)` or an `Origin`. Through the edge every call needs the bearer.
|
|
397
|
+
- **The GETs that run a service's, a workspace's or a test's command**
|
|
398
|
+
(`/api/service/exec`, `/api/run`, `/api/workspace/exec`,
|
|
399
|
+
`/api/workspace/sync`, `/api/aws/login`) are classed `mutating`, as §4
|
|
400
|
+
lists up, down and restart, not `shell-grade`: they need no sign-in on
|
|
401
|
+
loopback, and keep SEC-1a's same-origin-or-bearer check.
|
|
402
|
+
- **Audit entries (SEC-1c).** `actor: "agent"` now means a verified agent
|
|
403
|
+
token. Agent headers without one (an MCP server started with `--actor`
|
|
404
|
+
but no token, a script that sets `X-Den-Agent`) record the person (or
|
|
405
|
+
`user`) as the actor, with the headers under `claimed` and `unverified:
|
|
406
|
+
true`. A reader that filtered on `actor: "agent"` and the headers should
|
|
407
|
+
read `claimed` for those.
|
|
408
|
+
- **An agent's MCP server** is audited as the agent only when it inherits
|
|
409
|
+
the agent's `DEN_ACTOR_TOKEN` from the session (Den starts it so); a
|
|
410
|
+
hand-written MCP entry with `--actor agent:<id>` and no token is now a
|
|
411
|
+
claim.
|
|
412
|
+
- **`sessions start --keep-env DEN_PERSON_TOKEN`** (or of a variable that
|
|
413
|
+
holds a person token or a path to `web.secret`) is refused; it was
|
|
414
|
+
passed with a warning.
|
|
415
|
+
|
|
7
416
|
## [0.4.0] - 2026-11-06
|
|
8
417
|
|
|
9
418
|
0.4.0-dev.2 and later are built on 0.3.0 and include all of it (below).
|