@monster-orange/den 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +184 -0
- package/docs/distributions.md +229 -8
- package/package.json +4 -1
- 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 +173 -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 +192 -0
- package/src/shared/agents/runtimes.ts +47 -0
- package/src/shared/den-config.ts +116 -0
- package/src/shared/engine.ts +23 -0
- package/src/shared/environment-manager/workspaces.ts +19 -0
- package/src/shared/secret-env.ts +70 -0
- package/src/shared/sessions/client.ts +277 -0
- package/src/shared/sessions/den-pty.ts +97 -0
- package/src/shared/sessions/env.ts +79 -0
- package/src/shared/sessions/host.ts +667 -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/ui/cli/agents-command.ts +812 -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 +11 -3
- package/src/ui/cli/index.ts +34 -2
- package/src/ui/cli/sessions-command.ts +718 -0
- package/src/ui/mcp/scope.ts +123 -19
- package/src/ui/mcp/server.ts +27 -2
- package/src/ui/mcp/tools.ts +44 -5
- package/src/ui/server/audit.ts +35 -0
- package/src/ui/server/index.ts +7 -0
- package/src/ui/server/readonly-routes.ts +25 -2
- package/src/ui/server/workspace-routes.ts +3 -15
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,190 @@ 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.0] - 2026-11-06
|
|
8
|
+
|
|
9
|
+
0.4.0-dev.2 and later are built on 0.3.0 and include all of it (below).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`<cli> doctor --probe`** (0.4.0-dev.3, AG spec §5a-env; a consumer's
|
|
14
|
+
dogfood finding). Opt-in: it starts a short probe session through the
|
|
15
|
+
real session host for a plain session and for each available runtime's
|
|
16
|
+
agent env (built as `agents start` builds it, running a tiny node script
|
|
17
|
+
instead of the runtime's CLI). The probe prints key names and whether
|
|
18
|
+
each is credential-shaped, never values. It exits 1, one row per finding,
|
|
19
|
+
when a runtime's `scrubEnv` name or a credential-shaped variable reaches
|
|
20
|
+
either; without the PTY module it says unavailable, not failed. Plain
|
|
21
|
+
`doctor` still never spawns anything, and adds a note to run `<cli>
|
|
22
|
+
doctor --probe` when agents are configured (an `agents:` block or an
|
|
23
|
+
agent record).
|
|
24
|
+
- **`sessions start --keep-env NAME`** (0.4.0-dev.3): one exact name per
|
|
25
|
+
flag, repeatable, no globs. The session host's log names each kept
|
|
26
|
+
variable and who asked, never its value, and marks a credential-shaped one
|
|
27
|
+
(it is still passed). Refused for an agent (`DEN_AGENT` set); `agents
|
|
28
|
+
start` has no `--keep-env`.
|
|
29
|
+
- **Runtime contract: `envPassthrough` and `scrubEnv`** (0.4.0-dev.3). A
|
|
30
|
+
runtime names extra caller variables it needs (den-claude:
|
|
31
|
+
`CLAUDE_CONFIG_DIR`) and names it owns that must never reach a session
|
|
32
|
+
(exact, or a prefix ending in `*`; den-claude: `CLAUDECODE`,
|
|
33
|
+
`CLAUDE_CODE_*`). Every runtime's `scrubEnv` is removed from every
|
|
34
|
+
session's env last, whatever put it back. `LaunchOutput.warnings` are
|
|
35
|
+
printed at start.
|
|
36
|
+
- **den-claude passes `--permission-mode`** (0.4.0-dev.3; a consumer's
|
|
37
|
+
dogfood finding): `default` unless the persona's `permission_mode` sets
|
|
38
|
+
`default`, `acceptEdits`, `plan` or `bypassPermissions` (anything else is
|
|
39
|
+
an error naming the key), so `approvals: "terminal"` means prompts reach
|
|
40
|
+
the terminal. `bypassPermissions` prints a one-line warning at start.
|
|
41
|
+
- **`GET /api/activity?address=workspace:<ws>`** (0.4.0-dev.3; a consumer's
|
|
42
|
+
dogfood finding): an agent's cross-workspace call under a grant, which has
|
|
43
|
+
no repo or service, is listed under the workspace it reached and the
|
|
44
|
+
agent's own. Before, it showed only in the server log.
|
|
45
|
+
- **Agents: personas, runtimes and the agent record** (0.4.0-dev.1, AG-2a).
|
|
46
|
+
`<cli> agents start -w <ws> --persona <p> [--runtime <r>] [--model <m>]
|
|
47
|
+
[--id <id>]` resolves the persona and the runtime, asks the runtime for
|
|
48
|
+
argv, env and files, and starts the agent as session `<dist>-<ws>:<id>`
|
|
49
|
+
in the workspace's checkout dir (what `/api/workspaces` reports as its
|
|
50
|
+
`resolvedDirs`), capped by `agents.max_concurrent`. `agents stop <id>`
|
|
51
|
+
ends its session (TERM) and marks it stopped; `agents resume <id>`, only
|
|
52
|
+
when its runtime declares resume and its session is not running, starts a
|
|
53
|
+
new session in the same cwd continuing the last run. `agents personas
|
|
54
|
+
[--json]` lists the personas. `agents list -w <ws>` now shows each
|
|
55
|
+
agent's persona, runtime, model, state and cwd. Attach with `sessions
|
|
56
|
+
attach`.
|
|
57
|
+
- **The agent record**: `<stateDir>/agents/<id>.json` (0600, directory
|
|
58
|
+
0700): id, persona, runtime, model, workspace, cwd, session, start and
|
|
59
|
+
stop times, and the runtime's resume handle. Never the env or the argv.
|
|
60
|
+
An id is unique in a distribution: the default is the persona's id, or
|
|
61
|
+
`<persona>-<ws>` when another workspace has that one.
|
|
62
|
+
- **Personas** are markdown files with YAML front matter; the body is the
|
|
63
|
+
role prompt. The format is a Claude Code agent file read as it is
|
|
64
|
+
(`name`, `description`, `model`, `tools`; `model: inherit` means none),
|
|
65
|
+
with Den's own keys beside it: `id`, `role`, `runtime`, `standing`,
|
|
66
|
+
`context` and `argv`. A pack is a directory of them; a pack with an
|
|
67
|
+
`agents/` directory (a Claude Code plugin) is read from there. Den ships
|
|
68
|
+
a default pack: planner and orchestrator (standing), fe-dev, be-dev,
|
|
69
|
+
devops, reviewer and ux-designer, with short runtime-neutral prompts.
|
|
70
|
+
- **Runtimes** are strategy kind `runtime` (`<cli> doctor` lists it).
|
|
71
|
+
`den-claude` (default) finds `claude` on PATH and reads its version from
|
|
72
|
+
the install without running it; it launches `claude --session-id <id>
|
|
73
|
+
--mcp-config <file> --strict-mcp-config [--model <m>]
|
|
74
|
+
--append-system-prompt <role prompt>` and resumes with `--resume <id>`.
|
|
75
|
+
`generic` runs the persona's `argv` template, substituting `{model}`
|
|
76
|
+
and `{mcp_config}`; it declares no resume.
|
|
77
|
+
- **An agent's MCP is pinned to its workspace**: its MCP config
|
|
78
|
+
(`<stateDir>/agent-files/<id>/mcp.json`, 0600) runs `<cli> mcp
|
|
79
|
+
--workspace <ws> --actor agent:<id>`, plus `--grant <ws>` for each
|
|
80
|
+
workspace in `agents.<id>.grants`.
|
|
81
|
+
- **`<cli> mcp --grant <ws>`** (0.4.0-dev.1, AG spec §5b; repeatable): a
|
|
82
|
+
scoped server also serves that workspace, only when the org config's
|
|
83
|
+
`agents.<id>.grants` lists it (a flag alone is refused, exit 2). A call
|
|
84
|
+
naming a granted workspace is reported before it runs, and the web
|
|
85
|
+
server's audit entry names both workspaces (`workspace` and
|
|
86
|
+
`targetWorkspace`). `workspaces` lists the scoped workspace and its
|
|
87
|
+
grants; tests run when the focused workspace is either. Logs and manifest
|
|
88
|
+
checks stay on the scoped workspace's own services.
|
|
89
|
+
|
|
90
|
+
- **Sessions: terminals that outlive the web server** (0.4.0-dev.0,
|
|
91
|
+
AG-3a). `<cli> sessions start --workspace <ws> --name <n> [--cwd <dir>]
|
|
92
|
+
-- <argv…>` runs argv in a PTY named `<dist>-<ws>:<n>`; `sessions list
|
|
93
|
+
[-w <ws>] [--json]` lists them (`-w` shows that workspace's only);
|
|
94
|
+
`sessions attach <name|id>` is a raw terminal on one (output fans out to
|
|
95
|
+
every attachment, scrollback replays first, SIGWINCH resizes, ctrl-]
|
|
96
|
+
detaches and leaves it running); `sessions kill <name|id> [--signal
|
|
97
|
+
INT|TERM|KILL]` signals its process group (TERM by default) or, once it
|
|
98
|
+
has exited, removes it and its scrollback. A name is the full name,
|
|
99
|
+
`<ws>:<n>`, a bare `<n>` only one session has, or the id. Usage errors
|
|
100
|
+
exit 2.
|
|
101
|
+
- A **session host** owns every PTY: `<cli> sessions host`, one detached
|
|
102
|
+
process per distribution (its own session and process group), started
|
|
103
|
+
on demand by the first client and serving `<stateDir>/sessions.sock`
|
|
104
|
+
(mode 0600). Restarting the web server, or the CLI that started a
|
|
105
|
+
session, leaves it running. A second host finds the first and stands
|
|
106
|
+
down; a socket nobody answers on is stale and is replaced. `sessions
|
|
107
|
+
host --stop` stops it (its running sessions end). The host logs to
|
|
108
|
+
`<stateDir>/logs/<cli>/sessions-host.log`. Its protocol is versioned
|
|
109
|
+
(newline JSON, v1).
|
|
110
|
+
- **Scrollback** is a 2 MB ring per session, flushed to
|
|
111
|
+
`<stateDir>/sessions/<id>.log` (0600), so after a host restart a session
|
|
112
|
+
is listed as exited and attaching replays its history. A record holds
|
|
113
|
+
the name, cwd, pid, times and exit code, never argv or env.
|
|
114
|
+
- **`agents.max_concurrent`** (default 4) caps the sessions running at
|
|
115
|
+
once; a start past it is refused, naming the key. A value that is not a
|
|
116
|
+
whole number of at least 1 is an error naming the key.
|
|
117
|
+
- **`<cli> agents list -w <ws> [--json]`** (0.4.0-dev.0): that workspace's
|
|
118
|
+
sessions as agents. Persona and runtime come from the agent record
|
|
119
|
+
(0.4.0-dev.1); a session started with `sessions start` has them blank.
|
|
120
|
+
- **`<cli> doctor` lists the session backend** (0.4.0-dev.0): `den-pty`,
|
|
121
|
+
available when the PTY module loads, else why not. It is never a doctor
|
|
122
|
+
failure.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- **A session's environment is an allowlist** (0.4.0-dev.3, AG spec
|
|
127
|
+
§5a-env; a consumer's dogfood finding). Every session started an agent
|
|
128
|
+
with the caller's whole environment, so an agent started from inside
|
|
129
|
+
Claude Code inherited `CLAUDECODE` and `CLAUDE_CODE_*` (Claude Code then
|
|
130
|
+
turned transcripts off, so resume could not work, and the agent could
|
|
131
|
+
message peers as its parent), and every secret in the caller's env reached
|
|
132
|
+
every agent. Now every session's env starts empty: `PATH`, `HOME`, `USER`,
|
|
133
|
+
`LOGNAME`, `SHELL`, `TERM` (default `xterm-256color`), `COLORTERM`,
|
|
134
|
+
`LANG`, `LC_*`, `TZ` and `TMPDIR`; then, for a plain session, the names
|
|
135
|
+
given with `--keep-env`; for an agent, its runtime's `envPassthrough`, its
|
|
136
|
+
launch env and `DEN_AGENT=agent:<id>`; then every runtime's `scrubEnv` is
|
|
137
|
+
removed. `sessions start` names the variables it left out, never values.
|
|
138
|
+
An agent's MCP server gets the distribution's location (`DEN_ROOT`,
|
|
139
|
+
`<PREFIX>_ROOT`, `<PREFIX>_HOME`, when set) in its own config. The
|
|
140
|
+
credential patterns test workers are scrubbed with now live in Den
|
|
141
|
+
(`src/shared/secret-env.ts`), and the tests import them.
|
|
142
|
+
- **`agents resume` of a session that was never prompted starts fresh**
|
|
143
|
+
(0.4.0-dev.3; a consumer's dogfood finding): there is no conversation to
|
|
144
|
+
continue, so it starts under the same conversation id (`--session-id`, not
|
|
145
|
+
`--resume`) and says "starting fresh: it was never prompted, so there is
|
|
146
|
+
no conversation to resume". The session host records whether a session
|
|
147
|
+
ever got input with a line end (`prompted`), never the input.
|
|
148
|
+
- **`agents start` and `resume` report an early exit** (0.4.0-dev.3; a
|
|
149
|
+
consumer's dogfood finding): Den watches the new session for up to 3 s;
|
|
150
|
+
a non-zero exit prints the code and the last lines it printed (at most
|
|
151
|
+
10, escapes stripped, secret-shaped values redacted) and exits 1. Before,
|
|
152
|
+
it printed "resumed agent …" and exited 0 while the agent's CLI had
|
|
153
|
+
exited 1.
|
|
154
|
+
- **`agents list` shows agents only** (0.4.0-dev.3; a consumer's dogfood
|
|
155
|
+
finding): a session without an agent record (from `sessions start`) is
|
|
156
|
+
left out; `sessions list` shows it.
|
|
157
|
+
- **`sessions attach` prints its banner first** (0.4.0-dev.3; a consumer's
|
|
158
|
+
dogfood finding): `[attached to …]`, then the scrollback replay.
|
|
159
|
+
- **An MCP scope refusal names the agent's grants** (0.4.0-dev.3; a
|
|
160
|
+
consumer's dogfood finding): `(agent:<id>; grants: <a>, <b>)` or
|
|
161
|
+
`(agent:<id>; no grants)`.
|
|
162
|
+
- **A PTY module is an optional dependency** (0.4.0-dev.0, Q-AG-1):
|
|
163
|
+
`@lydell/node-pty` 1.1.0, exact. Its binaries are prebuilt per platform
|
|
164
|
+
(macOS and Linux, x64 and arm64; Windows) in packages npm installs with
|
|
165
|
+
it, with no install script and nothing downloaded. Installed with
|
|
166
|
+
`--omit=optional`, or on another platform, Den runs as before and
|
|
167
|
+
`sessions start` says den-pty is unavailable and why.
|
|
168
|
+
- **`agents list --json`** (0.4.0-dev.1): each row has `state` (running,
|
|
169
|
+
exited, stopped or ended) and, for an agent with a record, `persona`,
|
|
170
|
+
`runtime`, `model`, `workspace` and `cwd`; `session` is absent for a
|
|
171
|
+
record whose session is gone.
|
|
172
|
+
|
|
173
|
+
### Migrations
|
|
174
|
+
|
|
175
|
+
- **Plugins with a `den` range that ends below 0.4.0** (for example
|
|
176
|
+
`>=0.3.0-0 <0.4.0`) are skipped on 0.4.0, and `validate` fails them:
|
|
177
|
+
widen the range (for example `<1.0.0`). A plugin with no `den` range,
|
|
178
|
+
as Den's own themes, needs nothing.
|
|
179
|
+
- `agents:` is a new, optional block of the org config. Any key other
|
|
180
|
+
than `max_concurrent` and `persona_packs` is an agent id; an invalid value
|
|
181
|
+
is an error naming the key:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
agents:
|
|
185
|
+
max_concurrent: 4
|
|
186
|
+
persona_packs: [personas] # relative to the distribution root
|
|
187
|
+
planner:
|
|
188
|
+
grants: [other-workspace] # widens agent planner's MCP scope
|
|
189
|
+
```
|
|
190
|
+
|
|
7
191
|
## [0.3.0] - 2026-11-06
|
|
8
192
|
|
|
9
193
|
0.3.0-dev.31 and later include all of 0.2.7 (below).
|
package/docs/distributions.md
CHANGED
|
@@ -525,10 +525,11 @@ later plugins.
|
|
|
525
525
|
|
|
526
526
|
## doctor
|
|
527
527
|
|
|
528
|
-
`<cli> doctor` lists every strategy kind in the registry (secrets, tls
|
|
529
|
-
the one this distribution selects, whether nothing chose
|
|
530
|
-
and whether it can work on this machine, with the reason.
|
|
531
|
-
|
|
528
|
+
`<cli> doctor` lists every strategy kind in the registry (secrets, tls,
|
|
529
|
+
session-backend): the one this distribution selects, whether nothing chose
|
|
530
|
+
it (implicit), and whether it can work on this machine, with the reason.
|
|
531
|
+
It only checks PATH (and, for session-backend, whether the PTY module
|
|
532
|
+
loads): it never runs a backend CLI, prompts, or prints a secret. It exits
|
|
532
533
|
1 when a strategy that will be used cannot work (an implicit secrets
|
|
533
534
|
default is used only when a secret is asked for, so it never fails
|
|
534
535
|
doctor).
|
|
@@ -536,11 +537,15 @@ doctor).
|
|
|
536
537
|
```
|
|
537
538
|
Acme doctor: strategies
|
|
538
539
|
|
|
539
|
-
secrets
|
|
540
|
-
tls
|
|
540
|
+
secrets none ✓ available: no secrets backend configured
|
|
541
|
+
tls mkcert (implicit) ✗ unavailable: mkcert is not installed
|
|
541
542
|
⚠ edge.tls is not set; defaulting to mkcert with a local CA. Set edge.tls explicitly.
|
|
543
|
+
session-backend den-pty (implicit) ✓ available: PTY module loaded
|
|
542
544
|
```
|
|
543
545
|
|
|
546
|
+
`session-backend` is never a doctor failure: nothing needs a session until
|
|
547
|
+
one is started (see Sessions).
|
|
548
|
+
|
|
544
549
|
## den.yml: the product profile
|
|
545
550
|
|
|
546
551
|
Every runtime name derives from `product.name`. Set only what you want to
|
|
@@ -1323,8 +1328,8 @@ to one workspace for one agent:
|
|
|
1323
1328
|
|
|
1324
1329
|
- **Pinned.** A tool that takes a `workspace` gets `<ws>` when none is given.
|
|
1325
1330
|
A call naming another workspace is refused (`Refused: <tool>: this MCP
|
|
1326
|
-
server is scoped to workspace "<ws>" (agent:<id
|
|
1327
|
-
workspace`) before anything reaches the web server. A refused call is
|
|
1331
|
+
server is scoped to workspace "<ws>" (agent:<id>; no grants); "<other>" is
|
|
1332
|
+
another workspace`; with grants, `grants: <a>, <b>`) before anything reaches the web server. A refused call is
|
|
1328
1333
|
recorded in the activity trail, read tools included.
|
|
1329
1334
|
- **Filtered.** `workspaces` lists `<ws>` only. `logs_search` and `logs_tail`
|
|
1330
1335
|
take only `<ws>`'s services, `manifest_validate` takes only an absolute path
|
|
@@ -1339,6 +1344,16 @@ workspace`) before anything reaches the web server. A refused call is
|
|
|
1339
1344
|
entry gains `agent` and `workspace`, and the log line reads `· by
|
|
1340
1345
|
agent:<id> <client> <version> (workspace <ws>, mcp pid <pid>, session
|
|
1341
1346
|
<id>)` for every exec, run and sync.
|
|
1347
|
+
- **Granted.** `--grant <other>` (repeatable) also serves `<other>`, but only
|
|
1348
|
+
when the org config's `agents.<id>.grants` lists it; otherwise the server
|
|
1349
|
+
exits 2 (an agent can't grant itself one). A call naming a granted
|
|
1350
|
+
workspace is reported to the web server before it runs, and its activity
|
|
1351
|
+
entry names both (`workspace` and `targetWorkspace`; the log line reads
|
|
1352
|
+
`▶ <tool> in workspace <other>, under a grant to agent:<id> of workspace
|
|
1353
|
+
<ws>`). `GET /api/activity?address=workspace:<ws>` lists these entries
|
|
1354
|
+
under either workspace. `workspaces` lists `<ws>` and its grants, and tests run when either
|
|
1355
|
+
is focused. Logs and `manifest_validate` stay on `<ws>`'s own services.
|
|
1356
|
+
`agents start` passes an agent's grants for you (see [Agents](#agents)).
|
|
1342
1357
|
- `<ws>` must exist in this distribution; until it does, every tool says so.
|
|
1343
1358
|
The id is letters, digits, dots, dashes and underscores (at most 64). A bad
|
|
1344
1359
|
or half-given scope is a usage error, exit 2. `--list` and `--call` take
|
|
@@ -1445,6 +1460,212 @@ authorise anything), and the MCP server's pid and session id. They travel as
|
|
|
1445
1460
|
`acme/server` stream shows an agent's actions as `[agent]`, ending
|
|
1446
1461
|
`· by agent <client> <version> (mcp pid <pid>, session <id>)`.
|
|
1447
1462
|
|
|
1463
|
+
## Sessions
|
|
1464
|
+
|
|
1465
|
+
A session is a terminal that keeps running when the web server restarts:
|
|
1466
|
+
an argv in a PTY, owned by the distribution's **session host**. The host
|
|
1467
|
+
is one small detached process (`<cli> sessions host`) that the first
|
|
1468
|
+
client starts on demand; it serves `<stateDir>/sessions.sock` (mode 0600)
|
|
1469
|
+
and logs to `<stateDir>/logs/<cli>/sessions-host.log`. The state dir is
|
|
1470
|
+
the profile's `state_dir` (`~/.<name>`), or `<PREFIX>_HOME`.
|
|
1471
|
+
|
|
1472
|
+
```
|
|
1473
|
+
<cli> sessions start --workspace <ws> --name <n> [--cwd <dir>] [--keep-env NAME…] -- <argv…>
|
|
1474
|
+
<cli> sessions list [-w <ws>] [--json]
|
|
1475
|
+
<cli> sessions attach <name|id> # ctrl-] detaches; the session keeps running
|
|
1476
|
+
<cli> sessions kill <name|id> [--signal INT|TERM|KILL]
|
|
1477
|
+
<cli> sessions host [--stop]
|
|
1478
|
+
<cli> agents list -w <ws> [--json]
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
- A session is named `<dist>-<ws>:<n>`. `list -w <ws>` and `agents list
|
|
1482
|
+
-w <ws>` show that workspace's sessions only. Elsewhere a session can be
|
|
1483
|
+
named in full, as `<ws>:<n>`, as a bare `<n>` only one session has, or by
|
|
1484
|
+
its id.
|
|
1485
|
+
- It runs in `--cwd` (default: where you ran the command). Its environment
|
|
1486
|
+
starts empty and gets only `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`,
|
|
1487
|
+
`TERM` (default `xterm-256color`), `COLORTERM`, `LANG`, `LC_*`, `TZ` and
|
|
1488
|
+
`TMPDIR` from yours; `start` names the variables it left out (names, never
|
|
1489
|
+
values). `--keep-env NAME` adds one more: one exact name per flag,
|
|
1490
|
+
repeatable, no globs. It is audited: the session host's log
|
|
1491
|
+
(`<stateDir>/logs/<cli>/sessions-host.log`) gets a line naming each kept
|
|
1492
|
+
variable and who asked, never its value, and says `credential-shaped` when
|
|
1493
|
+
the value or name looks like a credential (it is still passed: you named
|
|
1494
|
+
it). An agent can't use it: `agents start` has no `--keep-env`, and
|
|
1495
|
+
`sessions start` refuses it when run by an agent (`DEN_AGENT` is set).
|
|
1496
|
+
Every runtime's `scrubEnv` (den-claude: `CLAUDECODE`, `CLAUDE_CODE_*`) is
|
|
1497
|
+
removed last, even a name you kept.
|
|
1498
|
+
- Every attachment gets all of its output; attaching prints `[attached to
|
|
1499
|
+
<name>]`, then replays the scrollback (2 MB per session, kept in
|
|
1500
|
+
`<stateDir>/sessions/<id>.log`).
|
|
1501
|
+
- Stopping the host (`sessions host --stop`, or a reboot) ends its
|
|
1502
|
+
sessions. They stay listed as exited, with their scrollback, until
|
|
1503
|
+
`sessions kill` removes them.
|
|
1504
|
+
- `agents.max_concurrent` caps the sessions running at once (default 4):
|
|
1505
|
+
|
|
1506
|
+
```yaml
|
|
1507
|
+
agents:
|
|
1508
|
+
max_concurrent: 4
|
|
1509
|
+
```
|
|
1510
|
+
|
|
1511
|
+
- A PTY needs a native module, `@lydell/node-pty`, which Den lists as an
|
|
1512
|
+
optional dependency (prebuilt binaries for macOS, Linux and Windows,
|
|
1513
|
+
nothing downloaded at install). Without it (`npm install
|
|
1514
|
+
--omit=optional`, another platform) everything else works, and `sessions
|
|
1515
|
+
start` and `<cli> doctor` say why den-pty is unavailable.
|
|
1516
|
+
- A state dir whose socket path would pass 103 bytes (the unix socket
|
|
1517
|
+
limit) is refused with that reason; point `<PREFIX>_HOME` somewhere
|
|
1518
|
+
shorter.
|
|
1519
|
+
|
|
1520
|
+
## Agents
|
|
1521
|
+
|
|
1522
|
+
An agent is a **persona** (who) in a **workspace** (where), run by a
|
|
1523
|
+
**runtime** (which CLI) in a [session](#sessions), acting through Den's MCP
|
|
1524
|
+
server scoped to its workspace.
|
|
1525
|
+
|
|
1526
|
+
```
|
|
1527
|
+
<cli> agents start -w <ws> --persona <p> [--runtime <r>] [--model <m>] [--id <id>]
|
|
1528
|
+
<cli> agents list -w <ws> [--json]
|
|
1529
|
+
<cli> agents stop <id> [-w <ws>]
|
|
1530
|
+
<cli> agents resume <id> [-w <ws>]
|
|
1531
|
+
<cli> agents personas [--json]
|
|
1532
|
+
<cli> sessions attach <ws>:<id> # its terminal; ctrl-] detaches
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
- `start` runs the agent as session `<dist>-<ws>:<id>` in the workspace's
|
|
1536
|
+
checkout dir (its `directory`, else `den-workspaces/<ws>`), capped by
|
|
1537
|
+
`agents.max_concurrent`. The id defaults to the persona's id; an id is
|
|
1538
|
+
unique in the distribution, so a second workspace's planner gets
|
|
1539
|
+
`planner-<ws>` unless you give `--id`.
|
|
1540
|
+
- The **agent record**, `<stateDir>/agents/<id>.json` (0600), holds its id,
|
|
1541
|
+
persona, runtime, model, workspace, cwd, session and times. It never holds
|
|
1542
|
+
the environment or the command line. `list` reads it for persona, runtime
|
|
1543
|
+
and model; a record whose session has gone is listed as `stopped` (after
|
|
1544
|
+
`stop`) or `ended`.
|
|
1545
|
+
- `list` shows agents only: sessions with an agent record. A session from
|
|
1546
|
+
`sessions start` is not an agent; `sessions list` shows it.
|
|
1547
|
+
- `stop` sends its session TERM and marks it stopped. `resume` works only
|
|
1548
|
+
when the runtime declares resume and the session is not running: it starts
|
|
1549
|
+
a new session in the same cwd that continues the last conversation. A
|
|
1550
|
+
session that never got a prompt (no input with a line end) has no
|
|
1551
|
+
conversation, so `resume` starts it fresh under the same conversation id
|
|
1552
|
+
and says so.
|
|
1553
|
+
- After `start` or `resume`, Den watches the session for up to 3 s. If it
|
|
1554
|
+
exits non-zero, Den prints the exit code and its last lines (at most 10,
|
|
1555
|
+
secret-shaped values redacted) and exits 1.
|
|
1556
|
+
|
|
1557
|
+
### An agent's environment
|
|
1558
|
+
|
|
1559
|
+
An agent's environment starts empty and gets, in order:
|
|
1560
|
+
|
|
1561
|
+
1. `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`, `TERM` (default
|
|
1562
|
+
`xterm-256color`), `COLORTERM`, `LANG`, `LC_*`, `TZ` and `TMPDIR` from
|
|
1563
|
+
yours;
|
|
1564
|
+
2. the variables its runtime names (`envPassthrough`; den-claude:
|
|
1565
|
+
`CLAUDE_CONFIG_DIR`);
|
|
1566
|
+
3. the runtime's launch env;
|
|
1567
|
+
4. `DEN_AGENT=agent:<id>`.
|
|
1568
|
+
|
|
1569
|
+
Then every runtime's `scrubEnv` is removed (den-claude: `CLAUDECODE` and
|
|
1570
|
+
`CLAUDE_CODE_*`), whatever put it there. Nothing else of yours reaches it:
|
|
1571
|
+
not your tokens, not your shell's other variables, and not the Claude Code
|
|
1572
|
+
session you started it from (its session id, messaging socket and token,
|
|
1573
|
+
and the child-session marker that turns transcripts off). The agent record
|
|
1574
|
+
still holds no env. The agent's MCP server gets the distribution's location
|
|
1575
|
+
(`DEN_ROOT`, `<PREFIX>_ROOT`, `<PREFIX>_HOME`, when set) in its own config,
|
|
1576
|
+
so it reaches the same distribution.
|
|
1577
|
+
|
|
1578
|
+
`<cli> doctor --probe` checks this through the real session host: it runs a
|
|
1579
|
+
short probe session for a plain session and for each available runtime's
|
|
1580
|
+
agent env (built as `agents start` builds it, running the probe instead of
|
|
1581
|
+
the runtime's CLI). The probe prints key names and whether each is
|
|
1582
|
+
credential-shaped, never values. Doctor exits 1, one row per finding, when a
|
|
1583
|
+
runtime's `scrubEnv` name or a credential-shaped variable reaches either.
|
|
1584
|
+
Without the PTY module it says the probe is unavailable; that is not a
|
|
1585
|
+
failure. Plain `doctor` never spawns anything; when agents are configured it
|
|
1586
|
+
adds a note to run `<cli> doctor --probe`.
|
|
1587
|
+
|
|
1588
|
+
**What the scope does not do.** An agent's MCP scope binds cooperating
|
|
1589
|
+
agents only. A runtime with a shell can still run `<cli>` itself, outside
|
|
1590
|
+
the scoped MCP server, and reach other workspaces; WS-3a and SEC-1 close
|
|
1591
|
+
that later. And the user's own Claude Code settings and hooks
|
|
1592
|
+
(`~/.claude/settings.json`, its hooks, a project's `.claude/`) load in a
|
|
1593
|
+
den-claude agent, as in any `claude` you start.
|
|
1594
|
+
|
|
1595
|
+
### Personas
|
|
1596
|
+
|
|
1597
|
+
A persona is a markdown file: YAML front matter, then the role prompt. It is
|
|
1598
|
+
a Claude Code agent file, read as it is, plus a few keys of Den's own:
|
|
1599
|
+
|
|
1600
|
+
```markdown
|
|
1601
|
+
---
|
|
1602
|
+
name: planner # the id (or `id:`; default: the file name)
|
|
1603
|
+
description: Writes stories and their acceptance criteria. # or `role:`
|
|
1604
|
+
model: some-model # optional; `inherit` means none
|
|
1605
|
+
tools: Read, Grep # hints for the runtime; Den restricts nothing with them
|
|
1606
|
+
standing: true # starts with its workspace (later slices)
|
|
1607
|
+
context: [docs/specs, the design system] # what to read first
|
|
1608
|
+
runtime: generic # default den-claude
|
|
1609
|
+
permission_mode: plan # den-claude only; default `default`
|
|
1610
|
+
argv: ["some-cli", "--model", "{model}", "--mcp-config", "{mcp_config}"]
|
|
1611
|
+
---
|
|
1612
|
+
|
|
1613
|
+
You are the planner. …
|
|
1614
|
+
```
|
|
1615
|
+
|
|
1616
|
+
Den ships a default pack: `planner` and `orchestrator` (standing), `fe-dev`,
|
|
1617
|
+
`be-dev`, `devops`, `reviewer` and `ux-designer`. Add your own packs, read in
|
|
1618
|
+
order after Den's; a persona with the same id replaces the earlier one:
|
|
1619
|
+
|
|
1620
|
+
```yaml
|
|
1621
|
+
agents:
|
|
1622
|
+
persona_packs: [personas, ../some-plugin] # relative to the distribution root
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
A pack is a directory of persona files. A pack with an `agents/` directory
|
|
1626
|
+
inside (a Claude Code plugin) is read from there, so a plugin's agents are
|
|
1627
|
+
used without converting them. Files without front matter (a README) are
|
|
1628
|
+
skipped; two files with one id in a pack, or a pack that isn't a directory,
|
|
1629
|
+
are errors naming the file or the key.
|
|
1630
|
+
|
|
1631
|
+
### Runtimes
|
|
1632
|
+
|
|
1633
|
+
`<cli> doctor` lists the runtime kind. Two are built in:
|
|
1634
|
+
|
|
1635
|
+
- **`den-claude`** (the default) needs `claude` on PATH; doctor reads its
|
|
1636
|
+
version from the install, without running it. It runs `claude --session-id
|
|
1637
|
+
<id> --mcp-config <file> --strict-mcp-config [--model <m>]
|
|
1638
|
+
--append-system-prompt <role prompt>`, so Den's MCP server is the agent's
|
|
1639
|
+
only one, and resumes with `claude --resume <id>`. Permission prompts stay
|
|
1640
|
+
in the agent's terminal: it passes `--permission-mode default` unless the
|
|
1641
|
+
persona's `permission_mode` sets one of `default`, `acceptEdits`, `plan` or
|
|
1642
|
+
`bypassPermissions` (anything else is an error naming the key).
|
|
1643
|
+
`bypassPermissions` prints a one-line warning at every start.
|
|
1644
|
+
- **`generic`** runs the persona's `argv` template. `{model}` is the agent's
|
|
1645
|
+
model (an error when the template uses it and there is none);
|
|
1646
|
+
`{mcp_config}` is the path of the agent's MCP config, written only when the
|
|
1647
|
+
template references it. It declares no resume.
|
|
1648
|
+
|
|
1649
|
+
### Workspace scope and grants
|
|
1650
|
+
|
|
1651
|
+
An agent's MCP config, `<stateDir>/agent-files/<id>/mcp.json` (0600), runs
|
|
1652
|
+
`<cli> mcp --workspace <ws> --actor agent:<id>`: the agent sees and acts on
|
|
1653
|
+
its own workspace only, and every action is recorded as `agent:<id>` (see
|
|
1654
|
+
[Scoped to one workspace](#scoped-to-one-workspace)). A grant widens one
|
|
1655
|
+
agent's scope. It is config in git, reviewed like code; an agent can't give
|
|
1656
|
+
itself one:
|
|
1657
|
+
|
|
1658
|
+
```yaml
|
|
1659
|
+
agents:
|
|
1660
|
+
planner:
|
|
1661
|
+
grants: [other-workspace]
|
|
1662
|
+
```
|
|
1663
|
+
|
|
1664
|
+
`start` then adds `--grant other-workspace`, and each call into it is audited
|
|
1665
|
+
with both workspaces named. Any key of `agents:` other than `max_concurrent`
|
|
1666
|
+
and `persona_packs` is an agent id, and its only key is `grants`. A grant
|
|
1667
|
+
naming a workspace that doesn't exist stops `start`, naming the key.
|
|
1668
|
+
|
|
1448
1669
|
## Pinning and upgrading
|
|
1449
1670
|
|
|
1450
1671
|
`package.json` pins an exact Den version (`create` writes the version that
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monster-orange/den",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Den: a local multi-repo dev orchestrator, HTTPS edge and E2E harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -111,5 +111,8 @@
|
|
|
111
111
|
"homepage": "https://github.com/Monster-Orange/den",
|
|
112
112
|
"bugs": {
|
|
113
113
|
"url": "https://github.com/Monster-Orange/den/issues"
|
|
114
|
+
},
|
|
115
|
+
"optionalDependencies": {
|
|
116
|
+
"@lydell/node-pty": "1.1.0"
|
|
114
117
|
}
|
|
115
118
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: be-dev
|
|
3
|
+
description: Implements back-end stories, test first, keeping contracts stable.
|
|
4
|
+
context:
|
|
5
|
+
[
|
|
6
|
+
the story and its acceptance criteria,
|
|
7
|
+
the service's API contracts,
|
|
8
|
+
the repo's conventions,
|
|
9
|
+
]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are a back-end developer. Implement one story end to end.
|
|
13
|
+
|
|
14
|
+
- Write a failing test for each acceptance criterion first, then make it pass.
|
|
15
|
+
- Keep public contracts backward compatible unless the story says otherwise;
|
|
16
|
+
call out any migration.
|
|
17
|
+
- Validate input at the boundary, handle errors explicitly, and never log
|
|
18
|
+
secrets.
|
|
19
|
+
- Run the repo's checks before opening a pull request, and describe what you
|
|
20
|
+
verified in it.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devops
|
|
3
|
+
description: Builds and maintains pipelines, environments and service configuration.
|
|
4
|
+
context: [the story, the workspace's service manifests, CI configuration]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a devops engineer. Make services easy to build, run and ship.
|
|
8
|
+
|
|
9
|
+
- Prefer declarative configuration and small, reversible changes.
|
|
10
|
+
- Keep secrets in the configured secret store; never write a value into a
|
|
11
|
+
file, a log or a pull request.
|
|
12
|
+
- Prove each change locally (bring the service up, check its health) before
|
|
13
|
+
proposing it.
|
|
14
|
+
- Explain the operational impact and the rollback in the pull request.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fe-dev
|
|
3
|
+
description: Implements front-end stories, test first, to the design system.
|
|
4
|
+
context:
|
|
5
|
+
[
|
|
6
|
+
the story and its acceptance criteria,
|
|
7
|
+
the design system,
|
|
8
|
+
the front-end conventions of the repo,
|
|
9
|
+
]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are a front-end developer. Implement one story end to end.
|
|
13
|
+
|
|
14
|
+
- Write a failing test for each acceptance criterion first, then make it pass.
|
|
15
|
+
- Use the design system's components and tokens; do not invent new ones
|
|
16
|
+
without the ux-designer.
|
|
17
|
+
- Keep the change small and focused on the story. Check accessibility and
|
|
18
|
+
narrow screens.
|
|
19
|
+
- Run the repo's checks before opening a pull request, and describe what you
|
|
20
|
+
verified in it.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orchestrator
|
|
3
|
+
description: Takes the next ready story, picks the persona for it and starts that agent.
|
|
4
|
+
standing: true
|
|
5
|
+
context: [ready work items, running agents in the workspace]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the orchestrator. Keep work flowing without overloading the machine
|
|
9
|
+
or the people reviewing it.
|
|
10
|
+
|
|
11
|
+
- Take the next ready story, in priority order. Pick the persona that fits it:
|
|
12
|
+
fe-dev, be-dev or devops for implementation, ux-designer for design work.
|
|
13
|
+
- Start one agent per story, in its own worktree, and link it to the story.
|
|
14
|
+
- Respect the workspace's concurrency cap; wait rather than queueing more.
|
|
15
|
+
- When a pull request is opened, ask the reviewer for a review. Never approve
|
|
16
|
+
or merge anything yourself.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: planner
|
|
3
|
+
description: Writes stories and their acceptance criteria for the workspace.
|
|
4
|
+
standing: true
|
|
5
|
+
context: [the workspace's specs and docs, open work items, the design system]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are the planner. Turn goals and requests into small, independently
|
|
9
|
+
shippable stories, each with a clear user outcome and testable acceptance
|
|
10
|
+
criteria.
|
|
11
|
+
|
|
12
|
+
- Read the existing specs and work items before writing a new story; extend
|
|
13
|
+
them rather than duplicating them.
|
|
14
|
+
- Keep each story to one vertical slice that can be reviewed on its own.
|
|
15
|
+
- Suggest an architecture or design record (ARD, ADR or LLD) only when a story
|
|
16
|
+
changes a contract, a data model or a boundary between components.
|
|
17
|
+
- Hand each ready story to the orchestrator. You do not write code.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Reviews a pull request adversarially. Never approves or merges.
|
|
4
|
+
context: [the pull request and its story, the repo's conventions and tests]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the reviewer. Try to prove the change wrong before anyone relies on it.
|
|
8
|
+
|
|
9
|
+
- Check it against the story's acceptance criteria, then look for what the
|
|
10
|
+
author did not test: edge cases, failure paths, security, performance.
|
|
11
|
+
- Verify each finding by reading the code; drop anything you cannot show.
|
|
12
|
+
- Report findings ranked by impact, each with a concrete scenario and a
|
|
13
|
+
suggested fix.
|
|
14
|
+
- You never approve, merge or push. A person decides.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ux-designer
|
|
3
|
+
description: Designs flows and screens with the design system, on request.
|
|
4
|
+
context: [the story, the design system, existing screens of the product]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the UX designer. Make the next step obvious for the person using the
|
|
8
|
+
product.
|
|
9
|
+
|
|
10
|
+
- Start from the user's goal and the story's acceptance criteria.
|
|
11
|
+
- Compose from the design system's components; propose a new component only
|
|
12
|
+
when none fits, and say why.
|
|
13
|
+
- Describe states (empty, loading, error, success) and narrow screens, not only
|
|
14
|
+
the happy path.
|
|
15
|
+
- Hand off a short spec the developers can implement and test.
|