@monster-orange/den 0.4.11 → 0.4.13
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 +169 -0
- package/README.md +7 -7
- package/docs/distributions.md +212 -12
- package/package.json +2 -1
- package/src/shared/agents/catalog.ts +77 -0
- package/src/shared/agents/default-pack/be-dev.md +1 -0
- package/src/shared/agents/default-pack/devops.md +1 -0
- package/src/shared/agents/default-pack/fe-dev.md +1 -0
- package/src/shared/agents/default-pack/orchestrator.md +3 -1
- package/src/shared/agents/default-pack/planner.md +1 -0
- package/src/shared/agents/default-pack/reviewer.md +1 -0
- package/src/shared/agents/default-pack/tester.md +16 -0
- package/src/shared/agents/default-pack/ux-designer.md +1 -0
- package/src/shared/agents/den-claude.ts +151 -17
- package/src/shared/agents/permissions.ts +108 -0
- package/src/shared/agents/persona-store.ts +286 -0
- package/src/shared/agents/personas.ts +116 -12
- package/src/shared/agents/records.ts +57 -0
- package/src/shared/agents/roles.ts +288 -0
- package/src/shared/agents/runtime.ts +49 -0
- package/src/shared/agents/session-name.ts +110 -0
- package/src/shared/agents/transcript-follow.ts +180 -0
- package/src/shared/den-config.ts +27 -1
- package/src/ui/cli/agent-ops.ts +578 -11
- package/src/ui/cli/agents-command.ts +12 -2
- package/src/ui/cli/env-probe.ts +4 -1
- package/src/ui/mcp/tools.ts +1 -1
- package/src/ui/server/agent-paths.ts +52 -0
- package/src/ui/server/agent-routes.ts +329 -19
- package/src/ui/server/agents-layout-pref.ts +43 -0
- package/src/ui/server/editor-open-pref.ts +41 -0
- package/src/ui/server/index.ts +22 -0
- package/src/ui/server/public/agent-start.js +3 -2
- package/src/ui/server/public/agents-page.js +1037 -67
- package/src/ui/server/public/agents-section.js +1 -1
- package/src/ui/server/public/agents.html +236 -20
- package/src/ui/server/public/header.js +2 -0
- package/src/ui/server/public/i18n/es.js +139 -0
- package/src/ui/server/public/i18n/ja.js +139 -0
- package/src/ui/server/public/persona-ui.css +539 -0
- package/src/ui/server/public/persona-ui.js +796 -0
- package/src/ui/server/public/settings-page.js +4 -9
- package/src/ui/server/public/settings-registry.js +66 -0
- package/src/ui/server/public/settings.html +1 -0
- package/src/ui/server/public/shell.js +137 -0
- package/src/ui/server/public/term-input.js +129 -0
- package/src/ui/server/public/topbar.js +22 -4
- package/src/ui/server/route-classes.ts +29 -0
- package/src/ui/server/session-routes.ts +34 -1
- package/src/ui/server/ui-assets.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,175 @@ 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.13] - 2026-11-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **The Start dialog.** + Start agent opens the dialog from the Den Shell
|
|
12
|
+
design. Pick a persona from a list you can search, with category and
|
|
13
|
+
source chips (built-in, distribution, mine); with no filter it is grouped
|
|
14
|
+
by category, and Plain session (your shell in the workspace, no agent) is
|
|
15
|
+
pinned last. Each persona shows its one-line description. "What's a
|
|
16
|
+
persona?" explains the idea, and Details shows the instructions, runtime,
|
|
17
|
+
model and version, permission mode and allowed commands. Then Runtime,
|
|
18
|
+
Workspace, an optional first instruction, and a collapsed Advanced
|
|
19
|
+
section: Model and Version, Permission mode, and Extra allowed commands,
|
|
20
|
+
with a preview of exactly what the runtime will be given.
|
|
21
|
+
- **First instruction.** Den types it into the agent's session once the
|
|
22
|
+
session is ready, as a bracketed paste when the runtime asks for one,
|
|
23
|
+
then Enter. It never goes on the command line and is never stored; the
|
|
24
|
+
audit log records its length only.
|
|
25
|
+
- **Your own personas.** + New persona, Duplicate and Edit write personas
|
|
26
|
+
of your own to `<state_dir>/personas/<id>.md` (0600). Settings ›
|
|
27
|
+
Personas lists every persona with the same search and chips: View or
|
|
28
|
+
Edit, Duplicate, Delete (your own only), + New persona. Built-in and
|
|
29
|
+
distribution personas are read-only; Duplicate makes your own copy.
|
|
30
|
+
Routes: `GET /api/personas/<id>`, `POST /api/personas`, `POST` and
|
|
31
|
+
`DELETE /api/personas/<id>`.
|
|
32
|
+
- **Models and versions.** A runtime lists its models (den-claude: opus,
|
|
33
|
+
sonnet and haiku, each "latest" or a pinned version, from a catalog
|
|
34
|
+
shipped with Den). A pinned version runs as `--model <full id>`. A
|
|
35
|
+
persona or session pinned to an older version says "newer available";
|
|
36
|
+
your own persona has Use latest.
|
|
37
|
+
- **Permissions as intent.** Ask for everything, Auto-approve edits in the
|
|
38
|
+
worktree, or Auto-approve all, plus extra allowed commands as plain
|
|
39
|
+
prefixes. den-claude turns them into `--permission-mode` and
|
|
40
|
+
`Bash(<prefix>)` / `Bash(<prefix> *)` allow rules beside Den's deny
|
|
41
|
+
rules. Other runtimes take none from Den yet, and the dialog says "not
|
|
42
|
+
supported on <runtime>" rather than dropping one.
|
|
43
|
+
- **Override tags.** A session started with choices that differ from its
|
|
44
|
+
persona is tagged in the list: "model: haiku", "pinned <version>",
|
|
45
|
+
"auto-approve edits", "custom tools", the runtime. "auto-approve all" is
|
|
46
|
+
always tagged. Start fresh and Relaunch keep the choices.
|
|
47
|
+
- **Roles and categories.** Den's roles are planner, orchestrator, sde,
|
|
48
|
+
reviewer, tester and designer. Each is a template (instructions,
|
|
49
|
+
permission mode, a read-only command preset) that fills in New persona
|
|
50
|
+
without overwriting what you typed, and it adds nothing hidden to a
|
|
51
|
+
prompt. A distribution adds roles and categories (`agents.roles`,
|
|
52
|
+
`agents.categories`) and may extend a Den role's preset, never redefine
|
|
53
|
+
it.
|
|
54
|
+
- **Plain session from the page.** `POST /api/workspaces/<ws>/sessions`
|
|
55
|
+
starts your login shell in the workspace's folder (capped by
|
|
56
|
+
`sessions.max_concurrent`).
|
|
57
|
+
- **A tester persona** in Den's default pack.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- `POST /api/workspaces/<ws>/agents` takes `version`, `permission`,
|
|
62
|
+
`allow` and `instruction`, and refuses an unknown field. Each is checked
|
|
63
|
+
against the runtime's catalog: a model or version it doesn't list, a
|
|
64
|
+
mode other than `ask`, `edits` or `all`, or a command that isn't a plain
|
|
65
|
+
prefix (at most 32, 120 characters each) is a 400. An agent's token gets
|
|
66
|
+
403 on every persona route and on a plain session.
|
|
67
|
+
- The agent record keeps `version`, `permission`, `allow` and `overrides`.
|
|
68
|
+
- A persona's `role` is a role key; a sentence there still reads as its
|
|
69
|
+
description. Personas gain `display_name`, `category`, `version` and
|
|
70
|
+
`allowed_commands`. `permission_mode` takes `ask`, `edits` or `all`
|
|
71
|
+
(Claude Code's own modes still read). `agents personas` shows SOURCE and
|
|
72
|
+
ROLE columns.
|
|
73
|
+
- fe-dev, be-dev and devops are role `sde`; ux-designer is role
|
|
74
|
+
`designer` in category Design.
|
|
75
|
+
- A persona whose runtime isn't installed starts on the first one that is,
|
|
76
|
+
and the Start dialog says so.
|
|
77
|
+
- **`sessionTitle` is the name a session shows.** `agents list --json`,
|
|
78
|
+
`GET /api/sessions` and the start and resume answers (MCP
|
|
79
|
+
`agent_resume`) now carry the resolved name (Den's rename or the
|
|
80
|
+
runtime's, the later) as `sessionTitle`. 0.4.12 called it `sessionName`,
|
|
81
|
+
which is the agent record's session handle (`<dist>-<ws>:<id>`) and has
|
|
82
|
+
been since 0.4.0. Records are unchanged.
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- **Settings shows a saved choice at once.** Agents tab layout and Open
|
|
87
|
+
file links in saved, but the pressed button stayed on the old value
|
|
88
|
+
until a reload. The page now takes every setting the save returns.
|
|
89
|
+
- **Claude Code's `/rename "x"` shows as x.** Its title keeps the quotes;
|
|
90
|
+
Den drops one pair around the whole name, for display only.
|
|
91
|
+
- **The terminal fit check is exact again.** The browser test compares
|
|
92
|
+
what is left below and beside a terminal with the cell size xterm drew
|
|
93
|
+
with (now on each pane as `data-cell-w` and `data-cell-h`), less than
|
|
94
|
+
one cell, so a missed row fails on any font.
|
|
95
|
+
|
|
96
|
+
### Migrations
|
|
97
|
+
|
|
98
|
+
- None required. A persona whose `role:` is a sentence keeps working (it
|
|
99
|
+
becomes the description); set `role:` to a role key to give it one.
|
|
100
|
+
- A persona pack may now set `display_name`, `category`, `version` and
|
|
101
|
+
`allowed_commands`. Use `permission_mode: ask | edits | all` for a mode
|
|
102
|
+
every runtime can translate.
|
|
103
|
+
- `agents.roles` and `agents.categories` are new keys of `agents:` in the
|
|
104
|
+
distribution's config (see docs/distributions.md, Personas).
|
|
105
|
+
- Read `sessionTitle`, not `sessionName`, for the name a session shows in
|
|
106
|
+
`agents list --json`, `GET /api/sessions` and the start and resume
|
|
107
|
+
answers. No record needs changing.
|
|
108
|
+
|
|
109
|
+
## [0.4.12] - 2026-11-07
|
|
110
|
+
|
|
111
|
+
### Added
|
|
112
|
+
|
|
113
|
+
- **Agents tab layouts.** A switch in the page header picks Focus, Tabs or
|
|
114
|
+
Tile, and so do ⌃⇧1, ⌃⇧2 and ⌃⇧3 (Ctrl+Shift+digit on every OS) and ⌘K
|
|
115
|
+
"Layout: Focus/Tabs/Tile". ⌘1-9 and Ctrl+1-9 stay the browser's, for its
|
|
116
|
+
tabs; inside a terminal the layout keys never reach the agent. Focus, the default, shows one pane: a
|
|
117
|
+
click replaces it, and Shift-click or ⌘-click adds one alongside. Tabs
|
|
118
|
+
shows one pane under a strip of tabs. Tile is a grid: one pane fills
|
|
119
|
+
it, two sit side by side, three or four make a 2×2. At phone width,
|
|
120
|
+
Focus and Tile show as Tabs. Your choice is saved for you
|
|
121
|
+
(`agents.layout` in `config/local/user.yml`, `agents_layout` in
|
|
122
|
+
`/api/settings`, and Settings › Agents), and the URL says it
|
|
123
|
+
(`&layout=`).
|
|
124
|
+
- **Named tabs.** Each tab shows the agent's name, then its persona and
|
|
125
|
+
workspace, and a dot when it is waiting. Double-click a tab to rename
|
|
126
|
+
the agent. The name is stored in its record (`displayName`) and shows in
|
|
127
|
+
the list, its pane, ⌘K, the Workspaces page and `agents list --json`.
|
|
128
|
+
Start fresh and Relaunch keep it. The id never changes. The route is
|
|
129
|
+
`POST /api/workspaces/<ws>/agents/<id>/name`.
|
|
130
|
+
- **Shift+Enter is a soft newline in agent terminals.** It sends the
|
|
131
|
+
runtime's `softNewline` from the runtime catalog, not Enter: ESC CR for
|
|
132
|
+
Claude Code and Gemini CLI (and by default), Ctrl+J for Codex CLI. A
|
|
133
|
+
plain session's Shift+Enter is still Enter. Catalog entries may set
|
|
134
|
+
`softNewline: esc-cr | lf | none`.
|
|
135
|
+
- **The session's name in the pane header.** Claude Code's `/rename` shows
|
|
136
|
+
after the runtime (` · api spike`), and in the tab, the list and ⌘K. Den
|
|
137
|
+
reads it from the session's transcript, following the file from where it
|
|
138
|
+
last stopped (the catalog marks this source `unofficial`; a name it
|
|
139
|
+
can't read is none). The most recent rename wins, Den's or the
|
|
140
|
+
runtime's. `GET /api/sessions`, `agents list --json` and the start and
|
|
141
|
+
resume answers carry it as `sessionName`. Catalog entries may set
|
|
142
|
+
`titleFromOsc` (no runtime does yet, and the session host doesn't read
|
|
143
|
+
titles yet).
|
|
144
|
+
- **Links and paths in agent panes are clickable.** ⌘-click (Ctrl-click on
|
|
145
|
+
Windows and Linux) opens a URL or an OSC 8 link in a new tab
|
|
146
|
+
(`noopener`), http and https only; a plain click still selects. A file
|
|
147
|
+
path inside the agent's workspace (`src/foo.ts:42`, absolute paths) is
|
|
148
|
+
a link: ⌘-click opens it in the editor set in Settings › Editor &
|
|
149
|
+
terminal › "Open file links in" (`editor_open`: vscode, cursor, zed, or
|
|
150
|
+
none to copy the path, the default). The server checks a path exists
|
|
151
|
+
inside the workspace with `GET /api/workspaces/<ws>/agents/<id>/paths`
|
|
152
|
+
(stat only). Den now ships `@xterm/addon-web-links` 0.12.0, served from
|
|
153
|
+
`/vendor/`.
|
|
154
|
+
|
|
155
|
+
### Changed
|
|
156
|
+
|
|
157
|
+
- **The Agents tab fills the window.** The list and the panes reach down to
|
|
158
|
+
14px above the runs dock at any window height, with the dock open or
|
|
159
|
+
shut. They used to stop at a fixed guess of the window less 300px.
|
|
160
|
+
- **Re-picking the workspace in view on a page with `?ws=`** (Workspaces'
|
|
161
|
+
detail pane) now updates `?ws=` before the reload, so the page shows the
|
|
162
|
+
pick and not the old workspace.
|
|
163
|
+
- **Every terminal fits its pane.** A terminal used to be sized for its
|
|
164
|
+
pane's padding as well, so its last row could be cut off. It now fits
|
|
165
|
+
the space inside the padding, in every layout, and refits when the
|
|
166
|
+
window or the dock changes size.
|
|
167
|
+
|
|
168
|
+
### Migrations
|
|
169
|
+
|
|
170
|
+
None. The record's new `displayName` and `displayNameAt` are optional:
|
|
171
|
+
records without them read as before. The layout pref is optional and
|
|
172
|
+
defaults to Focus; `editor_open` is optional and defaults to none. The
|
|
173
|
+
catalog's new `softNewline`, `sessionName` and `titleFromOsc` are
|
|
174
|
+
optional.
|
|
175
|
+
|
|
7
176
|
## [0.4.11] - 2026-11-07
|
|
8
177
|
|
|
9
178
|
### Changed
|
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.13/hero-dark.png">
|
|
14
|
+
<img src="docs/images/0.4.13/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.13/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.13/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.13/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.13/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.13/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
|
@@ -2054,10 +2054,25 @@ server scoped to its workspace.
|
|
|
2054
2054
|
unique in the distribution, so a second workspace's planner gets
|
|
2055
2055
|
`planner-<ws>` unless you give `--id`.
|
|
2056
2056
|
- The **agent record**, `<stateDir>/agents/<id>.json` (0600), holds its id,
|
|
2057
|
-
persona, runtime, model, workspace, cwd, session and times
|
|
2058
|
-
|
|
2057
|
+
persona, runtime, model, workspace, cwd, session and times, and what it was
|
|
2058
|
+
started with from the Start dialog: `version` (the model's, "latest" or
|
|
2059
|
+
pinned), `permission` (`ask`, `edits` or `all`), `allow` (extra allowed
|
|
2060
|
+
commands) and `overrides` (which of these differ from its persona). Start
|
|
2061
|
+
fresh and Relaunch keep them. It never holds the environment, the command
|
|
2062
|
+
line or a first instruction. `list` reads it for persona, runtime
|
|
2059
2063
|
and model; a record whose session has gone is listed as `stopped` (after
|
|
2060
2064
|
`stop`) or `ended`.
|
|
2065
|
+
- **Two name fields.** In the record, `sessionName` is the session's
|
|
2066
|
+
handle, `<dist>-<ws>:<id>` (what `sessions attach` takes); it has meant
|
|
2067
|
+
that since 0.4.0, and `displayName` is the name a person gave the agent.
|
|
2068
|
+
In `agents list --json`, `GET /api/sessions` and the start and resume
|
|
2069
|
+
answers (MCP `agent_resume`), `sessionTitle` is the name its session
|
|
2070
|
+
shows: the latest of Den's rename and the runtime's own (Claude Code's
|
|
2071
|
+
`/rename`, one pair of surrounding quotes dropped), absent when there is
|
|
2072
|
+
none. The handle there is `name` (`/api/sessions`) or `session` (the
|
|
2073
|
+
start and resume answers). 0.4.12 sent the name shown as `sessionName`;
|
|
2074
|
+
read `sessionTitle` instead. Records are unchanged, so none written
|
|
2075
|
+
before needs migrating.
|
|
2061
2076
|
- `list` shows agents only: sessions with an agent record. A session from
|
|
2062
2077
|
`sessions start` is not an agent; `sessions list` shows it.
|
|
2063
2078
|
- `stop` sends its session TERM and marks it stopped. `resume` works only
|
|
@@ -2104,13 +2119,78 @@ started. The workspace header counts them ("2 agents").
|
|
|
2104
2119
|
workspace, and **Open** on a row with a session opens its terminal there.
|
|
2105
2120
|
|
|
2106
2121
|
The routes: `GET /api/workspaces/<ws>/agents`, `POST
|
|
2107
|
-
/api/workspaces/<ws>/agents` (`{ persona, runtime?, model?,
|
|
2108
|
-
|
|
2109
|
-
|
|
2122
|
+
/api/workspaces/<ws>/agents` (`{ persona, runtime?, model?, version?,
|
|
2123
|
+
permission?, allow?, instruction?, id? }`, see
|
|
2124
|
+
[The Start dialog](#the-start-dialog)), `POST
|
|
2125
|
+
/api/workspaces/<ws>/agents/<id>/stop` and `…/resume`, `POST
|
|
2126
|
+
/api/workspaces/<ws>/sessions` (a plain session), `GET /api/personas`
|
|
2127
|
+
(personas, runtimes with their models and permission translation, roles
|
|
2128
|
+
and categories), and the persona routes under
|
|
2129
|
+
[Personas](#personas). Errors are the usual
|
|
2110
2130
|
`{ ok: false, error, code, requestId }`: `cap` (429),
|
|
2111
2131
|
`conflict` (409, e.g. already running, or no resume), `not_found` (404),
|
|
2112
2132
|
`exited_at_once` (502, with the session's last lines).
|
|
2113
2133
|
|
|
2134
|
+
### The Start dialog
|
|
2135
|
+
|
|
2136
|
+
**+ Start agent** on the Agents tab (or `/agents?start=1`) opens the Start
|
|
2137
|
+
dialog:
|
|
2138
|
+
|
|
2139
|
+
- **Persona**: a list you can search (name, role, description), with
|
|
2140
|
+
category chips (only the categories that have personas) and source chips
|
|
2141
|
+
(built-in, distribution, mine). With no filter set it is grouped by
|
|
2142
|
+
category. Each row is the persona's name ("Rita (reviewer)", else its
|
|
2143
|
+
id), its source badge and its one-line description, and "not installed"
|
|
2144
|
+
when its runtime isn't. **Plain session** is pinned last: your login
|
|
2145
|
+
shell in the workspace's folder, no agent. **What's a persona?** explains
|
|
2146
|
+
the idea; **Details** shows the selected persona's instructions, runtime,
|
|
2147
|
+
model and version, permission mode, role preset and extra commands.
|
|
2148
|
+
**+ New persona**, **Duplicate** and (for your own) **Edit** open the
|
|
2149
|
+
persona editor, and the dialog picks what you saved.
|
|
2150
|
+
- **Runtime** and **Workspace**. Installed runtimes come first; the rest
|
|
2151
|
+
are listed, disabled, and named under the field ("Not found on this
|
|
2152
|
+
machine: …"). A persona whose runtime isn't installed starts on the first
|
|
2153
|
+
one that is, and the dialog says so. The line under the runtime says what
|
|
2154
|
+
will run (see below).
|
|
2155
|
+
- **First instruction** (optional, up to 8000 characters): the agent's
|
|
2156
|
+
first message. Den types it into the agent's session once the session is
|
|
2157
|
+
ready (it has printed and gone quiet, or after 30 s), through the session
|
|
2158
|
+
host, as a bracketed paste when the runtime asked for one, then Enter. It
|
|
2159
|
+
is never a command-line argument and never stored; the audit log records
|
|
2160
|
+
its length only.
|
|
2161
|
+
- **Advanced** (collapsed): **Model** and **Version** (the runtime's own
|
|
2162
|
+
list; "Persona default" first), the **Permission mode** (Ask for
|
|
2163
|
+
everything, Auto-approve edits in the worktree, Auto-approve all), the
|
|
2164
|
+
role's command preset as chips, **Extra allowed commands** (plain command
|
|
2165
|
+
prefixes, one per line, no patterns), and a preview of exactly what the
|
|
2166
|
+
runtime will be given. A command a runtime can't take says "not supported
|
|
2167
|
+
on <runtime>", and a pattern says "use a plain prefix"; neither is ever
|
|
2168
|
+
dropped silently. "Edit raw runtime config…" is not there yet.
|
|
2169
|
+
|
|
2170
|
+
What you change in Advanced applies to that launch only, and the session's
|
|
2171
|
+
row is tagged: "model: haiku", "pinned claude-opus-4-8", "auto-approve
|
|
2172
|
+
edits", "custom tools", the runtime, and always "auto-approve all" for an
|
|
2173
|
+
agent started so. An agent pinned to an older version says "newer
|
|
2174
|
+
available".
|
|
2175
|
+
|
|
2176
|
+
Every field is checked on the server, not only in the page. The model and
|
|
2177
|
+
version must be ones the runtime lists, the permission one of `ask`,
|
|
2178
|
+
`edits` or `all`, and `allow` a list of at most 32 plain prefixes (letters,
|
|
2179
|
+
digits, single spaces and `. _ / : = @ % + , -`, at most 120 characters
|
|
2180
|
+
each). An unknown field is refused. A person's start only: an agent's
|
|
2181
|
+
token gets 403.
|
|
2182
|
+
|
|
2183
|
+
**How each runtime takes the choices.** den-claude (Claude Code): a model is
|
|
2184
|
+
`--model <alias>` (`opus`, `sonnet`, `haiku`), a pinned version is `--model
|
|
2185
|
+
<full id>`. The model list is a catalog shipped with each Den release,
|
|
2186
|
+
because `claude` can't list its models. The permission mode is
|
|
2187
|
+
`--permission-mode` (`default`, `acceptEdits`, `bypassPermissions`), and
|
|
2188
|
+
each command prefix becomes two allow rules, `Bash(<prefix>)` and
|
|
2189
|
+
`Bash(<prefix> *)`, in the agent's settings file beside Den's deny rules
|
|
2190
|
+
(deny wins). Codex CLI, Gemini CLI, Grok Build and generic take no model,
|
|
2191
|
+
mode or commands from Den yet: the dialog says "not supported on <runtime>"
|
|
2192
|
+
and the start is refused if you set one.
|
|
2193
|
+
|
|
2114
2194
|
### The Agents tab
|
|
2115
2195
|
|
|
2116
2196
|
"Agents" in the rail (Develop, after Graph) is `/agents`: every session and
|
|
@@ -2132,15 +2212,78 @@ agent of the workspaces in view, and their terminals in the browser.
|
|
|
2132
2212
|
it, or start it again.
|
|
2133
2213
|
- **A pane** is the session's terminal: it shows the scrollback, then the
|
|
2134
2214
|
live output, and your keys go to the session; its size follows the
|
|
2135
|
-
pane.
|
|
2136
|
-
|
|
2137
|
-
|
|
2215
|
+
pane. Up to four are open at once (a fifth closes the oldest). The URL
|
|
2216
|
+
holds the open panes (`/agents?open=<id>,<id>`), so a link, a reload or
|
|
2217
|
+
Back reopens them. The list and the panes fill the window down to the
|
|
2218
|
+
runs dock.
|
|
2219
|
+
- **Layouts.** The switch in the page header picks one, or ⌃⇧1, ⌃⇧2, ⌃⇧3
|
|
2220
|
+
(Ctrl+Shift+1/2/3 on every OS; ⌘1-9 and Ctrl+1-9 stay the browser's, for
|
|
2221
|
+
its tabs), or ⌘K "Layout: Focus", "Layout: Tabs", "Layout: Tile". Inside
|
|
2222
|
+
a terminal those keys switch the layout and never reach the agent:
|
|
2223
|
+
- **Focus** (the default) shows one pane. A click replaces it;
|
|
2224
|
+
Shift-click or ⌘-click adds one alongside.
|
|
2225
|
+
- **Tabs** shows one pane under a strip of tabs. Each tab has the
|
|
2226
|
+
agent's name, then its persona and workspace ("reviewer · north"), and
|
|
2227
|
+
a dot when it is waiting.
|
|
2228
|
+
- **Tile** shows the open panes in a grid: one fills it, two sit side
|
|
2229
|
+
by side, three or four make a 2×2.
|
|
2230
|
+
|
|
2231
|
+
At phone width, Focus and Tile show as Tabs. The choice is yours: it is
|
|
2232
|
+
saved in `config/local/user.yml` as `agents.layout` (`POST /api/settings
|
|
2233
|
+
{ agents_layout: "focus" | "tabs" | "tile" | null }`, also in Settings ›
|
|
2234
|
+
Agents). The URL says it too (`&layout=tabs`); a link's layout is used
|
|
2235
|
+
for that page and saves nothing.
|
|
2236
|
+
|
|
2237
|
+
- **Rename an agent** by double-clicking its tab: Enter saves, Esc keeps
|
|
2238
|
+
the old name. The name (trimmed, up to 40 characters, no control
|
|
2239
|
+
characters) is stored in the agent's record as `displayName`, and the
|
|
2240
|
+
list, its pane, ⌘K and `agents list --json` show it. The agent's id
|
|
2241
|
+
never changes. Clear the name to go back to the id. The route is `POST
|
|
2242
|
+
/api/workspaces/<ws>/agents/<id>/name` (`{ name: string | null }`), for
|
|
2243
|
+
a person only.
|
|
2138
2244
|
- **Close** (×) detaches, as leaving the page does: the session keeps
|
|
2139
2245
|
running, and opening it again replays what you missed. **Stop** (an
|
|
2140
2246
|
agent only) is `agents stop`. **+ Start agent** (or
|
|
2141
2247
|
`/agents?start=1`) starts one and opens its pane; under All it asks for
|
|
2142
2248
|
the workspace. It says before Start whether the runtime can run, as the
|
|
2143
2249
|
Workspaces page's form does. It never asks for the admin password.
|
|
2250
|
+
- **Shift+Enter** adds a soft newline instead of submitting. It sends the
|
|
2251
|
+
runtime's `softNewline` from the runtime catalog: `esc-cr` (ESC CR, the
|
|
2252
|
+
default; Claude Code and Gemini CLI), `lf` (Ctrl+J; Codex CLI) or `none`
|
|
2253
|
+
(Enter itself). A plain session's Shift+Enter is Enter. A catalog entry
|
|
2254
|
+
a distribution or `user.yml` adds may set `softNewline`.
|
|
2255
|
+
- **The session's name.** A runtime may name its conversation: Claude
|
|
2256
|
+
Code's `/rename` writes a `custom-title` record to its transcript, and
|
|
2257
|
+
Den reads the latest one (the catalog marks this source `unofficial`:
|
|
2258
|
+
the format is undocumented, so a name that can't be read is simply
|
|
2259
|
+
none). It reads on from where it last stopped, never the whole file at
|
|
2260
|
+
once. The most recent rename wins, Den's (a tab's) or the runtime's. The
|
|
2261
|
+
pane header adds ` · <name>` after the runtime, and the tab, the list and
|
|
2262
|
+
⌘K add it too, when it is not empty and says more than the agent's own
|
|
2263
|
+
name or persona. `GET /api/sessions`, `agents list --json` and the start
|
|
2264
|
+
and resume answers (MCP `agent_resume`) carry it as `sessionTitle`
|
|
2265
|
+
(0.4.12: `sessionName`, which is the record's session handle). A
|
|
2266
|
+
`/rename "api spike"` shows as api spike: one pair of quotes around the
|
|
2267
|
+
whole name is dropped, for display only.
|
|
2268
|
+
Codex CLI, Gemini CLI and Grok Build have no name of their own yet. A
|
|
2269
|
+
catalog entry's `titleFromOsc: true` marks a runtime whose terminal
|
|
2270
|
+
title (OSC 0/2) names the session; no built-in runtime sets it, and the
|
|
2271
|
+
session host doesn't read titles yet.
|
|
2272
|
+
- **Links.** A URL in a pane, and an OSC 8 hyperlink, open on ⌘-click
|
|
2273
|
+
(Ctrl-click on Windows and Linux) in a new tab, with `noopener`. Only
|
|
2274
|
+
`http:` and `https:` open; an OSC 8 link with any other scheme
|
|
2275
|
+
(`javascript:`, `file:`) is no link. A plain click selects text as
|
|
2276
|
+
ever. A file path an agent prints (`src/foo.ts:42`, `./a/b.js:3:7`, an
|
|
2277
|
+
absolute path) is a link when it exists inside the agent's workspace
|
|
2278
|
+
(its directory, or the agent's own folder), resolved against the
|
|
2279
|
+
agent's folder; one outside it never is. The server checks with `GET
|
|
2280
|
+
/api/workspaces/<ws>/agents/<id>/paths?p=<path>&p=…` (stat only, nothing
|
|
2281
|
+
is read; at most 64 at once), which answers each path's absolute path
|
|
2282
|
+
or null. ⌘-click opens it in the editor of Settings › Editor & terminal ›
|
|
2283
|
+
"Open file links in" (`editor_open` in `config/local/user.yml` and
|
|
2284
|
+
`/api/settings`: `vscode`, `cursor` or `zed`, by its URL scheme at the
|
|
2285
|
+
line), or, with `none` (the default), copies its absolute path and says
|
|
2286
|
+
"Copied".
|
|
2144
2287
|
- ⌘K lists each as "Agents › <ws> / <name>", and "Start agent…".
|
|
2145
2288
|
- Several browsers and `<cli> sessions attach` can attach to one session
|
|
2146
2289
|
at once and each sees every byte; the terminal's size is the last
|
|
@@ -2326,13 +2469,18 @@ a Claude Code agent file, read as it is, plus a few keys of Den's own:
|
|
|
2326
2469
|
```markdown
|
|
2327
2470
|
---
|
|
2328
2471
|
name: planner # the id (or `id:`; default: the file name)
|
|
2329
|
-
|
|
2472
|
+
display_name: Rita # optional; shown as "Rita (reviewer)"
|
|
2473
|
+
role: planner # a role (see Roles); a sentence here is the description
|
|
2474
|
+
category: engineering # see Categories; default: the role's
|
|
2475
|
+
description: Writes stories and their acceptance criteria. # one line
|
|
2330
2476
|
model: some-model # optional; `inherit` means none
|
|
2477
|
+
version: latest # or a pinned version of the model
|
|
2478
|
+
allowed_commands: [npm test] # extra plain prefixes allowed without asking
|
|
2331
2479
|
tools: Read, Grep # hints for the runtime; Den restricts nothing with them
|
|
2332
2480
|
standing: true # starts with its workspace (later slices)
|
|
2333
2481
|
context: [docs/specs, the design system] # what to read first
|
|
2334
2482
|
runtime: generic # default den-claude
|
|
2335
|
-
permission_mode:
|
|
2483
|
+
permission_mode: ask # ask, edits or all (Claude Code's own modes still read)
|
|
2336
2484
|
den_hooks: false # Den's guardrail hooks for this persona (see Guardrails)
|
|
2337
2485
|
den_instructions: false # Den's shared instructions for this persona
|
|
2338
2486
|
argv: ["some-cli", "--model", "{model}", "--mcp-config", "{mcp_config}"]
|
|
@@ -2342,8 +2490,9 @@ You are the planner. …
|
|
|
2342
2490
|
```
|
|
2343
2491
|
|
|
2344
2492
|
Den ships a default pack: `planner` and `orchestrator` (standing), `fe-dev`,
|
|
2345
|
-
`be-dev
|
|
2346
|
-
|
|
2493
|
+
`be-dev` and `devops` (role `sde`), `reviewer`, `tester` and `ux-designer`
|
|
2494
|
+
(role `designer`, category Design). Add your own packs, read in order after
|
|
2495
|
+
Den's; a persona with the same id replaces the earlier one:
|
|
2347
2496
|
|
|
2348
2497
|
```yaml
|
|
2349
2498
|
agents:
|
|
@@ -2356,6 +2505,53 @@ used without converting them. Files without front matter (a README) are
|
|
|
2356
2505
|
skipped; two files with one id in a pack, or a pack that isn't a directory,
|
|
2357
2506
|
are errors naming the file or the key.
|
|
2358
2507
|
|
|
2508
|
+
**Your own personas** live in `<stateDir>/personas/<id>.md` (0600), in the
|
|
2509
|
+
same format, read last: one wins over a pack's persona of the same id.
|
|
2510
|
+
Settings › Personas lists every persona with the Start dialog's search and
|
|
2511
|
+
chips: **View** (or **Edit** for your own), **Duplicate**, **Delete** (your
|
|
2512
|
+
own only) and **+ New persona**. A built-in or distribution persona is
|
|
2513
|
+
read-only; Duplicate makes your own copy to edit. "newer available" marks a
|
|
2514
|
+
persona pinned to an older version, with **Use latest** for your own. The
|
|
2515
|
+
routes: `GET /api/personas/<id>` (with its instructions), `POST
|
|
2516
|
+
/api/personas` (new), `POST /api/personas/<id>` (edit; 409 `read_only` for
|
|
2517
|
+
one that isn't yours) and `DELETE /api/personas/<id>`. Each field is
|
|
2518
|
+
checked: the role, category, runtime, model and version must be registered,
|
|
2519
|
+
the description one line of at most 200 characters, the name at most 40,
|
|
2520
|
+
the instructions at most 20000 characters with no control characters but
|
|
2521
|
+
newlines and tabs. An agent's token can't create, change or delete a
|
|
2522
|
+
persona (403); a person sets what agents may do.
|
|
2523
|
+
|
|
2524
|
+
**Roles.** A role is a template and a key: default instructions, a
|
|
2525
|
+
permission mode and a command preset (read-only commands, allowed without
|
|
2526
|
+
asking). Den's roles are `planner`, `orchestrator`, `sde`, `reviewer`,
|
|
2527
|
+
`tester` and `designer`. Picking a role in New persona fills the form in,
|
|
2528
|
+
never over text you typed; a role adds nothing hidden to an agent's prompt.
|
|
2529
|
+
A distribution adds roles, and may extend a Den role's preset but not
|
|
2530
|
+
redefine it:
|
|
2531
|
+
|
|
2532
|
+
```yaml
|
|
2533
|
+
agents:
|
|
2534
|
+
roles:
|
|
2535
|
+
copywriter:
|
|
2536
|
+
description: Drafts release notes and site copy.
|
|
2537
|
+
instructions: Draft copy in the house voice. Leave drafts for review.
|
|
2538
|
+
mode: ask # ask, edits or all
|
|
2539
|
+
preset: [vale]
|
|
2540
|
+
category: marketing
|
|
2541
|
+
reviewer:
|
|
2542
|
+
preset: [semgrep] # added to Den's preset
|
|
2543
|
+
```
|
|
2544
|
+
|
|
2545
|
+
**Categories** group personas in the Start list and Settings › Personas.
|
|
2546
|
+
Den's are Engineering, Design, Product, Marketing and Operations; a
|
|
2547
|
+
distribution adds more:
|
|
2548
|
+
|
|
2549
|
+
```yaml
|
|
2550
|
+
agents:
|
|
2551
|
+
categories:
|
|
2552
|
+
- { id: legal, label: Legal }
|
|
2553
|
+
```
|
|
2554
|
+
|
|
2359
2555
|
### Guardrails: hooks, shared instructions, hand-backs
|
|
2360
2556
|
|
|
2361
2557
|
Every persona in Den's default pack gets two things with no setup: Den's
|
|
@@ -2626,6 +2822,10 @@ agents:
|
|
|
2626
2822
|
dirs: [~/.local/bin, $AIDER_DIR, "{system}"]
|
|
2627
2823
|
launch:
|
|
2628
2824
|
argv: ["{bin}"] # "{bin}" first: the path found
|
|
2825
|
+
# Optional, how it behaves in an Agents tab pane (AG-4b):
|
|
2826
|
+
softNewline: esc-cr # what Shift+Enter sends: esc-cr (default), lf, none
|
|
2827
|
+
titleFromOsc: false # its terminal title (OSC 0/2) names the session
|
|
2828
|
+
# sessionName: official | unofficial (built-in adapters only)
|
|
2629
2829
|
```
|
|
2630
2830
|
|
|
2631
2831
|
**Where a runtime's CLI is found.** As your terminal would find it, not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monster-orange/den",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"description": "Den: a local multi-repo dev orchestrator, HTTPS edge and E2E harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"@playwright/test": "^1.61.1",
|
|
65
65
|
"@slack/webhook": "^7.0.2",
|
|
66
66
|
"@xterm/addon-fit": "0.11.0",
|
|
67
|
+
"@xterm/addon-web-links": "0.12.0",
|
|
67
68
|
"@xterm/xterm": "6.0.0",
|
|
68
69
|
"ajv": "^8.12.0",
|
|
69
70
|
"ajv-formats": "^2.1.1",
|
|
@@ -17,6 +17,18 @@
|
|
|
17
17
|
* /usr/local/bin and npm's global bin)
|
|
18
18
|
* launch argv: its interactive CLI, "{bin}" first (the path found)
|
|
19
19
|
*
|
|
20
|
+
* And, optional, how it behaves in a terminal pane (AG-4b):
|
|
21
|
+
*
|
|
22
|
+
* softNewline what Shift+Enter sends in an Agents tab pane: "esc-cr"
|
|
23
|
+
* (ESC CR, the default), "lf" (Ctrl+J) or "none" (Enter
|
|
24
|
+
* itself: Shift+Enter submits)
|
|
25
|
+
* sessionName where the runtime adapter reads a session's name from:
|
|
26
|
+
* "official" (a documented interface) or "unofficial" (an
|
|
27
|
+
* undocumented file format, read defensively). None: the
|
|
28
|
+
* runtime has no name of its own; Den's rename still works.
|
|
29
|
+
* titleFromOsc the terminal title (OSC 0/2) is a session name. Off by
|
|
30
|
+
* default: many CLIs put a spinner or the cwd there.
|
|
31
|
+
*
|
|
20
32
|
* Each built-in entry was checked against the vendor's own docs or
|
|
21
33
|
* installer (the URLs beside it). A vendor with no official CLI is left
|
|
22
34
|
* out (docs/distributions.md, Runtimes, says which, and why); nothing is
|
|
@@ -38,6 +50,19 @@ export interface CatalogIdentity {
|
|
|
38
50
|
realpath_under?: string[];
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
/** What Shift+Enter sends in a pane (AG-4b). */
|
|
54
|
+
export const SOFT_NEWLINES = ["esc-cr", "lf", "none"] as const;
|
|
55
|
+
export type SoftNewline = (typeof SOFT_NEWLINES)[number];
|
|
56
|
+
const SOFT_NEWLINE_SEQ: Record<SoftNewline, string> = {
|
|
57
|
+
"esc-cr": "\x1b\r",
|
|
58
|
+
lf: "\n",
|
|
59
|
+
// none: Shift+Enter is Enter.
|
|
60
|
+
none: "\r",
|
|
61
|
+
};
|
|
62
|
+
/** How stable a runtime's session-name source is (AG-4b). */
|
|
63
|
+
export const SESSION_NAME_SOURCES = ["official", "unofficial"] as const;
|
|
64
|
+
export type SessionNameSource = (typeof SESSION_NAME_SOURCES)[number];
|
|
65
|
+
|
|
41
66
|
export interface CatalogEntry {
|
|
42
67
|
id: string;
|
|
43
68
|
vendor: string;
|
|
@@ -46,6 +71,19 @@ export interface CatalogEntry {
|
|
|
46
71
|
version: { args: string[]; identity: CatalogIdentity };
|
|
47
72
|
dirs: string[];
|
|
48
73
|
launch: { argv: string[] };
|
|
74
|
+
/** What Shift+Enter sends (AG-4b); unset: "esc-cr". */
|
|
75
|
+
softNewline?: SoftNewline;
|
|
76
|
+
/** The adapter reads a session name, from a source this stable. */
|
|
77
|
+
sessionName?: SessionNameSource;
|
|
78
|
+
/** The terminal title (OSC 0/2) names the session. Unset: no. */
|
|
79
|
+
titleFromOsc?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The bytes Shift+Enter sends for an entry (ESC CR when it says none). */
|
|
83
|
+
export function softNewlineSeq(
|
|
84
|
+
e: Pick<CatalogEntry, "softNewline"> | undefined,
|
|
85
|
+
): string {
|
|
86
|
+
return SOFT_NEWLINE_SEQ[e?.softNewline ?? "esc-cr"];
|
|
49
87
|
}
|
|
50
88
|
|
|
51
89
|
/** The keys an entry takes (the validator refuses any other). */
|
|
@@ -57,6 +95,9 @@ const ENTRY_KEYS = [
|
|
|
57
95
|
"version",
|
|
58
96
|
"dirs",
|
|
59
97
|
"launch",
|
|
98
|
+
"softNewline",
|
|
99
|
+
"sessionName",
|
|
100
|
+
"titleFromOsc",
|
|
60
101
|
] as const;
|
|
61
102
|
|
|
62
103
|
/** Den's built-in catalog, in the order lists show them. */
|
|
@@ -80,6 +121,12 @@ export const BUILTIN_CATALOG: readonly CatalogEntry[] = [
|
|
|
80
121
|
},
|
|
81
122
|
dirs: ["~/.local/bin", "~/.claude/local", "{system}"],
|
|
82
123
|
launch: { argv: ["{bin}"] },
|
|
124
|
+
// https://code.claude.com/docs/en/terminal-config: Option+Enter (Meta,
|
|
125
|
+
// ESC CR) is a newline; /terminal-setup binds Shift+Enter to it.
|
|
126
|
+
softNewline: "esc-cr",
|
|
127
|
+
// `/rename` appends {"type":"custom-title","customTitle":…} to the
|
|
128
|
+
// session's transcript (<token>.jsonl). Undocumented: read defensively.
|
|
129
|
+
sessionName: "unofficial",
|
|
83
130
|
},
|
|
84
131
|
// Codex CLI. https://github.com/openai/codex: binary `codex`, npm
|
|
85
132
|
// @openai/codex, Homebrew cask codex. The standalone installer
|
|
@@ -98,6 +145,9 @@ export const BUILTIN_CATALOG: readonly CatalogEntry[] = [
|
|
|
98
145
|
},
|
|
99
146
|
dirs: ["$CODEX_INSTALL_DIR", "~/.local/bin", "{system}"],
|
|
100
147
|
launch: { argv: ["{bin}"] },
|
|
148
|
+
// The composer's insert_newline is Ctrl+J (codex-rs/tui keymap): a
|
|
149
|
+
// plain terminal's Shift+Enter and Alt+Enter submit (openai/codex#20580).
|
|
150
|
+
softNewline: "lf",
|
|
101
151
|
},
|
|
102
152
|
// Gemini CLI. https://github.com/google-gemini/gemini-cli: binary
|
|
103
153
|
// `gemini`, npm @google/gemini-cli, Homebrew formula gemini-cli;
|
|
@@ -115,6 +165,9 @@ export const BUILTIN_CATALOG: readonly CatalogEntry[] = [
|
|
|
115
165
|
},
|
|
116
166
|
dirs: ["{system}"],
|
|
117
167
|
launch: { argv: ["{bin}"] },
|
|
168
|
+
// https://geminicli.com/docs/reference/keyboard-shortcuts: Alt+Enter
|
|
169
|
+
// (ESC CR), Ctrl+J and Shift+Enter insert a newline.
|
|
170
|
+
softNewline: "esc-cr",
|
|
118
171
|
},
|
|
119
172
|
// Grok Build. https://docs.x.ai/build/overview: install with
|
|
120
173
|
// https://x.ai/cli/install.sh, run `grok` in a project directory. The
|
|
@@ -279,6 +332,29 @@ export function validateCatalogEntry(
|
|
|
279
332
|
throw new Error(
|
|
280
333
|
`${where}.launch.argv[0]: must be "{bin}" (the path Den found)`,
|
|
281
334
|
);
|
|
335
|
+
const traits: Pick<
|
|
336
|
+
CatalogEntry,
|
|
337
|
+
"softNewline" | "sessionName" | "titleFromOsc"
|
|
338
|
+
> = {};
|
|
339
|
+
if (raw.softNewline !== undefined) {
|
|
340
|
+
if (!(SOFT_NEWLINES as readonly unknown[]).includes(raw.softNewline))
|
|
341
|
+
throw new Error(
|
|
342
|
+
`${where}.softNewline: ${shown(raw.softNewline)} must be one of ${SOFT_NEWLINES.join(", ")}`,
|
|
343
|
+
);
|
|
344
|
+
traits.softNewline = raw.softNewline as SoftNewline;
|
|
345
|
+
}
|
|
346
|
+
if (raw.sessionName !== undefined) {
|
|
347
|
+
if (!(SESSION_NAME_SOURCES as readonly unknown[]).includes(raw.sessionName))
|
|
348
|
+
throw new Error(
|
|
349
|
+
`${where}.sessionName: ${shown(raw.sessionName)} must be one of ${SESSION_NAME_SOURCES.join(", ")}`,
|
|
350
|
+
);
|
|
351
|
+
traits.sessionName = raw.sessionName as SessionNameSource;
|
|
352
|
+
}
|
|
353
|
+
if (raw.titleFromOsc !== undefined) {
|
|
354
|
+
if (typeof raw.titleFromOsc !== "boolean")
|
|
355
|
+
throw new Error(`${where}.titleFromOsc: must be true or false`);
|
|
356
|
+
traits.titleFromOsc = raw.titleFromOsc;
|
|
357
|
+
}
|
|
282
358
|
return {
|
|
283
359
|
id,
|
|
284
360
|
vendor: text(`${where}.vendor`, raw.vendor),
|
|
@@ -287,6 +363,7 @@ export function validateCatalogEntry(
|
|
|
287
363
|
version: { args, identity },
|
|
288
364
|
dirs,
|
|
289
365
|
launch: { argv },
|
|
366
|
+
...traits,
|
|
290
367
|
};
|
|
291
368
|
}
|
|
292
369
|
|