@monster-orange/den 0.3.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 +593 -0
- package/docs/distributions.md +429 -18
- package/package.json +6 -1
- package/src/shared/actor-token.ts +275 -0
- package/src/shared/agent-actor.ts +12 -2
- package/src/shared/agents/default-pack/be-dev.md +20 -0
- package/src/shared/agents/default-pack/devops.md +14 -0
- package/src/shared/agents/default-pack/fe-dev.md +20 -0
- package/src/shared/agents/default-pack/orchestrator.md +16 -0
- package/src/shared/agents/default-pack/planner.md +17 -0
- package/src/shared/agents/default-pack/reviewer.md +14 -0
- package/src/shared/agents/default-pack/ux-designer.md +15 -0
- package/src/shared/agents/den-claude.ts +271 -0
- package/src/shared/agents/generic.ts +52 -0
- package/src/shared/agents/personas.ts +218 -0
- package/src/shared/agents/records.ts +118 -0
- package/src/shared/agents/runtime.ts +199 -0
- package/src/shared/agents/runtimes.ts +54 -0
- package/src/shared/den-config.ts +116 -0
- package/src/shared/engine.ts +23 -0
- package/src/shared/environment-manager/scope.ts +22 -0
- package/src/shared/environment-manager/service-runner.ts +94 -2
- package/src/shared/environment-manager/workspaces.ts +19 -0
- package/src/shared/profile.ts +15 -4
- package/src/shared/secret-env.ts +70 -0
- package/src/shared/sessions/client.ts +284 -0
- package/src/shared/sessions/den-pty.ts +97 -0
- package/src/shared/sessions/env.ts +89 -0
- package/src/shared/sessions/host.ts +690 -0
- package/src/shared/sessions/names.ts +85 -0
- package/src/shared/sessions/paths.ts +27 -0
- package/src/shared/sessions/protocol.ts +72 -0
- package/src/shared/sessions/pty.ts +141 -0
- package/src/shared/sessions/ring-buffer.ts +160 -0
- package/src/shared/sessions/types.ts +81 -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 +294 -0
- package/src/ui/cli/doctor-command.ts +138 -9
- package/src/ui/cli/env-probe.ts +266 -0
- package/src/ui/cli/flag-parser.ts +32 -3
- package/src/ui/cli/index.ts +128 -12
- package/src/ui/cli/sessions-command.ts +750 -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/scope.ts +123 -19
- package/src/ui/mcp/server.ts +100 -7
- package/src/ui/mcp/tools.ts +44 -5
- package/src/ui/server/agent-routes.ts +323 -0
- package/src/ui/server/audit.ts +111 -22
- package/src/ui/server/http-errors.ts +21 -3
- package/src/ui/server/index.ts +820 -65
- 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/readonly-routes.ts +25 -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 +16 -22
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,599 @@ 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
|
+
|
|
416
|
+
## [0.4.0] - 2026-11-06
|
|
417
|
+
|
|
418
|
+
0.4.0-dev.2 and later are built on 0.3.0 and include all of it (below).
|
|
419
|
+
|
|
420
|
+
### Added
|
|
421
|
+
|
|
422
|
+
- **`<cli> doctor --probe`** (0.4.0-dev.3, AG spec §5a-env; a consumer's
|
|
423
|
+
dogfood finding). Opt-in: it starts a short probe session through the
|
|
424
|
+
real session host for a plain session and for each available runtime's
|
|
425
|
+
agent env (built as `agents start` builds it, running a tiny node script
|
|
426
|
+
instead of the runtime's CLI). The probe prints key names and whether
|
|
427
|
+
each is credential-shaped, never values. It exits 1, one row per finding,
|
|
428
|
+
when a runtime's `scrubEnv` name or a credential-shaped variable reaches
|
|
429
|
+
either; without the PTY module it says unavailable, not failed. Plain
|
|
430
|
+
`doctor` still never spawns anything, and adds a note to run `<cli>
|
|
431
|
+
doctor --probe` when agents are configured (an `agents:` block or an
|
|
432
|
+
agent record).
|
|
433
|
+
- **`sessions start --keep-env NAME`** (0.4.0-dev.3): one exact name per
|
|
434
|
+
flag, repeatable, no globs. The session host's log names each kept
|
|
435
|
+
variable and who asked, never its value, and marks a credential-shaped one
|
|
436
|
+
(it is still passed). Refused for an agent (`DEN_AGENT` set); `agents
|
|
437
|
+
start` has no `--keep-env`.
|
|
438
|
+
- **Runtime contract: `envPassthrough` and `scrubEnv`** (0.4.0-dev.3). A
|
|
439
|
+
runtime names extra caller variables it needs (den-claude:
|
|
440
|
+
`CLAUDE_CONFIG_DIR`) and names it owns that must never reach a session
|
|
441
|
+
(exact, or a prefix ending in `*`; den-claude: `CLAUDECODE`,
|
|
442
|
+
`CLAUDE_CODE_*`). Every runtime's `scrubEnv` is removed from every
|
|
443
|
+
session's env last, whatever put it back. `LaunchOutput.warnings` are
|
|
444
|
+
printed at start.
|
|
445
|
+
- **den-claude passes `--permission-mode`** (0.4.0-dev.3; a consumer's
|
|
446
|
+
dogfood finding): `default` unless the persona's `permission_mode` sets
|
|
447
|
+
`default`, `acceptEdits`, `plan` or `bypassPermissions` (anything else is
|
|
448
|
+
an error naming the key), so `approvals: "terminal"` means prompts reach
|
|
449
|
+
the terminal. `bypassPermissions` prints a one-line warning at start.
|
|
450
|
+
- **`GET /api/activity?address=workspace:<ws>`** (0.4.0-dev.3; a consumer's
|
|
451
|
+
dogfood finding): an agent's cross-workspace call under a grant, which has
|
|
452
|
+
no repo or service, is listed under the workspace it reached and the
|
|
453
|
+
agent's own. Before, it showed only in the server log.
|
|
454
|
+
- **Agents: personas, runtimes and the agent record** (0.4.0-dev.1, AG-2a).
|
|
455
|
+
`<cli> agents start -w <ws> --persona <p> [--runtime <r>] [--model <m>]
|
|
456
|
+
[--id <id>]` resolves the persona and the runtime, asks the runtime for
|
|
457
|
+
argv, env and files, and starts the agent as session `<dist>-<ws>:<id>`
|
|
458
|
+
in the workspace's checkout dir (what `/api/workspaces` reports as its
|
|
459
|
+
`resolvedDirs`), capped by `agents.max_concurrent`. `agents stop <id>`
|
|
460
|
+
ends its session (TERM) and marks it stopped; `agents resume <id>`, only
|
|
461
|
+
when its runtime declares resume and its session is not running, starts a
|
|
462
|
+
new session in the same cwd continuing the last run. `agents personas
|
|
463
|
+
[--json]` lists the personas. `agents list -w <ws>` now shows each
|
|
464
|
+
agent's persona, runtime, model, state and cwd. Attach with `sessions
|
|
465
|
+
attach`.
|
|
466
|
+
- **The agent record**: `<stateDir>/agents/<id>.json` (0600, directory
|
|
467
|
+
0700): id, persona, runtime, model, workspace, cwd, session, start and
|
|
468
|
+
stop times, and the runtime's resume handle. Never the env or the argv.
|
|
469
|
+
An id is unique in a distribution: the default is the persona's id, or
|
|
470
|
+
`<persona>-<ws>` when another workspace has that one.
|
|
471
|
+
- **Personas** are markdown files with YAML front matter; the body is the
|
|
472
|
+
role prompt. The format is a Claude Code agent file read as it is
|
|
473
|
+
(`name`, `description`, `model`, `tools`; `model: inherit` means none),
|
|
474
|
+
with Den's own keys beside it: `id`, `role`, `runtime`, `standing`,
|
|
475
|
+
`context` and `argv`. A pack is a directory of them; a pack with an
|
|
476
|
+
`agents/` directory (a Claude Code plugin) is read from there. Den ships
|
|
477
|
+
a default pack: planner and orchestrator (standing), fe-dev, be-dev,
|
|
478
|
+
devops, reviewer and ux-designer, with short runtime-neutral prompts.
|
|
479
|
+
- **Runtimes** are strategy kind `runtime` (`<cli> doctor` lists it).
|
|
480
|
+
`den-claude` (default) finds `claude` on PATH and reads its version from
|
|
481
|
+
the install without running it; it launches `claude --session-id <id>
|
|
482
|
+
--mcp-config <file> --strict-mcp-config [--model <m>]
|
|
483
|
+
--append-system-prompt <role prompt>` and resumes with `--resume <id>`.
|
|
484
|
+
`generic` runs the persona's `argv` template, substituting `{model}`
|
|
485
|
+
and `{mcp_config}`; it declares no resume.
|
|
486
|
+
- **An agent's MCP is pinned to its workspace**: its MCP config
|
|
487
|
+
(`<stateDir>/agent-files/<id>/mcp.json`, 0600) runs `<cli> mcp
|
|
488
|
+
--workspace <ws> --actor agent:<id>`, plus `--grant <ws>` for each
|
|
489
|
+
workspace in `agents.<id>.grants`.
|
|
490
|
+
- **`<cli> mcp --grant <ws>`** (0.4.0-dev.1, AG spec §5b; repeatable): a
|
|
491
|
+
scoped server also serves that workspace, only when the org config's
|
|
492
|
+
`agents.<id>.grants` lists it (a flag alone is refused, exit 2). A call
|
|
493
|
+
naming a granted workspace is reported before it runs, and the web
|
|
494
|
+
server's audit entry names both workspaces (`workspace` and
|
|
495
|
+
`targetWorkspace`). `workspaces` lists the scoped workspace and its
|
|
496
|
+
grants; tests run when the focused workspace is either. Logs and manifest
|
|
497
|
+
checks stay on the scoped workspace's own services.
|
|
498
|
+
|
|
499
|
+
- **Sessions: terminals that outlive the web server** (0.4.0-dev.0,
|
|
500
|
+
AG-3a). `<cli> sessions start --workspace <ws> --name <n> [--cwd <dir>]
|
|
501
|
+
-- <argv…>` runs argv in a PTY named `<dist>-<ws>:<n>`; `sessions list
|
|
502
|
+
[-w <ws>] [--json]` lists them (`-w` shows that workspace's only);
|
|
503
|
+
`sessions attach <name|id>` is a raw terminal on one (output fans out to
|
|
504
|
+
every attachment, scrollback replays first, SIGWINCH resizes, ctrl-]
|
|
505
|
+
detaches and leaves it running); `sessions kill <name|id> [--signal
|
|
506
|
+
INT|TERM|KILL]` signals its process group (TERM by default) or, once it
|
|
507
|
+
has exited, removes it and its scrollback. A name is the full name,
|
|
508
|
+
`<ws>:<n>`, a bare `<n>` only one session has, or the id. Usage errors
|
|
509
|
+
exit 2.
|
|
510
|
+
- A **session host** owns every PTY: `<cli> sessions host`, one detached
|
|
511
|
+
process per distribution (its own session and process group), started
|
|
512
|
+
on demand by the first client and serving `<stateDir>/sessions.sock`
|
|
513
|
+
(mode 0600). Restarting the web server, or the CLI that started a
|
|
514
|
+
session, leaves it running. A second host finds the first and stands
|
|
515
|
+
down; a socket nobody answers on is stale and is replaced. `sessions
|
|
516
|
+
host --stop` stops it (its running sessions end). The host logs to
|
|
517
|
+
`<stateDir>/logs/<cli>/sessions-host.log`. Its protocol is versioned
|
|
518
|
+
(newline JSON, v1).
|
|
519
|
+
- **Scrollback** is a 2 MB ring per session, flushed to
|
|
520
|
+
`<stateDir>/sessions/<id>.log` (0600), so after a host restart a session
|
|
521
|
+
is listed as exited and attaching replays its history. A record holds
|
|
522
|
+
the name, cwd, pid, times and exit code, never argv or env.
|
|
523
|
+
- **`agents.max_concurrent`** (default 4) caps the sessions running at
|
|
524
|
+
once; a start past it is refused, naming the key. A value that is not a
|
|
525
|
+
whole number of at least 1 is an error naming the key.
|
|
526
|
+
- **`<cli> agents list -w <ws> [--json]`** (0.4.0-dev.0): that workspace's
|
|
527
|
+
sessions as agents. Persona and runtime come from the agent record
|
|
528
|
+
(0.4.0-dev.1); a session started with `sessions start` has them blank.
|
|
529
|
+
- **`<cli> doctor` lists the session backend** (0.4.0-dev.0): `den-pty`,
|
|
530
|
+
available when the PTY module loads, else why not. It is never a doctor
|
|
531
|
+
failure.
|
|
532
|
+
|
|
533
|
+
### Changed
|
|
534
|
+
|
|
535
|
+
- **A session's environment is an allowlist** (0.4.0-dev.3, AG spec
|
|
536
|
+
§5a-env; a consumer's dogfood finding). Every session started an agent
|
|
537
|
+
with the caller's whole environment, so an agent started from inside
|
|
538
|
+
Claude Code inherited `CLAUDECODE` and `CLAUDE_CODE_*` (Claude Code then
|
|
539
|
+
turned transcripts off, so resume could not work, and the agent could
|
|
540
|
+
message peers as its parent), and every secret in the caller's env reached
|
|
541
|
+
every agent. Now every session's env starts empty: `PATH`, `HOME`, `USER`,
|
|
542
|
+
`LOGNAME`, `SHELL`, `TERM` (default `xterm-256color`), `COLORTERM`,
|
|
543
|
+
`LANG`, `LC_*`, `TZ` and `TMPDIR`; then, for a plain session, the names
|
|
544
|
+
given with `--keep-env`; for an agent, its runtime's `envPassthrough`, its
|
|
545
|
+
launch env and `DEN_AGENT=agent:<id>`; then every runtime's `scrubEnv` is
|
|
546
|
+
removed. `sessions start` names the variables it left out, never values.
|
|
547
|
+
An agent's MCP server gets the distribution's location (`DEN_ROOT`,
|
|
548
|
+
`<PREFIX>_ROOT`, `<PREFIX>_HOME`, when set) in its own config. The
|
|
549
|
+
credential patterns test workers are scrubbed with now live in Den
|
|
550
|
+
(`src/shared/secret-env.ts`), and the tests import them.
|
|
551
|
+
- **`agents resume` of a session that was never prompted starts fresh**
|
|
552
|
+
(0.4.0-dev.3; a consumer's dogfood finding): there is no conversation to
|
|
553
|
+
continue, so it starts under the same conversation id (`--session-id`, not
|
|
554
|
+
`--resume`) and says "starting fresh: it was never prompted, so there is
|
|
555
|
+
no conversation to resume". The session host records whether a session
|
|
556
|
+
ever got input with a line end (`prompted`), never the input.
|
|
557
|
+
- **`agents start` and `resume` report an early exit** (0.4.0-dev.3; a
|
|
558
|
+
consumer's dogfood finding): Den watches the new session for up to 3 s;
|
|
559
|
+
a non-zero exit prints the code and the last lines it printed (at most
|
|
560
|
+
10, escapes stripped, secret-shaped values redacted) and exits 1. Before,
|
|
561
|
+
it printed "resumed agent …" and exited 0 while the agent's CLI had
|
|
562
|
+
exited 1.
|
|
563
|
+
- **`agents list` shows agents only** (0.4.0-dev.3; a consumer's dogfood
|
|
564
|
+
finding): a session without an agent record (from `sessions start`) is
|
|
565
|
+
left out; `sessions list` shows it.
|
|
566
|
+
- **`sessions attach` prints its banner first** (0.4.0-dev.3; a consumer's
|
|
567
|
+
dogfood finding): `[attached to …]`, then the scrollback replay.
|
|
568
|
+
- **An MCP scope refusal names the agent's grants** (0.4.0-dev.3; a
|
|
569
|
+
consumer's dogfood finding): `(agent:<id>; grants: <a>, <b>)` or
|
|
570
|
+
`(agent:<id>; no grants)`.
|
|
571
|
+
- **A PTY module is an optional dependency** (0.4.0-dev.0, Q-AG-1):
|
|
572
|
+
`@lydell/node-pty` 1.1.0, exact. Its binaries are prebuilt per platform
|
|
573
|
+
(macOS and Linux, x64 and arm64; Windows) in packages npm installs with
|
|
574
|
+
it, with no install script and nothing downloaded. Installed with
|
|
575
|
+
`--omit=optional`, or on another platform, Den runs as before and
|
|
576
|
+
`sessions start` says den-pty is unavailable and why.
|
|
577
|
+
- **`agents list --json`** (0.4.0-dev.1): each row has `state` (running,
|
|
578
|
+
exited, stopped or ended) and, for an agent with a record, `persona`,
|
|
579
|
+
`runtime`, `model`, `workspace` and `cwd`; `session` is absent for a
|
|
580
|
+
record whose session is gone.
|
|
581
|
+
|
|
582
|
+
### Migrations
|
|
583
|
+
|
|
584
|
+
- **Plugins with a `den` range that ends below 0.4.0** (for example
|
|
585
|
+
`>=0.3.0-0 <0.4.0`) are skipped on 0.4.0, and `validate` fails them:
|
|
586
|
+
widen the range (for example `<1.0.0`). A plugin with no `den` range,
|
|
587
|
+
as Den's own themes, needs nothing.
|
|
588
|
+
- `agents:` is a new, optional block of the org config. Any key other
|
|
589
|
+
than `max_concurrent` and `persona_packs` is an agent id; an invalid value
|
|
590
|
+
is an error naming the key:
|
|
591
|
+
|
|
592
|
+
```yaml
|
|
593
|
+
agents:
|
|
594
|
+
max_concurrent: 4
|
|
595
|
+
persona_packs: [personas] # relative to the distribution root
|
|
596
|
+
planner:
|
|
597
|
+
grants: [other-workspace] # widens agent planner's MCP scope
|
|
598
|
+
```
|
|
599
|
+
|
|
7
600
|
## [0.3.0] - 2026-11-06
|
|
8
601
|
|
|
9
602
|
0.3.0-dev.31 and later include all of 0.2.7 (below).
|