@monster-orange/den 0.4.14 → 0.4.16
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 +194 -0
- package/README.md +7 -7
- package/docs/distributions.md +226 -19
- package/package.json +1 -1
- package/src/shared/actor-token.ts +12 -0
- package/src/shared/agents/conversations.ts +155 -8
- package/src/shared/agents/den-claude.ts +25 -3
- package/src/shared/agents/records.ts +37 -0
- package/src/shared/agents/runtime.ts +11 -1
- package/src/shared/claim-code.ts +163 -0
- package/src/shared/cli-command.ts +31 -0
- package/src/shared/den-config.ts +12 -0
- package/src/shared/environment-manager/workspaces.ts +20 -0
- package/src/shared/github-app.ts +7 -0
- package/src/shared/web-session.ts +64 -5
- package/src/ui/cli/agent-ops.ts +372 -23
- package/src/ui/cli/agents-command.ts +71 -4
- package/src/ui/cli/doctor-command.ts +29 -3
- package/src/ui/cli/doctor-onboard.ts +26 -0
- package/src/ui/cli/flag-parser.ts +9 -1
- package/src/ui/cli/index.ts +29 -3
- package/src/ui/cli/onboard-command.ts +180 -0
- package/src/ui/cli/onboard-reads.ts +613 -0
- package/src/ui/cli/repo-command.ts +156 -10
- package/src/ui/cli/web-command.ts +92 -1
- package/src/ui/mcp/api.ts +14 -2
- package/src/ui/mcp/server.ts +12 -2
- package/src/ui/mcp/tools.ts +368 -1
- package/src/ui/server/agent-routes.ts +91 -7
- package/src/ui/server/approval-routes.ts +390 -0
- package/src/ui/server/approvals.ts +416 -0
- package/src/ui/server/github-routes.ts +13 -5
- package/src/ui/server/held-actions.ts +203 -0
- package/src/ui/server/http-helpers.ts +35 -0
- package/src/ui/server/index.ts +308 -21
- package/src/ui/server/lifecycle-exec.ts +70 -0
- package/src/ui/server/onboard-routes.ts +132 -0
- package/src/ui/server/pair-routes.ts +7 -4
- package/src/ui/server/public/agents-page.js +378 -30
- package/src/ui/server/public/agents.html +1 -0
- package/src/ui/server/public/error-page.js +97 -18
- package/src/ui/server/public/error.html +18 -0
- package/src/ui/server/public/header.js +1 -1
- package/src/ui/server/public/i18n/es.js +65 -5
- package/src/ui/server/public/i18n/ja.js +67 -5
- package/src/ui/server/public/index.html +1 -1
- package/src/ui/server/public/local-dev.html +5 -4
- package/src/ui/server/public/persona-ui.css +32 -0
- package/src/ui/server/public/release.html +0 -1
- package/src/ui/server/public/settings-registry.js +3 -1
- package/src/ui/server/public/settings.html +9 -1
- package/src/ui/server/public/shell.js +348 -5
- package/src/ui/server/request-guard.ts +2 -0
- package/src/ui/server/route-classes.ts +34 -1
- package/src/ui/server/session-routes.ts +14 -3
- package/src/ui/server/workspace-routes.ts +21 -10
- package/themes/den/den-theme.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,200 @@ 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.16] - 2026-11-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Change an agent's persona (AG-18).** **Change persona…** in the
|
|
12
|
+
agent pane's ⋯ menu (running or stopped), and `<cli> agents persona
|
|
13
|
+
<id> <persona> [--model <m>] [--now | --after-turn]`. Den stops the
|
|
14
|
+
session and resumes the same conversation with the new persona's role
|
|
15
|
+
prompt, permission mode and allowed commands; the history, id, name,
|
|
16
|
+
workspace, folder, tab and the model it runs stay. The dialog marks the
|
|
17
|
+
current persona and lists what changes. Mid-turn it never stops a turn
|
|
18
|
+
unasked: "It's working. Change after this turn, or stop it now?" (409
|
|
19
|
+
`busy`); After this turn waits for the turn to end, Stop it now stops
|
|
20
|
+
it. The wait lives in the server's memory: if Den restarts first, the
|
|
21
|
+
change is dropped, the record no longer marks it pending, and the audit
|
|
22
|
+
trail says "persona change cancelled: Den restarted". A person's only: an agent's token is refused (403), and there is no
|
|
23
|
+
MCP tool. Only a runtime that can resume offers it. The record keeps
|
|
24
|
+
`personaChanges`, and the audit trail records each change. Route `POST
|
|
25
|
+
/api/workspaces/<ws>/agents/<id>/persona`.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **Bring in keeps the conversation's model, and says what the persona
|
|
30
|
+
changes (AG-17.1c).** It used to switch the conversation to the
|
|
31
|
+
persona's model, and drop MCP servers the conversation had outside Den,
|
|
32
|
+
without a word. Now the model stays the one its last answer came from
|
|
33
|
+
("Keeps <model> from the conversation"; Advanced or `--model` picks
|
|
34
|
+
another), and a short list under the persona says "From now on it works
|
|
35
|
+
as <persona>: its role, its permissions (<mode>), Den's tools and
|
|
36
|
+
guards. Its history stays.", plus "It won't have: <servers>." for MCP
|
|
37
|
+
servers seen in its transcript that Den won't load. The persona of the
|
|
38
|
+
latest agent that worked in its folder is preselected (`suggest`), never
|
|
39
|
+
submitted. The list adds `model`, `lost` and `suggest`; the import takes
|
|
40
|
+
`model` and `version`.
|
|
41
|
+
- **The signed-out page shows Sign in with GitHub once the owner is
|
|
42
|
+
linked, with no reload.** With no owner yet it watches the session
|
|
43
|
+
probe (which now says `github.state` to a signed-out browser), and step 1
|
|
44
|
+
turns done and the button appears as soon as `web owner link` finishes.
|
|
45
|
+
|
|
46
|
+
- **The signed-out page explains Sign in with GitHub (SEC-2a-UX).** It
|
|
47
|
+
shows the same three steps whether or not an owner is linked: on the
|
|
48
|
+
machine Den runs on, run `<cli> web owner link` (or use Settings ›
|
|
49
|
+
Access there); enter the code it shows at github.com/login/device and
|
|
50
|
+
approve; come back and press Sign in with GitHub with the same account.
|
|
51
|
+
Once an owner is linked, step 1 shows as done ("Owner: @login") and the
|
|
52
|
+
button follows the steps. Below them: only the owner's GitHub account
|
|
53
|
+
can sign in this way, anyone else uses Approve this computer, and Learn
|
|
54
|
+
more opens docs/distributions.md, "Signing in with GitHub". English,
|
|
55
|
+
Spanish and Japanese.
|
|
56
|
+
- Another GitHub account is refused with "@login isn't the owner of this
|
|
57
|
+
Den. Only the owner's account can use Sign in with GitHub. Use Approve
|
|
58
|
+
this computer instead." It no longer says to ask the owner to add you:
|
|
59
|
+
a Den has one owner, and no one to add.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- An open agent pane stayed on the old session after Change persona, Resume
|
|
64
|
+
or a rotation, showing "[the session exited (143)]" while the list showed
|
|
65
|
+
the new one running. A pane now follows its agent: it moves to the new
|
|
66
|
+
session in the same slot, keeps its scrollback, and says "Switched to
|
|
67
|
+
<persona>". After a session ends the page looks again every second for
|
|
68
|
+
15 s, so the switch shows within about 2 s.
|
|
69
|
+
|
|
70
|
+
### Migrations
|
|
71
|
+
|
|
72
|
+
- None.
|
|
73
|
+
|
|
74
|
+
## [0.4.15] - 2026-11-07
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- **Onboarding reads for agents, the CLI and the browser (ON-3).** Three new
|
|
79
|
+
MCP read tools, always registered, each with a CLI twin and a web API
|
|
80
|
+
route (GET, a read in the route table) that a page can show as it is:
|
|
81
|
+
- `onboard_status`, `<cli> onboard status [--json]`,
|
|
82
|
+
`/api/onboard/status`: what's set up (a distribution, Node, git, Docker,
|
|
83
|
+
the web server, workspaces, repos, a running service, Den's MCP), each
|
|
84
|
+
`ok`, `warn` or `todo`, and the one next step in plain words with its
|
|
85
|
+
command. It never runs an agent's CLI to check Den's MCP.
|
|
86
|
+
- `repos_detect`, `<cli> repo detect <folder> [--json]`,
|
|
87
|
+
`/api/onboard/repos`: the git repos in a folder, one or two levels down,
|
|
88
|
+
how each one runs (the same detection as `repo add`), and suggested
|
|
89
|
+
workspaces (the repos in one parent folder go in one workspace named
|
|
90
|
+
after it). It never writes, runs a repo's tools or follows a symlink,
|
|
91
|
+
skips dot-folders, `node_modules` and `vendor`, and stops at 50 repos.
|
|
92
|
+
- `manifest_draft`, `<cli> repo add <path> --dry-run --json`,
|
|
93
|
+
`/api/onboard/manifest`: the side-car manifest `repo add` would write,
|
|
94
|
+
checked by the same validator, with nothing written. Several ways to
|
|
95
|
+
start the repo is a question with its choices, as `repo add` asks.
|
|
96
|
+
- A path for `repos_detect` or `manifest_draft` must be a folder inside the
|
|
97
|
+
person's home folder and outside Den's state folder; anything else is
|
|
98
|
+
refused with the reason. A workspace-scoped MCP server can't scan folders,
|
|
99
|
+
and drafts only inside its workspace's repos.
|
|
100
|
+
- **Onboarding writes, held for the person (ON-3).** Three new MCP write
|
|
101
|
+
tools, `workspace_create`, `repo_add` and `service_try`, on by default
|
|
102
|
+
and gated by the person's Allow (not by `mcp.actions`). They change
|
|
103
|
+
nothing by themselves: each asks Den to hold the change, and every Den page opens a
|
|
104
|
+
dialog in the person's browser, "The `<agent>` agent wants to: … ·
|
|
105
|
+
Review · Later · Deny · Allow". Den writes the line and the Review from
|
|
106
|
+
the action and its arguments (`repo_add`'s Review is its dry run); the
|
|
107
|
+
agent's note is only quoted. Allow runs it once, from the stored
|
|
108
|
+
arguments checked again, through the code paths the pages and the CLI
|
|
109
|
+
use (the Workspaces page's create, `repo add --json`, `up` as the
|
|
110
|
+
Services page starts it). Deny, 10 minutes with no answer, or the agent
|
|
111
|
+
being stopped run nothing. The agent waits up to 2 minutes, then
|
|
112
|
+
`approval_check` reads where its request stands; meanwhile it is
|
|
113
|
+
"waiting" on the Agents page. Routes: `POST /api/approvals`,
|
|
114
|
+
`GET /api/approvals`, `GET /api/approvals/<id>`,
|
|
115
|
+
`POST /api/approvals/<id>/decide`. See [Onboarding writes, held for the
|
|
116
|
+
person](docs/distributions.md#onboarding-writes-held-for-the-person).
|
|
117
|
+
- **`mcp.approvals: false`** in the distribution config turns the three
|
|
118
|
+
held writes off: they aren't registered, and `POST /api/approvals`
|
|
119
|
+
answers 403 `approvals_off`. `doctor` (its `mcp:approvals` check in
|
|
120
|
+
`--json`), the MCP server's instructions and `mcp --list` (on stderr) say
|
|
121
|
+
which is in effect. A value that isn't `true` or `false` is an error.
|
|
122
|
+
- **Who asked.** An agent's token shows its name ("The `<agent>` agent
|
|
123
|
+
wants to:"). A request with no agent identity reads "Something on this
|
|
124
|
+
machine (no agent identity) wants to:", with "If you didn't ask an agent
|
|
125
|
+
to do this, Deny." under it. In English, Spanish and Japanese.
|
|
126
|
+
- **Only the person approves.** Allow and Deny need a browser signed in on
|
|
127
|
+
this Den and holding its approval key, even on localhost. Every sign-in
|
|
128
|
+
mints the key, handed over only in the sign-in's own answer (a 303's
|
|
129
|
+
`#ak=` fragment, or the sign-in poll); the session keeps its hash and
|
|
130
|
+
`GET /api/session` never returns it. A cookie alone (any localhost port
|
|
131
|
+
receives it), the CLI's bearer, an agent's token or an anonymous request
|
|
132
|
+
from this machine is refused with `signin_to_approve`.
|
|
133
|
+
- **Only the person sees what waits.** `GET /api/approvals` needs what
|
|
134
|
+
Allow needs: the cookie, that session's CSRF token and the approval key.
|
|
135
|
+
Anything else gets 403 `signin_to_approve` with a count (`waiting`), and
|
|
136
|
+
a signed-out page shows a banner to sign in. An agent's token lists only
|
|
137
|
+
its own requests and their outcomes. An ask with no agent token gets a
|
|
138
|
+
ticket that reads that one request (`X-Den-Approval-Ticket`); Den's MCP
|
|
139
|
+
server keeps it and never shows it to the agent.
|
|
140
|
+
- **The setup link.** `<cli> web claim-link [--json]` prints a one-time
|
|
141
|
+
link (`/auth?claim=<code>`, 30 minutes) that signs this browser in, for
|
|
142
|
+
an install that hands the person over to the browser. Opened twice, it
|
|
143
|
+
says it was used and that someone else may have opened it.
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
|
|
147
|
+
- Pairing a device, approving one, and linking or unlinking the GitHub
|
|
148
|
+
owner (`POST /api/web/pair`, `/api/web/approve`, `/api/web/owner/link`,
|
|
149
|
+
`/api/web/owner/unlink`) need the person: the CLI's bearer, or a
|
|
150
|
+
signed-in browser holding its approval key. On plain localhost,
|
|
151
|
+
Settings › Access asks you to sign in before pairing.
|
|
152
|
+
- Inside an agent's session (`DEN_AGENT` or `DEN_ACTOR_TOKEN` set), the CLI
|
|
153
|
+
refuses every command that lets a browser in: `web login`, `web approve`,
|
|
154
|
+
`web owner link|unlink`, `web claim-link`; `web open` opens the plain
|
|
155
|
+
page.
|
|
156
|
+
- What a test run writes and Den serves (`/report/*`, the Playwright
|
|
157
|
+
report, and `/reports/security/*`) is served sandboxed (an opaque
|
|
158
|
+
origin, no scripts reaching Den's), with nosniff, no-store and
|
|
159
|
+
no-referrer.
|
|
160
|
+
- A workspace name is at most 64 characters.
|
|
161
|
+
- **A Name is the agent's id (AG-10.10b).** A Name given at start (the
|
|
162
|
+
dialog's Name, the API's `name`, the CLI's new `--name`) makes the id its
|
|
163
|
+
slug: "API spike" starts `api-spike`, titled "API spike". The dialog says
|
|
164
|
+
so as you type. A Name that matches a running agent in the workspace, by
|
|
165
|
+
id or by title, is refused: the dialog says "api-spike is already
|
|
166
|
+
running · Open it" and Start is off. A stopped one offers Resume it, or
|
|
167
|
+
Start makes `api-spike-2`. No two live agents in a workspace share a
|
|
168
|
+
title. With no Name the persona's id stays the default; `--id` is exact.
|
|
169
|
+
- The Den theme's light mode (2.0.1): `--ok`, `--warn`, `--bad`,
|
|
170
|
+
`--remote` and `--accent-text` are darker, so a status pill (the token
|
|
171
|
+
on a tint of itself) meets WCAG AA (4.5:1) on every surface, `--sunken`
|
|
172
|
+
included. They measured 3.7 to 4.4:1 there. Dark mode is unchanged.
|
|
173
|
+
|
|
174
|
+
### Fixed
|
|
175
|
+
|
|
176
|
+
- The signed-out page's Open Terminal link was `ssh://<host>` with no
|
|
177
|
+
account, so an SSH app filled in its own default (an iPad's asked for
|
|
178
|
+
`user@…`'s password). The link and the ssh one-liner are now
|
|
179
|
+
`<user>@<host>`, the account Den runs as, and the note says "Log in with
|
|
180
|
+
your <machine> account (<user>) and its password, or an SSH key."
|
|
181
|
+
`web.ssh_user` names another account; `web.ssh_user: ""` leaves it off.
|
|
182
|
+
- Text dimmed with opacity fell under WCAG AA on the light theme. Not
|
|
183
|
+
installed editors and terminals in Settings (3.1:1, their "not found"
|
|
184
|
+
2.2:1), a repo already in the workspace in Add service (1.9:1), the
|
|
185
|
+
Release table's loading sub-line (3.7:1), and a primary button on hover
|
|
186
|
+
(4.4:1). They are full-strength now, in `--muted`; hover darkens.
|
|
187
|
+
- `/reports/security/*` was always 404: the `/report` prefix shadowed it.
|
|
188
|
+
- Bring in on a shared repos root (the usual layout) listed every
|
|
189
|
+
conversation under the root, so workspaces showed the same 50 and their
|
|
190
|
+
own were crowded out. A conversation is a workspace's when it ran in one
|
|
191
|
+
of its repos' folders or inside one, in its own folder, or exactly at a
|
|
192
|
+
shared root; the 50 cap applies after that. Another workspace's
|
|
193
|
+
conversation is refused by id: "This conversation belongs to <folder>,
|
|
194
|
+
not to workspace <ws>" (CLI, API and MCP).
|
|
195
|
+
|
|
196
|
+
### Migrations
|
|
197
|
+
|
|
198
|
+
None. Sessions signed in before 0.4.15 hold no approval key: sign in again
|
|
199
|
+
to approve or to pair a device.
|
|
200
|
+
|
|
7
201
|
## [0.4.14] - 2026-11-07
|
|
8
202
|
|
|
9
203
|
### Added
|
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.
|
|
14
|
-
<img src="docs/images/0.4.
|
|
13
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.16/hero-dark.png">
|
|
14
|
+
<img src="docs/images/0.4.16/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
|
## Set up by your agent
|
|
@@ -38,7 +38,7 @@ It doesn't replace your tools. Your repos, Docker, your test runners, your secre
|
|
|
38
38
|
yours. Den connects them and turns on good practice from the start.
|
|
39
39
|
|
|
40
40
|
<p align="center">
|
|
41
|
-
<img src="docs/images/0.4.
|
|
41
|
+
<img src="docs/images/0.4.16/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">
|
|
42
42
|
</p>
|
|
43
43
|
|
|
44
44
|
## How it helps
|
|
@@ -68,10 +68,10 @@ yours. Den connects them and turns on good practice from the start.
|
|
|
68
68
|
lives in your own repo.
|
|
69
69
|
|
|
70
70
|
<p align="center">
|
|
71
|
-
<img src="docs/images/0.4.
|
|
72
|
-
<img src="docs/images/0.4.
|
|
73
|
-
<img src="docs/images/0.4.
|
|
74
|
-
<img src="docs/images/0.4.
|
|
71
|
+
<img src="docs/images/0.4.16/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">
|
|
72
|
+
<img src="docs/images/0.4.16/workspaces.png" width="49%" alt="The Workspaces page: the demo workspace's repos with their health, mode and Start, Stop, Restart and Logs actions">
|
|
73
|
+
<img src="docs/images/0.4.16/status-drawer.png" width="49%" alt="The status drawer: nothing needs attention, and every service in the demo workspace, by application, with its latency">
|
|
74
|
+
<img src="docs/images/0.4.16/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">
|
|
75
75
|
</p>
|
|
76
76
|
|
|
77
77
|
## Get started
|
package/docs/distributions.md
CHANGED
|
@@ -83,6 +83,9 @@ question for the person (`ask`, sometimes with `choices`).
|
|
|
83
83
|
| ---------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
84
84
|
| `npx @monster-orange/den create <name> [dir] --yes --json` | `{ ok, dir, cli, existed, files, port, portPicked?, next }` |
|
|
85
85
|
| `<cli> repo add <path\|git-url> --json` | `{ ok, existed, repo, workspace, mode, path, manifest, services, next }` |
|
|
86
|
+
| `<cli> repo detect <folder> --json` | `{ ok, path, repos, workspaces, truncated, skipped, rule, next }` |
|
|
87
|
+
| `<cli> repo add <path> --dry-run --json` | `{ ok, dryRun, repo, workspace, path, manifest, draft, validation, next }` |
|
|
88
|
+
| `<cli> onboard status --json` | `{ ok, checks: [{ id, status, detail, fix?, ask? }], next }` |
|
|
86
89
|
| `<cli> up <repo> --json` | `{ ok, workspace, services: [{ address, result, health, detail? }], next }` |
|
|
87
90
|
| `<cli> web up --no-open --json` | `{ ok, url, port, version? }` |
|
|
88
91
|
| `<cli> doctor --json` | `{ ok, checks: [{ id, status, detail, url?, port?, fix?, ask? }] }` |
|
|
@@ -121,6 +124,115 @@ question for the person (`ask`, sometimes with `choices`).
|
|
|
121
124
|
else the focused one, else a new `default` workspace.
|
|
122
125
|
- The same repo again answers `ok` with `existed: true`. A different repo
|
|
123
126
|
under the same name is an error that offers `relocate` or `--name`.
|
|
127
|
+
- `--dry-run` (a local path only) runs the same detection and validator and
|
|
128
|
+
writes nothing: no side-car, no workspace entry, no catalog entry. The
|
|
129
|
+
answer carries the side-car as YAML (`draft`), the validator's answer
|
|
130
|
+
(`validation`), where it would go (`manifest.file`, `written: false`) and,
|
|
131
|
+
in `next`, the same command without `--dry-run`. A git URL is refused: it
|
|
132
|
+
would have to be cloned first.
|
|
133
|
+
|
|
134
|
+
### Onboarding reads
|
|
135
|
+
|
|
136
|
+
Three reads help a person, or an agent, finish setting Den up. Each one is
|
|
137
|
+
a CLI command, an MCP read tool and a web API route, with the same answer
|
|
138
|
+
(the browser can show the route's JSON as it is):
|
|
139
|
+
|
|
140
|
+
| CLI | MCP tool | Web API (GET) |
|
|
141
|
+
| ---------------------------------------- | ---------------- | ----------------------- |
|
|
142
|
+
| `<cli> onboard status [--json]` | `onboard_status` | `/api/onboard/status` |
|
|
143
|
+
| `<cli> repo detect <folder> [--json]` | `repos_detect` | `/api/onboard/repos` |
|
|
144
|
+
| `<cli> repo add <path> --dry-run --json` | `manifest_draft` | `/api/onboard/manifest` |
|
|
145
|
+
|
|
146
|
+
- **`onboard status`** says what's set up and the one next step. `checks`
|
|
147
|
+
are, in order: `distribution` (this folder has its own `bin/<cli>`),
|
|
148
|
+
`node`, `git`, `docker` and `web` (the same probes as `doctor --json`),
|
|
149
|
+
`workspaces` (how many, and the active ones), `repos` (how many are
|
|
150
|
+
added), `service` (how many services Den started are running) and `mcp`.
|
|
151
|
+
Each is `ok`, `warn` or `todo`, with a `fix` command or an `ask` for the
|
|
152
|
+
person when there is one. `next` is `{ id, step, command }`: the step in
|
|
153
|
+
plain words and the command for it, or `id: "done"` when everything is
|
|
154
|
+
set up. `mcp` never runs an agent's CLI: it is `ok` when the question
|
|
155
|
+
came through Den's MCP, or when a client's config file names this
|
|
156
|
+
distribution's server; with only Claude Code or Codex installed (they
|
|
157
|
+
keep their list behind their own CLI) it is a `warn` pointing at
|
|
158
|
+
`<cli> mcp clients`. It exits 0 whenever it answers.
|
|
159
|
+
- **`repo detect <folder>`** finds the git repos in one folder: the folder
|
|
160
|
+
itself when it is a repo, else its subfolders one and two levels down (a
|
|
161
|
+
repo's own subfolders are not scanned). For each repo: `manifest` (`repo`,
|
|
162
|
+
`sidecar` or `none`), `runs` (what [`repo add`](#repo-add) would detect)
|
|
163
|
+
or `ask` and `choices`, the suggested `workspace`, and `registered` when
|
|
164
|
+
it is already in one. `workspaces` groups them by one simple rule: the
|
|
165
|
+
repos in one parent folder go in one workspace named after that folder,
|
|
166
|
+
and a repo directly in the scanned folder gets the scanned folder's name.
|
|
167
|
+
It skips dot-folders, `node_modules` and `vendor`, never follows a
|
|
168
|
+
symlink, stops at 50 repos or 5,000 folder entries (`truncated: true`),
|
|
169
|
+
and never writes or runs a repo's tools.
|
|
170
|
+
- **`repo add --dry-run`** is the draft: see [`repo add`](#repo-add).
|
|
171
|
+
- **The path rule** (`repos_detect`, `manifest_draft` and both routes): a
|
|
172
|
+
path is required, absolute or starting with `~/`. It is resolved with its
|
|
173
|
+
symlinks, and must be a folder inside the person's home folder and outside
|
|
174
|
+
Den's own state folder; anything else is refused with the reason (a 400
|
|
175
|
+
from the route, `Refused:` from the tool, exit 1 from the CLI).
|
|
176
|
+
|
|
177
|
+
### Onboarding writes, held for the person
|
|
178
|
+
|
|
179
|
+
An agent finishes setup with three MCP write tools. None of them changes
|
|
180
|
+
anything by itself: each asks Den to hold the change, and the person's
|
|
181
|
+
browser opens a dialog on whatever Den page is open: "The `<agent>` agent
|
|
182
|
+
wants to: … · Review · Later · Deny · Allow". A request with no agent
|
|
183
|
+
token (an MCP server outside an agent's session, or anything else on this
|
|
184
|
+
machine) reads "Something on this machine (no agent identity) wants to:",
|
|
185
|
+
with "If you didn't ask an agent to do this, Deny." under it.
|
|
186
|
+
|
|
187
|
+
| MCP tool | What Allow runs |
|
|
188
|
+
| ------------------ | ------------------------------------------------------- |
|
|
189
|
+
| `workspace_create` | The Workspaces page's create: an empty workspace |
|
|
190
|
+
| `repo_add` | `repo add <path> --json`, a local folder inside home |
|
|
191
|
+
| `service_try` | `up` for one service, as the Services page's Start does |
|
|
192
|
+
|
|
193
|
+
- **Den writes the dialog.** Its line and its Review come from the action
|
|
194
|
+
and its arguments, never from the agent. `repo_add`'s Review is its dry
|
|
195
|
+
run: the folder, the workspace (new or not) and the side-car Den would
|
|
196
|
+
write. The agent's `note` is shown only quoted, as "Agent says".
|
|
197
|
+
- **Bad arguments are refused before anything waits**: the same checks as
|
|
198
|
+
the page, `repo add` and the path rule of
|
|
199
|
+
[Onboarding reads](#onboarding-reads).
|
|
200
|
+
- **Only the person approves.** Allow and Deny need a browser signed in on
|
|
201
|
+
this Den holding its approval key: a session cookie alone, the CLI's
|
|
202
|
+
bearer, an agent's token, or an anonymous request from this machine (an
|
|
203
|
+
agent with a shell) is refused with `signin_to_approve`. On a fresh Den,
|
|
204
|
+
`<cli> web claim-link` prints a one-time setup link that signs the
|
|
205
|
+
browser in.
|
|
206
|
+
- **Allow runs it once**, from the arguments stored when it was asked,
|
|
207
|
+
checked again first. Deny, 10 minutes with no answer, or the agent being
|
|
208
|
+
stopped run nothing. An agent may have 5 requests waiting; a restart drops
|
|
209
|
+
what waits (the agent asks again).
|
|
210
|
+
- **The agent waits** up to 2 minutes for the answer, then gets the request
|
|
211
|
+
id: `approval_check` says `pending`, `done` (with what the run said),
|
|
212
|
+
`denied` or `expired`. While a request waits, its agent is "waiting" on
|
|
213
|
+
the Agents page.
|
|
214
|
+
- **Only the person sees what waits.** `GET /api/approvals` needs what
|
|
215
|
+
Allow needs: the session cookie, that session's CSRF token
|
|
216
|
+
(`X-Den-CSRF`) and the approval key. Anything else gets 403
|
|
217
|
+
`signin_to_approve` with only `waiting`, a count, so a signed-out page
|
|
218
|
+
can show "Something is waiting for your approval. Sign in to see it."
|
|
219
|
+
An agent's token lists its own requests and their outcomes (`mine`),
|
|
220
|
+
never another agent's. An ask with no agent token gets a `ticket` in the
|
|
221
|
+
reply; `X-Den-Approval-Ticket: <ticket>` reads that one request and no
|
|
222
|
+
other. Den's MCP server keeps it and never shows it to the agent.
|
|
223
|
+
- **Routes:** `POST /api/approvals` (`{ action, args, note? }`, 202 with the
|
|
224
|
+
request), `GET /api/approvals` (the person's: what waits; an agent's
|
|
225
|
+
token: its own), `GET /api/approvals/<id>` (the asking agent's own, the
|
|
226
|
+
ticket holder's, or the person's) and
|
|
227
|
+
`POST /api/approvals/<id>/decide` (`{ decision: "allow" | "deny" }`).
|
|
228
|
+
Every ask and decision is in the activity trail.
|
|
229
|
+
- **On by default; `mcp.approvals: false` turns them off.** The person's
|
|
230
|
+
Allow is the gate, not `mcp.actions`. Off, the three tools aren't
|
|
231
|
+
registered and `POST /api/approvals` answers 403 `approvals_off`.
|
|
232
|
+
`doctor` (and its `mcp:approvals` check in `--json`), the MCP server's
|
|
233
|
+
instructions and `mcp --list` (on stderr) say which is in effect. A
|
|
234
|
+
workspace-scoped MCP server refuses `workspace_create` and `repo_add`:
|
|
235
|
+
they change Den beyond its workspace.
|
|
124
236
|
|
|
125
237
|
### up, doctor and mcp connect
|
|
126
238
|
|
|
@@ -1434,6 +1546,15 @@ user, and the CLI does that for you:
|
|
|
1434
1546
|
host name (up to the first dot). It is never taken from the browser's
|
|
1435
1547
|
Host. The ssh one-liner's target is the Host the browser used, without
|
|
1436
1548
|
the port (the machine's host name if it had none).
|
|
1549
|
+
- **The ssh account.** The ssh one-liner and the Open Terminal link log in
|
|
1550
|
+
as `<user>@<host>`, where user is the account the Den server runs as, so
|
|
1551
|
+
an SSH app doesn't fill in its own default. The note under the button
|
|
1552
|
+
says "Log in with your <machine> account (<user>) and its password, or
|
|
1553
|
+
an SSH key." `web.ssh_user` names another account (`config/local/user.yml`,
|
|
1554
|
+
else the distribution's `config/den.yml`; read at once). `web.ssh_user:
|
|
1555
|
+
""` leaves the account off, for a distribution that doesn't want it on a
|
|
1556
|
+
page anyone on the network can see. It takes letters, digits, `.`, `_`
|
|
1557
|
+
and `-`; anything else is refused at start, naming the key.
|
|
1437
1558
|
- **Codes.** About 30 bits (Crockford base32: no I, L, O or U; typed in any
|
|
1438
1559
|
case, the dash optional); the page polls with a separate 128-bit id. A
|
|
1439
1560
|
code expires after 10 minutes and works once; at most five wait at once
|
|
@@ -1566,14 +1687,31 @@ the account's id and login, never a token, and no other account gets in.
|
|
|
1566
1687
|
`web owner unlink`. Settings › Access does the same in the browser
|
|
1567
1688
|
(Link your GitHub account, Unlink): shell-grade actions, for a signed-in
|
|
1568
1689
|
browser and never an agent's token.
|
|
1569
|
-
- **Sign in with GitHub** on the signed-out page shows
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1690
|
+
- **Sign in with GitHub** on the signed-out page shows the same three
|
|
1691
|
+
steps whether or not an owner is linked:
|
|
1692
|
+
1. On the machine Den runs on, run `web owner link`, or use
|
|
1693
|
+
Settings › Access there.
|
|
1694
|
+
2. It shows a code. Enter it at `github.com/login/device` and approve.
|
|
1695
|
+
3. Come back to the signed-out page and press **Sign in with GitHub**,
|
|
1696
|
+
with the same GitHub account.
|
|
1697
|
+
|
|
1698
|
+
Once an owner is linked, step 1 shows as done ("Owner: @<login>") and
|
|
1699
|
+
the button follows the steps; before that there is no button. Below the
|
|
1700
|
+
steps the page says only the owner's GitHub account can sign in this
|
|
1701
|
+
way, and that anyone else uses **Approve this computer**; **Learn more**
|
|
1702
|
+
opens this section. With no owner yet, the page watches (the session
|
|
1703
|
+
probe, `GET /api/session`, says `github.state` to a signed-out
|
|
1704
|
+
browser): once the owner is linked on the machine, step 1 shows done
|
|
1705
|
+
and the button appears, with no reload.
|
|
1706
|
+
|
|
1707
|
+
- Pressing the button shows a code and `github.com/login/device`. The
|
|
1708
|
+
server runs the flow, and when the owner approves it sets the same
|
|
1709
|
+
never-expiring session cookie a one-time link sets, and the page reloads
|
|
1710
|
+
signed in. Another account sees "@<login> isn't the owner of this Den.
|
|
1711
|
+
Only the owner's account can use Sign in with GitHub. Use Approve this
|
|
1712
|
+
computer instead." Den has one owner, so there is no one to add. The
|
|
1713
|
+
token is thrown away either way. At most three sign-ins wait for GitHub
|
|
1714
|
+
at once; a code expires as GitHub says (15 minutes).
|
|
1577
1715
|
- **Settings › Access** lists the linked devices (the browser, how it
|
|
1578
1716
|
signed in, the client address when the edge vouched for one, created and
|
|
1579
1717
|
last seen; "This device" on yours), each with **Revoke**, which ends its session at once (its
|
|
@@ -1785,13 +1923,17 @@ to one workspace for one agent:
|
|
|
1785
1923
|
(`map: "org-wide"`: every active workspace's graph merged, `workspaces`
|
|
1786
1924
|
naming them); with one, that workspace's (`map: "workspace"`).
|
|
1787
1925
|
- **Filtered.** `workspaces` lists `<ws>` only. `logs_search` and `logs_tail`
|
|
1788
|
-
take only `<ws>`'s services, `manifest_validate`
|
|
1789
|
-
inside one of its repos (or
|
|
1926
|
+
take only `<ws>`'s services, `manifest_validate` and `manifest_draft` take
|
|
1927
|
+
only an absolute path inside one of its repos (or, for `manifest_validate`,
|
|
1928
|
+
the content), and `workspace_sync` syncs `<ws>`. `repos_detect` is refused:
|
|
1929
|
+
scanning folders reaches outside any workspace, so onboarding is for the
|
|
1930
|
+
unscoped server.
|
|
1790
1931
|
Tests run against the focused workspace, so `tests_available` and
|
|
1791
1932
|
`run_tests` are refused unless `<ws>` is the focused one.
|
|
1792
1933
|
- **Not workspace data**, served as unscoped: `environments`,
|
|
1793
|
-
`security_latest`, `release_status`, `edge_status
|
|
1794
|
-
org's and the distribution's, not
|
|
1934
|
+
`security_latest`, `release_status`, `edge_status`, `onboard_status` (counts
|
|
1935
|
+
and checks only) and `org_summary` (the org's and the distribution's, not
|
|
1936
|
+
a workspace's).
|
|
1795
1937
|
- **The org at a glance.** `org_summary` gives every agent every workspace
|
|
1796
1938
|
and repo with status counts, the graph, the edge and the environments
|
|
1797
1939
|
(see [Tools](#tools)). Detail and actions stay scoped: for workspaces
|
|
@@ -1857,10 +1999,12 @@ mcp:
|
|
|
1857
1999
|
envs: [local] # the default; production is refused even if listed
|
|
1858
2000
|
logs:
|
|
1859
2001
|
remote: false # the default; true lets the log tools read non-local sources
|
|
2002
|
+
approvals: true # the default: the held writes, each run on the person's Allow
|
|
1860
2003
|
```
|
|
1861
2004
|
|
|
1862
2005
|
An unknown action or environment name is an error, so a typo never turns a
|
|
1863
|
-
guard off
|
|
2006
|
+
guard off; so is an `approvals` that isn't `true` or `false`. See
|
|
2007
|
+
[Onboarding writes, held for the person](#onboarding-writes-held-for-the-person).
|
|
1864
2008
|
|
|
1865
2009
|
### Tools
|
|
1866
2010
|
|
|
@@ -1883,6 +2027,9 @@ Read tools (always registered):
|
|
|
1883
2027
|
| `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health; an empty list says `why` |
|
|
1884
2028
|
| `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) |
|
|
1885
2029
|
| `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 |
|
|
2030
|
+
| `onboard_status` | `/api/onboard/status` | What's set up and the one next step: `checks` (`ok`, `warn`, `todo`) and `next` (`step`, `command`); see [Onboarding reads](#onboarding-reads) |
|
|
2031
|
+
| `repos_detect` | `/api/onboard/repos` | The git repos in `path` (one or two levels down), how each runs, and suggested `workspaces`; never writes or follows a symlink; refused on a scoped server |
|
|
2032
|
+
| `manifest_draft` | `/api/onboard/manifest` | The side-car `repo add` would write for `path`, validated (`draft`, `validation`), nothing written; `ask` and `choices` when it can start in several ways (call again with `run`) |
|
|
1886
2033
|
|
|
1887
2034
|
**Health states.** A service with no verdict says why in `why`: no health
|
|
1888
2035
|
check in the manifest for the env, remote with no check or `target.url`
|
|
@@ -2045,12 +2192,13 @@ An agent is a **persona** (who) in a **workspace** (where), run by a
|
|
|
2045
2192
|
server scoped to its workspace.
|
|
2046
2193
|
|
|
2047
2194
|
```
|
|
2048
|
-
<cli> agents start -w <ws> --persona <p> [--runtime <r>] [--model <m>] [--id <id>]
|
|
2195
|
+
<cli> agents start -w <ws> --persona <p> [--runtime <r>] [--model <m>] [--name <name>] [--id <id>]
|
|
2049
2196
|
<cli> agents list -w <ws> [--json]
|
|
2050
2197
|
<cli> agents stop <id> [-w <ws>]
|
|
2051
2198
|
<cli> agents resume <id> [-w <ws>]
|
|
2052
2199
|
<cli> agents import -w <ws> [--runtime <r>] [--json] # conversations to bring in
|
|
2053
|
-
<cli> agents import -w <ws> --session <id> --persona <p>
|
|
2200
|
+
<cli> agents import -w <ws> --session <id> --persona <p> [--model <m>] # bring one in
|
|
2201
|
+
<cli> agents persona <id> <persona> [-w <ws>] [--model <m>] [--now | --after-turn] # change its persona
|
|
2054
2202
|
<cli> agents personas [--json]
|
|
2055
2203
|
<cli> agents doctor [-w <ws>] [--json] # can each runtime run here, and why not
|
|
2056
2204
|
<cli> agents handback-check <file|-> # is a hand-back complete and plain (see Guardrails)
|
|
@@ -2065,6 +2213,9 @@ server scoped to its workspace.
|
|
|
2065
2213
|
agent there, running or stopped, starts another: `planner-2`,
|
|
2066
2214
|
`planner-3`. A stopped agent is continued by Resume or Start fresh,
|
|
2067
2215
|
never by a new start. `--id` is exact: a running id is refused.
|
|
2216
|
+
`--name "API spike"` makes the id its slug, `api-spike`, titled as
|
|
2217
|
+
typed; a running agent there with that id or title is refused, and a
|
|
2218
|
+
stopped one makes it `api-spike-2`.
|
|
2068
2219
|
- The **agent record**, `<stateDir>/agents/<id>.json` (0600), holds its id,
|
|
2069
2220
|
persona, runtime, model, workspace, cwd, session and times, and what it was
|
|
2070
2221
|
started with from the Start dialog: `version` (the model's, "latest" or
|
|
@@ -2100,8 +2251,11 @@ server scoped to its workspace.
|
|
|
2100
2251
|
#### Bring in a conversation
|
|
2101
2252
|
|
|
2102
2253
|
A conversation you started in a terminal outside Den (Claude Code, in the
|
|
2103
|
-
workspace's folder
|
|
2104
|
-
a repo added by path outside it too) can become a Den agent.
|
|
2254
|
+
workspace's own folder or one inside it, or a folder of one of its repos,
|
|
2255
|
+
a repo added by path outside it too) can become a Den agent. When
|
|
2256
|
+
workspaces share one repos root, only each one's repos' folders count,
|
|
2257
|
+
and the root itself only exactly; another workspace's conversation is
|
|
2258
|
+
refused by id. Agents ›
|
|
2105
2259
|
**Bring in** lists them, newest first, with the persona list; `agents
|
|
2106
2260
|
import -w <ws>` lists them too.
|
|
2107
2261
|
|
|
@@ -2115,6 +2269,24 @@ import -w <ws>` lists them too.
|
|
|
2115
2269
|
Den's token, MCP config, hooks, guards and audit trail apply. It is an
|
|
2116
2270
|
agent like any other: stop, resume, rename. The record notes
|
|
2117
2271
|
`broughtInAt`.
|
|
2272
|
+
- **What the persona changes.** The persona is the role it takes on from
|
|
2273
|
+
now on: its role prompt, its permission mode and allowed commands, and
|
|
2274
|
+
Den's tools and guards. The history stays. The dialog says so under the
|
|
2275
|
+
persona list: "From now on it works as <persona>: its role, its
|
|
2276
|
+
permissions (<mode>), Den's tools and guards. Its history stays."
|
|
2277
|
+
- **The model stays the conversation's own.** Den reads the model of its
|
|
2278
|
+
last answer (`message.model` on the last assistant record, in the ends
|
|
2279
|
+
it reads) and runs it on that: "Keeps <model> from the conversation".
|
|
2280
|
+
Advanced (or `--model`) picks another. With no model in the
|
|
2281
|
+
transcript, the persona's applies.
|
|
2282
|
+
- **The MCP servers it won't have.** An agent runs with Den's MCP only
|
|
2283
|
+
(`--strict-mcp-config`). When the transcript shows tool calls from other
|
|
2284
|
+
servers (`mcp__<server>__…`), the dialog names them: "It won't have:
|
|
2285
|
+
<servers>." The list's `lost` field has them.
|
|
2286
|
+
- **A suggested persona.** The list's `suggest` is the persona of the
|
|
2287
|
+
latest agent of the workspace that worked in the conversation's folder
|
|
2288
|
+
(or a folder inside it, or one containing it), else the workspace's
|
|
2289
|
+
latest agent's. The dialog preselects it; you confirm with Bring in.
|
|
2118
2290
|
- **One writer only.** A conversation written in the last minute is taken
|
|
2119
2291
|
to be still open in another terminal: "Still open in another terminal.
|
|
2120
2292
|
Close it there first" (HTTP 409 `open_elsewhere`). A conversation an
|
|
@@ -2124,8 +2296,43 @@ import -w <ws>` lists them too.
|
|
|
2124
2296
|
- Only den-claude can bring one in for now. Other runtimes say "<runtime>
|
|
2125
2297
|
can't bring in a conversation yet".
|
|
2126
2298
|
- Routes: `GET /api/workspaces/<ws>/conversations[?runtime=<r>]` and `POST
|
|
2127
|
-
/api/workspaces/<ws>/conversations/<id>/import` `{ persona, runtime?, id
|
|
2128
|
-
}`. A person's only, unless MCP `agent_import` is opted
|
|
2299
|
+
/api/workspaces/<ws>/conversations/<id>/import` `{ persona, runtime?, id?,
|
|
2300
|
+
model?, version? }`. A person's only, unless MCP `agent_import` is opted
|
|
2301
|
+
in.
|
|
2302
|
+
|
|
2303
|
+
#### Change an agent's persona
|
|
2304
|
+
|
|
2305
|
+
**Change persona…** (the agent pane's ⋯ menu, running or stopped) and
|
|
2306
|
+
`agents persona <id> <persona>` switch an agent to another persona on the
|
|
2307
|
+
same conversation. Den stops its session and resumes the conversation with
|
|
2308
|
+
the new persona's role prompt, permission mode and allowed commands (the
|
|
2309
|
+
old persona's mode and commands go). The history stays, and so do the
|
|
2310
|
+
agent's id, name, workspace, folder, grants, tab and slot, and the model it
|
|
2311
|
+
runs unless Advanced (`--model`) picks another. The dialog lists the
|
|
2312
|
+
personas with the current one marked, and the same "From now on it works
|
|
2313
|
+
as…" line as Bring in.
|
|
2314
|
+
|
|
2315
|
+
- **Never mid-turn without asking.** While it works (den-claude: its
|
|
2316
|
+
transcript's last record is a tool call, or a prompt or tool result with
|
|
2317
|
+
no answer yet), the change is refused: "It's working. Change after this
|
|
2318
|
+
turn, or stop it now?" (HTTP 409 `busy`). **After this turn**
|
|
2319
|
+
(`--after-turn`, `when: "after-turn"`) waits for the turn to end, up to
|
|
2320
|
+
30 minutes, and then switches (the API answers 202 `pending` at once;
|
|
2321
|
+
the record marks it `pendingPersona` while it waits). The wait lives in
|
|
2322
|
+
the server's memory: if Den restarts first, the change is dropped, the
|
|
2323
|
+
mark is cleared at start, and the audit trail records "persona change
|
|
2324
|
+
cancelled: Den restarted";
|
|
2325
|
+
**Stop it now** (`--now`, `when: "now"`) stops it and switches.
|
|
2326
|
+
- **A person's only.** An agent could widen its own permissions with it,
|
|
2327
|
+
so an agent's token is refused (403), the CLI refuses inside an agent's
|
|
2328
|
+
session, and there is no MCP tool.
|
|
2329
|
+
- Only a runtime that can resume offers it (`personaChange: true` on the
|
|
2330
|
+
agent's row); others can't.
|
|
2331
|
+
- The record keeps each change in `personaChanges` (from, to, when, and
|
|
2332
|
+
who), and the audit trail records "persona <id> in <ws>: <from> → <to>,
|
|
2333
|
+
by <who>".
|
|
2334
|
+
- Route: `POST /api/workspaces/<ws>/agents/<id>/persona` `{ persona, model?,
|
|
2335
|
+
version?, when? }`.
|
|
2129
2336
|
|
|
2130
2337
|
### On the Workspaces page
|
|
2131
2338
|
|
package/package.json
CHANGED
|
@@ -199,6 +199,18 @@ export function revokedAgentIds(stateDir: string): Set<string> {
|
|
|
199
199
|
return out;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Whether agent `agentId` holds a token that isn't revoked: stopping it
|
|
204
|
+
* deletes its tokens, `web logout --all` marks them revoked (ON-3: a held
|
|
205
|
+
* write of an agent that is gone never runs).
|
|
206
|
+
*/
|
|
207
|
+
export function agentTokenLive(stateDir: string, agentId: string): boolean {
|
|
208
|
+
const actor = `agent:${agentId}`;
|
|
209
|
+
return Object.values(load(stateDir).tokens).some(
|
|
210
|
+
(r) => r.actor === actor && !r.revokedAt,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
202
214
|
/** What a presented token was minted as, live or not; null when unknown. */
|
|
203
215
|
export function lookupActorToken(
|
|
204
216
|
stateDir: string,
|