@monster-orange/den 0.4.13 → 0.4.15
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 +176 -0
- package/README.md +7 -7
- package/docs/distributions.md +185 -7
- package/package.json +1 -1
- package/src/shared/actor-token.ts +12 -0
- package/src/shared/agents/conversations.ts +256 -0
- package/src/shared/agents/den-claude.ts +19 -0
- package/src/shared/agents/records.ts +23 -0
- package/src/shared/agents/runtime.ts +14 -0
- package/src/shared/claim-code.ts +163 -0
- package/src/shared/cli-command.ts +31 -0
- package/src/shared/den-config.ts +14 -0
- package/src/shared/environment-manager/service-runner.ts +31 -12
- package/src/shared/environment-manager/workspaces.ts +31 -0
- package/src/shared/web-session.ts +64 -5
- package/src/ui/cli/agent-ops.ts +320 -18
- package/src/ui/cli/agents-command.ts +89 -2
- package/src/ui/cli/doctor-command.ts +29 -3
- package/src/ui/cli/doctor-onboard.ts +26 -0
- package/src/ui/cli/flag-parser.ts +9 -1
- package/src/ui/cli/index.ts +29 -3
- package/src/ui/cli/onboard-command.ts +180 -0
- package/src/ui/cli/onboard-reads.ts +613 -0
- package/src/ui/cli/repo-command.ts +156 -10
- package/src/ui/cli/web-command.ts +92 -1
- package/src/ui/mcp/api.ts +26 -3
- package/src/ui/mcp/server.ts +12 -2
- package/src/ui/mcp/tools.ts +427 -1
- package/src/ui/server/agent-resume-grant.ts +35 -0
- package/src/ui/server/agent-routes.ts +149 -1
- package/src/ui/server/approval-routes.ts +390 -0
- package/src/ui/server/approvals.ts +416 -0
- package/src/ui/server/github-routes.ts +12 -4
- package/src/ui/server/held-actions.ts +203 -0
- package/src/ui/server/http-helpers.ts +35 -0
- package/src/ui/server/index.ts +286 -16
- package/src/ui/server/lifecycle-exec.ts +70 -0
- package/src/ui/server/onboard-routes.ts +132 -0
- package/src/ui/server/pair-routes.ts +7 -4
- package/src/ui/server/public/agents-page.js +288 -8
- package/src/ui/server/public/agents.html +18 -0
- package/src/ui/server/public/error-page.js +27 -3
- package/src/ui/server/public/header.js +1 -1
- package/src/ui/server/public/i18n/es.js +52 -0
- package/src/ui/server/public/i18n/ja.js +55 -0
- package/src/ui/server/public/index.html +1 -1
- package/src/ui/server/public/local-dev.html +5 -4
- package/src/ui/server/public/persona-ui.css +45 -0
- package/src/ui/server/public/release.html +0 -1
- package/src/ui/server/public/settings-registry.js +3 -1
- package/src/ui/server/public/settings.html +9 -1
- package/src/ui/server/public/shell.js +336 -0
- package/src/ui/server/request-guard.ts +19 -4
- package/src/ui/server/route-classes.ts +41 -1
- package/src/ui/server/session-routes.ts +11 -3
- package/src/ui/server/workspace-routes.ts +21 -10
- package/themes/den/den-theme.json +6 -6
|
@@ -95,7 +95,8 @@ export const ROUTES: readonly RouteEntry[] = [
|
|
|
95
95
|
"/theme-favicon",
|
|
96
96
|
"/theme-favicon/*",
|
|
97
97
|
"/vendor/*",
|
|
98
|
-
"/report
|
|
98
|
+
"/report",
|
|
99
|
+
"/report/*",
|
|
99
100
|
"/reports/security/*",
|
|
100
101
|
].map((p) => R(p)),
|
|
101
102
|
R("/_den/throw", "a test-only route (DEN_DEBUG_ROUTES): it throws"),
|
|
@@ -159,6 +160,10 @@ export const ROUTES: readonly RouteEntry[] = [
|
|
|
159
160
|
"/api/personas/:id",
|
|
160
161
|
"edits or deletes a person's own persona (AG-10.7); never an agent's token",
|
|
161
162
|
),
|
|
163
|
+
R(
|
|
164
|
+
"/api/workspaces/:ws/conversations",
|
|
165
|
+
"lists a runtime's own conversations in the workspace's folder: titles only, the ends of each file (AG-17.1)",
|
|
166
|
+
),
|
|
162
167
|
R(
|
|
163
168
|
"/api/workspaces/:ws/agents/:id/paths",
|
|
164
169
|
"which paths an agent printed exist in its workspace (stat only, AG-4b)",
|
|
@@ -170,6 +175,30 @@ export const ROUTES: readonly RouteEntry[] = [
|
|
|
170
175
|
R("/api/health", "runs the manifests' health probes: read-only checks"),
|
|
171
176
|
R("/api/test-sources", "git rev-parse in each repo: read-only"),
|
|
172
177
|
R("/api/manifest/validate", "GET validates a file at a path: read-only"),
|
|
178
|
+
// ON-3 (onboard-routes.ts): reads only; nothing written, no repo tool run.
|
|
179
|
+
R("/api/onboard/status", "what's set up and the next step (file reads)"),
|
|
180
|
+
R(
|
|
181
|
+
"/api/onboard/repos",
|
|
182
|
+
"lists the git repos in a folder in home; never follows a symlink",
|
|
183
|
+
),
|
|
184
|
+
R(
|
|
185
|
+
"/api/onboard/manifest",
|
|
186
|
+
"drafts and validates a side-car manifest; writes nothing",
|
|
187
|
+
),
|
|
188
|
+
// ON-3's held writes (approval-routes.ts): asking runs nothing; Allow is
|
|
189
|
+
// the person's signed-in browser only (approvals.ts approverAllowed).
|
|
190
|
+
R("/api/approvals", "what waits for the person (never an agent's token)"),
|
|
191
|
+
R("/api/approvals/:id", "one held request: the asker's own, or the person's"),
|
|
192
|
+
M(
|
|
193
|
+
["POST"],
|
|
194
|
+
"/api/approvals",
|
|
195
|
+
"holds an agent's write for the person; nothing runs until they allow it",
|
|
196
|
+
),
|
|
197
|
+
M(
|
|
198
|
+
["POST"],
|
|
199
|
+
"/api/approvals/:id/decide",
|
|
200
|
+
"the person's Allow runs the held write once; Deny runs nothing",
|
|
201
|
+
),
|
|
173
202
|
R("/api/service/logs", "reads (and follows) a service's log"),
|
|
174
203
|
R("/api/service/taillog", "tails a service's log (tail, docker logs)"),
|
|
175
204
|
R(
|
|
@@ -353,6 +382,11 @@ export const ROUTES: readonly RouteEntry[] = [
|
|
|
353
382
|
"/api/workspaces/:ws/agents",
|
|
354
383
|
"starts an agent: a runtime in a PTY",
|
|
355
384
|
),
|
|
385
|
+
S(
|
|
386
|
+
["POST"],
|
|
387
|
+
"/api/workspaces/:ws/conversations/:id/import",
|
|
388
|
+
"brings in a conversation: its runtime's resume in a PTY (AG-17.1)",
|
|
389
|
+
),
|
|
356
390
|
S(
|
|
357
391
|
["POST"],
|
|
358
392
|
"/api/workspaces/:ws/sessions",
|
|
@@ -438,11 +472,17 @@ export const REGEX_ROUTES: Readonly<Record<string, readonly string[]>> = {
|
|
|
438
472
|
"agent-routes.ts:RUNTIME_PATH": ["/api/agents/runtimes/:id/path"],
|
|
439
473
|
"agent-routes.ts:PERSONA_PATH": ["/api/personas/:id"],
|
|
440
474
|
"agent-routes.ts:PLAIN_PATH": ["/api/workspaces/:ws/sessions"],
|
|
475
|
+
"agent-routes.ts:CONVERSATIONS_PATH": [
|
|
476
|
+
"/api/workspaces/:ws/conversations",
|
|
477
|
+
"/api/workspaces/:ws/conversations/:id/import",
|
|
478
|
+
],
|
|
441
479
|
"session-routes.ts:IO_PATH": ["/api/sessions/:id/io"],
|
|
442
480
|
"github-routes.ts:SIGNIN_PATH": ["/api/github/signin/:id"],
|
|
443
481
|
"github-routes.ts:LINK_PATH": ["/api/web/owner/link/:id"],
|
|
444
482
|
"pair-routes.ts:POLL_PATH": ["/api/signin/code/:id"],
|
|
445
483
|
"index.ts:LOGIN_WAIT_PATH": ["/api/web/logins/:id"],
|
|
484
|
+
"approval-routes.ts:ONE_PATH": ["/api/approvals/:id"],
|
|
485
|
+
"approval-routes.ts:DECIDE_PATH": ["/api/approvals/:id/decide"],
|
|
446
486
|
};
|
|
447
487
|
|
|
448
488
|
/** Whether `pathname` matches a table path. */
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* `<cli> agents list` does (agent-ops.ts agentRows), and adds the plain
|
|
10
10
|
* sessions (`sessions start`), labelled kind "session". An agent whose
|
|
11
11
|
* actor token `web logout --all` revoked is marked (tokenRevoked). The
|
|
12
|
-
*
|
|
13
|
-
* (AG-6a
|
|
12
|
+
* agent with one of Den's held writes waiting for the person (ON-3,
|
|
13
|
+
* approval-routes.ts) is `waiting` (AG-6a); a runtime's own permission
|
|
14
|
+
* prompts are not seen here yet. Asking never starts a session host.
|
|
14
15
|
*
|
|
15
16
|
* The io route bridges one WebSocket to one attachment on the session host
|
|
16
17
|
* (its own connection to the host, so a browser and the CLI, or two
|
|
@@ -78,6 +79,8 @@ export interface SessionRouteDeps {
|
|
|
78
79
|
/** Whether workspaces.yml names `ws` (active or not). */
|
|
79
80
|
workspaceExists?: (ws: string) => boolean;
|
|
80
81
|
pty?: () => PtyLoad;
|
|
82
|
+
/** The agent ids with an approval waiting for the person (AG-6a, ON-3). */
|
|
83
|
+
waitingAgents?: () => ReadonlySet<string>;
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
interface Resolved {
|
|
@@ -87,6 +90,7 @@ interface Resolved {
|
|
|
87
90
|
activeWorkspaces: () => string[];
|
|
88
91
|
workspaceExists: (ws: string) => boolean;
|
|
89
92
|
pty: () => PtyLoad;
|
|
93
|
+
waitingAgents: () => ReadonlySet<string>;
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
/** A workspace name as `?ws=` may give it. */
|
|
@@ -116,6 +120,7 @@ function resolve(d: SessionRouteDeps = {}): Resolved {
|
|
|
116
120
|
ws,
|
|
117
121
|
)),
|
|
118
122
|
pty: d.pty ?? (() => loadPty()),
|
|
123
|
+
waitingAgents: d.waitingAgents ?? (() => new Set<string>()),
|
|
119
124
|
};
|
|
120
125
|
}
|
|
121
126
|
|
|
@@ -189,7 +194,8 @@ export interface ListedSession {
|
|
|
189
194
|
/** The list, from the host's sessions and the agent records. */
|
|
190
195
|
export function listedSessions(
|
|
191
196
|
sessions: SessionInfo[],
|
|
192
|
-
d: Pick<Resolved, "stateDir" | "dist" | "activeWorkspaces"
|
|
197
|
+
d: Pick<Resolved, "stateDir" | "dist" | "activeWorkspaces"> &
|
|
198
|
+
Partial<Pick<Resolved, "waitingAgents">>,
|
|
193
199
|
/** A workspace asked for by name (`?ws=`), listed even when inactive. */
|
|
194
200
|
also?: string,
|
|
195
201
|
): ListedSession[] {
|
|
@@ -204,6 +210,7 @@ export function listedSessions(
|
|
|
204
210
|
active.has(r.workspace),
|
|
205
211
|
);
|
|
206
212
|
const revoked = revokedAgentIds(d.stateDir());
|
|
213
|
+
const waiting = d.waitingAgents?.() ?? new Set<string>();
|
|
207
214
|
const out: ListedSession[] = [];
|
|
208
215
|
const agentSessions = new Set<string>();
|
|
209
216
|
noteEnded(d.stateDir(), mine, records);
|
|
@@ -242,6 +249,7 @@ export function listedSessions(
|
|
|
242
249
|
...(r.interruptedAt ? { interruptedAt: r.interruptedAt } : {}),
|
|
243
250
|
...(r.rotation?.due ? { rotation: r.rotation } : {}),
|
|
244
251
|
...(revoked.has(rec.id) && s && !s.exited ? { tokenRevoked: true } : {}),
|
|
252
|
+
...(waiting.has(rec.id) && s && !s.exited ? { waiting: true } : {}),
|
|
245
253
|
});
|
|
246
254
|
}
|
|
247
255
|
for (const s of mine) {
|
|
@@ -344,6 +344,25 @@ const availableReposCache = singleFlight<AvailableRepos>(2_000);
|
|
|
344
344
|
* Same contract as the original inline handlers: uses return sendJson/send400
|
|
345
345
|
* to end the response. Caller checks res.writableEnded to know if handled.
|
|
346
346
|
*/
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Why a workspace can't be created with this name, or null: the Workspaces
|
|
350
|
+
* page's create and ON-3's held workspace_create (approval-routes.ts) both
|
|
351
|
+
* ask this.
|
|
352
|
+
*/
|
|
353
|
+
export function workspaceCreateRefusal(
|
|
354
|
+
name: string | undefined,
|
|
355
|
+
): string | null {
|
|
356
|
+
if (!name) return "name required";
|
|
357
|
+
// eslint-disable-next-line no-control-regex
|
|
358
|
+
if (/["'\\/<>\x00-\x1f]/.test(name))
|
|
359
|
+
return "name must not contain quotes, slashes, or control characters";
|
|
360
|
+
if (name.length > 64) return "name must be 64 characters or fewer";
|
|
361
|
+
if (readWorkspacesFile().workspaces[name])
|
|
362
|
+
return `workspace "${name}" already exists`;
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
347
366
|
export async function handleWorkspaceRoutes(
|
|
348
367
|
req: http.IncomingMessage,
|
|
349
368
|
res: http.ServerResponse,
|
|
@@ -639,16 +658,8 @@ export async function handleWorkspaceRoutes(
|
|
|
639
658
|
try {
|
|
640
659
|
const body = await readBody(req);
|
|
641
660
|
const { name } = JSON.parse(body) as { name?: string };
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
if (/["'\\/<>\x00-\x1f]/.test(name))
|
|
645
|
-
return send400(
|
|
646
|
-
res,
|
|
647
|
-
"name must not contain quotes, slashes, or control characters",
|
|
648
|
-
);
|
|
649
|
-
const file = readWorkspacesFile();
|
|
650
|
-
if (file.workspaces[name])
|
|
651
|
-
return send400(res, `workspace "${name}" already exists`);
|
|
661
|
+
const refusal = workspaceCreateRefusal(name);
|
|
662
|
+
if (refusal || !name) return send400(res, refusal ?? "name required");
|
|
652
663
|
createWorkspace(name, { services: {} });
|
|
653
664
|
return sendJson(res, { ok: true, name });
|
|
654
665
|
} catch (e) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"schema": "den.theme/v1",
|
|
4
4
|
"id": "den",
|
|
5
5
|
"name": "Den",
|
|
6
|
-
"version": "2.0.
|
|
6
|
+
"version": "2.0.1",
|
|
7
7
|
"description": "Den's default: a sheltered workspace in walnut and umber neutrals with a moss-green accent. System fonts.",
|
|
8
8
|
"author": "Monster Orange",
|
|
9
9
|
"license": "MIT",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"border": "#dcd3c7",
|
|
38
38
|
"border-strong": "#8d7f6d",
|
|
39
39
|
"accent": "#557a2a",
|
|
40
|
-
"accent-text": "#
|
|
40
|
+
"accent-text": "#415e1f",
|
|
41
41
|
"accent-fill": "#4a6b23",
|
|
42
42
|
"on-accent": "#ffffff",
|
|
43
|
-
"ok": "#
|
|
44
|
-
"warn": "#
|
|
45
|
-
"bad": "#
|
|
46
|
-
"remote": "#
|
|
43
|
+
"ok": "#27613b",
|
|
44
|
+
"warn": "#774c0e",
|
|
45
|
+
"bad": "#983124",
|
|
46
|
+
"remote": "#205d6e",
|
|
47
47
|
"shadow": "0 8px 24px rgb(48 36 24 / 0.14)",
|
|
48
48
|
"bar": "linear-gradient(90deg, #557a2a 0%, #8a6a48 100%)"
|
|
49
49
|
},
|