@monster-orange/den 0.4.3 → 0.4.5

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +348 -0
  2. package/README.md +12 -11
  3. package/docs/distributions.md +438 -91
  4. package/docs/reference.md +8 -3
  5. package/package.json +1 -1
  6. package/src/shared/agents/catalog-runtime.ts +178 -0
  7. package/src/shared/agents/catalog.ts +343 -0
  8. package/src/shared/agents/den-claude.ts +14 -18
  9. package/src/shared/agents/runtime-search.ts +142 -15
  10. package/src/shared/agents/runtime.ts +18 -1
  11. package/src/shared/agents/runtimes.ts +23 -1
  12. package/src/shared/auth/sudo-vault.ts +131 -11
  13. package/src/shared/cli-command.ts +186 -0
  14. package/src/shared/confirm-code.ts +34 -0
  15. package/src/shared/den-config.ts +64 -3
  16. package/src/shared/dev-metrics/emit.ts +31 -1
  17. package/src/shared/edge/config.ts +39 -0
  18. package/src/shared/edge/render.ts +92 -18
  19. package/src/shared/edge/runtime.ts +57 -1
  20. package/src/shared/edge/status.ts +134 -0
  21. package/src/shared/edge/types.ts +11 -1
  22. package/src/shared/environment-manager/health.ts +82 -8
  23. package/src/shared/environment-manager/workspaces.ts +54 -0
  24. package/src/shared/github-app.ts +94 -0
  25. package/src/shared/github-device.ts +286 -0
  26. package/src/shared/health/not-checked.ts +42 -0
  27. package/src/shared/health/rollup.ts +4 -0
  28. package/src/shared/process/spawn-streaming.ts +8 -3
  29. package/src/shared/secrets/onepassword.ts +27 -4
  30. package/src/shared/sessions/client.ts +5 -1
  31. package/src/shared/sessions/host.ts +29 -1
  32. package/src/shared/sessions/protocol.ts +6 -1
  33. package/src/shared/sessions/types.ts +6 -0
  34. package/src/shared/stacks/status.ts +24 -0
  35. package/src/shared/strategies/registry.ts +5 -0
  36. package/src/shared/types/index.ts +6 -0
  37. package/src/shared/web-owner.ts +91 -0
  38. package/src/shared/web-session.ts +135 -1
  39. package/src/ui/cli/agent-ops.ts +210 -19
  40. package/src/ui/cli/agents-command.ts +50 -21
  41. package/src/ui/cli/edge-command.ts +16 -3
  42. package/src/ui/cli/flag-parser.ts +1 -1
  43. package/src/ui/cli/health-display.ts +4 -1
  44. package/src/ui/cli/web-command.ts +512 -33
  45. package/src/ui/mcp/scope.ts +23 -3
  46. package/src/ui/mcp/server.ts +31 -7
  47. package/src/ui/mcp/tools.ts +327 -20
  48. package/src/ui/mcp/views.ts +450 -0
  49. package/src/ui/server/agent-routes.ts +118 -28
  50. package/src/ui/server/device-flows.ts +274 -0
  51. package/src/ui/server/edge-exposure.ts +16 -4
  52. package/src/ui/server/github-routes.ts +321 -0
  53. package/src/ui/server/http-errors.ts +10 -0
  54. package/src/ui/server/index.ts +540 -33
  55. package/src/ui/server/login-waits.ts +203 -0
  56. package/src/ui/server/pair-requests.ts +237 -0
  57. package/src/ui/server/pair-routes.ts +196 -0
  58. package/src/ui/server/public/agent-start.js +46 -7
  59. package/src/ui/server/public/agents-page.js +232 -97
  60. package/src/ui/server/public/agents-section.js +3 -5
  61. package/src/ui/server/public/agents.html +12 -28
  62. package/src/ui/server/public/error-page.js +452 -40
  63. package/src/ui/server/public/error.html +59 -0
  64. package/src/ui/server/public/graph-model.js +2 -0
  65. package/src/ui/server/public/header.js +2 -3
  66. package/src/ui/server/public/health-rollup.js +20 -8
  67. package/src/ui/server/public/i18n/es.js +89 -21
  68. package/src/ui/server/public/i18n/ja.js +88 -21
  69. package/src/ui/server/public/index.html +17 -4
  70. package/src/ui/server/public/local-dev.html +44 -21
  71. package/src/ui/server/public/qr.js +317 -0
  72. package/src/ui/server/public/services.html +21 -5
  73. package/src/ui/server/public/settings-page.js +10 -2
  74. package/src/ui/server/public/settings-registry.js +617 -1
  75. package/src/ui/server/public/settings.html +90 -1
  76. package/src/ui/server/public/shell.js +151 -33
  77. package/src/ui/server/public/topbar.js +170 -206
  78. package/src/ui/server/public/workspace-picker.js +1 -1
  79. package/src/ui/server/readonly-routes.ts +29 -5
  80. package/src/ui/server/request-guard.ts +103 -36
  81. package/src/ui/server/route-classes.ts +116 -4
  82. package/src/ui/server/session-routes.ts +9 -1
  83. package/src/ui/server/sudo-store.ts +170 -6
  84. package/src/ui/server/ui-assets.ts +2 -0
  85. package/src/ui/server/workspace-routes.ts +10 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,354 @@ 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.5] - 2026-11-07
8
+
9
+ Sign in with GitHub as the owner, from your other devices; approve a
10
+ computer with a code; see that you're signed in (SEC-2a). Save the admin
11
+ password without editing YAML (LK-4). Dev-metrics stay on this machine
12
+ unless you opt in.
13
+
14
+ ### Added
15
+
16
+ - **Sign in with GitHub.** A Den has one owner, a GitHub account:
17
+ `<cli> web owner link` runs GitHub's device flow in the terminal (a code
18
+ to enter at https://github.com/login/device) and stores only
19
+ `github:<id>` and the login (`web-owner.json`, 0600), never the token.
20
+ `web owner show` and `web owner unlink` too. The signed-out page then
21
+ has **Sign in with GitHub**: the server runs the flow, and the owner's
22
+ approval sets the same never-expiring session a one-time link does;
23
+ any other account sees "@<login> isn't allowed on this Den". At most
24
+ three sign-ins wait at once. Before an owner is linked the page says to
25
+ run `web owner link`. See docs/distributions.md, "Signing in with GitHub".
26
+ - **Settings › Access.** The owner (link and unlink: shell-grade, so a
27
+ signed-in browser and never an agent's token), the
28
+ linked devices (browser, how it signed in, the client address the edge
29
+ vouched for, first and last seen) each with **Revoke**, which ends that
30
+ session at once, and **Pair a device**: a one-time link and its QR code
31
+ for an edge hostname you pick.
32
+ - **`web.github_client_id`**: the GitHub App's client id (public). The
33
+ default is Den's own App, "Monster Orange Den" (`Iv23liKL12gnN6WvRV5b`);
34
+ a distribution sets its own App's id here, and an empty value turns Sign
35
+ in with GitHub off (the page then offers only the code and the link).
36
+ The App needs "Enable Device Flow" ticked and "Expire user authorization
37
+ tokens" off (docs/distributions.md, "The GitHub App").
38
+ - **Expiring GitHub tokens are noticed.** If the App hands out expiring
39
+ tokens (`expires_in` or `refresh_token` in the answer), sign-in still
40
+ completes, and Den says once to switch off token expiry in the App's
41
+ settings (never with the token). `GET /api/web/owner` reports it as
42
+ `tokenExpiry`. Den never asks for `offline_access`.
43
+ - **Approve this computer with a code.** The signed-out page shows a
44
+ six-character code (`XXX-XXX`, about 30 bits; the page polls with its own
45
+ 128-bit id) and the command that approves it on the machine Den runs on:
46
+ `<cli> web approve <code>` (any case, dash optional; `--list` shows what
47
+ waits). The page signs itself in when it's approved, onto the page it
48
+ asked for. From a machine with SSH access the page gives the one-liner
49
+ (`ssh <host> '<cli> web approve <code>'`) and, for a host name, an Open
50
+ Terminal link. Codes expire after 10 minutes, are single-use, at most
51
+ five wait at once (the oldest drops) and are kept in memory only;
52
+ approvals are rate-limited and audited with who approved. The device is
53
+ labelled "approved with a code".
54
+ - **Settings › Access › Waiting devices**: the computers waiting with a
55
+ code (browser and OS, address, when), each with **Approve** and **Deny**.
56
+ - **`web.machine_name`** (Settings › Access › Machine name, in user.yml):
57
+ the name the signed-out page gives this machine. Default: the short
58
+ hostname. It's never taken from the browser's Host; the ssh one-liner's
59
+ target is the Host the browser used (without the port).
60
+ - **The signed-out page says where to act.** "Sign in to <product>", which
61
+ machine Den runs on and why it asks (once per computer), Sign in with
62
+ GitHub (only with a client id), Approve this computer, and the one-time
63
+ link under "Other ways". Every command it prints names the machine it
64
+ runs on, has a copy button, and is `<cli>` when that's on PATH and this
65
+ distribution's, else the absolute path of its bin. English, Japanese and
66
+ Spanish.
67
+ - **See that you're signed in.** A chip at the foot of the rail says
68
+ "Local", "Signed in · this device" or "@<login>" and opens Settings ›
69
+ Access. The first page after a sign-in says "This device is signed in
70
+ to <product> on <machine>", once. Linked devices lists every session
71
+ (GitHub, a code, a one-time link) with how it signed in, when, from
72
+ where, last seen and Revoke, and marks the one you're using as "This
73
+ device".
74
+ - **Save the admin password without editing YAML (LK-4).** When a secrets
75
+ backend that keeps secrets (1Password, Doppler) is available and no
76
+ `secrets.refs.sudo` is set, the admin password prompt (shown when a
77
+ command is about to run sudo, LK-0) still offers "Save to <backend>".
78
+ Saving creates the item (1Password: "<Product> admin password" in your
79
+ default vault; Doppler: `<PREFIX>_LOCAL_ADMIN_PWD`) and writes
80
+ `secrets.refs.sudo` to your `config/local/user.yml`, so the password
81
+ survives restarts. A ref in the distribution's config still wins. The
82
+ vault status says so as `canSave`. A save error no longer carries the
83
+ backend's command line.
84
+ - **`edge.proxy_protocol.trusted_ips`** (empty by default): the front
85
+ proxies (a TCP passthrough in front of the edge) whose PROXY protocol
86
+ every entrypoint accepts, so Den sees the real client through them.
87
+ - **`web.edge_unattested: local | signin`** (default `signin`, read only
88
+ from `config/local/user.yml`): whether an edge client Den can't place
89
+ signs in. `local` keeps 0.4.4's (and 0.4.3's) behaviour.
90
+
91
+ ### Changed
92
+
93
+ - **Dev-metrics are local unless you opt in.** Den sent its dev-metrics
94
+ whenever a DSN was present (in the env or a synced
95
+ `dev-metrics.config`). Sending now needs `metrics: { share: true }` in
96
+ `config/local/user.yml` (the boolean only); a DSN alone never sends. The
97
+ local `events.jsonl` is unchanged, and `CI=true` and
98
+ `DEV_METRICS_DISABLED=1` still stop sending.
99
+ - **`<cli> web login` waits.** It prints the link and an 8-character code,
100
+ and blocks until the link is used ("Signed in: <browser> (<address>) at
101
+ <time>") or expires ("Expired: nobody used the link", exit 1). The link
102
+ asks for the code before it signs in; a wrong code is refused (five burn
103
+ the link, and wrong codes are rate-limited) and the link stays
104
+ single-use. `--no-wait` prints the link alone, as before, and asks for no
105
+ code; with no server running it behaves like `--no-wait` and says so.
106
+ - **Edge visitors Den can't place sign in.** A client the edge reports
107
+ with no `X-Forwarded-For`, or with a Docker/VM gateway or bridge address
108
+ (what a TCP passthrough or Docker Desktop and OrbStack hand it), now sees
109
+ the signed-out page with Sign in with GitHub, unless
110
+ `web.edge_unattested: local`. Loopback and this machine's own addresses,
111
+ as the edge reports them, stay local, and localhost straight to the
112
+ server's port never signs in.
113
+ - **The client is the right-most `X-Forwarded-For` entry**, the one the
114
+ edge appended (0.4.4 and earlier read the left-most, which a client could
115
+ write).
116
+ - **The edge believes forwarded headers only from its own networks.** Den
117
+ renders `forwardedHeaders.trustedIPs` (loopback and the Docker ranges)
118
+ on every entrypoint, never `forwardedHeaders.insecure`. An `edge.args`
119
+ entry that sets `forwardedHeaders` or `proxyProtocol` is dropped with a
120
+ notice from `edge up`.
121
+ - **"Your edge is reachable from other machines"** now reads "A browser it
122
+ can't place as this one signs in first (Sign in with GitHub, or a
123
+ pairing link)." (with `web.edge_unattested: local`, that anyone it can't
124
+ place can use terminals).
125
+ - **`edge up` says when the running edge's command differs** from the one
126
+ it renders, and why, before recreating it.
127
+
128
+ ### Fixed
129
+
130
+ - **The workspace commands run in is named: "CLI default".** Local Dev's
131
+ workspace list marked it with a bare accent dot and no label, and
132
+ picking a workspace in the top bar never moved the dot (by design: that
133
+ picks the view), so it looked broken. It now says "CLI default", with
134
+ a tooltip: CLI commands run there when you don't name a workspace, and
135
+ the top bar only changes what pages show. The top bar's Workspace menu
136
+ tags it "CLI default" (was "focus") and offers "Make <ws> the CLI
137
+ default"; the Workspaces pane's badge reads "active · CLI default" and
138
+ its button "Make CLI default". Nothing behaves differently.
139
+
140
+ - **A browser signs in on a plain-http LAN bind** (`DEN_BIND_HOST` with
141
+ `web.allow_plain_http_remote`). A `web login --origin http://<LAN IP>:<port>`
142
+ link got 403 `origin_rejected`: the browser sends a plain-http origin that
143
+ isn't localhost no `Sec-Fetch-*` headers, and a navigation sends no
144
+ Origin. The sign-in GETs (`/auth` and the GitHub sign-in poll), which
145
+ carry their own one-time credential, now pass with neither header. Either
146
+ header, when present, is still checked, and every other GET that runs
147
+ something still needs this server's page or a bearer.
148
+ - **`web up` with `DEN_BIND_HOST` on one address is ready when it serves.**
149
+ Its readiness probe (and `web sessions`/`web logout`'s calls) asked
150
+ 127.0.0.1, so a server bound to a LAN IP or `::1` printed "did not become
151
+ ready" and exited non-zero. They ask the bound address now (loopback for
152
+ 0.0.0.0 and `::`), with a Host the allowlist accepts and the CLI's
153
+ bearer: a LAN address answers an unsigned probe 401 `signin_required`.
154
+ `web up` names the bound address (`http://<LAN IP>:<port>`), not
155
+ localhost, which a LAN-only bind doesn't answer.
156
+
157
+ ### Migrations
158
+
159
+ - **One edge recreate.** The edge's command changes (forwarded-header
160
+ trust), so the first `<cli> edge up` (or `stack up`) after upgrading
161
+ recreates it once, and says so: "<org>-edge's settings changed
162
+ (forwarded-header trust): recreating it once." Routes drop only while it
163
+ restarts.
164
+ - **Edge visitors now see Sign in with GitHub** where 0.4.4 let a client
165
+ it couldn't place in. Link an owner (`<cli> web owner link`), approve
166
+ the computer with its code (`<cli> web approve <code>`) or pair with
167
+ `<cli> web login --host <name>`;
168
+ or set `web.edge_unattested: local` in `config/local/user.yml` to keep
169
+ 0.4.4's behaviour on that machine.
170
+ - **Scripts that run `<cli> web login` and read its output** should pass
171
+ `--no-wait`: it now waits for the link to be used.
172
+ - **Remove `forwardedHeaders.*` and `proxyProtocol.*` from `edge.args`.**
173
+ They are ignored now; use `edge.proxy_protocol.trusted_ips` for a front
174
+ proxy.
175
+
176
+ ## [0.4.4] - 2026-11-07
177
+
178
+ The admin password is for sudo, and nothing else (LK-0). An agent's
179
+ terminal uses its pane's width, and Den knows Codex, Gemini and Grok as
180
+ well as Claude Code (AG-0.5). A scoped agent gets a whole, honest picture
181
+ through Den's MCP (MCP-2).
182
+
183
+ ### Changed
184
+
185
+ - **Nothing asks for the admin password up front.** It was a lock: agent
186
+ start, stop and resume answered `423 locked` without it, and the pages
187
+ asked for it before agent actions, service actions and syncs. It is no
188
+ access check (that is sign-in, the Host/Origin/CSRF checks and agent
189
+ tokens), and Den now asks for it only when a command it runs is about to
190
+ run sudo. Agents, sessions, terminals and services whose commands run no
191
+ sudo never ask. The agent routes' `423 locked` is gone, and so is the
192
+ Start agent dialog's password field.
193
+ - **A run that needs sudo says so, and the page asks then.** Every service
194
+ command (up, down, restart, setup, sync) gets a `sudo` shim first on its
195
+ PATH. With the password held, it gives it to sudo, as before. Without
196
+ it, sudo that needs no password (NOPASSWD, a warm cache) runs as it
197
+ would; anything else never prompts: the run's events carry
198
+ `{ "type": "needs-sudo", "command": "sudo <command>", "step": "<service>" }`
199
+ (sudo's own "a password is required" counts too, without the command).
200
+ The page then says "`sudo <command>` needs your admin password", in the
201
+ top layer, and runs the action again after it. Before asking, it asks
202
+ the server (another tab or device may have set it) and, when one is
203
+ stored, the vault. Local Dev no longer loads it from the vault on page
204
+ load.
205
+ - **No Locked/Unlocked chip.** The top bar shows "🔑 Admin password
206
+ needed" only while a run waits for it. Settings › Command line › Admin
207
+ password says "Saved in 1Password", "Saved for this session" or "Not
208
+ saved", with Forget (`POST /api/sudo-password/forget`, which also clears
209
+ the vault's copy when this server saved or loaded it there).
210
+ `GET /api/sudo-password/status` adds `saved` and `vault`.
211
+ - **Scoped answers say so (MCP-2.2).** `status`, `services`,
212
+ `applications`, `graph`, `stacks` and `workspaces` on a scoped server
213
+ carry a `scope` block: the workspace shown and what is left out
214
+ (`hidden: { repos: 6 }`), pointing at `org_summary`. Their descriptions
215
+ say "This workspace only", and a pinned server registers them without
216
+ the `workspace` parameter (with grants it stays, as the scope's own or a
217
+ granted one). A scoped `graph` is the workspace's own repos and the edges
218
+ out of them; unscoped it is labelled `map: "org-wide"`. Before, `graph`
219
+ listed every repo while every other tool showed one workspace's.
220
+ - **Refusals say how to get more (MCP-2.3).** A call naming another
221
+ workspace says a person adds it to `agents.<id>.grants` in the org config
222
+ and restarts the agent; a refused log read says logs stay inside each
223
+ workspace and a grant does not widen them.
224
+ - **`status` (MCP tool) is an object:** `{ env, counts, services }`, each
225
+ service with a `state` and, when it has no plain verdict, `why`. It was
226
+ the bare `/api/health` array.
227
+
228
+ ### Added
229
+
230
+ - **The runtime catalog (AG-0.5).** Den ships, as data, the vendor CLIs it
231
+ can find: Claude Code (`den-claude`), OpenAI's Codex CLI (`codex`),
232
+ Google's Gemini CLI (`gemini`) and xAI's Grok Build (`grok`), each with
233
+ its binary names, version command, install directories and launch
234
+ command, checked against the vendor's docs. Installed runtimes are
235
+ listed first, with their versions, in the Start agent dialog, Settings
236
+ and `agents doctor`. A runtime other than Claude Code runs its own
237
+ interactive CLI in the workspace's directory, tagged "Den tools:
238
+ coming"; nothing is offered as startable unless it can start here.
239
+ When a catalog runtime is still not found, Den asks your interactive
240
+ shell (`$SHELL -ilc`) once, and says so (`interactive-shell`).
241
+ - **Runtime paths per workspace.** `<ws>.agents.runtimes.<id>.bin` in
242
+ `config/local/workspaces.yml` wins over `agents.runtimes.<id>.bin` in
243
+ `config/local/user.yml`, which wins over what Den finds. **Settings ›
244
+ Agents › Runtimes** shows each runtime's status, version, path and its
245
+ source; **Set path…** runs the version command and refuses a path that
246
+ isn't that runtime, and a selector edits one workspace's paths.
247
+ `agents doctor -w <ws>` shows that workspace's.
248
+ - **The Start agent dialog is the approved width** (the mock's 600px of
249
+ content: 638px wide, never wider than the window less 16px a side).
250
+ - **`org_summary`, the org at a glance for every agent (MCP-2.1).** A new
251
+ read tool: every workspace and repo with status counts (running, partial,
252
+ stopped, inactive; up, down, redirect, not checked), the dependency graph
253
+ (names and edge kinds), the edge (up, TLS, whether HTTPS serves) and the
254
+ environments. Scoped, `yours` adds the agent's own workspace's services
255
+ in full, so "what are our servers?" is answered without `docker ps`. For
256
+ other workspaces it carries names and counts only: never logs, env
257
+ values, commands, URLs, paths or check output.
258
+ - **`edge_status.https` (MCP-2.5)**: the TLS strategy, who supplies the
259
+ cert (`den` or `distribution`) and whether HTTPS is serving, from a TLS
260
+ handshake on the edge's https port. `edge status` prints it too.
261
+
262
+ ### Fixed
263
+
264
+ - **A service's first lines reach the page.** A started service writes to
265
+ its log, and Den tails that log back to the page. The tail started at the
266
+ log's end, so a command that wrote and exited before the tail opened the
267
+ file (on Linux, a refused sudo) showed nothing, and the page never asked
268
+ for the admin password. The tail now reads from the start of this run.
269
+
270
+ - **An agent's terminal uses the pane's width.** A session has one PTY
271
+ size, and the last viewer to resize it won: a second, narrower browser
272
+ (or a hidden tab) left the TUI wrapping at its width in every other
273
+ viewer. The viewer in use now wins: focusing a pane, typing in it after
274
+ another viewer changed the size, and the page being shown again each
275
+ send that pane's size; a hidden pane never resizes the PTY, and no
276
+ resize under 2×2 is sent (the server refuses one too). The server tells
277
+ every viewer the PTY's size on attach and on each change (a `size`
278
+ frame), so a viewer knows when it is out of step. A session host
279
+ that outlived an upgrade sends no such frame, so then every keystroke
280
+ says the typing viewer's size (the same size again redraws nothing). The cells are also
281
+ measured again once fonts finish loading and after a theme is applied,
282
+ so a theme's web font no longer leaves the terminal fitted to the
283
+ fallback font's width.
284
+ - **A Settings row with its own control is drawn.** A custom row without a
285
+ write was drawn as an empty read-only value, so Browser sign-in showed
286
+ nothing; it now shows its state and Sign out.
287
+ - **A redirect is not down (MCP-2.4).** A health check answered `302 →
288
+ /login` (a production app's login page) was reported down. The bug was
289
+ in the shared HTTP check, so the web health row and `<cli> status` had
290
+ it too. A 3xx is now `redirect`: ok null, with its status and target (the
291
+ query dropped), never green, so an ALB's 301 for a stopped service is
292
+ still not taken for up. `target.expect` listing the code counts it as up.
293
+ `<cli> status` shows `redirect 302 → /login`, and `up` and `restart` no
294
+ longer wait out a service that answers with a redirect.
295
+ - **An unchecked service says why (MCP-2.4).** `ok: null` with no reason
296
+ (`runs: none` in beta and prod) now carries `why` on the web health row
297
+ and in `status`: no health check for the env, remote with no check, not
298
+ deployed in the env, or no manifest.
299
+ - **An empty `stacks` explains itself (MCP-2.4):** `/api/stacks` says the
300
+ org config declares no `stacks:` and no repo of the workspace has a
301
+ `compose:` block (or the workspace doesn't exist); scoped, that the
302
+ stacks listed belong to repos outside it.
303
+ - **`certIssued: false` while HTTPS works (MCP-2.5).** It meant "Den minted
304
+ the cert", which is false under `tls: files`. Renamed `certIssuedByDen`.
305
+ - **Security: a scoped `mcp --call` goes through the scoped path, and
306
+ says so.** A review reported that `mcp --workspace A --actor agent:<id>
307
+ --call services '{"workspace":"B"}'` could read workspace B's services
308
+ (paths and commands included). A one-shot now lists and validates
309
+ against the pinned tools exactly as the stdio server registers them, a
310
+ `workspace` naming another workspace always reaches Den's refusal, and
311
+ `--call` prints its scope on stderr (`<cli> mcp --call: scoped to A as
312
+ agent:<id>`), so an unscoped run can't pass for a scoped one. Tests now
313
+ drive the real `bin/den mcp --call` entry.
314
+ - **An ungranted workspace gets Den's refusal (MCP-2.3).** On a scoped
315
+ server with grants, naming a workspace that is not granted answered
316
+ `MCP error -32602 … expected one of "A"|"C"` (the schema). The
317
+ `workspace` parameter is now a name whose description lists the allowed
318
+ ones, and any other gets the refusal naming `agents.<id>.grants` and how
319
+ a person grants it.
320
+ - **The org-wide map is org-wide.** `org_summary.graph` and an unscoped
321
+ `graph` held only the focused workspace's repos, so a repo only in
322
+ another active workspace was missing. They now merge every active
323
+ workspace's graph (`workspaces` names them; `order`, a startup order
324
+ within one workspace, only when one is active); `org_summary` keeps
325
+ names and edge kinds only. `graph` with a `workspace` is labelled
326
+ `map: "workspace"`.
327
+ - **A service's command runs in its own workspace.** The Services page's
328
+ and the tests page's setup, up, down and restart sent no workspace, so a
329
+ service only in a non-focused active workspace failed: `Command "setup"
330
+ not found for <repo>/<svc> … Active workspace: <focus>`. They now send
331
+ the workspace the row was built from. (0.4.3 had the same bug.)
332
+ - **`agents doctor -w <ws>` works.** It answered `workspaces not loaded`;
333
+ it now loads the workspaces config first, as `agents start` does.
334
+ - **A redirect is a redirect in `status`.** A repo row's reason counted a
335
+ service that answered with a redirect as "1 not checked"; it now says
336
+ "1 redirect" (the web header and graph cards too).
337
+
338
+ ### Migrations
339
+
340
+ - A page script that imported `requireUnlock`, `checkUnlock`,
341
+ `isUnlocked` or `onLockChange` from `header.js` or `topbar.js`: they are
342
+ gone. Run the action; when its run's events carry `needs-sudo`, the
343
+ shell asks (`sudoRun()` from `header.js`: `see(msg)` on each event,
344
+ `done(retry)` when the run ends).
345
+ - A client that handled `423 locked` from the agent routes: it no longer
346
+ happens.
347
+ - A distribution may add runtimes with `agents.runtime_catalog` (see
348
+ Runtimes in docs/distributions.md).
349
+ - A script reading `<cli> mcp --call status` gets `{ env, counts, services
350
+ }` in place of the array: read `.services`.
351
+ - `edge_status.certIssued` (and `/api/edge`'s) is deprecated: read
352
+ `certIssuedByDen`, or `https.serving` for whether HTTPS works. It goes in
353
+ the release after 0.4.4.
354
+
7
355
  ## [0.4.3] - 2026-11-06
8
356
 
9
357
  Agents start, and the edge counts as local.
package/README.md CHANGED
@@ -10,8 +10,8 @@
10
10
  </p>
11
11
 
12
12
  <picture>
13
- <source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.3/hero-dark.png">
14
- <img src="docs/images/0.4.3/hero-light.png" width="100%" alt="Den's Graph page: the demo workspace's services grouped by application, all healthy, with the status chip and the left rail">
13
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.5/hero-dark.png">
14
+ <img src="docs/images/0.4.5/hero-light.png" width="100%" alt="Den's Graph page: the demo workspace's services grouped by application, all healthy, with the status chip and the left rail">
15
15
  </picture>
16
16
 
17
17
  ## What Den is
@@ -24,7 +24,7 @@ It doesn't replace your tools. Your repos, Docker, your test runners, your secre
24
24
  yours. Den connects them and turns on good practice from the start.
25
25
 
26
26
  <p align="center">
27
- <img src="docs/images/0.4.3/up.gif" width="100%" alt="make up in the Graph page: every stack down, then demo-api, demo-web and demo-notes come up in dependency order until the status chip reads All 3 healthy">
27
+ <img src="docs/images/0.4.5/up.gif" width="100%" alt="make up in the Graph page: every stack down, then demo-api, demo-web and demo-notes come up in dependency order until the status chip reads All 3 healthy">
28
28
  </p>
29
29
 
30
30
  ## How it helps
@@ -43,20 +43,21 @@ yours. Den connects them and turns on good practice from the start.
43
43
  - **Agents work by the same rules as you.** Start an agent from a persona (planner, orchestrator, developers,
44
44
  devops, reviewer, UX designer, or your own) in a workspace. Its terminal opens in the Agents tab and keeps
45
45
  running when you close the page or restart the web server. Over MCP it sees your stack with the same guards
46
- and audit trail as a person, under a token of its own. Agents can read and investigate. Acting is opt-in,
47
- and the lock still guards starting and stopping things.
46
+ and audit trail as a person, under a token of its own. Agents can read and investigate, and acting is opt-in.
47
+ Starting an agent never asks for your admin password: Den asks for it only when a command it runs needs sudo.
48
48
  - **Safe on localhost, signed in beyond it.** On localhost there is nothing to sign in to. Reached through the
49
- edge or your network, every page and call needs a sign-in, and there is no password: `acme web login` prints a
50
- one-time link.
49
+ edge from another machine, or over your network, every page and call needs a sign-in, and there is no password:
50
+ Sign in with GitHub as the Den's owner, approve the computer with a code (`acme web approve <code>`), or open a
51
+ one-time link from `acme web login`.
51
52
  - **Opinionated, not locked in.** Good defaults are switched on, and almost everything can be switched off. The
52
53
  safety guards stay on. Den never needs our accounts, secrets or certificates. Everything specific to you
53
54
  lives in your own repo.
54
55
 
55
56
  <p align="center">
56
- <img src="docs/images/0.4.3/agents.png" width="49%" alt="The Agents tab: the demo workspace's sessions on the left, and one open in a terminal pane on the right, running demo-api's test and then the service">
57
- <img src="docs/images/0.4.3/workspaces.png" width="49%" alt="The Workspaces page: the demo workspace's repos with their health, mode and Start, Stop, Restart and Logs actions">
58
- <img src="docs/images/0.4.3/status-drawer.png" width="49%" alt="The status drawer: nothing needs attention, and every service in the demo workspace, by application, with its latency">
59
- <img src="docs/images/0.4.3/edge.png" width="49%" alt="The Edge page: the edge container up, its ports and TLS, and every route it serves, Den's own and each stack's, with its upstream">
57
+ <img src="docs/images/0.4.5/agents.png" width="49%" alt="The Agents tab: the demo workspace's sessions on the left, and one open in a terminal pane on the right, running demo-api's test and then the service">
58
+ <img src="docs/images/0.4.5/workspaces.png" width="49%" alt="The Workspaces page: the demo workspace's repos with their health, mode and Start, Stop, Restart and Logs actions">
59
+ <img src="docs/images/0.4.5/status-drawer.png" width="49%" alt="The status drawer: nothing needs attention, and every service in the demo workspace, by application, with its latency">
60
+ <img src="docs/images/0.4.5/edge.png" width="49%" alt="The Edge page: the edge container up, its ports and TLS, and every route it serves, Den's own and each stack's, with its upstream">
60
61
  </p>
61
62
 
62
63
  ## Get started