@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
|
@@ -410,13 +410,23 @@ async function listPersonas(
|
|
|
410
410
|
return 0;
|
|
411
411
|
}
|
|
412
412
|
for (const l of columns([
|
|
413
|
-
[
|
|
413
|
+
[
|
|
414
|
+
"PERSONA",
|
|
415
|
+
"SOURCE",
|
|
416
|
+
"STANDING",
|
|
417
|
+
"RUNTIME",
|
|
418
|
+
"MODEL",
|
|
419
|
+
"ROLE",
|
|
420
|
+
"DESCRIPTION",
|
|
421
|
+
],
|
|
414
422
|
...all.map((x) => [
|
|
415
423
|
x.id,
|
|
424
|
+
x.origin,
|
|
416
425
|
x.standing ? "yes" : "-",
|
|
417
426
|
x.runtime ?? DEFAULT_RUNTIME_ID,
|
|
418
427
|
x.model ?? "-",
|
|
419
|
-
x.role,
|
|
428
|
+
x.role ?? "-",
|
|
429
|
+
x.description,
|
|
420
430
|
]),
|
|
421
431
|
]))
|
|
422
432
|
d.out(l);
|
package/src/ui/cli/env-probe.ts
CHANGED
|
@@ -112,7 +112,10 @@ function probeInput(d: ProbeDeps, dir: string): LaunchInput {
|
|
|
112
112
|
return {
|
|
113
113
|
persona: {
|
|
114
114
|
id: PROBE_ID,
|
|
115
|
-
|
|
115
|
+
description: "",
|
|
116
|
+
category: "engineering",
|
|
117
|
+
allowedCommands: [],
|
|
118
|
+
origin: "builtin",
|
|
116
119
|
prompt: "",
|
|
117
120
|
standing: false,
|
|
118
121
|
tools: [],
|
package/src/ui/mcp/tools.ts
CHANGED
|
@@ -740,7 +740,7 @@ const agentResumeTool: DenTool = {
|
|
|
740
740
|
name: "agent_resume",
|
|
741
741
|
title: "Resume an agent",
|
|
742
742
|
description:
|
|
743
|
-
"Resume a stopped or interrupted agent in the same conversation, persona, workspace and folder. Never starts a new conversation: an agent whose runtime can't resume is refused. Never the caller itself.",
|
|
743
|
+
"Resume a stopped or interrupted agent in the same conversation, persona, workspace and folder. Never starts a new conversation: an agent whose runtime can't resume is refused. Never the caller itself. Answers with agent: its id, persona, runtime, workspace, cwd, session (the session handle, <dist>-<ws>:<id>), sessionId, displayName (a person's name for it) and sessionTitle (the name its session shows: Den's rename or the runtime's, the later), when set.",
|
|
744
744
|
action: true,
|
|
745
745
|
shape: {
|
|
746
746
|
agent: z.string().describe("The agent's id"),
|
|
@@ -12,8 +12,26 @@
|
|
|
12
12
|
* runs its version command and
|
|
13
13
|
* refuses a path that isn't
|
|
14
14
|
* that runtime (AG-0.5)
|
|
15
|
+
* GET /api/personas/<id> one persona with its
|
|
16
|
+
* instructions (Details)
|
|
17
|
+
* POST /api/personas New persona (AG-10.7): a
|
|
18
|
+
* local one; a person only
|
|
19
|
+
* POST /api/personas/<id> Edit: a local one only
|
|
20
|
+
* (409 read_only otherwise)
|
|
21
|
+
* DELETE /api/personas/<id> Delete: a local one only
|
|
15
22
|
* GET /api/workspaces/<ws>/agents the workspace's agents
|
|
16
|
-
* POST /api/workspaces/<ws>/agents start { persona, runtime?,
|
|
23
|
+
* POST /api/workspaces/<ws>/agents start { persona, runtime?,
|
|
24
|
+
* model?, version?,
|
|
25
|
+
* permission?, allow?,
|
|
26
|
+
* instruction?, id? }
|
|
27
|
+
* (AG-10.1): each checked
|
|
28
|
+
* against the catalog; the
|
|
29
|
+
* instruction is typed into
|
|
30
|
+
* the session once it is
|
|
31
|
+
* ready, never an argument
|
|
32
|
+
* POST /api/workspaces/<ws>/sessions a plain session: the login
|
|
33
|
+
* shell in the workspace's
|
|
34
|
+
* folder (AG-10.1)
|
|
17
35
|
* POST /api/workspaces/<ws>/agents/<id>/stop
|
|
18
36
|
* POST /api/workspaces/<ws>/agents/<id>/resume the same conversation (AG-14)
|
|
19
37
|
* POST /api/workspaces/<ws>/agents/<id>/fresh a new conversation, same
|
|
@@ -75,8 +93,16 @@ import { HostError } from "../../shared/sessions/client.js";
|
|
|
75
93
|
import {
|
|
76
94
|
AgentError,
|
|
77
95
|
agentRecord,
|
|
96
|
+
checkInstruction,
|
|
97
|
+
createPersona,
|
|
98
|
+
deletePersona,
|
|
78
99
|
listAgents,
|
|
79
100
|
listPersonas,
|
|
101
|
+
personaDetail,
|
|
102
|
+
registries,
|
|
103
|
+
sendFirstInstruction,
|
|
104
|
+
startPlainSession,
|
|
105
|
+
updatePersona,
|
|
80
106
|
listRuntimes,
|
|
81
107
|
markInterrupted,
|
|
82
108
|
NOT_IN_WORKSPACE,
|
|
@@ -112,14 +138,18 @@ import type { Actor } from "../../shared/agent-actor.js";
|
|
|
112
138
|
export interface AgentRouteDeps {
|
|
113
139
|
/** The operations' deps (tests: an in-process host, a temp state dir). */
|
|
114
140
|
agents?: AgentsDeps;
|
|
141
|
+
/** When a first instruction counts its session ready (tests: shorter). */
|
|
142
|
+
instruction?: { quietMs?: number; maxMs?: number };
|
|
115
143
|
}
|
|
116
144
|
|
|
117
145
|
const AGENTS_PATH =
|
|
118
146
|
/^\/api\/workspaces\/([^/]+)\/agents(?:\/([^/]+)\/(stop|resume|fresh|relaunch|remove|name|paths))?$/;
|
|
119
147
|
|
|
120
148
|
const RUNTIME_PATH = /^\/api\/agents\/runtimes\/([^/]+)\/path$/;
|
|
121
|
-
|
|
122
|
-
const
|
|
149
|
+
const PERSONA_PATH = /^\/api\/personas\/([^/]+)$/;
|
|
150
|
+
const PLAIN_PATH = /^\/api\/workspaces\/([^/]+)\/sessions$/;
|
|
151
|
+
/** A start's body: a few names, commands and a first instruction. */
|
|
152
|
+
const MAX_BODY = 32 * 1024;
|
|
123
153
|
/** A list of names (a page's workspaces, an Undo's agents). */
|
|
124
154
|
const MANY_BODY = 64 * 1024;
|
|
125
155
|
const NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
@@ -174,12 +204,12 @@ function sendError(res: http.ServerResponse, e: unknown, ws?: string): void {
|
|
|
174
204
|
throw e;
|
|
175
205
|
}
|
|
176
206
|
|
|
177
|
-
function
|
|
207
|
+
function sessionTitleOf(
|
|
178
208
|
rec: Started["rec"],
|
|
179
209
|
d: AgentsResolved,
|
|
180
|
-
): {
|
|
210
|
+
): { sessionTitle?: string } {
|
|
181
211
|
const n = agentSessionName(rec, d.stateDir());
|
|
182
|
-
return n ? {
|
|
212
|
+
return n ? { sessionTitle: n } : {};
|
|
183
213
|
}
|
|
184
214
|
|
|
185
215
|
/** A started or resumed agent's answer: its record, or its early exit. */
|
|
@@ -187,6 +217,7 @@ function sendStarted(
|
|
|
187
217
|
res: http.ServerResponse,
|
|
188
218
|
s: Started & { fresh?: boolean },
|
|
189
219
|
d: AgentsResolved,
|
|
220
|
+
extra: Record<string, unknown> = {},
|
|
190
221
|
): void {
|
|
191
222
|
if (s.early) {
|
|
192
223
|
const code =
|
|
@@ -217,21 +248,47 @@ function sendStarted(
|
|
|
217
248
|
// The host's id: the Agents tab opens its pane by it (?open=<id>).
|
|
218
249
|
sessionId: s.info.ref.id,
|
|
219
250
|
startedAt: s.rec.startedAt,
|
|
251
|
+
...(s.rec.model ? { model: s.rec.model } : {}),
|
|
252
|
+
...(s.rec.version ? { version: s.rec.version } : {}),
|
|
253
|
+
...(s.rec.permission ? { permission: s.rec.permission } : {}),
|
|
254
|
+
...(s.rec.overrides?.length ? { overrides: s.rec.overrides } : {}),
|
|
220
255
|
...(s.rec.displayName ? { displayName: s.rec.displayName } : {}),
|
|
221
|
-
// AG-4b: its session
|
|
222
|
-
// lists show it (MCP agent_resume answers with this).
|
|
223
|
-
|
|
256
|
+
// AG-4b: the name its session shows (Den's rename or the runtime's),
|
|
257
|
+
// as the lists show it (MCP agent_resume answers with this). Its
|
|
258
|
+
// handle is `session`.
|
|
259
|
+
...sessionTitleOf(s.rec, d),
|
|
224
260
|
},
|
|
225
261
|
// A resume with nothing to continue yet started fresh (same id).
|
|
226
262
|
...(s.fresh ? { fresh: true } : {}),
|
|
227
263
|
...(s.warnings.length ? { warnings: s.warnings } : {}),
|
|
264
|
+
...extra,
|
|
228
265
|
});
|
|
229
266
|
}
|
|
230
267
|
|
|
231
|
-
/** What a
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
268
|
+
/** What a start's body names: optional strings, a command list, an instruction. */
|
|
269
|
+
interface StartBody {
|
|
270
|
+
persona?: string;
|
|
271
|
+
runtime?: string;
|
|
272
|
+
model?: string;
|
|
273
|
+
id?: string;
|
|
274
|
+
version?: string;
|
|
275
|
+
permission?: string;
|
|
276
|
+
allow?: unknown;
|
|
277
|
+
instruction?: unknown;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const START_KEYS = [
|
|
281
|
+
"persona",
|
|
282
|
+
"runtime",
|
|
283
|
+
"model",
|
|
284
|
+
"id",
|
|
285
|
+
"version",
|
|
286
|
+
"permission",
|
|
287
|
+
"allow",
|
|
288
|
+
"instruction",
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
async function startBody(req: http.IncomingMessage): Promise<StartBody> {
|
|
235
292
|
const raw = await readBody(req).catch(() => "");
|
|
236
293
|
if (raw.length > MAX_BODY)
|
|
237
294
|
throw new AgentError("request too large", { status: 413, usage: true });
|
|
@@ -243,15 +300,38 @@ async function startBody(
|
|
|
243
300
|
}
|
|
244
301
|
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
245
302
|
throw new AgentError("the body must be a JSON object", { usage: true });
|
|
246
|
-
const
|
|
247
|
-
for (const k of
|
|
248
|
-
|
|
303
|
+
const o = v as Record<string, unknown>;
|
|
304
|
+
for (const k of Object.keys(o))
|
|
305
|
+
if (!START_KEYS.includes(k))
|
|
306
|
+
throw new AgentError(
|
|
307
|
+
`${k}: unknown field (a start takes ${START_KEYS.join(", ")})`,
|
|
308
|
+
{ usage: true },
|
|
309
|
+
);
|
|
310
|
+
const out: Record<string, unknown> = {};
|
|
311
|
+
for (const k of [
|
|
312
|
+
"persona",
|
|
313
|
+
"runtime",
|
|
314
|
+
"model",
|
|
315
|
+
"id",
|
|
316
|
+
"version",
|
|
317
|
+
"permission",
|
|
318
|
+
]) {
|
|
319
|
+
const x = o[k];
|
|
249
320
|
if (x === undefined || x === null || x === "") continue;
|
|
250
321
|
if (typeof x !== "string")
|
|
251
322
|
throw new AgentError(`${k} must be a string`, { usage: true });
|
|
323
|
+
if (x.length > 128 || /[^\x21-\x7e]/.test(x))
|
|
324
|
+
throw new AgentError(
|
|
325
|
+
`${k} must be a short name (no spaces or control characters)`,
|
|
326
|
+
{
|
|
327
|
+
usage: true,
|
|
328
|
+
},
|
|
329
|
+
);
|
|
252
330
|
out[k] = x;
|
|
253
331
|
}
|
|
254
|
-
|
|
332
|
+
if (o.allow !== undefined && o.allow !== null) out.allow = o.allow;
|
|
333
|
+
if (o.instruction !== undefined) out.instruction = o.instruction;
|
|
334
|
+
return out as StartBody;
|
|
255
335
|
}
|
|
256
336
|
|
|
257
337
|
/**
|
|
@@ -267,6 +347,8 @@ export async function handleAgentRoutes(
|
|
|
267
347
|
): Promise<boolean> {
|
|
268
348
|
const m = AGENTS_PATH.exec(pathname);
|
|
269
349
|
const rp = RUNTIME_PATH.exec(pathname);
|
|
350
|
+
const pp = PERSONA_PATH.exec(pathname);
|
|
351
|
+
const plain = PLAIN_PATH.exec(pathname);
|
|
270
352
|
// AG-14's routes over many agents at once.
|
|
271
353
|
const many =
|
|
272
354
|
pathname === "/api/agents/clear-stopped" ||
|
|
@@ -277,22 +359,34 @@ export async function handleAgentRoutes(
|
|
|
277
359
|
pathname !== "/api/agents/runtimes" &&
|
|
278
360
|
!m &&
|
|
279
361
|
!rp &&
|
|
362
|
+
!pp &&
|
|
363
|
+
!plain &&
|
|
280
364
|
!many
|
|
281
365
|
)
|
|
282
366
|
return false;
|
|
283
367
|
const method = req.method ?? "GET";
|
|
284
368
|
const d = webDeps(deps.agents);
|
|
285
369
|
|
|
370
|
+
if (pp) return personaRoute(req, res, safeDecode(pp[1]), d);
|
|
371
|
+
if (plain) return plainSession(req, res, safeDecode(plain[1]), d);
|
|
372
|
+
if (pathname === "/api/personas" && method === "POST")
|
|
373
|
+
return personaRoute(req, res, null, d);
|
|
374
|
+
|
|
286
375
|
if (rp) return runtimePath(req, res, safeDecode(rp[1]), d);
|
|
287
376
|
if (many) return manyAgents(req, res, pathname, d);
|
|
288
377
|
if (!m) {
|
|
289
378
|
if (method !== "GET" && method !== "HEAD")
|
|
290
|
-
return methodNotAllowed(
|
|
379
|
+
return methodNotAllowed(
|
|
380
|
+
res,
|
|
381
|
+
pathname === "/api/personas" ? "GET, POST" : "GET",
|
|
382
|
+
);
|
|
291
383
|
try {
|
|
292
384
|
const ws = queryWorkspace(req, d);
|
|
293
385
|
const runtimes = await listRuntimes(d, ws);
|
|
294
386
|
sendJson(res, {
|
|
295
|
-
...(pathname === "/api/personas"
|
|
387
|
+
...(pathname === "/api/personas"
|
|
388
|
+
? { personas: listPersonas(d), ...registries(d) }
|
|
389
|
+
: {}),
|
|
296
390
|
runtimes,
|
|
297
391
|
...(ws ? { workspace: ws } : {}),
|
|
298
392
|
// AG-0.2: the file a person edits to set a runtime's path.
|
|
@@ -403,16 +497,54 @@ export async function handleAgentRoutes(
|
|
|
403
497
|
if (!body.persona)
|
|
404
498
|
throw new AgentError("persona is required", { usage: true });
|
|
405
499
|
what = `start ${body.id ?? body.persona} in ${ws}`;
|
|
500
|
+
const instruction = checkInstruction(body.instruction);
|
|
406
501
|
const s = await startAgent(d, {
|
|
407
502
|
workspace: ws,
|
|
408
503
|
persona: body.persona,
|
|
409
504
|
runtime: body.runtime,
|
|
410
505
|
model: body.model,
|
|
411
506
|
id: body.id,
|
|
507
|
+
...(body.version !== undefined ? { version: body.version } : {}),
|
|
508
|
+
...(body.permission !== undefined
|
|
509
|
+
? { permission: body.permission }
|
|
510
|
+
: {}),
|
|
511
|
+
...(body.allow !== undefined ? { allow: body.allow } : {}),
|
|
512
|
+
strict: true,
|
|
412
513
|
});
|
|
413
|
-
|
|
514
|
+
const ov = s.rec.overrides?.length
|
|
515
|
+
? `; overrides ${s.rec.overrides.join(", ")}`
|
|
516
|
+
: "";
|
|
517
|
+
what = `start ${s.rec.id} (${s.rec.persona}, ${s.rec.runtime}${ov}) in ${ws}`;
|
|
414
518
|
audit(what, s.early ? "exited at once" : undefined);
|
|
415
|
-
sendStarted(res, s, d);
|
|
519
|
+
sendStarted(res, s, d, instruction ? { instruction: "pending" } : {});
|
|
520
|
+
// AG-10.1: its first message, once it is ready: typed into its
|
|
521
|
+
// session (the host's write path), never an argument. Its text is
|
|
522
|
+
// never logged; its length is.
|
|
523
|
+
if (instruction && !s.early) {
|
|
524
|
+
const id = s.rec.id;
|
|
525
|
+
void sendFirstInstruction(
|
|
526
|
+
d,
|
|
527
|
+
s.info.ref.id,
|
|
528
|
+
s.rec.runtime,
|
|
529
|
+
instruction,
|
|
530
|
+
deps.instruction ?? {},
|
|
531
|
+
).then((sent) =>
|
|
532
|
+
recordAction(
|
|
533
|
+
actor,
|
|
534
|
+
{
|
|
535
|
+
verb: "agents",
|
|
536
|
+
detail: `first instruction to ${id} in ${ws} (${instruction.length} characters)`,
|
|
537
|
+
...(sent
|
|
538
|
+
? {}
|
|
539
|
+
: {
|
|
540
|
+
outcome: "refused" as const,
|
|
541
|
+
reason: "its session ended or no host",
|
|
542
|
+
}),
|
|
543
|
+
},
|
|
544
|
+
principalOf(req),
|
|
545
|
+
),
|
|
546
|
+
);
|
|
547
|
+
}
|
|
416
548
|
} else if (verb === "stop") {
|
|
417
549
|
// The answer comes once the session has ended (200, stopped: true),
|
|
418
550
|
// or, still running after the wait, 202 with stillStopping: true.
|
|
@@ -586,6 +718,113 @@ function queryWorkspace(
|
|
|
586
718
|
return ws;
|
|
587
719
|
}
|
|
588
720
|
|
|
721
|
+
/**
|
|
722
|
+
* The persona routes (AG-10.6, 10.7): GET one (anyone who may read);
|
|
723
|
+
* POST (new: `id` null; edit: a local one), DELETE (a local one): a
|
|
724
|
+
* person only. An agent's token can't make or change a persona (its
|
|
725
|
+
* permissions are the person's to set). Every change is audited, refused
|
|
726
|
+
* or done; never the instructions themselves.
|
|
727
|
+
*/
|
|
728
|
+
async function personaRoute(
|
|
729
|
+
req: http.IncomingMessage,
|
|
730
|
+
res: http.ServerResponse,
|
|
731
|
+
id: string | null,
|
|
732
|
+
d: AgentsResolved,
|
|
733
|
+
): Promise<true> {
|
|
734
|
+
const method = req.method ?? "GET";
|
|
735
|
+
if (id !== null && (method === "GET" || method === "HEAD")) {
|
|
736
|
+
try {
|
|
737
|
+
sendJson(res, { persona: personaDetail(d, id) });
|
|
738
|
+
} catch (e) {
|
|
739
|
+
sendError(res, e);
|
|
740
|
+
}
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
if (method !== "POST" && !(method === "DELETE" && id !== null))
|
|
744
|
+
return methodNotAllowed(
|
|
745
|
+
res,
|
|
746
|
+
id === null ? "GET, POST" : "GET, POST, DELETE",
|
|
747
|
+
);
|
|
748
|
+
const actor = auditActorOf(req);
|
|
749
|
+
const verb = id === null ? "create" : method === "DELETE" ? "delete" : "edit";
|
|
750
|
+
let what = `persona ${verb}${id ? ` ${id}` : ""}`;
|
|
751
|
+
const audit = (reason?: string) =>
|
|
752
|
+
recordAction(
|
|
753
|
+
actor,
|
|
754
|
+
{
|
|
755
|
+
verb: "agents",
|
|
756
|
+
detail: what,
|
|
757
|
+
...(reason ? { outcome: "refused" as const, reason } : {}),
|
|
758
|
+
},
|
|
759
|
+
principalOf(req),
|
|
760
|
+
);
|
|
761
|
+
try {
|
|
762
|
+
if (actor && !actor.unverified)
|
|
763
|
+
throw new AgentError(
|
|
764
|
+
"an agent can't create, change or delete personas: a person sets what agents may do",
|
|
765
|
+
{ status: 403 },
|
|
766
|
+
);
|
|
767
|
+
if (verb === "delete") {
|
|
768
|
+
deletePersona(d, id!);
|
|
769
|
+
audit();
|
|
770
|
+
sendJson(res, { ok: true, deleted: id });
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
const body = await jsonBody(req);
|
|
774
|
+
const p =
|
|
775
|
+
verb === "create" ? createPersona(d, body) : updatePersona(d, id!, body);
|
|
776
|
+
what = `persona ${verb} ${p.id}`;
|
|
777
|
+
audit();
|
|
778
|
+
sendJson(res, { ok: true, persona: p });
|
|
779
|
+
} catch (e) {
|
|
780
|
+
audit(e instanceof Error ? e.message : String(e));
|
|
781
|
+
sendError(res, e);
|
|
782
|
+
}
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* POST /api/workspaces/<ws>/sessions (AG-10.1 Plain session): a person's
|
|
788
|
+
* login shell in the workspace's folder. A person only; audited.
|
|
789
|
+
*/
|
|
790
|
+
async function plainSession(
|
|
791
|
+
req: http.IncomingMessage,
|
|
792
|
+
res: http.ServerResponse,
|
|
793
|
+
ws: string,
|
|
794
|
+
d: AgentsResolved,
|
|
795
|
+
): Promise<true> {
|
|
796
|
+
if (req.method !== "POST") return methodNotAllowed(res, "POST");
|
|
797
|
+
const actor = auditActorOf(req);
|
|
798
|
+
let what = `plain session in ${ws}`;
|
|
799
|
+
const audit = (reason?: string) =>
|
|
800
|
+
recordAction(
|
|
801
|
+
actor,
|
|
802
|
+
{
|
|
803
|
+
verb: "sessions",
|
|
804
|
+
detail: what,
|
|
805
|
+
...(reason ? { outcome: "refused" as const, reason } : {}),
|
|
806
|
+
},
|
|
807
|
+
principalOf(req),
|
|
808
|
+
);
|
|
809
|
+
try {
|
|
810
|
+
if (actor && !actor.unverified)
|
|
811
|
+
throw new AgentError("an agent can't start a session from the page", {
|
|
812
|
+
status: 403,
|
|
813
|
+
});
|
|
814
|
+
const { info } = await startPlainSession(d, ws);
|
|
815
|
+
what = `plain session ${info.name} in ${ws}`;
|
|
816
|
+
audit();
|
|
817
|
+
sendJson(res, {
|
|
818
|
+
ok: true,
|
|
819
|
+
session: { id: info.ref.id, name: info.name, workspace: ws },
|
|
820
|
+
});
|
|
821
|
+
} catch (e) {
|
|
822
|
+
audit(e instanceof Error ? e.message : String(e));
|
|
823
|
+
sendError(res, e, ws);
|
|
824
|
+
}
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
|
|
589
828
|
/**
|
|
590
829
|
* POST /api/agents/runtimes/<id>/path: a person only, never an agent (it
|
|
591
830
|
* runs the path given). Audited, refused or done; never the path's output.
|
|
@@ -37,9 +37,10 @@ export function personaOptions(personas) {
|
|
|
37
37
|
.join("");
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/** The help line under the persona select: the picked persona's
|
|
40
|
+
/** The help line under the persona select: the picked persona's description. */
|
|
41
41
|
export function personaHelp(personas, id) {
|
|
42
|
-
|
|
42
|
+
const p = (personas ?? []).find((x) => x.id === id);
|
|
43
|
+
return p?.description ?? p?.role ?? "";
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/** A runtime's name for people ("Claude Code"); its id when it has none. */
|