@monster-orange/den 0.4.26 → 0.4.28
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 +88 -0
- package/docs/distributions.md +145 -20
- package/package.json +1 -1
- package/src/shared/agents/claude-helpers.ts +623 -0
- package/src/shared/agents/den-claude.ts +54 -0
- package/src/shared/agents/helpers.ts +175 -0
- package/src/shared/agents/runtime.ts +15 -2
- package/src/shared/den-config.ts +42 -1
- package/src/shared/hub.ts +314 -82
- package/src/shared/secrets/onepassword.ts +52 -15
- package/src/ui/cli/agent-ops.ts +161 -3
- package/src/ui/cli/agents-command.ts +127 -1
- package/src/ui/cli/doctor-command.ts +11 -2
- package/src/ui/mcp/api.ts +8 -2
- package/src/ui/mcp/server.ts +13 -1
- package/src/ui/mcp/tools.ts +50 -0
- package/src/ui/server/agent-routes.ts +133 -1
- package/src/ui/server/hub-connect.ts +499 -0
- package/src/ui/server/index.ts +7 -1
- package/src/ui/server/public/agent-helpers.js +311 -0
- package/src/ui/server/public/agents-page.js +76 -9
- package/src/ui/server/public/i18n/es.js +41 -0
- package/src/ui/server/public/i18n/ja.js +39 -0
- package/src/ui/server/public/settings-registry.js +232 -33
- package/src/ui/server/public/settings.html +23 -0
- package/src/ui/server/public/shell.js +201 -0
- package/src/ui/server/public/topbar.js +5 -1
- package/src/ui/server/public/transcript-view.js +202 -0
- package/src/ui/server/request-guard.ts +3 -1
- package/src/ui/server/route-classes.ts +33 -0
- package/src/ui/server/session-routes.ts +71 -1
- package/src/ui/server/ui-assets.ts +3 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D32: the helpers an agent starts inside its own session (Claude Code's
|
|
3
|
+
* Agent and Workflow subagents). They belong to the agent's session, not
|
|
4
|
+
* to Den: Den shows them, read-only, and never stops or steers them.
|
|
5
|
+
*
|
|
6
|
+
* Runtime-neutral shapes; a runtime that can read its helpers implements
|
|
7
|
+
* Runtime.helpers / Runtime.helperTranscript (den-claude: claude-helpers.ts).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** running: working now. idle: no activity for a while, not known done. */
|
|
11
|
+
export type HelperStatus = "running" | "idle" | "done" | "failed" | "stopped";
|
|
12
|
+
|
|
13
|
+
export interface Helper {
|
|
14
|
+
/** The runtime's id for it (Claude Code: agent-<id>'s <id>). */
|
|
15
|
+
id: string;
|
|
16
|
+
/** What the agent asked it to do, in a few words. Missing: show the id. */
|
|
17
|
+
description?: string;
|
|
18
|
+
/** Its kind (general-purpose, Explore, a plugin's agent, …). */
|
|
19
|
+
type?: string;
|
|
20
|
+
model?: string;
|
|
21
|
+
/** The worktree it works in, when it has its own. */
|
|
22
|
+
branch?: string;
|
|
23
|
+
worktree?: string;
|
|
24
|
+
/** Started in the background (the agent kept going meanwhile). */
|
|
25
|
+
background?: boolean;
|
|
26
|
+
/** 1: started by the agent; 2+: started by another helper (parentId). */
|
|
27
|
+
depth: number;
|
|
28
|
+
parentId?: string;
|
|
29
|
+
/** When it ran inside a workflow. */
|
|
30
|
+
workflow?: { runId: string; name?: string; phase?: string; label?: string };
|
|
31
|
+
/** It ran in one of the agent's earlier conversations (before a relaunch). */
|
|
32
|
+
earlier?: true;
|
|
33
|
+
status: HelperStatus;
|
|
34
|
+
/** How the status was told, for "why does it say that". */
|
|
35
|
+
statusFrom: string;
|
|
36
|
+
/** Epoch ms. */
|
|
37
|
+
startedAt?: number;
|
|
38
|
+
lastActivityAt?: number;
|
|
39
|
+
durationMs?: number;
|
|
40
|
+
/** The tool it used last, when known. */
|
|
41
|
+
lastTool?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One line of a helper's transcript, tool calls summarized. */
|
|
45
|
+
export interface HelperTranscriptItem {
|
|
46
|
+
at?: number;
|
|
47
|
+
kind: "prompt" | "text" | "tool" | "result" | "note";
|
|
48
|
+
/** For a tool call: its name. */
|
|
49
|
+
tool?: string;
|
|
50
|
+
text: string;
|
|
51
|
+
error?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface HelperTranscript {
|
|
55
|
+
helper: Helper;
|
|
56
|
+
items: HelperTranscriptItem[];
|
|
57
|
+
/** Earlier lines left out (only the last ones are shown). */
|
|
58
|
+
omitted: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Read-only, and why: shown wherever helpers are. */
|
|
62
|
+
export const HELPERS_READ_ONLY_NOTE =
|
|
63
|
+
"Helpers run inside this agent's own session, so Den shows them but can't stop or steer them. To change what they do, tell the agent.";
|
|
64
|
+
|
|
65
|
+
/** D32 settings (agents.helpers.*), permissive defaults. */
|
|
66
|
+
export interface HelperSettings {
|
|
67
|
+
/** No activity for this long and not known done: idle, not running. */
|
|
68
|
+
activeMinutes: number;
|
|
69
|
+
/** Done, failed and stopped helpers fold away after this long. */
|
|
70
|
+
collapseDoneMinutes: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const HELPER_DEFAULTS: HelperSettings = {
|
|
74
|
+
activeMinutes: 10,
|
|
75
|
+
collapseDoneMinutes: 30,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** What a runtime's helpers hooks are given. */
|
|
79
|
+
export interface HelperReadOptions {
|
|
80
|
+
env?: NodeJS.ProcessEnv;
|
|
81
|
+
now?: number;
|
|
82
|
+
settings?: Partial<HelperSettings>;
|
|
83
|
+
/** Whether the agent itself is running now. */
|
|
84
|
+
agentLive?: boolean;
|
|
85
|
+
/** A transcript's last lines (default 500). */
|
|
86
|
+
limit?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Running helpers first, then the newest; a helper's own helpers right
|
|
91
|
+
* under it, in the same order (a tree, depth first). One whose parent
|
|
92
|
+
* isn't in the list sits at the top level.
|
|
93
|
+
*/
|
|
94
|
+
export function sortHelpers(hs: Helper[]): Helper[] {
|
|
95
|
+
const ids = new Set(hs.map((h) => h.id));
|
|
96
|
+
const kids = new Map<string, Helper[]>();
|
|
97
|
+
const roots: Helper[] = [];
|
|
98
|
+
for (const h of hs)
|
|
99
|
+
if (h.parentId && h.parentId !== h.id && ids.has(h.parentId))
|
|
100
|
+
kids.set(h.parentId, [...(kids.get(h.parentId) ?? []), h]);
|
|
101
|
+
else roots.push(h);
|
|
102
|
+
// Running counts for the whole branch: a done helper with a running
|
|
103
|
+
// helper under it sorts with the running ones (0.4.28-dev.1's gate).
|
|
104
|
+
const busy = new Map<string, boolean>();
|
|
105
|
+
const runs = (h: Helper, path = new Set<string>()): boolean => {
|
|
106
|
+
const known = busy.get(h.id);
|
|
107
|
+
if (known !== undefined) return known;
|
|
108
|
+
if (path.has(h.id)) return h.status === "running";
|
|
109
|
+
path.add(h.id);
|
|
110
|
+
const r =
|
|
111
|
+
h.status === "running" ||
|
|
112
|
+
(kids.get(h.id) ?? []).some((k) => runs(k, path));
|
|
113
|
+
path.delete(h.id);
|
|
114
|
+
busy.set(h.id, r);
|
|
115
|
+
return r;
|
|
116
|
+
};
|
|
117
|
+
const live = (h: Helper) => (runs(h) ? 0 : 1);
|
|
118
|
+
const order = (a: Helper, b: Helper) =>
|
|
119
|
+
live(a) - live(b) ||
|
|
120
|
+
(b.startedAt ?? b.lastActivityAt ?? 0) -
|
|
121
|
+
(a.startedAt ?? a.lastActivityAt ?? 0) ||
|
|
122
|
+
a.id.localeCompare(b.id);
|
|
123
|
+
const out: Helper[] = [];
|
|
124
|
+
const seen = new Set<string>();
|
|
125
|
+
const walk = (h: Helper) => {
|
|
126
|
+
if (seen.has(h.id)) return;
|
|
127
|
+
seen.add(h.id);
|
|
128
|
+
out.push(h);
|
|
129
|
+
for (const k of (kids.get(h.id) ?? []).sort(order)) walk(k);
|
|
130
|
+
};
|
|
131
|
+
for (const r of roots.sort(order)) walk(r);
|
|
132
|
+
// A loop of parents (never from Claude Code): still listed, flat.
|
|
133
|
+
for (const h of [...hs].sort(order)) if (!seen.has(h.id)) out.push(h);
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* At most `n` of `list`, in its order: the running ones first to stay,
|
|
139
|
+
* then the rest (a running helper is never cut for a done one).
|
|
140
|
+
*/
|
|
141
|
+
export function capHelpers(list: readonly Helper[], n: number): Helper[] {
|
|
142
|
+
const kept = new Set(
|
|
143
|
+
[
|
|
144
|
+
...list.filter((h) => h.status === "running"),
|
|
145
|
+
...list.filter((h) => h.status !== "running"),
|
|
146
|
+
].slice(0, Math.max(0, n)),
|
|
147
|
+
);
|
|
148
|
+
return list.filter((h) => kept.has(h));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* How far each helper of `list` (in sortHelpers' order) is indented: one
|
|
153
|
+
* step under a parent shown above it, none when its parent isn't in the
|
|
154
|
+
* list. The page's twin is agent-helpers.js shownDepths.
|
|
155
|
+
*/
|
|
156
|
+
export function shownDepths(list: readonly Helper[]): Map<string, number> {
|
|
157
|
+
const at = new Map<string, number>();
|
|
158
|
+
for (const h of list)
|
|
159
|
+
at.set(
|
|
160
|
+
h.id,
|
|
161
|
+
h.parentId && at.has(h.parentId) ? at.get(h.parentId)! + 1 : 0,
|
|
162
|
+
);
|
|
163
|
+
return at;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** {running, total}: what a badge shows. */
|
|
167
|
+
export function helperCounts(hs: readonly Helper[]): {
|
|
168
|
+
running: number;
|
|
169
|
+
total: number;
|
|
170
|
+
} {
|
|
171
|
+
return {
|
|
172
|
+
running: hs.filter((h) => h.status === "running").length,
|
|
173
|
+
total: hs.length,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Built-ins register here exactly as a runtime plugin would (AG-8a):
|
|
8
8
|
* den-claude and generic.
|
|
9
9
|
*/
|
|
10
|
+
import type { Helper, HelperReadOptions, HelperTranscript } from "./helpers.js";
|
|
10
11
|
import * as fs from "fs";
|
|
11
12
|
import * as path from "path";
|
|
12
13
|
import {
|
|
@@ -62,7 +63,7 @@ export interface AgentMcp {
|
|
|
62
63
|
scope: string;
|
|
63
64
|
/**
|
|
64
65
|
* HUB-1.5: Den Hub's MCP server (Streamable HTTP), when hub.url is set:
|
|
65
|
-
* its
|
|
66
|
+
* its MCP endpoint (HUB-1.7: Hub's mcp_url, default <hub.url>/mcp) and the headers (the bearer token among them). The
|
|
66
67
|
* headers go only into the agent's own 0600 MCP file, never into argv,
|
|
67
68
|
* a record, a log or a page.
|
|
68
69
|
*/
|
|
@@ -252,6 +253,18 @@ export interface Runtime {
|
|
|
252
253
|
* throw; its catalog entry's `sessionName` says how stable its source is.
|
|
253
254
|
*/
|
|
254
255
|
sessionName?(rec: AgentRecord, env?: NodeJS.ProcessEnv): string | null;
|
|
256
|
+
/**
|
|
257
|
+
* D32: the helpers the agent started inside its own session (Claude
|
|
258
|
+
* Code's Agent and Workflow subagents), in every conversation it has had,
|
|
259
|
+
* running first. Read-only. None: the runtime can't tell. Never throws.
|
|
260
|
+
*/
|
|
261
|
+
helpers?(rec: AgentRecord, opts?: HelperReadOptions): Helper[];
|
|
262
|
+
/** D32: one helper's transcript, tool calls summarized; null if none. Never throws. */
|
|
263
|
+
helperTranscript?(
|
|
264
|
+
rec: AgentRecord,
|
|
265
|
+
id: string,
|
|
266
|
+
opts?: HelperReadOptions,
|
|
267
|
+
): HelperTranscript | null;
|
|
255
268
|
/** Where permission prompts surface (Q-AG-2). */
|
|
256
269
|
approvals?: "terminal" | "hook";
|
|
257
270
|
/**
|
|
@@ -353,7 +366,7 @@ export function mcpConfigJson(mcp: AgentMcp): string {
|
|
|
353
366
|
? {
|
|
354
367
|
[mcp.hub.name]: {
|
|
355
368
|
type: "http",
|
|
356
|
-
url:
|
|
369
|
+
url: mcp.hub.url,
|
|
357
370
|
headers: mcp.hub.headers,
|
|
358
371
|
},
|
|
359
372
|
}
|
package/src/shared/den-config.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* The user file can toggle local_dev on/off without touching the shared config.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { HELPER_DEFAULTS, type HelperSettings } from "./agents/helpers.js";
|
|
11
12
|
import * as fs from "fs";
|
|
12
13
|
import * as os from "os";
|
|
13
14
|
import * as path from "path";
|
|
@@ -916,6 +917,13 @@ export interface AgentsConfig {
|
|
|
916
917
|
* false` keeps standing personas from rotating (default true).
|
|
917
918
|
*/
|
|
918
919
|
rotate?: { after_compactions?: unknown; standing?: unknown };
|
|
920
|
+
/**
|
|
921
|
+
* D32, the helpers an agent starts in its own session:
|
|
922
|
+
* `{ active_minutes, collapse_done_minutes }`. A helper with no activity
|
|
923
|
+
* for `active_minutes` (default 10) and not known done shows idle; done
|
|
924
|
+
* ones fold away after `collapse_done_minutes` (default 30).
|
|
925
|
+
*/
|
|
926
|
+
helpers?: { active_minutes?: unknown; collapse_done_minutes?: unknown };
|
|
919
927
|
/**
|
|
920
928
|
* Any other key is an agent id: `agents.<id>.grants` widens that agent's
|
|
921
929
|
* MCP scope to other workspaces (AG spec §5b). Config only: an agent
|
|
@@ -940,6 +948,8 @@ export const AGENTS_RESERVED_KEYS = [
|
|
|
940
948
|
"runtime_catalog",
|
|
941
949
|
"default_pack",
|
|
942
950
|
"rotate",
|
|
951
|
+
// D32: how helpers show.
|
|
952
|
+
"helpers",
|
|
943
953
|
// AG-10.5, AG-11.1: the distribution's persona categories and roles.
|
|
944
954
|
"categories",
|
|
945
955
|
"roles",
|
|
@@ -968,12 +978,42 @@ export interface ResolvedAgentsConfig {
|
|
|
968
978
|
rotate?: RotateSettings;
|
|
969
979
|
/** D28: agents.rotate as written, the org scope. */
|
|
970
980
|
rotateOrg?: OrgRotate;
|
|
981
|
+
/** agents.helpers (D32) with its defaults. */
|
|
982
|
+
helpers?: HelperSettings;
|
|
971
983
|
/** agents.categories (AG-10.5): the distribution's, after Den's. */
|
|
972
984
|
categories?: Category[];
|
|
973
985
|
/** agents.roles (AG-11.1): new roles and Den roles' preset extensions. */
|
|
974
986
|
roles?: RolesConfig;
|
|
975
987
|
}
|
|
976
988
|
|
|
989
|
+
/** `agents.helpers` (D32) checked; a bad key or value throws naming it. */
|
|
990
|
+
export function helpersBlock(v: unknown): HelperSettings {
|
|
991
|
+
const out = { ...HELPER_DEFAULTS };
|
|
992
|
+
if (v === undefined || v === null) return out;
|
|
993
|
+
if (!isMapping(v))
|
|
994
|
+
throw new Error(
|
|
995
|
+
`agents.helpers: must be a mapping of active_minutes, collapse_done_minutes (got ${JSON.stringify(v)})`,
|
|
996
|
+
);
|
|
997
|
+
for (const [k, x] of Object.entries(v)) {
|
|
998
|
+
const key =
|
|
999
|
+
k === "active_minutes"
|
|
1000
|
+
? "activeMinutes"
|
|
1001
|
+
: k === "collapse_done_minutes"
|
|
1002
|
+
? "collapseDoneMinutes"
|
|
1003
|
+
: null;
|
|
1004
|
+
if (!key)
|
|
1005
|
+
throw new Error(
|
|
1006
|
+
`agents.helpers.${k}: unknown key (agents.helpers takes active_minutes, collapse_done_minutes)`,
|
|
1007
|
+
);
|
|
1008
|
+
if (typeof x !== "number" || !Number.isFinite(x) || x <= 0 || x > 100_000)
|
|
1009
|
+
throw new Error(
|
|
1010
|
+
`agents.helpers.${k}: must be a number of minutes, above 0 and at most 100000 (got ${JSON.stringify(x)})`,
|
|
1011
|
+
);
|
|
1012
|
+
out[key] = x;
|
|
1013
|
+
}
|
|
1014
|
+
return out;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
977
1017
|
/** `agents.rotate` checked; any other key, or a bad value, throws naming it. */
|
|
978
1018
|
/**
|
|
979
1019
|
* `agents.rotate` checked, both ways: den#4's settings (afterCompactions 0
|
|
@@ -1077,6 +1117,7 @@ export function readAgentsConfig(
|
|
|
1077
1117
|
return { rotate: r.settings, rotateOrg: r.org };
|
|
1078
1118
|
})(),
|
|
1079
1119
|
};
|
|
1120
|
+
out.helpers = helpersBlock(raw.helpers);
|
|
1080
1121
|
out.categories = checkCategoriesConfig(raw.categories);
|
|
1081
1122
|
out.roles = checkRolesConfig(raw.roles, [
|
|
1082
1123
|
...DEN_CATEGORIES.map((c) => c.id),
|
|
@@ -1114,7 +1155,7 @@ export function readAgentsConfig(
|
|
|
1114
1155
|
const where = `agents.${id}`;
|
|
1115
1156
|
if (!AGENT_KEY_RE.test(id) || !isMapping(block))
|
|
1116
1157
|
throw new Error(
|
|
1117
|
-
`${where}: unknown key (agents: takes max_concurrent, persona_packs, runtime_catalog, default_pack, rotate, categories, roles, ` +
|
|
1158
|
+
`${where}: unknown key (agents: takes max_concurrent, persona_packs, runtime_catalog, default_pack, rotate, helpers, categories, roles, ` +
|
|
1118
1159
|
`or an agent id mapping to { grants: [...] })`,
|
|
1119
1160
|
);
|
|
1120
1161
|
for (const k of Object.keys(block))
|