@monster-orange/den 0.4.12 → 0.4.14

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/README.md +7 -7
  3. package/docs/distributions.md +189 -11
  4. package/package.json +1 -1
  5. package/src/shared/agents/conversations.ts +227 -0
  6. package/src/shared/agents/default-pack/be-dev.md +1 -0
  7. package/src/shared/agents/default-pack/devops.md +1 -0
  8. package/src/shared/agents/default-pack/fe-dev.md +1 -0
  9. package/src/shared/agents/default-pack/orchestrator.md +3 -1
  10. package/src/shared/agents/default-pack/planner.md +1 -0
  11. package/src/shared/agents/default-pack/reviewer.md +1 -0
  12. package/src/shared/agents/default-pack/tester.md +16 -0
  13. package/src/shared/agents/default-pack/ux-designer.md +1 -0
  14. package/src/shared/agents/den-claude.ts +128 -13
  15. package/src/shared/agents/permissions.ts +108 -0
  16. package/src/shared/agents/persona-store.ts +286 -0
  17. package/src/shared/agents/personas.ts +116 -12
  18. package/src/shared/agents/records.ts +33 -0
  19. package/src/shared/agents/roles.ts +288 -0
  20. package/src/shared/agents/runtime.ts +50 -0
  21. package/src/shared/den-config.ts +22 -1
  22. package/src/shared/environment-manager/service-runner.ts +31 -12
  23. package/src/shared/environment-manager/workspaces.ts +11 -0
  24. package/src/ui/cli/agent-ops.ts +796 -29
  25. package/src/ui/cli/agents-command.ts +97 -3
  26. package/src/ui/cli/env-probe.ts +4 -1
  27. package/src/ui/mcp/api.ts +12 -1
  28. package/src/ui/mcp/tools.ts +60 -1
  29. package/src/ui/server/agent-resume-grant.ts +35 -0
  30. package/src/ui/server/agent-routes.ts +412 -22
  31. package/src/ui/server/public/agent-start.js +3 -2
  32. package/src/ui/server/public/agents-page.js +693 -49
  33. package/src/ui/server/public/agents.html +76 -15
  34. package/src/ui/server/public/i18n/es.js +143 -0
  35. package/src/ui/server/public/i18n/ja.js +144 -0
  36. package/src/ui/server/public/persona-ui.css +563 -0
  37. package/src/ui/server/public/persona-ui.js +796 -0
  38. package/src/ui/server/public/settings-page.js +4 -9
  39. package/src/ui/server/public/settings-registry.js +22 -0
  40. package/src/ui/server/public/settings.html +1 -0
  41. package/src/ui/server/public/shell.js +140 -0
  42. package/src/ui/server/public/topbar.js +2 -2
  43. package/src/ui/server/request-guard.ts +17 -4
  44. package/src/ui/server/route-classes.ts +31 -0
  45. package/src/ui/server/session-routes.ts +21 -5
  46. package/src/ui/server/ui-assets.ts +3 -0
@@ -148,15 +148,10 @@ export async function mountSettings(deps) {
148
148
  });
149
149
  if (!r.ok) return false;
150
150
  const reply = await r.json().catch(() => ({}));
151
- for (const k of [
152
- "editor",
153
- "terminal",
154
- "editor_overrides",
155
- "graph",
156
- "appearance",
157
- "shell",
158
- ])
159
- if (reply[k] !== undefined) ctx.data[k] = reply[k];
151
+ // Every setting the reply returns (0.4.13): a fixed list here left a
152
+ // new row's pressed button on its old value until a reload.
153
+ for (const [k, v] of Object.entries(reply))
154
+ if (k !== "ok" && v !== undefined) ctx.data[k] = v;
160
155
  return true;
161
156
  }
162
157
  async function saveWorkspace(patch) {
@@ -29,6 +29,7 @@
29
29
  */
30
30
  import { themeNotice, themeOptions } from "./appearance.js";
31
31
  import { qrSvg } from "./qr.js";
32
+ import { renderPersonasSettings } from "./persona-ui.js";
32
33
 
33
34
  export const DEN_SETTING_CATEGORIES = [
34
35
  {
@@ -879,6 +880,27 @@ export const DEN_SETTINGS = [
879
880
  read: () => null,
880
881
  },
881
882
 
883
+ {
884
+ id: "agents.personas",
885
+ category: "agents",
886
+ label: "Personas",
887
+ description:
888
+ "Agent setups you can start from. Built-in ones ship with Den, distribution ones with your distribution, and yours live on this machine. View or Edit, Duplicate, + New persona; only your own can be edited or deleted.",
889
+ keywords: [
890
+ "persona",
891
+ "role",
892
+ "agent",
893
+ "model",
894
+ "version",
895
+ "permission",
896
+ "allowed commands",
897
+ "instructions",
898
+ ],
899
+ scope: "user",
900
+ control: { type: "custom", render: renderPersonasSettings },
901
+ read: () => null,
902
+ },
903
+
882
904
  // ── Command line ───────────────────────────────────────────────────────
883
905
  {
884
906
  id: "cli.local_dev",
@@ -26,6 +26,7 @@
26
26
  })();
27
27
  </script>
28
28
  <link rel="stylesheet" href="/theme.css" id="theme-css" />
29
+ <link rel="stylesheet" href="/persona-ui.css" />
29
30
  <title>__DEN_TITLE__ · Settings</title>
30
31
  <style>
31
32
  /* The Settings page (R21): the prototype's (den-shell.html) page
@@ -554,6 +554,146 @@
554
554
  "ag.review": "Review",
555
555
  "ag.resumedAll": "Resumed {n} agents",
556
556
  "ag.resumedAll.one": "Resumed {n} agent",
557
+ "pe.configFirst": "Fix the config first: the personas are read from it.",
558
+ "ag.new.title": "Start an agent",
559
+ "rt.missingLine": "Not found on this machine:",
560
+ "rt.install": "Install…",
561
+ // The Start dialog and personas (AG-10 to AG-13).
562
+ "ag.new.what": "What's a persona?",
563
+ "ag.new.details": "Details: {n}",
564
+ "ag.new.hide": "Hide details",
565
+ "ag.new.newP": "+ New persona",
566
+ "ag.new.plain": "Plain session",
567
+ "ag.new.plainP": "A terminal in the workspace folder. No agent.",
568
+ "ag.new.task": "First instruction (optional)",
569
+ "ag.new.name": "Name (optional)",
570
+ "ag.new.as": "Starts as {id}",
571
+ "ag.new.openRunning": "Open the running one",
572
+ "ag.new.taskPh":
573
+ "e.g. Take the empty-state story. Match the design and add a test.",
574
+ "ag.new.adv": "Advanced: model and permissions",
575
+ "ag.new.advNote":
576
+ "Defaults come from the persona. Changes here apply to this session only, and the session is marked in the list.",
577
+ "ag.new.model": "Model",
578
+ "ag.new.modelDef": "Persona default ({m})",
579
+ "ag.new.ver": "Version",
580
+ "ag.new.verDef": "Persona default ({v})",
581
+ // AG-17.1, Bring in.
582
+ "ag.bring": "Bring in",
583
+ "ag.bring.title": "Bring in a conversation",
584
+ "ag.bring.conv": "Conversation",
585
+ "ag.bring.help":
586
+ "Conversations started outside Den in this workspace's folder or its repos' folders, newest first.",
587
+ "ag.bring.none":
588
+ "No conversations started outside Den in this workspace's folder or its repos' folders.",
589
+ "ag.bring.untitled": "Untitled conversation",
590
+ "ag.bring.open": "Open elsewhere",
591
+ "ag.bring.openTip": "Still open in another terminal. Close it there first",
592
+ "ag.bring.agent": "Agent {id}",
593
+ "ag.bring.note":
594
+ "Continues it in the folder it ran in, with Den's tools and guards from now on. It keeps running if you close the pane. Close it in any other terminal first: Den only sees a conversation as open if it was written in the last minute.",
595
+ "ag.bring.go": "Bring in",
596
+ "ag.bring.going": "Bringing in…",
597
+ "ag.bring.done": "Brought in as {id}",
598
+ "ag.bring.now": "just now",
599
+ "ag.bring.min": "{n} min ago",
600
+ "ag.bring.h": "{n} h ago",
601
+ "ag.new.note":
602
+ "Starts in the workspace folder. It keeps running if you close the pane.",
603
+ "ag.new.started": "Started {p} in {w}",
604
+ "pm.ask": "Ask for everything",
605
+ "pm.edits": "Auto-approve edits in the worktree",
606
+ "pm.all": "Auto-approve all",
607
+ "pm.tag.ask": "asks for all",
608
+ "pm.tag.edits": "auto-approve edits",
609
+ "pm.tag.all": "auto-approve all",
610
+ "ov.runtime": "runtime: {r}",
611
+ "ov.model": "model: {m}",
612
+ "ov.latest": "latest",
613
+ "ov.tools": "custom tools",
614
+ "pe.what":
615
+ "A persona is a saved agent setup: a role, its instructions, a default runtime and model, and what it may do without asking. You can have several with the same role, each with its own name.",
616
+ "pe.preset": "Role preset",
617
+ "pe.extra": "Extra allowed commands",
618
+ "pe.extraS": "extra",
619
+ "pe.extraP":
620
+ "Plain command prefixes, one per line. No patterns or regex. Den translates them for each runtime.",
621
+ "pe.allows": "What this allows on {r}",
622
+ "pe.ns": "not supported on {r}",
623
+ "pe.nsMode": "{r} takes no permission mode from Den yet",
624
+ "pe.nsModel": "{r} takes no model from Den",
625
+ "pe.nsPat": "use a plain prefix, not a pattern",
626
+ "pe.nsBad": "not a plain prefix",
627
+ "pe.badCmd":
628
+ '"{c}" isn\'t a plain command prefix: use letters, digits, single spaces and . _ / : = @ % + , -',
629
+ "pe.raw": "Edit raw runtime config…",
630
+ "pe.rawLater": "Opening a runtime's own config file is coming later.",
631
+ "pe.vendor": "vendor-specific",
632
+ "pe.instr": "Instructions",
633
+ "pe.runtime": "Default runtime",
634
+ "pe.model": "Model",
635
+ "pe.ver": "Version",
636
+ "pe.rtDefault": "the runtime's default",
637
+ "pe.perms": "Permission mode",
638
+ "pe.noneCmd": "none",
639
+ "pe.loading": "Loading…",
640
+ "pe.loadFail": "The personas did not load.",
641
+ "pe.src.builtin": "built-in",
642
+ "pe.src.distribution": "distribution",
643
+ "pe.src.local": "mine",
644
+ "pe.src.hub": "hub",
645
+ "pe.src.builtin.p":
646
+ "Ships with Den. Read-only here; duplicate it to customize.",
647
+ "pe.src.distribution.p":
648
+ "Ships with your distribution. Read-only here; duplicate it to customize.",
649
+ "pe.src.local.p": "Yours, on this machine.",
650
+ "pe.src.hub.p": "Shared through Den Hub. Read-only here.",
651
+ "pe.new": "New persona",
652
+ "pe.dupT": "Duplicate persona",
653
+ "pe.edit": "Edit persona",
654
+ "pe.dup": "Duplicate",
655
+ "pe.editB": "Edit",
656
+ "pe.view": "View",
657
+ "pe.save": "Save persona",
658
+ "pe.del": "Delete",
659
+ "pe.deleted": "Deleted {n}",
660
+ "pe.name": "Name",
661
+ "pe.nameP": "e.g. Rita",
662
+ "pe.role": "Role",
663
+ "pe.roleHelp":
664
+ "A role sets this persona's starting template and lets Den hand work to it. It adds nothing hidden to the instructions.",
665
+ "pe.desc": "One-line description",
666
+ "pe.cat": "Category",
667
+ "pe.copy": "{n} (copy)",
668
+ "pe.saved": "Saved {n}",
669
+ "pe.ro": "Read-only here. Duplicate it to make a copy of your own.",
670
+ "pf.search": "Search personas by name, role or description",
671
+ "pf.cat": "Category",
672
+ "pf.src": "Source",
673
+ "pf.all": "All",
674
+ "pf.none": "No persona matches. Clear the filters or create one.",
675
+ "pf.clear": "Clear filters",
676
+ "cat.engineering": "Engineering",
677
+ "cat.design": "Design",
678
+ "cat.product": "Product",
679
+ "cat.marketing": "Marketing",
680
+ "cat.operations": "Operations",
681
+ "rt.installed": "Installed",
682
+ "rt.missing": "Not installed",
683
+ "rt.notInst": "{r} not installed",
684
+ "rt.later": "later",
685
+ "rt.fallback":
686
+ "{p}'s default runtime, {r}, isn't installed here, so this starts on {f}.",
687
+ "ver.latest": "latest",
688
+ "ver.latestP": "Latest (tracks the newest release)",
689
+ "ver.newer": "newer available",
690
+ "ver.newerP":
691
+ "A newer version of this model is out. This one stays pinned until you change it.",
692
+ "ver.newerOf": "Newer: {v}",
693
+ "ver.use": "Use latest",
694
+ "ver.pinned": "pinned {v}",
695
+ "ver.usedLatest": "{n} now tracks the latest version",
696
+ "set.agents.personas": "Personas",
557
697
  // The Agents tab (AG-4a-term, agents-page.js).
558
698
  "n.agents": "Agents",
559
699
  "kind.agent": "Agent",
@@ -881,7 +881,7 @@ function paletteItems() {
881
881
  const base =
882
882
  x.kind === "agent" ? x.displayName || (x.persona ?? x.label) : x.label;
883
883
  // AG-4b: the session's name (a /rename, or Den's), when it says more.
884
- const sn = x.kind === "agent" ? (x.sessionName ?? "").trim() : "";
884
+ const sn = x.kind === "agent" ? (x.sessionTitle ?? "").trim() : "";
885
885
  const extra =
886
886
  sn && sn !== base && sn !== x.persona && sn !== x.label ? sn : "";
887
887
  const name = extra ? `${base} · ${extra}` : base;
@@ -899,7 +899,7 @@ function paletteItems() {
899
899
  x.label,
900
900
  x.persona ?? "",
901
901
  x.displayName ?? "",
902
- x.sessionName ?? "",
902
+ x.sessionTitle ?? "",
903
903
  "agents",
904
904
  "terminal",
905
905
  ],
@@ -17,9 +17,9 @@
17
17
  * and sign-in (/api/web/*, /auth, /api/session/logout), the lock (R8:
18
18
  * /api/sudo-password*), or a shell-grade route other than its own
19
19
  * session's /api/sessions/<id>/*: agent start and resume mint tokens.
20
- * 403 `agent_forbidden`. One exception (AG-14.4): MCP agent_resume,
21
- * when mcp.actions lists it, for another agent of its own workspace
22
- * or a granted one (agent-resume-grant.ts). WS-3a (0.4.2) adds the workspace check at
20
+ * 403 `agent_forbidden`. Two exceptions: MCP agent_resume (AG-14.4)
21
+ * and agent_import (AG-17.1), each when mcp.actions lists it, inside
22
+ * its own workspace or a granted one (agent-resume-grant.ts). WS-3a (0.4.2) adds the workspace check at
23
23
  * agentRefusal's seam.
24
24
  * - Sign-in (§4, route-classes.ts): beyond loopback (a LAN name, a peer
25
25
  * that isn't loopback, or the edge hostname for a client on another
@@ -61,7 +61,12 @@
61
61
  import { randomBytes, timingSafeEqual, createHash } from "crypto";
62
62
  import type * as http from "http";
63
63
  import { readMcpConfig } from "../../shared/den-config.js";
64
- import { agentResumeRefusal, agentResumeTarget } from "./agent-resume-grant.js";
64
+ import {
65
+ agentImportRefusal,
66
+ agentImportTarget,
67
+ agentResumeRefusal,
68
+ agentResumeTarget,
69
+ } from "./agent-resume-grant.js";
65
70
  import * as net from "net";
66
71
  import * as os from "os";
67
72
  import {
@@ -718,6 +723,14 @@ export function agentRefusal(
718
723
  agentResumeRefusal(p, target.ws, target.id, mcpActions()) ??
719
724
  workspaceRefusal(p, pathname)
720
725
  );
726
+ // AG-17.1: MCP agent_import, opted in, inside the agent's scope (its
727
+ // list as well as its import: conversation titles are a person's).
728
+ const imp = agentImportTarget(pathname);
729
+ if (imp)
730
+ return (
731
+ agentImportRefusal(p, imp.ws, mcpActions()) ??
732
+ workspaceRefusal(p, pathname)
733
+ );
721
734
  if (cls === "shell-grade")
722
735
  return `${p.actor} can't ${method === "GET" ? "open" : "use"} ${pathname}: starting or resuming an agent, and the browser sessions, are a person's.`;
723
736
  return workspaceRefusal(p, pathname);
@@ -148,6 +148,21 @@ export const ROUTES: readonly RouteEntry[] = [
148
148
  "/api/agents/runtimes",
149
149
  "/api/workspaces/:ws/agents",
150
150
  ].map((p) => R(p)),
151
+ R("/api/personas/:id", "one persona with its instructions (AG-10.6 Details)"),
152
+ M(
153
+ ["POST"],
154
+ "/api/personas",
155
+ "New persona: a person's own, in the state dir (AG-10.7); never an agent's token",
156
+ ),
157
+ M(
158
+ ["POST", "DELETE"],
159
+ "/api/personas/:id",
160
+ "edits or deletes a person's own persona (AG-10.7); never an agent's token",
161
+ ),
162
+ R(
163
+ "/api/workspaces/:ws/conversations",
164
+ "lists a runtime's own conversations in the workspace's folder: titles only, the ends of each file (AG-17.1)",
165
+ ),
151
166
  R(
152
167
  "/api/workspaces/:ws/agents/:id/paths",
153
168
  "which paths an agent printed exist in its workspace (stat only, AG-4b)",
@@ -342,6 +357,16 @@ export const ROUTES: readonly RouteEntry[] = [
342
357
  "/api/workspaces/:ws/agents",
343
358
  "starts an agent: a runtime in a PTY",
344
359
  ),
360
+ S(
361
+ ["POST"],
362
+ "/api/workspaces/:ws/conversations/:id/import",
363
+ "brings in a conversation: its runtime's resume in a PTY (AG-17.1)",
364
+ ),
365
+ S(
366
+ ["POST"],
367
+ "/api/workspaces/:ws/sessions",
368
+ "starts a plain session: the person's login shell in the workspace (AG-10.1)",
369
+ ),
345
370
  S(
346
371
  ["POST"],
347
372
  "/api/workspaces/:ws/agents/:id/resume",
@@ -420,6 +445,12 @@ export const REGEX_ROUTES: Readonly<Record<string, readonly string[]>> = {
420
445
  "/api/workspaces/:ws/agents/:id/paths",
421
446
  ],
422
447
  "agent-routes.ts:RUNTIME_PATH": ["/api/agents/runtimes/:id/path"],
448
+ "agent-routes.ts:PERSONA_PATH": ["/api/personas/:id"],
449
+ "agent-routes.ts:PLAIN_PATH": ["/api/workspaces/:ws/sessions"],
450
+ "agent-routes.ts:CONVERSATIONS_PATH": [
451
+ "/api/workspaces/:ws/conversations",
452
+ "/api/workspaces/:ws/conversations/:id/import",
453
+ ],
423
454
  "session-routes.ts:IO_PATH": ["/api/sessions/:id/io"],
424
455
  "github-routes.ts:SIGNIN_PATH": ["/api/github/signin/:id"],
425
456
  "github-routes.ts:LINK_PATH": ["/api/web/owner/link/:id"],
@@ -55,7 +55,8 @@ import { parseSessionName } from "../../shared/sessions/names.js";
55
55
  import { sessionPaths } from "../../shared/sessions/paths.js";
56
56
  import { loadPty, type PtyLoad } from "../../shared/sessions/pty.js";
57
57
  import type { SessionInfo } from "../../shared/sessions/types.js";
58
- import { agentRows, noteEnded } from "../cli/agent-ops.js";
58
+ import { agentRows, newerOf, noteEnded } from "../cli/agent-ops.js";
59
+ import type { Override } from "../../shared/agents/records.js";
59
60
  import { state as sessionState } from "../cli/sessions-command.js";
60
61
  import { recordAction } from "./audit.js";
61
62
  import { sendJson } from "./http-helpers.js";
@@ -137,10 +138,11 @@ export interface ListedSession {
137
138
  /** The name a person gave the agent (AG-4b), when one did. */
138
139
  displayName?: string;
139
140
  /**
140
- * Its session's name (AG-4b): the latest of Den's rename and the
141
- * runtime's (Claude Code's /rename), when there is one.
141
+ * The name its session shows (AG-4b): the latest of Den's rename and the
142
+ * runtime's (Claude Code's /rename), when there is one. Its handle is
143
+ * `name`.
142
144
  */
143
- sessionName?: string;
145
+ sessionTitle?: string;
144
146
  /**
145
147
  * What Shift+Enter sends to it (AG-4b, its catalog entry's softNewline):
146
148
  * ESC CR unless the runtime says otherwise. A plain session: none (Enter).
@@ -149,6 +151,15 @@ export interface ListedSession {
149
151
  persona?: string;
150
152
  runtime?: string;
151
153
  model?: string;
154
+ /** A pinned model version (AG-12), and the newer one when there is. */
155
+ version?: string;
156
+ newer?: string;
157
+ /** Its permission intent (AG-13): ask, edits, all. */
158
+ permission?: string;
159
+ /** Extra allowed commands it was started with (AG-13.3). */
160
+ allow?: string[];
161
+ /** What it overrides of its persona (AG-10.9): its row's tags. */
162
+ overrides?: Override[];
152
163
  cwd?: string;
153
164
  /** running, stopping, exited (<code>), stopped, interrupted (AG-14). */
154
165
  state: string;
@@ -208,11 +219,16 @@ export function listedSessions(
208
219
  label: rec.id,
209
220
  agent: rec.id,
210
221
  ...(rec.displayName ? { displayName: rec.displayName } : {}),
211
- ...(r.sessionName ? { sessionName: r.sessionName } : {}),
222
+ ...(r.sessionTitle ? { sessionTitle: r.sessionTitle } : {}),
212
223
  softNewline: softNewlineSeq(getRuntime(rec.runtime)?.catalog),
213
224
  persona: rec.persona,
214
225
  runtime: rec.runtime,
215
226
  ...(rec.model ? { model: rec.model } : {}),
227
+ ...(rec.version ? { version: rec.version } : {}),
228
+ ...newerOf(rec),
229
+ ...(rec.permission ? { permission: rec.permission } : {}),
230
+ ...(rec.allow?.length ? { allow: [...rec.allow] } : {}),
231
+ ...(rec.overrides?.length ? { overrides: [...rec.overrides] } : {}),
216
232
  cwd: rec.cwd,
217
233
  state: r.state,
218
234
  exited: !s || s.exited === true,
@@ -36,6 +36,9 @@ export const UI_MODULES: readonly string[] = [
36
36
  "/agents-page.js",
37
37
  // The Start agent form's choices, shared by both (AG-10.0).
38
38
  "/agent-start.js",
39
+ // AG-10.4-10.7: the persona picker, editor and Settings › Personas.
40
+ "/persona-ui.js",
41
+ "/persona-ui.css",
39
42
  // AG-4b: the panes' keys and links (Shift+Enter, ⌃⇧1-3, paths).
40
43
  "/term-input.js",
41
44
  // SEC-2a: Pair a device's QR code (a tiny encoder of Den's own).