@monster-orange/den 0.4.2 → 0.4.4
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 +273 -0
- package/README.md +9 -9
- package/docs/distributions.md +313 -50
- package/package.json +1 -1
- package/src/shared/agents/catalog-runtime.ts +178 -0
- package/src/shared/agents/catalog.ts +343 -0
- package/src/shared/agents/den-claude.ts +53 -12
- package/src/shared/agents/generic.ts +1 -0
- package/src/shared/agents/runtime-search.ts +543 -0
- package/src/shared/agents/runtime.ts +43 -1
- package/src/shared/agents/runtimes.ts +53 -4
- package/src/shared/auth/sudo-vault.ts +16 -0
- package/src/shared/den-config.ts +92 -2
- package/src/shared/edge/render.ts +31 -0
- package/src/shared/edge/status.ts +134 -0
- package/src/shared/environment-manager/health.ts +82 -8
- package/src/shared/environment-manager/workspaces.ts +54 -0
- package/src/shared/health/not-checked.ts +42 -0
- package/src/shared/health/rollup.ts +4 -0
- package/src/shared/process/spawn-streaming.ts +8 -3
- package/src/shared/sessions/client.ts +5 -1
- package/src/shared/sessions/host.ts +29 -1
- package/src/shared/sessions/protocol.ts +6 -1
- package/src/shared/sessions/types.ts +6 -0
- package/src/shared/stacks/status.ts +24 -0
- package/src/shared/strategies/registry.ts +5 -0
- package/src/shared/types/index.ts +6 -0
- package/src/ui/cli/agent-ops.ts +298 -19
- package/src/ui/cli/agents-command.ts +129 -4
- package/src/ui/cli/edge-command.ts +16 -3
- package/src/ui/cli/health-display.ts +4 -1
- package/src/ui/cli/sessions-command.ts +9 -4
- package/src/ui/cli/web-command.ts +53 -8
- package/src/ui/mcp/scope.ts +23 -3
- package/src/ui/mcp/server.ts +31 -7
- package/src/ui/mcp/tools.ts +327 -20
- package/src/ui/mcp/views.ts +450 -0
- package/src/ui/server/agent-routes.ts +123 -26
- package/src/ui/server/edge-exposure.ts +143 -0
- package/src/ui/server/index.ts +171 -25
- package/src/ui/server/public/agent-start.js +241 -0
- package/src/ui/server/public/agents-page.js +246 -45
- package/src/ui/server/public/agents-section.js +7 -21
- package/src/ui/server/public/agents.html +55 -1
- package/src/ui/server/public/graph-model.js +2 -0
- package/src/ui/server/public/header.js +2 -3
- package/src/ui/server/public/health-rollup.js +20 -8
- package/src/ui/server/public/i18n/es.js +26 -13
- package/src/ui/server/public/i18n/ja.js +25 -13
- package/src/ui/server/public/index.html +17 -4
- package/src/ui/server/public/local-dev.html +62 -22
- package/src/ui/server/public/services.html +21 -5
- package/src/ui/server/public/settings-page.js +3 -2
- package/src/ui/server/public/settings-registry.js +226 -1
- package/src/ui/server/public/settings.html +90 -1
- package/src/ui/server/public/shell.js +67 -22
- package/src/ui/server/public/topbar.js +217 -158
- package/src/ui/server/readonly-routes.ts +29 -5
- package/src/ui/server/request-guard.ts +123 -8
- package/src/ui/server/route-classes.ts +14 -2
- package/src/ui/server/session-routes.ts +9 -1
- package/src/ui/server/sudo-store.ts +170 -6
- package/src/ui/server/ui-assets.ts +2 -0
- package/src/ui/server/workspace-routes.ts +10 -3
package/docs/distributions.md
CHANGED
|
@@ -352,7 +352,7 @@ where it is, under `secrets.refs`:
|
|
|
352
352
|
|
|
353
353
|
| Ref | Feature |
|
|
354
354
|
| -------------------- | ------------------------------------------------------------ |
|
|
355
|
-
| `sudo` | The saved
|
|
355
|
+
| `sudo` | The saved admin password, for sudo (auto-load, "Save to …") |
|
|
356
356
|
| `journeys_agent_key` | The journeys agent's API key, when `CURSOR_API_KEY` is unset |
|
|
357
357
|
|
|
358
358
|
```yaml
|
|
@@ -377,12 +377,38 @@ secrets:
|
|
|
377
377
|
and `env` need a `key` (or an env-shaped `field`). There are no defaults:
|
|
378
378
|
a ref that is not set is **not configured**, and its feature says so
|
|
379
379
|
("not configured: set secrets.provider and secrets.refs.sudo in
|
|
380
|
-
config/acme.yml") instead of running a CLI.
|
|
380
|
+
config/acme.yml") instead of running a CLI. Typing the password always
|
|
381
381
|
works; only the saved password needs `refs.sudo`.
|
|
382
382
|
`/api/sudo-password/vault-status` reports `backend`, `label`,
|
|
383
383
|
`configured`, `backendAvailable`, `reason` and `hasStored` (`opAvailable`
|
|
384
384
|
is a deprecated alias of `backendAvailable`, kept for one minor).
|
|
385
385
|
|
|
386
|
+
The admin password is for sudo, and nothing else (LK-0). It is no sign-in
|
|
387
|
+
and no access check: starting, stopping and resuming agents, sessions and
|
|
388
|
+
terminals, and a service whose commands run no sudo, never ask for it.
|
|
389
|
+
Den asks only when a command it runs is about to run sudo without it:
|
|
390
|
+
every service command (up, down, restart, setup, sync) gets a `sudo` shim
|
|
391
|
+
first on its `PATH`. With a password held, the shim gives it to sudo. With
|
|
392
|
+
none, sudo that needs no password (NOPASSWD, a warm cache) runs as it
|
|
393
|
+
would, and anything else never prompts: the shim prints
|
|
394
|
+
`<cli>: needs-sudo: sudo <command>` and fails, and the run's event stream
|
|
395
|
+
carries `{ "type": "needs-sudo", "command": "sudo <command>", "step":
|
|
396
|
+
"<service>" }` (sudo's own "a password is required" counts too, without
|
|
397
|
+
the command). Only then does the page ask, saying "`sudo <command>` needs
|
|
398
|
+
your admin password", with the 🔑 chip in the top bar while it waits. It
|
|
399
|
+
asks the server first (another tab or device may have set it), then, when
|
|
400
|
+
a password is stored, loads it from the vault (a page view alone never
|
|
401
|
+
wakes the vault), and only then shows the password field, always on top,
|
|
402
|
+
even over a dialog. After the password, the action runs again.
|
|
403
|
+
|
|
404
|
+
Settings › Command line › Admin password says whether it is saved ("Saved
|
|
405
|
+
in 1Password", "Saved for this session" or "Not saved"), with **Forget**:
|
|
406
|
+
`POST /api/sudo-password/forget` drops the password the server holds and,
|
|
407
|
+
when this server saved it to the vault or loaded it from there, clears the
|
|
408
|
+
vault's copy. `GET /api/sudo-password/status` answers `set`,
|
|
409
|
+
`passwordless`, `saved` (`vault`, `session` or null) and `vault` (`{
|
|
410
|
+
configured, label }`, read from the config alone).
|
|
411
|
+
|
|
386
412
|
A `1password` distribution with no refs gets one warning, in the web
|
|
387
413
|
server's startup log and in `<cli> validate`, with the YAML to add.
|
|
388
414
|
|
|
@@ -1182,10 +1208,12 @@ are the same directory.
|
|
|
1182
1208
|
## Signing in
|
|
1183
1209
|
|
|
1184
1210
|
On localhost nothing asks you to sign in: every page, the agents and the
|
|
1185
|
-
terminal work as they are, and the
|
|
1186
|
-
|
|
1187
|
-
this machine
|
|
1188
|
-
|
|
1211
|
+
terminal work as they are, and the admin password (R8) is the only prompt,
|
|
1212
|
+
asked only when a command Den runs needs sudo. The edge counts as local too (0.4.3): opening
|
|
1213
|
+
`https://acme.acme.localhost` from this machine asks for nothing.
|
|
1214
|
+
Signing in is for when the server is reached from another machine: the
|
|
1215
|
+
edge for a client the edge says is elsewhere, a LAN address, or a bind
|
|
1216
|
+
that isn't loopback (`DEN_BIND_HOST`).
|
|
1189
1217
|
|
|
1190
1218
|
There is no password. Reading `web.secret` (32 random bytes, mode 0600 in
|
|
1191
1219
|
the state dir, made at the first `web up`) proves you are this computer's
|
|
@@ -1205,7 +1233,11 @@ user, and the CLI does that for you:
|
|
|
1205
1233
|
within 60 seconds, and only this server's secret makes one. It is printed
|
|
1206
1234
|
only on the one line you asked for (`web login`, or `web up` when it
|
|
1207
1235
|
couldn't open the browser), never logged. Through the edge or a LAN name,
|
|
1208
|
-
the cookie belongs to that name: `web login --
|
|
1236
|
+
the cookie belongs to that name: `web login --host acme.acme.localhost`
|
|
1237
|
+
mints the link for an edge hostname (any edge domain or alias, a VPN name
|
|
1238
|
+
among them; its URL comes from the edge config, and a name the edge
|
|
1239
|
+
doesn't route here is refused, saying what to add), and
|
|
1240
|
+
`web login --origin http://<lan name>:<port>` for any other allowed name
|
|
1209
1241
|
(the signed-out page there shows the exact command).
|
|
1210
1242
|
- **The cookie** (`den_session_<port>`) is HttpOnly, SameSite=Strict and
|
|
1211
1243
|
Path=/, and Secure when the request came over HTTPS through the edge. The
|
|
@@ -1222,10 +1254,38 @@ user, and the CLI does that for you:
|
|
|
1222
1254
|
open, and changes (an agent's start or resume and the terminal included)
|
|
1223
1255
|
need only the page's own Origin and CSRF token, which the shell sends.
|
|
1224
1256
|
The Host allowlist, the Origin check and the CSRF token are what stop
|
|
1225
|
-
another site or a rebinding DNS name.
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1257
|
+
another site or a rebinding DNS name. From another machine, every page
|
|
1258
|
+
and call needs a signed-in browser (or a bearer). A page there shows
|
|
1259
|
+
"Sign in to use <name>" with the command to run; it never asks for a
|
|
1260
|
+
secret. `/api` answers 401 `signin_required`.
|
|
1261
|
+
- **The edge counts as local.** A request for the edge hostname from the
|
|
1262
|
+
edge (an edge peer, below) is treated as loopback: no sign-in on any
|
|
1263
|
+
route, the agents and the terminal included. Host, Origin, CSRF and the
|
|
1264
|
+
agent-token rules still apply. Who the client is comes from the left-most
|
|
1265
|
+
`X-Forwarded-For` entry the edge sends:
|
|
1266
|
+
- loopback, or one of this machine's own interface addresses (a LAN
|
|
1267
|
+
address, or a VPN's such as a Tailscale `100.x`): local;
|
|
1268
|
+
- missing, or a container/VM network's address (the Docker ranges, or a
|
|
1269
|
+
subnet of one of this machine's Docker/OrbStack/VM bridges): Den can't
|
|
1270
|
+
tell (Docker Desktop and OrbStack on macOS hand the edge their
|
|
1271
|
+
gateway's address, and a TCP passthrough in front of the edge hides the
|
|
1272
|
+
client), so local;
|
|
1273
|
+
- any other address: another machine, which needs a sign-in.
|
|
1274
|
+
|
|
1275
|
+
With `DEN_BIND_HOST` beyond loopback (a deliberate exposure, other than
|
|
1276
|
+
the Linux edge's Docker bridge address) the edge needs a sign-in as
|
|
1277
|
+
before. With `forwardedHeaders.insecure` (the edge's default) Traefik
|
|
1278
|
+
keeps an `X-Forwarded-For` the client sent, so this is a convenience,
|
|
1279
|
+
not a boundary: whoever can reach the edge can use the terminal.
|
|
1280
|
+
|
|
1281
|
+
- **"Your edge is reachable from other machines".** When Den can tell, at
|
|
1282
|
+
start, that the edge is reachable beyond this machine (an edge hostname
|
|
1283
|
+
that resolves to an address that isn't loopback, such as a LAN or
|
|
1284
|
+
Tailscale address, or the edge container publishing a port on every
|
|
1285
|
+
interface, from `docker inspect`), it writes "Your edge is reachable from
|
|
1286
|
+
other machines; anyone who can reach it can use terminals. Auth is
|
|
1287
|
+
coming." once to `web.log`, and every page shows it as a banner (dismissed
|
|
1288
|
+
per browser).
|
|
1229
1289
|
- **localhost is the canonical name.** A cookie set on `localhost` isn't
|
|
1230
1290
|
sent to `127.0.0.1`, so the links Den prints use `localhost`.
|
|
1231
1291
|
- **Browsers.** Chromium, Firefox and Safari 16.4 or later: a sign-in link
|
|
@@ -1406,19 +1466,40 @@ port.
|
|
|
1406
1466
|
`--workspace <ws>` and `--actor agent:<id>` (both or neither) scope the server
|
|
1407
1467
|
to one workspace for one agent:
|
|
1408
1468
|
|
|
1409
|
-
- **Pinned.** A tool that takes a `workspace` gets `<ws
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1469
|
+
- **Pinned.** A tool that takes a `workspace` gets `<ws>`. The server
|
|
1470
|
+
registers each workspace tool with "This workspace only" in its
|
|
1471
|
+
description and without the `workspace` parameter (with grants it stays,
|
|
1472
|
+
a name whose description lists `<ws>` and the granted ones; any other
|
|
1473
|
+
name gets the refusal below, not a schema error). A call naming another
|
|
1474
|
+
workspace is refused
|
|
1475
|
+
(`Refused: <tool>: this MCP server is scoped to workspace "<ws>"
|
|
1476
|
+
(agent:<id>; no grants); "<other>" is another workspace`; with grants,
|
|
1477
|
+
`grants: <a>, <b>`) before anything reaches the web server. The refusal
|
|
1478
|
+
says how to get more: a person adds `<other>` to `agents.<id>.grants` in
|
|
1479
|
+
the org config and restarts the agent; an agent can't grant itself one. A
|
|
1480
|
+
refused call is recorded in the activity trail, read tools included.
|
|
1481
|
+
- **Says so.** Every scoped answer (`status`, `services`, `applications`,
|
|
1482
|
+
`graph`, `stacks`, `workspaces`) carries a `scope` block: the workspace
|
|
1483
|
+
shown, how many other workspaces there are and how much it leaves out,
|
|
1484
|
+
for example `hidden: { repos: 6 }`, and a note pointing at `org_summary`.
|
|
1485
|
+
A scoped `graph` is the workspace's own repos and the edges out of them
|
|
1486
|
+
(`map: "workspace"`, with `outside` naming the repos they depend on
|
|
1487
|
+
elsewhere). Unscoped with no `workspace` it is the org-wide map
|
|
1488
|
+
(`map: "org-wide"`: every active workspace's graph merged, `workspaces`
|
|
1489
|
+
naming them); with one, that workspace's (`map: "workspace"`).
|
|
1414
1490
|
- **Filtered.** `workspaces` lists `<ws>` only. `logs_search` and `logs_tail`
|
|
1415
1491
|
take only `<ws>`'s services, `manifest_validate` takes only an absolute path
|
|
1416
1492
|
inside one of its repos (or the content), and `workspace_sync` syncs `<ws>`.
|
|
1417
1493
|
Tests run against the focused workspace, so `tests_available` and
|
|
1418
1494
|
`run_tests` are refused unless `<ws>` is the focused one.
|
|
1419
1495
|
- **Not workspace data**, served as unscoped: `environments`,
|
|
1420
|
-
`security_latest`, `release_status` and `
|
|
1421
|
-
distribution's, not a workspace's).
|
|
1496
|
+
`security_latest`, `release_status`, `edge_status` and `org_summary` (the
|
|
1497
|
+
org's and the distribution's, not a workspace's).
|
|
1498
|
+
- **The org at a glance.** `org_summary` gives every agent every workspace
|
|
1499
|
+
and repo with status counts, the graph, the edge and the environments
|
|
1500
|
+
(see [Tools](#tools)). Detail and actions stay scoped: for workspaces
|
|
1501
|
+
other than `<ws>` it carries names and counts only, never logs, env
|
|
1502
|
+
values, commands, URLs, paths or check output.
|
|
1422
1503
|
- **Stamped.** Every web API call carries `x-den-mcp-agent: agent:<id>` and
|
|
1423
1504
|
`x-den-mcp-workspace: <ws>` beside the usual audit headers. The activity
|
|
1424
1505
|
entry gains `agent` and `workspace`, and the log line reads `· by
|
|
@@ -1437,7 +1518,9 @@ agent:<id> <client> <version> (workspace <ws>, mcp pid <pid>, session
|
|
|
1437
1518
|
- `<ws>` must exist in this distribution; until it does, every tool says so.
|
|
1438
1519
|
The id is letters, digits, dots, dashes and underscores (at most 64). A bad
|
|
1439
1520
|
or half-given scope is a usage error, exit 2. `--list` and `--call` take
|
|
1440
|
-
the scope too
|
|
1521
|
+
the scope too: they list and validate against the pinned tools, as the
|
|
1522
|
+
server registers them, and `--call` names its scope on stderr
|
|
1523
|
+
(`acme mcp --call: scoped to <ws> as agent:<id>`).
|
|
1441
1524
|
|
|
1442
1525
|
**It binds cooperating agents only.** The scope lives in the MCP server's
|
|
1443
1526
|
process. The web server's HTTP API does not check it yet, so an agent that
|
|
@@ -1483,22 +1566,60 @@ guard off.
|
|
|
1483
1566
|
|
|
1484
1567
|
Read tools (always registered):
|
|
1485
1568
|
|
|
1486
|
-
| Tool | Web API | What
|
|
1487
|
-
| ------------------- | ------------------------------------------ |
|
|
1488
|
-
| `status` | `/api/health` | Service health
|
|
1489
|
-
| `services` | `/api/services` | Services, their mode (managed or dev-managed) and commands
|
|
1490
|
-
| `applications` | `/api/applications` | Org > Application > Repo > Services
|
|
1491
|
-
| `graph` | `/api/graph` | The derived dependency graph and startup order
|
|
1492
|
-
| `workspaces` | `/api/workspaces` | Workspaces, active and focused
|
|
1493
|
-
| `environments` | `/api/environments` | The enabled environment names (never their values)
|
|
1494
|
-
| `logs_search` | `/api/logs/search` | Search services' full log history
|
|
1495
|
-
| `logs_tail` | `/api/service/taillog` | The last lines of a service's log (bounded, no follow)
|
|
1496
|
-
| `tests_available` | `/api/modules`, `/api/test-sources` | A pillar's modules and the repos they come from
|
|
1497
|
-
| `security_latest` | `/api/security/latest` | The newest security scan per mode
|
|
1498
|
-
| `release_status` | `/api/release/runs`, `/api/release/status` | Recent CD runs, and an env's deployed-version table
|
|
1499
|
-
| `manifest_validate` | `/api/manifest/validate` | Validate a manifest by path or content
|
|
1500
|
-
| `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health
|
|
1501
|
-
| `edge_status` | `/api/edge` | The edge: up or down, its routes and cert names
|
|
1569
|
+
| Tool | Web API | What |
|
|
1570
|
+
| ------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1571
|
+
| `status` | `/api/health` | Service health: `counts`, then each service's `state` (`up`, `down`, `redirect`, `pending`, `unknown`, `not-checked`) and, without a plain verdict, `why`; `url` (HTTP check) or `check` (shell, or `container <project>/<service>`); `ok` true up, false down, null not known |
|
|
1572
|
+
| `services` | `/api/services` | Services, their mode (managed or dev-managed) and commands |
|
|
1573
|
+
| `applications` | `/api/applications` | Org > Application > Repo > Services |
|
|
1574
|
+
| `graph` | `/api/graph` | The derived dependency graph (`map: "org-wide"`, every active workspace's; with `workspace`, that one's and its startup order; scoped, the workspace's own) |
|
|
1575
|
+
| `workspaces` | `/api/workspaces` | Workspaces, active and focused |
|
|
1576
|
+
| `environments` | `/api/environments` | The enabled environment names (never their values) |
|
|
1577
|
+
| `logs_search` | `/api/logs/search` | Search services' full log history |
|
|
1578
|
+
| `logs_tail` | `/api/service/taillog` | The last lines of a service's log (bounded, no follow) |
|
|
1579
|
+
| `tests_available` | `/api/modules`, `/api/test-sources` | A pillar's modules and the repos they come from |
|
|
1580
|
+
| `security_latest` | `/api/security/latest` | The newest security scan per mode |
|
|
1581
|
+
| `release_status` | `/api/release/runs`, `/api/release/status` | Recent CD runs, and an env's deployed-version table |
|
|
1582
|
+
| `manifest_validate` | `/api/manifest/validate` | Validate a manifest by path or content |
|
|
1583
|
+
| `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health; an empty list says `why` |
|
|
1584
|
+
| `edge_status` | `/api/edge` | The edge: up or down, its routes and cert names; `https` (TLS strategy, who supplies the cert, whether HTTPS is serving) |
|
|
1585
|
+
| `org_summary` | several (read only) | Every workspace and repo with status counts, the graph, the edge and the environments; scoped, `yours` adds your workspace's services |
|
|
1586
|
+
|
|
1587
|
+
**Health states.** A service with no verdict says why in `why`: no health
|
|
1588
|
+
check in the manifest for the env, remote with no check or `target.url`
|
|
1589
|
+
for its env, not deployed in the env (no commands and no check there), no
|
|
1590
|
+
manifest, or checked but no answer yet. A redirect (`302 → /login`) is
|
|
1591
|
+
`redirect` with its status and target (the query is dropped), not down:
|
|
1592
|
+
something answered. It is never green either, so an unexpected redirect
|
|
1593
|
+
(an ALB answering for a stopped service) is not taken for up; list the code
|
|
1594
|
+
in `target.expect` to count it as up. The web health row and `<cli> status`
|
|
1595
|
+
use the same rule: the web row shows a redirect muted (with `status` and `redirect`
|
|
1596
|
+
in `/api/health`), and `<cli> status` as `redirect 302 → /login`.
|
|
1597
|
+
|
|
1598
|
+
**`org_summary`** reads `/api/workspaces`, `/api/health` (local, every
|
|
1599
|
+
active workspace), `/api/graph`, `/api/edge` and `/api/environments`:
|
|
1600
|
+
|
|
1601
|
+
- `workspaces`: each workspace's name, whether it is active, focused or
|
|
1602
|
+
yours, its state (`running`: every checked service answers; `partial`:
|
|
1603
|
+
some are down; `stopped`: all checked ones are down; `unknown`: nothing
|
|
1604
|
+
checked; `inactive`: not polled), its counts (up, down, redirect,
|
|
1605
|
+
pending, not checked) and its repos, each with its rolled-up state.
|
|
1606
|
+
- `graph`: the org-wide map, every active workspace's repos merged (each
|
|
1607
|
+
workspace's graph from the web server; `workspaces` names them, and
|
|
1608
|
+
`unavailable` any whose graph failed), repo names and edge kinds only (no
|
|
1609
|
+
evidence, paths or URLs).
|
|
1610
|
+
- `edge`: up, state, domains, the route count and `https`.
|
|
1611
|
+
- `environments`: the enabled names, never their values.
|
|
1612
|
+
- scoped, `yours`: your workspace's services as `status` gives them.
|
|
1613
|
+
|
|
1614
|
+
**`edge_status.https`** says which TLS strategy is in use (`mkcert`,
|
|
1615
|
+
`files`, `none`), who supplies the cert (`den`, or `distribution` under
|
|
1616
|
+
`tls: files` and the legacy `issue_cert: false`) and whether HTTPS is
|
|
1617
|
+
serving: a TLS handshake on the edge's published https port (`serving`
|
|
1618
|
+
true or false, null when a proxy in front owns the port), with `detail`.
|
|
1619
|
+
`certIssuedByDen` replaces `certIssued`, which is kept for 0.4.4 with a
|
|
1620
|
+
note in `deprecated`: it said whether Den minted the cert, so it was
|
|
1621
|
+
`false` under `tls: files` while HTTPS worked. `edge status` prints the
|
|
1622
|
+
same `https` line.
|
|
1502
1623
|
|
|
1503
1624
|
Action tools (only those named in `mcp.actions`): `service_up`,
|
|
1504
1625
|
`service_down`, `service_restart` (through `/api/service/exec`),
|
|
@@ -1616,6 +1737,7 @@ server scoped to its workspace.
|
|
|
1616
1737
|
<cli> agents stop <id> [-w <ws>]
|
|
1617
1738
|
<cli> agents resume <id> [-w <ws>]
|
|
1618
1739
|
<cli> agents personas [--json]
|
|
1740
|
+
<cli> agents doctor [-w <ws>] [--json] # can each runtime run here, and why not
|
|
1619
1741
|
<cli> sessions attach <ws>:<id> # its terminal; ctrl-] detaches
|
|
1620
1742
|
```
|
|
1621
1743
|
|
|
@@ -1654,13 +1776,21 @@ started. The workspace header counts them ("2 agents").
|
|
|
1654
1776
|
not running.
|
|
1655
1777
|
- **+ Start agent** picks a persona (Den's pack, then
|
|
1656
1778
|
`agents.persona_packs`) and a runtime (the persona's own unless you pick
|
|
1657
|
-
one; one that can't run here is listed, disabled), then Start.
|
|
1779
|
+
one; one that can't run here is listed, disabled), then Start. Under the
|
|
1780
|
+
runtime a line says what Start will run, with its version and path
|
|
1781
|
+
("Starts Claude Code 2.1.0 from ~/.local/bin/claude"). When that runtime
|
|
1782
|
+
can't run here, Start is disabled and the line says why and how to fix
|
|
1783
|
+
it: where it looked, then "Install it, or set
|
|
1784
|
+
`agents.runtimes.<id>.bin` to its path in <your user.yml>, or run
|
|
1785
|
+
`<cli> agents doctor`" (see [Runtimes](#runtimes)). Nothing fails only
|
|
1786
|
+
after Start.
|
|
1658
1787
|
- The page and the CLI call the same operations and read the same agent
|
|
1659
1788
|
records, so an agent started in one is listed in the other. A workspace
|
|
1660
1789
|
lists its own agents only.
|
|
1661
|
-
- Start, Stop and Resume
|
|
1662
|
-
|
|
1663
|
-
for an agent (agents don't
|
|
1790
|
+
- Start, Stop and Resume never ask for the admin password (it is for
|
|
1791
|
+
sudo, and no agent action runs sudo), are capped by
|
|
1792
|
+
`agents.max_concurrent`, and are refused for an agent (agents don't
|
|
1793
|
+
start agents from the page). Each one is in the
|
|
1664
1794
|
server log's activity, with you as its actor. The agent's environment is
|
|
1665
1795
|
the one below, built from the web server's environment.
|
|
1666
1796
|
- **Open in Agents →** (the section header) opens the Agents tab for the
|
|
@@ -1670,7 +1800,7 @@ The routes: `GET /api/workspaces/<ws>/agents`, `POST
|
|
|
1670
1800
|
/api/workspaces/<ws>/agents` (`{ persona, runtime?, model?, id? }`), `POST
|
|
1671
1801
|
/api/workspaces/<ws>/agents/<id>/stop` and `…/resume`, and `GET
|
|
1672
1802
|
/api/personas` (personas and runtimes). Errors are the usual
|
|
1673
|
-
`{ ok: false, error, code, requestId }`: `
|
|
1803
|
+
`{ ok: false, error, code, requestId }`: `cap` (429),
|
|
1674
1804
|
`conflict` (409, e.g. already running, or no resume), `not_found` (404),
|
|
1675
1805
|
`exited_at_once` (502, with the session's last lines).
|
|
1676
1806
|
|
|
@@ -1693,9 +1823,10 @@ agent of the workspaces in view, and their terminals in the browser.
|
|
|
1693
1823
|
reload or Back reopens them.
|
|
1694
1824
|
- **Close** (×) detaches, as leaving the page does: the session keeps
|
|
1695
1825
|
running, and opening it again replays what you missed. **Stop** (an
|
|
1696
|
-
agent only) is `agents stop
|
|
1826
|
+
agent only) is `agents stop`. **+ Start agent** (or
|
|
1697
1827
|
`/agents?start=1`) starts one and opens its pane; under All it asks for
|
|
1698
|
-
the workspace.
|
|
1828
|
+
the workspace. It says before Start whether the runtime can run, as the
|
|
1829
|
+
Workspaces page's form does. It never asks for the admin password.
|
|
1699
1830
|
- ⌘K lists each as "Agents › <ws> / <name>", and "Start agent…".
|
|
1700
1831
|
- Several browsers and `<cli> sessions attach` can attach to one session
|
|
1701
1832
|
at once and each sees every byte; the terminal's size is the last
|
|
@@ -1721,7 +1852,8 @@ An agent's environment starts empty and gets, in order:
|
|
|
1721
1852
|
|
|
1722
1853
|
1. `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TERM` (default
|
|
1723
1854
|
`xterm-256color`), `COLORTERM`, `LANG`, `LC_*`, `TZ` and `TMPDIR` from
|
|
1724
|
-
yours
|
|
1855
|
+
yours (`PATH` with the login shell's and the runtimes' install
|
|
1856
|
+
directories added: [Runtimes](#runtimes));
|
|
1725
1857
|
2. the variables its runtime names (`envPassthrough`; den-claude:
|
|
1726
1858
|
`CLAUDE_CONFIG_DIR`);
|
|
1727
1859
|
3. the runtime's launch env;
|
|
@@ -1759,8 +1891,9 @@ header (agent headers without the token are recorded as an unverified
|
|
|
1759
1891
|
claim).
|
|
1760
1892
|
|
|
1761
1893
|
An agent's token reaches only its own session's routes, and never a
|
|
1762
|
-
shell-grade route, `/api/web/*`, sign-in or sign-out, or the
|
|
1763
|
-
|
|
1894
|
+
shell-grade route, `/api/web/*`, sign-in or sign-out, or the admin
|
|
1895
|
+
password's routes: it can't set the admin password, use it for sudo or
|
|
1896
|
+
start another agent (403
|
|
1764
1897
|
`agent_forbidden`). A person's credential never reaches a session:
|
|
1765
1898
|
`DEN_PERSON_TOKEN`, a person token's value and a path to `web.secret` are
|
|
1766
1899
|
always removed, and `sessions start --keep-env` of one is refused and logged.
|
|
@@ -1781,7 +1914,8 @@ it does not catch every spelling: a path relative to the agent's cwd
|
|
|
1781
1914
|
(`cat ../.den/web.secret`, say), another program, a script or a copy under
|
|
1782
1915
|
another name all get past it. The server's token rules (above)
|
|
1783
1916
|
are the boundary: whatever an agent reads, its token reaches only its own
|
|
1784
|
-
session's routes, never a shell-grade route or the
|
|
1917
|
+
session's routes, never a shell-grade route or the admin password's.
|
|
1918
|
+
OS-user isolation
|
|
1785
1919
|
(a separate user, a container, a sandbox) is not in 0.4.x. `web logout
|
|
1786
1920
|
--all` revokes every agent's token (above, under Signing in).
|
|
1787
1921
|
|
|
@@ -1830,10 +1964,12 @@ are errors naming the file or the key.
|
|
|
1830
1964
|
|
|
1831
1965
|
### Runtimes
|
|
1832
1966
|
|
|
1833
|
-
`<cli> doctor` lists the runtime kind.
|
|
1967
|
+
`<cli> doctor` lists the runtime kind. Den knows the runtimes in its
|
|
1968
|
+
**catalog** (below); two are special:
|
|
1834
1969
|
|
|
1835
|
-
- **`den-claude`** (the default)
|
|
1836
|
-
version from the install, without running
|
|
1970
|
+
- **`den-claude`** (the default) runs Claude Code's `claude`, found as
|
|
1971
|
+
below; `<cli> doctor` reads its version from the install, without running
|
|
1972
|
+
it. It runs `claude --session-id
|
|
1837
1973
|
<id> --mcp-config <file> --strict-mcp-config --settings <file> [--model <m>]
|
|
1838
1974
|
--append-system-prompt <role prompt>`, so Den's MCP server is the agent's
|
|
1839
1975
|
only one, and resumes with `claude --resume <id>`. Permission prompts stay
|
|
@@ -1844,7 +1980,134 @@ are errors naming the file or the key.
|
|
|
1844
1980
|
- **`generic`** runs the persona's `argv` template. `{model}` is the agent's
|
|
1845
1981
|
model (an error when the template uses it and there is none);
|
|
1846
1982
|
`{mcp_config}` is the path of the agent's MCP config, written only when the
|
|
1847
|
-
template references it. It declares no resume.
|
|
1983
|
+
template references it. It declares no resume. It is startable only for
|
|
1984
|
+
a persona that has an `argv`.
|
|
1985
|
+
|
|
1986
|
+
**The runtime catalog.** Den ships, as data, every vendor CLI it knows how
|
|
1987
|
+
to find. Each entry was checked against the vendor's own docs or installer:
|
|
1988
|
+
|
|
1989
|
+
| id | Vendor | CLI | Found in (after PATH) | Source |
|
|
1990
|
+
| ------------ | --------- | --------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
1991
|
+
| `den-claude` | Anthropic | Claude Code, `claude` | `~/.local/bin`, `~/.claude/local`, system dirs | [setup](https://code.claude.com/docs/en/setup) |
|
|
1992
|
+
| `codex` | OpenAI | Codex CLI, `codex` | `$CODEX_INSTALL_DIR`, `~/.local/bin`, system dirs | [openai/codex](https://github.com/openai/codex), [installer](https://chatgpt.com/codex/install.sh) |
|
|
1993
|
+
| `gemini` | Google | Gemini CLI, `gemini` | system dirs | [google-gemini/gemini-cli](https://github.com/google-gemini/gemini-cli) |
|
|
1994
|
+
| `grok` | xAI | Grok Build, `grok` | `$GROK_BIN_DIR`, `~/.grok/bin`, `~/.local/bin`, `/usr/local/bin` | [Grok Build](https://docs.x.ai/build/overview), [installer](https://x.ai/cli/install.sh) |
|
|
1995
|
+
|
|
1996
|
+
"System dirs" are `/opt/homebrew/bin`, `/usr/local/bin` and npm's global
|
|
1997
|
+
bin. Each one's version is `<bin> --version`. Every vendor here publishes
|
|
1998
|
+
an official CLI, so none is left out; a vendor without one is left out
|
|
1999
|
+
rather than guessed at (community CLIs, such as third-party "grok" CLIs,
|
|
2000
|
+
are not xAI's and are not in the catalog).
|
|
2001
|
+
|
|
2002
|
+
Claude Code runs as `den-claude` (above). Every other installed runtime
|
|
2003
|
+
runs its own interactive CLI (`codex`, `gemini`, `grok`) in the
|
|
2004
|
+
workspace's directory; Den's tools (MCP) and permission modes are coming
|
|
2005
|
+
for them, and lists tag them **Den tools: coming**. A runtime is offered
|
|
2006
|
+
as startable only when it can start here: installed (or, for `generic`,
|
|
2007
|
+
with the persona's `argv`).
|
|
2008
|
+
|
|
2009
|
+
A distribution adds entries with `agents.runtime_catalog` in its config,
|
|
2010
|
+
and a person with the same key in `config/local/user.yml`. Both go
|
|
2011
|
+
through the validator the built-in entries pass; an entry can't reuse a
|
|
2012
|
+
catalog id (set a path instead, below):
|
|
2013
|
+
|
|
2014
|
+
```yaml
|
|
2015
|
+
agents:
|
|
2016
|
+
runtime_catalog:
|
|
2017
|
+
- id: aider # lowercase, starts with a letter
|
|
2018
|
+
vendor: Aider
|
|
2019
|
+
name: Aider
|
|
2020
|
+
bins: [aider] # executable names, no paths
|
|
2021
|
+
version:
|
|
2022
|
+
args: [--version]
|
|
2023
|
+
identity: # at least one: how a path is known to be it
|
|
2024
|
+
output: "aider" # a pattern its version output matches
|
|
2025
|
+
# package: aider-chat (the npm package it resolves into)
|
|
2026
|
+
# realpath_under: [~/.aider] (where it really lives)
|
|
2027
|
+
dirs: [~/.local/bin, $AIDER_DIR, "{system}"]
|
|
2028
|
+
launch:
|
|
2029
|
+
argv: ["{bin}"] # "{bin}" first: the path found
|
|
2030
|
+
```
|
|
2031
|
+
|
|
2032
|
+
**Where a runtime's CLI is found.** As your terminal would find it, not
|
|
2033
|
+
only on the web server's own PATH (a launchd service's PATH leaves out
|
|
2034
|
+
`~/.local/bin`, where Claude Code's native installer puts `claude`). The
|
|
2035
|
+
first match wins:
|
|
2036
|
+
|
|
2037
|
+
1. **A workspace's own path**, `<ws>.agents.runtimes.<id>.bin` in
|
|
2038
|
+
`config/local/workspaces.yml`, for that workspace's agents. Checked the
|
|
2039
|
+
same way as the next one.
|
|
2040
|
+
2. **`agents.runtimes.<id>.bin`** in `config/local/user.yml`: the absolute
|
|
2041
|
+
path of the runtime's CLI. Paths are per machine, so it is read from
|
|
2042
|
+
user.yml only, never from the distribution's config. A bad value (not
|
|
2043
|
+
absolute, missing, not an executable file) makes the runtime
|
|
2044
|
+
unavailable with that reason; Den doesn't fall back to a search.
|
|
2045
|
+
|
|
2046
|
+
```yaml
|
|
2047
|
+
# config/local/user.yml (gitignored)
|
|
2048
|
+
agents:
|
|
2049
|
+
runtimes:
|
|
2050
|
+
den-claude:
|
|
2051
|
+
bin: /Users/me/.local/bin/claude
|
|
2052
|
+
```
|
|
2053
|
+
|
|
2054
|
+
3. **Your login shell's PATH**: the web server asks `$SHELL -lc` once at
|
|
2055
|
+
start, in the background, under a 3-second timeout. On a timeout or
|
|
2056
|
+
any failure it is left out.
|
|
2057
|
+
4. **The server's own PATH.**
|
|
2058
|
+
5. **The runtime's install directories** (its catalog entry's; for
|
|
2059
|
+
den-claude: `~/.local/bin`, `~/.claude/local`, `/opt/homebrew/bin`,
|
|
2060
|
+
`/usr/local/bin` and npm's global bin, `$npm_config_prefix/bin` and
|
|
2061
|
+
node's own directory). Den never runs npm to find it.
|
|
2062
|
+
6. **Your interactive shell's PATH**, only while a catalog runtime is
|
|
2063
|
+
still not found: `$SHELL -ilc` once (stdin closed, 3-second timeout,
|
|
2064
|
+
the answer kept), for a PATH set only in an interactive startup file
|
|
2065
|
+
such as `~/.zshrc`. Its source is `interactive-shell`.
|
|
2066
|
+
|
|
2067
|
+
Paths are this machine's only: Den reads them from `config/local` files,
|
|
2068
|
+
never from the distribution's config. **Settings › Agents › Runtimes**
|
|
2069
|
+
shows each runtime's status, version, the path in use and its source.
|
|
2070
|
+
**Set path…** runs the runtime's version command and refuses a path that
|
|
2071
|
+
isn't that runtime (its output, npm package or install location must name
|
|
2072
|
+
it); **Clear** removes it. The **Paths for** selector switches between
|
|
2073
|
+
every workspace (`user.yml`) and one workspace (`workspaces.yml`). Only a
|
|
2074
|
+
person sets a path, never an agent (`POST /api/agents/runtimes/<id>/path`,
|
|
2075
|
+
audited).
|
|
2076
|
+
|
|
2077
|
+
Sessions and agents Den starts get the same directories on their PATH
|
|
2078
|
+
(and a configured CLI's own directory), so the runtime's own tools are
|
|
2079
|
+
found too. Two engine variables override the search for tests and
|
|
2080
|
+
diagnosis: `DEN_LOGIN_SHELL` (the shell to ask instead of `$SHELL`; `off`
|
|
2081
|
+
for none) and `DEN_RUNTIME_DIRS` (a PATH-style list that replaces every
|
|
2082
|
+
runtime's install directories; empty for none).
|
|
2083
|
+
|
|
2084
|
+
**`<cli> agents doctor`** prints, for each runtime (installed first), its
|
|
2085
|
+
status, the CLI it runs and where that came from (the workspace's or the
|
|
2086
|
+
user.yml `agents.runtimes` setting, the login shell's PATH, PATH, an
|
|
2087
|
+
install directory, or the interactive shell's PATH), its version (`claude
|
|
2088
|
+
--version`, run once under a timeout), the directories it searched (names
|
|
2089
|
+
only, never an environment value), and the fix when it's missing. It
|
|
2090
|
+
resolves the login shell's PATH first, as the web server does. It exits 1
|
|
2091
|
+
when the default runtime is unavailable; `-w <ws>` uses that workspace's
|
|
2092
|
+
paths; `--json` prints what the Start dialog gets. A search list the same
|
|
2093
|
+
as the one above it prints once.
|
|
2094
|
+
|
|
2095
|
+
```
|
|
2096
|
+
den-claude (Claude Code, Anthropic) [default]
|
|
2097
|
+
status ✗ unavailable: claude not found (searched 5 directories)
|
|
2098
|
+
searched /usr/bin
|
|
2099
|
+
/bin
|
|
2100
|
+
…
|
|
2101
|
+
fix install Claude Code, or set agents.runtimes.den-claude.bin to its absolute path in /…/config/local/user.yml
|
|
2102
|
+
then run `<cli> agents doctor` again
|
|
2103
|
+
```
|
|
2104
|
+
|
|
2105
|
+
`GET /api/personas` and `GET /api/agents/runtimes` (each with an optional
|
|
2106
|
+
`?workspace=<ws>`) give each runtime its `vendor`, `denTools`, `version`,
|
|
2107
|
+
`path`, `source` (`workspace`, `setting`, `login-shell`, `PATH`,
|
|
2108
|
+
`well-known` or `interactive-shell`), `searched`, `setting` (the user.yml
|
|
2109
|
+
key) and `workspaceSetting` (the workspaces.yml key), and `userConfig`,
|
|
2110
|
+
the user.yml to edit.
|
|
1848
2111
|
|
|
1849
2112
|
### Workspace scope and grants
|
|
1850
2113
|
|