@monster-orange/den 0.4.12 → 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 +102 -0
- package/README.md +7 -7
- package/docs/distributions.md +146 -10
- package/package.json +1 -1
- 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 +119 -13
- 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 +28 -0
- package/src/shared/agents/roles.ts +288 -0
- package/src/shared/agents/runtime.ts +41 -0
- package/src/shared/den-config.ts +20 -1
- package/src/ui/cli/agent-ops.ts +535 -17
- 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-routes.ts +260 -21
- package/src/ui/server/public/agent-start.js +3 -2
- package/src/ui/server/public/agents-page.js +499 -48
- package/src/ui/server/public/agents.html +58 -15
- package/src/ui/server/public/i18n/es.js +120 -0
- package/src/ui/server/public/i18n/ja.js +120 -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 +22 -0
- package/src/ui/server/public/settings.html +1 -0
- package/src/ui/server/public/shell.js +117 -0
- package/src/ui/server/public/topbar.js +2 -2
- package/src/ui/server/route-classes.ts +18 -0
- package/src/ui/server/session-routes.ts +21 -5
- package/src/ui/server/ui-assets.ts +3 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roles and persona categories (AG-10.5, AG-11): two small registries.
|
|
3
|
+
*
|
|
4
|
+
* A role is a template and a dispatch key. Den owns planner, orchestrator,
|
|
5
|
+
* sde, reviewer, tester and designer. Each has default instructions, a
|
|
6
|
+
* permission mode and a command preset (plain command prefixes the agent
|
|
7
|
+
* may run without asking). Picking a role in New persona fills the form in
|
|
8
|
+
* (never over text already there), and the role adds nothing hidden to an
|
|
9
|
+
* agent's prompt: what it receives is the persona's instructions.
|
|
10
|
+
*
|
|
11
|
+
* A distribution adds roles in its config (`agents.roles`), and may extend
|
|
12
|
+
* a Den role's preset there, never redefine it:
|
|
13
|
+
*
|
|
14
|
+
* agents:
|
|
15
|
+
* roles:
|
|
16
|
+
* copywriter: { description: …, instructions: …, mode: ask, preset: [], category: marketing }
|
|
17
|
+
* reviewer: { preset: [semgrep] } # adds to Den's preset only
|
|
18
|
+
*
|
|
19
|
+
* Categories group personas in the Start list and Settings › Personas.
|
|
20
|
+
* Den's are Engineering, Design, Product, Marketing and Operations; a
|
|
21
|
+
* distribution adds more (`agents.categories: [{ id, label }]`).
|
|
22
|
+
*/
|
|
23
|
+
import { checkCommands, PERMISSION_INTENTS } from "./permissions.js";
|
|
24
|
+
import type { PermissionIntent } from "./permissions.js";
|
|
25
|
+
|
|
26
|
+
/** Where a role or category came from (hub: later, once Den Hub exists). */
|
|
27
|
+
export type RegistrySource = "den" | "distribution" | "project" | "hub";
|
|
28
|
+
|
|
29
|
+
export interface RoleTemplate {
|
|
30
|
+
id: string;
|
|
31
|
+
source: RegistrySource;
|
|
32
|
+
/** One line: what a persona of this role does. */
|
|
33
|
+
description: string;
|
|
34
|
+
/** The starting instructions New persona fills in. */
|
|
35
|
+
instructions: string;
|
|
36
|
+
/** The starting permission mode. */
|
|
37
|
+
mode: PermissionIntent;
|
|
38
|
+
/** Plain command prefixes allowed without asking (AG-13.3). */
|
|
39
|
+
preset: string[];
|
|
40
|
+
/** The category a new persona of this role starts in. */
|
|
41
|
+
category: string;
|
|
42
|
+
/** Set when a distribution extended a Den role's preset. */
|
|
43
|
+
extendedBy?: RegistrySource;
|
|
44
|
+
/** The commands that extension added (shown apart from Den's). */
|
|
45
|
+
extension?: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface Category {
|
|
49
|
+
id: string;
|
|
50
|
+
label: string;
|
|
51
|
+
source: RegistrySource;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A role or category id: lowercase letters, digits and "-", 1-32 long. */
|
|
55
|
+
export const REGISTRY_ID_RE = /^[a-z][a-z0-9-]{0,31}$/;
|
|
56
|
+
|
|
57
|
+
/** Den's categories, in the order lists show them. */
|
|
58
|
+
export const DEN_CATEGORIES: readonly Category[] = [
|
|
59
|
+
{ id: "engineering", label: "Engineering", source: "den" },
|
|
60
|
+
{ id: "design", label: "Design", source: "den" },
|
|
61
|
+
{ id: "product", label: "Product", source: "den" },
|
|
62
|
+
{ id: "marketing", label: "Marketing", source: "den" },
|
|
63
|
+
{ id: "operations", label: "Operations", source: "den" },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Den's roles. The presets are read-only commands only: a preset is
|
|
68
|
+
* allowed without asking, so Den's defaults never let an agent change
|
|
69
|
+
* anything unasked.
|
|
70
|
+
*/
|
|
71
|
+
export const DEN_ROLES: readonly RoleTemplate[] = [
|
|
72
|
+
{
|
|
73
|
+
id: "planner",
|
|
74
|
+
source: "den",
|
|
75
|
+
description:
|
|
76
|
+
"Turns goals into small, ready stories with acceptance criteria.",
|
|
77
|
+
instructions:
|
|
78
|
+
"Read the workspace's specs and open work items. Split goals into stories small enough for one pull request, each with testable acceptance criteria. Hand ready stories to the orchestrator. Don't write code.",
|
|
79
|
+
mode: "ask",
|
|
80
|
+
preset: ["gh issue list", "gh issue view"],
|
|
81
|
+
category: "engineering",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "orchestrator",
|
|
85
|
+
source: "den",
|
|
86
|
+
description:
|
|
87
|
+
"Hands ready stories to other agents and follows them to done.",
|
|
88
|
+
instructions:
|
|
89
|
+
"Take the next ready story in priority order and start the persona that fits it, one agent per story. Respect the workspace's concurrency cap. Ask a person before anything irreversible; never approve or merge.",
|
|
90
|
+
mode: "ask",
|
|
91
|
+
preset: ["gh issue list", "gh pr view"],
|
|
92
|
+
category: "engineering",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "sde",
|
|
96
|
+
source: "den",
|
|
97
|
+
description: "Implements one story, test first, and opens a pull request.",
|
|
98
|
+
instructions:
|
|
99
|
+
"Take one story. Write a failing test for each acceptance criterion first, then make it pass. Keep the change small, run the repo's checks, and open a pull request that says what you verified.",
|
|
100
|
+
mode: "ask",
|
|
101
|
+
preset: ["git status", "git diff", "git log"],
|
|
102
|
+
category: "engineering",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "reviewer",
|
|
106
|
+
source: "den",
|
|
107
|
+
description:
|
|
108
|
+
"Reviews a pull request adversarially. Never approves or merges.",
|
|
109
|
+
instructions:
|
|
110
|
+
"Check the change against its story's acceptance criteria, then look for what wasn't tested: edge cases, failure paths, security. Verify each finding in the code and report them ranked by impact. Never approve, merge or push.",
|
|
111
|
+
mode: "ask",
|
|
112
|
+
preset: ["git diff", "git log", "gh pr diff", "gh pr view"],
|
|
113
|
+
category: "engineering",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "tester",
|
|
117
|
+
source: "den",
|
|
118
|
+
description: "Runs the test suites and reports failures with repro steps.",
|
|
119
|
+
instructions:
|
|
120
|
+
"Run the workspace's unit, integration and end-to-end suites. For each failure, write the exact repro and the smallest failing command. Don't fix code.",
|
|
121
|
+
mode: "ask",
|
|
122
|
+
preset: ["git status"],
|
|
123
|
+
category: "engineering",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "designer",
|
|
127
|
+
source: "den",
|
|
128
|
+
description: "Designs flows and screens with the design system.",
|
|
129
|
+
instructions:
|
|
130
|
+
"Start from the user's goal and the story's acceptance criteria. Compose from the design system's components, and describe every state (empty, loading, error, success) and narrow screens. Hand off a short spec the developers can build and test.",
|
|
131
|
+
mode: "ask",
|
|
132
|
+
preset: [],
|
|
133
|
+
category: "design",
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const isMapping = (v: unknown): v is Record<string, unknown> =>
|
|
138
|
+
v !== null && typeof v === "object" && !Array.isArray(v);
|
|
139
|
+
const shown = (v: unknown) =>
|
|
140
|
+
(JSON.stringify(v) ?? "undefined").replace(/[^\x20-\x7e]/g, "?").slice(0, 60);
|
|
141
|
+
|
|
142
|
+
function line(where: string, v: unknown, max: number): string {
|
|
143
|
+
if (
|
|
144
|
+
typeof v !== "string" ||
|
|
145
|
+
!v.trim() ||
|
|
146
|
+
[...v].length > max ||
|
|
147
|
+
/[\p{Cc}\p{Cf}]/u.test(v)
|
|
148
|
+
)
|
|
149
|
+
throw new Error(
|
|
150
|
+
`${where}: must be one line of 1-${max} characters, no control characters`,
|
|
151
|
+
);
|
|
152
|
+
return v.trim();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* `agents.categories` checked: a list of { id, label }. A Den category's
|
|
157
|
+
* id is refused (it can't be renamed). Anything else throws, naming it.
|
|
158
|
+
*/
|
|
159
|
+
export function checkCategoriesConfig(raw: unknown): Category[] {
|
|
160
|
+
if (raw === undefined || raw === null) return [];
|
|
161
|
+
if (!Array.isArray(raw))
|
|
162
|
+
throw new Error("agents.categories: must be a list of { id, label }");
|
|
163
|
+
const seen = new Set(DEN_CATEGORIES.map((c) => c.id));
|
|
164
|
+
return raw.map((x, i) => {
|
|
165
|
+
const where = `agents.categories[${i}]`;
|
|
166
|
+
if (!isMapping(x))
|
|
167
|
+
throw new Error(`${where}: must be a mapping { id, label }`);
|
|
168
|
+
for (const k of Object.keys(x))
|
|
169
|
+
if (k !== "id" && k !== "label")
|
|
170
|
+
throw new Error(`${where}.${k}: unknown key (takes id, label)`);
|
|
171
|
+
if (typeof x.id !== "string" || !REGISTRY_ID_RE.test(x.id))
|
|
172
|
+
throw new Error(
|
|
173
|
+
`${where}.id: ${shown(x.id)} must be lowercase letters, digits and "-" (1-32)`,
|
|
174
|
+
);
|
|
175
|
+
if (seen.has(x.id))
|
|
176
|
+
throw new Error(`${where}.id: "${x.id}" is already a category`);
|
|
177
|
+
seen.add(x.id);
|
|
178
|
+
return {
|
|
179
|
+
id: x.id,
|
|
180
|
+
label: line(`${where}.label`, x.label, 40),
|
|
181
|
+
source: "distribution" as const,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** What `agents.roles` holds, checked (merged with Den's by roleRegistry). */
|
|
187
|
+
export interface RolesConfig {
|
|
188
|
+
/** New roles. */
|
|
189
|
+
added: RoleTemplate[];
|
|
190
|
+
/** Den roles' preset extensions, by role id. */
|
|
191
|
+
extended: Record<string, string[]>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* `agents.roles` checked. A Den role takes `preset` only (an extension);
|
|
196
|
+
* a new role takes description, instructions, mode, preset and category.
|
|
197
|
+
* `categories` are the ids a new role's category may name.
|
|
198
|
+
*/
|
|
199
|
+
export function checkRolesConfig(
|
|
200
|
+
raw: unknown,
|
|
201
|
+
categories: readonly string[] = DEN_CATEGORIES.map((c) => c.id),
|
|
202
|
+
): RolesConfig {
|
|
203
|
+
const out: RolesConfig = { added: [], extended: {} };
|
|
204
|
+
if (raw === undefined || raw === null) return out;
|
|
205
|
+
if (!isMapping(raw))
|
|
206
|
+
throw new Error(
|
|
207
|
+
"agents.roles: must be a mapping of role id to its template",
|
|
208
|
+
);
|
|
209
|
+
for (const [id, v] of Object.entries(raw)) {
|
|
210
|
+
const where = `agents.roles.${id}`;
|
|
211
|
+
if (!REGISTRY_ID_RE.test(id))
|
|
212
|
+
throw new Error(
|
|
213
|
+
`${where}: a role id must be lowercase letters, digits and "-" (1-32)`,
|
|
214
|
+
);
|
|
215
|
+
if (!isMapping(v)) throw new Error(`${where}: must be a mapping`);
|
|
216
|
+
const den = DEN_ROLES.find((r) => r.id === id);
|
|
217
|
+
if (den) {
|
|
218
|
+
for (const k of Object.keys(v))
|
|
219
|
+
if (k !== "preset")
|
|
220
|
+
throw new Error(
|
|
221
|
+
`${where}.${k}: ${id} is Den's role: a distribution may only extend its preset`,
|
|
222
|
+
);
|
|
223
|
+
out.extended[id] = checkCommands(v.preset, `${where}.preset`);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const keys = ["description", "instructions", "mode", "preset", "category"];
|
|
227
|
+
for (const k of Object.keys(v))
|
|
228
|
+
if (!keys.includes(k))
|
|
229
|
+
throw new Error(
|
|
230
|
+
`${where}.${k}: unknown key (takes ${keys.join(", ")})`,
|
|
231
|
+
);
|
|
232
|
+
const mode = v.mode ?? "ask";
|
|
233
|
+
if (!(PERMISSION_INTENTS as readonly unknown[]).includes(mode))
|
|
234
|
+
throw new Error(
|
|
235
|
+
`${where}.mode: ${shown(mode)} must be one of ${PERMISSION_INTENTS.join(", ")}`,
|
|
236
|
+
);
|
|
237
|
+
const category = v.category ?? "engineering";
|
|
238
|
+
if (typeof category !== "string" || !categories.includes(category))
|
|
239
|
+
throw new Error(
|
|
240
|
+
`${where}.category: ${shown(category)} is not a category (${categories.join(", ")})`,
|
|
241
|
+
);
|
|
242
|
+
const instructions = v.instructions ?? "";
|
|
243
|
+
if (typeof instructions !== "string" || instructions.length > 4000)
|
|
244
|
+
throw new Error(
|
|
245
|
+
`${where}.instructions: must be text of up to 4000 characters`,
|
|
246
|
+
);
|
|
247
|
+
out.added.push({
|
|
248
|
+
id,
|
|
249
|
+
source: "distribution",
|
|
250
|
+
description: line(`${where}.description`, v.description, 200),
|
|
251
|
+
instructions: instructions.trim(),
|
|
252
|
+
mode: mode as PermissionIntent,
|
|
253
|
+
preset: checkCommands(v.preset, `${where}.preset`),
|
|
254
|
+
category,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Den's roles with a distribution's extensions, then its new roles. */
|
|
261
|
+
export function roleRegistry(
|
|
262
|
+
cfg: RolesConfig = { added: [], extended: {} },
|
|
263
|
+
): RoleTemplate[] {
|
|
264
|
+
const den = DEN_ROLES.map((r) => {
|
|
265
|
+
const ext = cfg.extended[r.id];
|
|
266
|
+
if (!ext?.length) return { ...r, preset: [...r.preset] };
|
|
267
|
+
return {
|
|
268
|
+
...r,
|
|
269
|
+
preset: [...new Set([...r.preset, ...ext])],
|
|
270
|
+
extendedBy: "distribution" as const,
|
|
271
|
+
extension: [...ext],
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
return [...den, ...cfg.added];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Den's categories, then a distribution's. */
|
|
278
|
+
export function categoryRegistry(extra: readonly Category[] = []): Category[] {
|
|
279
|
+
return [...DEN_CATEGORIES, ...extra];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** A role's command preset ([] for none, or a role that isn't registered). */
|
|
283
|
+
export function rolePreset(
|
|
284
|
+
roles: readonly RoleTemplate[],
|
|
285
|
+
role: string | undefined,
|
|
286
|
+
): string[] {
|
|
287
|
+
return role ? (roles.find((r) => r.id === role)?.preset ?? []) : [];
|
|
288
|
+
}
|
|
@@ -20,6 +20,7 @@ import type { CatalogEntry } from "./catalog.js";
|
|
|
20
20
|
import type { AgentRecord } from "./records.js";
|
|
21
21
|
import type { Persona } from "./personas.js";
|
|
22
22
|
import type { BinSource, RuntimeFind } from "./runtime-search.js";
|
|
23
|
+
import type { PermissionIntent } from "./permissions.js";
|
|
23
24
|
|
|
24
25
|
export const RUNTIME_KIND = "runtime";
|
|
25
26
|
|
|
@@ -95,6 +96,16 @@ export interface LaunchInput {
|
|
|
95
96
|
* session rotates (0: never). Den's hooks count them. Default 3.
|
|
96
97
|
*/
|
|
97
98
|
rotateAfter?: number;
|
|
99
|
+
/**
|
|
100
|
+
* The permission intent to launch with (AG-13): the agent's, else its
|
|
101
|
+
* persona's `permission_mode`. Only a runtime with `permissions` takes it.
|
|
102
|
+
*/
|
|
103
|
+
permission?: PermissionIntent;
|
|
104
|
+
/**
|
|
105
|
+
* Plain command prefixes allowed without asking (AG-13.3, 13.4): the
|
|
106
|
+
* role's preset and the extra commands, checked (permissions.ts).
|
|
107
|
+
*/
|
|
108
|
+
allow?: string[];
|
|
98
109
|
/**
|
|
99
110
|
* The new conversation's first prompt (a relaunch from handoff.md).
|
|
100
111
|
* Only a runtime with `firstPrompt: true` is given one.
|
|
@@ -116,6 +127,29 @@ export interface LaunchOutput {
|
|
|
116
127
|
|
|
117
128
|
export type ResumeInput = LaunchInput;
|
|
118
129
|
|
|
130
|
+
/**
|
|
131
|
+
* A model a runtime offers (AG-12.3): its id as the Model list shows it,
|
|
132
|
+
* and the versions it can be pinned to, newest first. "Latest" tracks the
|
|
133
|
+
* newest release; a pinned version is one of `versions`.
|
|
134
|
+
*/
|
|
135
|
+
export interface RuntimeModel {
|
|
136
|
+
id: string;
|
|
137
|
+
label: string;
|
|
138
|
+
versions: string[];
|
|
139
|
+
latest: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* How a runtime expresses Den's permission intents (AG-13.2), as its own
|
|
144
|
+
* launch config: a read-only preview of each mode ("What this allows on
|
|
145
|
+
* <runtime>"), and how one plain command prefix becomes its rules
|
|
146
|
+
* (`{c}` the prefix). The runtime's launch writes exactly these.
|
|
147
|
+
*/
|
|
148
|
+
export interface RuntimePermissions {
|
|
149
|
+
modes: Record<PermissionIntent, string>;
|
|
150
|
+
commandRules: string[];
|
|
151
|
+
}
|
|
152
|
+
|
|
119
153
|
export interface Runtime {
|
|
120
154
|
/** "den-claude", "generic". */
|
|
121
155
|
id: string;
|
|
@@ -142,6 +176,13 @@ export interface Runtime {
|
|
|
142
176
|
* firstPrompt): Relaunch from handoff needs it (den#4).
|
|
143
177
|
*/
|
|
144
178
|
firstPrompt?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* The models it offers (AG-12.3), from the catalog shipped with each Den
|
|
181
|
+
* release; none: it takes no model choice from Den.
|
|
182
|
+
*/
|
|
183
|
+
models?(): RuntimeModel[];
|
|
184
|
+
/** How it expresses permission intents (AG-13); none: it can't yet. */
|
|
185
|
+
permissions?: RuntimePermissions;
|
|
145
186
|
/** Throws LaunchError when this persona or this machine can't run it. */
|
|
146
187
|
launch(input: LaunchInput): LaunchOutput;
|
|
147
188
|
/** A new run that continues the last one; null when there is nothing to resume. */
|
package/src/shared/den-config.ts
CHANGED
|
@@ -21,6 +21,13 @@ import { ConfigError, isConfigError } from "./config-error.js";
|
|
|
21
21
|
import { validateCatalog, type CatalogEntry } from "./agents/catalog.js";
|
|
22
22
|
import type { Guardrails } from "./agents/guardrails.js";
|
|
23
23
|
import { DEFAULT_ROTATE, type RotateSettings } from "./agents/rotation.js";
|
|
24
|
+
import {
|
|
25
|
+
checkCategoriesConfig,
|
|
26
|
+
checkRolesConfig,
|
|
27
|
+
DEN_CATEGORIES,
|
|
28
|
+
type Category,
|
|
29
|
+
type RolesConfig,
|
|
30
|
+
} from "./agents/roles.js";
|
|
24
31
|
import type { TestAccountsConfig } from "./auth/test-accounts.js";
|
|
25
32
|
|
|
26
33
|
import { product } from "./product.js";
|
|
@@ -918,6 +925,9 @@ export const AGENTS_RESERVED_KEYS = [
|
|
|
918
925
|
"runtime_catalog",
|
|
919
926
|
"default_pack",
|
|
920
927
|
"rotate",
|
|
928
|
+
// AG-10.5, AG-11.1: the distribution's persona categories and roles.
|
|
929
|
+
"categories",
|
|
930
|
+
"roles",
|
|
921
931
|
];
|
|
922
932
|
|
|
923
933
|
/** One agent's own block, `agents.<id>`. */
|
|
@@ -941,6 +951,10 @@ export interface ResolvedAgentsConfig {
|
|
|
941
951
|
runtimeCatalog: CatalogEntry[];
|
|
942
952
|
/** agents.rotate with its defaults (den#4); unset: DEFAULT_ROTATE. */
|
|
943
953
|
rotate?: RotateSettings;
|
|
954
|
+
/** agents.categories (AG-10.5): the distribution's, after Den's. */
|
|
955
|
+
categories?: Category[];
|
|
956
|
+
/** agents.roles (AG-11.1): new roles and Den roles' preset extensions. */
|
|
957
|
+
roles?: RolesConfig;
|
|
944
958
|
}
|
|
945
959
|
|
|
946
960
|
/** `agents.rotate` checked; any other key, or a bad value, throws naming it. */
|
|
@@ -1034,6 +1048,11 @@ export function readAgentsConfig(
|
|
|
1034
1048
|
),
|
|
1035
1049
|
rotate: rotateBlock(raw.rotate),
|
|
1036
1050
|
};
|
|
1051
|
+
out.categories = checkCategoriesConfig(raw.categories);
|
|
1052
|
+
out.roles = checkRolesConfig(raw.roles, [
|
|
1053
|
+
...DEN_CATEGORIES.map((c) => c.id),
|
|
1054
|
+
...out.categories.map((c) => c.id),
|
|
1055
|
+
]);
|
|
1037
1056
|
const max = raw.max_concurrent;
|
|
1038
1057
|
if (max !== undefined) {
|
|
1039
1058
|
if (typeof max !== "number" || !Number.isInteger(max) || max < 1)
|
|
@@ -1066,7 +1085,7 @@ export function readAgentsConfig(
|
|
|
1066
1085
|
const where = `agents.${id}`;
|
|
1067
1086
|
if (!AGENT_KEY_RE.test(id) || !isMapping(block))
|
|
1068
1087
|
throw new Error(
|
|
1069
|
-
`${where}: unknown key (agents: takes max_concurrent, persona_packs, runtime_catalog, default_pack, rotate, ` +
|
|
1088
|
+
`${where}: unknown key (agents: takes max_concurrent, persona_packs, runtime_catalog, default_pack, rotate, categories, roles, ` +
|
|
1070
1089
|
`or an agent id mapping to { grants: [...] })`,
|
|
1071
1090
|
);
|
|
1072
1091
|
for (const k of Object.keys(block))
|