pi-roundtable-web 0.24.2 → 0.26.0
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 +48 -0
- package/README.md +195 -16
- package/dist/account-zk4efb87.css +102 -0
- package/dist/chunk-account-hw49gd0d.js +1 -0
- package/dist/chunk-action-menu-rm5cyqkj.js +1 -0
- package/dist/chunk-agent-b017vswv.js +1 -0
- package/dist/chunk-arranged-rooms-q5v3dm1r.js +1 -0
- package/dist/chunk-character-creator-5dxcvd16.js +1 -0
- package/dist/chunk-codeeditor-pyt09fwg.js +2 -0
- package/dist/chunk-confirm-521yqmta.js +1 -0
- package/dist/chunk-connectors-hrm06nx8.js +1 -0
- package/dist/chunk-conversations-e26rrf01.js +1 -0
- package/dist/chunk-drag-v41gx2mc.js +1 -0
- package/dist/chunk-entry-jxxb3ath.js +1 -0
- package/dist/chunk-folder-list-b97xk12b.js +1 -0
- package/dist/chunk-folds-b8q7eags.js +1 -0
- package/dist/chunk-gate-8k61stsc.js +1 -0
- package/dist/chunk-group-scene-dx6wt19m.js +1 -0
- package/dist/chunk-group-table-e4jr8r3a.js +1 -0
- package/dist/chunk-host-state-g7e7c0k5.js +1 -0
- package/dist/chunk-host-ui-x7k6n07v.js +939 -0
- package/dist/chunk-index-014rjv9k.js +2 -0
- package/dist/chunk-index-2d0kg096.js +2 -0
- package/dist/chunk-index-2qd81wbx.js +2 -0
- package/dist/chunk-index-2stynxw6.css +1 -0
- package/dist/chunk-index-459drsft.js +2 -0
- package/dist/chunk-index-4mfj596y.js +2 -0
- package/dist/chunk-index-66nskrdj.js +47 -0
- package/dist/chunk-index-awx6nnta.js +2 -0
- package/dist/chunk-index-bbsytycb.js +2 -0
- package/dist/chunk-index-c07p8k0x.js +4 -0
- package/dist/chunk-index-cq1e9xdz.js +2 -0
- package/dist/chunk-index-d1prgfbp.js +2 -0
- package/dist/chunk-index-dhpt8qr9.js +2 -0
- package/dist/chunk-index-e5sd30xy.js +2 -0
- package/dist/chunk-index-g351921r.js +1 -0
- package/dist/chunk-index-hby0ap91.js +2 -0
- package/dist/chunk-index-hssjbr1k.js +2 -0
- package/dist/chunk-index-k2qq1gdj.js +2 -0
- package/dist/chunk-index-ke9ynm0n.js +2 -0
- package/dist/chunk-index-km40bhsz.js +2 -0
- package/dist/chunk-index-kpscvg85.js +2 -0
- package/dist/chunk-index-m0d100mg.js +2 -0
- package/dist/chunk-index-mb72snk9.js +2 -0
- package/dist/chunk-index-mjqqver5.js +4 -0
- package/dist/chunk-index-nqfkwkfp.js +2 -0
- package/dist/chunk-index-nwqk447w.js +2 -0
- package/dist/chunk-index-pcfhpm8v.js +302 -0
- package/dist/chunk-index-q8h8xzbm.js +2 -0
- package/dist/chunk-index-r5vszgfs.js +2 -0
- package/dist/{chunk-index-mpm90318.js → chunk-index-rhtvcbsq.js} +2 -2
- package/dist/chunk-index-rjv7vrwd.js +13 -0
- package/dist/chunk-index-v23m38p6.js +50 -0
- package/dist/chunk-index-va0g7d88.js +1 -0
- package/dist/{chunk-index-6t68gbex.js → chunk-index-wnjp6wv0.js} +2 -2
- package/dist/chunk-index-xgkd15n3.js +2 -0
- package/dist/chunk-index-y8rnq8zf.js +14 -0
- package/dist/chunk-index-z3k18atc.js +2 -0
- package/dist/chunk-lang-css-6bepqpnx.js +1 -0
- package/dist/{chunk-lang-dockerfile-ey0nvhhm.js → chunk-lang-dockerfile-hrav55t7.js} +1 -1
- package/dist/{chunk-lang-go-403qsw9f.js → chunk-lang-go-x9ahv6e5.js} +3 -3
- package/dist/chunk-lang-html-8jc72zt4.js +1 -0
- package/dist/chunk-lang-javascript-2p2qtbnf.js +1 -0
- package/dist/chunk-lang-json-hsemyd0r.js +1 -0
- package/dist/chunk-lang-markdown-zkwb4c9n.js +6 -0
- package/dist/{chunk-lang-python-9jk5a49y.js → chunk-lang-python-tewx7006.js} +2 -2
- package/dist/chunk-lang-rust-0ydx84s1.js +1 -0
- package/dist/{chunk-lang-shell-cgq6q0kj.js → chunk-lang-shell-gq1tmdz4.js} +1 -1
- package/dist/chunk-lang-sql-xt1qyycg.js +2 -0
- package/dist/{chunk-lang-toml-xh45nm68.js → chunk-lang-toml-0hamc5fc.js} +1 -1
- package/dist/chunk-lang-typescript-82xf7z2e.js +1 -0
- package/dist/{chunk-lang-xml-1mh879j9.js → chunk-lang-xml-wf6h5pt2.js} +1 -1
- package/dist/{chunk-lang-yaml-jac98srt.js → chunk-lang-yaml-7vk521h1.js} +1 -1
- package/dist/chunk-layout-actions-zh7bezk7.js +1 -0
- package/dist/chunk-menu-items-1n9qargb.js +2 -0
- package/dist/chunk-notes-t15bh04m.js +1 -0
- package/dist/chunk-notice-bell-w3y0h0sr.js +1 -0
- package/dist/chunk-notices-fa3g7qze.js +1 -0
- package/dist/chunk-onboarding-34v3nr17.js +1 -0
- package/dist/chunk-overview-km5vq375.js +1 -0
- package/dist/chunk-palette-a8txdgd0.js +1 -0
- package/dist/chunk-placement-items-wgaxsbvs.js +1 -0
- package/dist/chunk-profile-ypxdbsk1.js +1 -0
- package/dist/chunk-prompt-history-wm9dsy6s.js +1 -0
- package/dist/chunk-prompts-7mhhq7zs.js +1 -0
- package/dist/chunk-room-confirms-t11a1gsw.js +1 -0
- package/dist/chunk-room-menu-2tm2xtfx.js +1 -0
- package/dist/chunk-rooms-menu-button-wxnsxjwc.js +1 -0
- package/dist/chunk-schedules-2ft6xxyw.js +1 -0
- package/dist/chunk-seat-menu-d3npdtx1.js +1 -0
- package/dist/chunk-sheet-hosts-vngvp7wv.js +1 -0
- package/dist/chunk-skill-editor-65n2hsq4.js +8 -0
- package/dist/chunk-skills-shkdwes8.js +1 -0
- package/dist/chunk-switcher-list-g6hhmr1t.js +1 -0
- package/dist/chunk-table-1b6bsq2y.js +1 -0
- package/dist/chunk-table-seats-vy1qhdks.js +1 -0
- package/dist/chunk-table2d-n26b7gd0.js +1 -0
- package/dist/chunk-table3d-8aysyf56.js +4220 -0
- package/dist/chunk-transcript-355faceq.js +1 -0
- package/dist/chunk-where-j5qdt8a4.js +1 -0
- package/dist/chunk-workspace-vrbs6zx1.js +1 -0
- package/dist/chunk-zh-tw-zeg600bk.js +1 -0
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +5 -4
- package/src/api-types.ts +515 -6
- package/src/app/api-http.ts +66 -0
- package/src/app/app.ts +214 -33
- package/src/app/card-log.ts +6 -0
- package/src/app/desk.ts +47 -22
- package/src/app/files.ts +73 -7
- package/src/app/layout-routes.ts +239 -0
- package/src/app/layout.ts +133 -0
- package/src/app/presence.ts +2 -2
- package/src/app/prompt-routes.ts +167 -0
- package/src/app/protocol.ts +63 -1
- package/src/app/room-log.ts +148 -14
- package/src/app/rooms.ts +23 -0
- package/src/app/routes.ts +278 -156
- package/src/app/skill-doc-routes.ts +122 -0
- package/src/app/socket.ts +309 -77
- package/src/app/sockets.ts +108 -20
- package/src/app/surface.ts +72 -36
- package/src/app/team-routes.ts +457 -0
- package/src/app/threads.ts +14 -0
- package/src/app/uploads.ts +33 -7
- package/src/auth/authenticator.ts +71 -8
- package/src/auth/next.ts +52 -0
- package/src/auth/oidc-pages.ts +60 -0
- package/src/auth/oidc.ts +611 -0
- package/src/auth/sessions.ts +193 -0
- package/src/authz.ts +172 -0
- package/src/console-api.ts +122 -25
- package/src/console-server.ts +70 -6
- package/src/gate.ts +90 -0
- package/src/host/access.ts +161 -0
- package/src/host/cards.ts +99 -0
- package/src/host/context.ts +141 -0
- package/src/host/forms.ts +237 -0
- package/src/host/frames.ts +342 -0
- package/src/host/host-app.ts +176 -0
- package/src/host/host-context.ts +49 -0
- package/src/host/host-routes.ts +363 -0
- package/src/host/pages.ts +266 -0
- package/src/host/reads.ts +155 -0
- package/src/host/retention.ts +41 -0
- package/src/host/room-views.ts +137 -0
- package/src/host/rooms-service.ts +414 -0
- package/src/host/stored-json.ts +13 -0
- package/src/host/views.ts +103 -0
- package/src/i18n/zh-tw.ts +309 -10
- package/src/layout-order.ts +210 -0
- package/src/notices/api.ts +43 -0
- package/src/notices/mount.ts +292 -0
- package/src/notices/service.ts +294 -0
- package/src/notices/store.ts +298 -0
- package/src/notices/urls.ts +38 -0
- package/src/onboarding/api.ts +32 -0
- package/src/onboarding/service.ts +149 -0
- package/src/onboarding/steps.ts +75 -0
- package/src/onboarding/waivers.ts +40 -0
- package/src/options.ts +498 -34
- package/src/profile/api.ts +175 -0
- package/src/profile/links.ts +162 -0
- package/src/push/api.ts +189 -52
- package/src/push/dispatch.ts +156 -0
- package/src/push/drain.ts +139 -0
- package/src/push/mutes.ts +17 -0
- package/src/push/outbox.ts +218 -0
- package/src/push/prefs.ts +118 -0
- package/src/push/sender.ts +7 -2
- package/src/push/triggers.ts +56 -5
- package/src/push/vapid.ts +19 -0
- package/src/push/web-push-transport.ts +106 -0
- package/src/push-sender/index.ts +14 -0
- package/src/push-sender/run.ts +118 -0
- package/src/push-sender/session.ts +54 -0
- package/src/visitors.ts +94 -6
- package/src/web-app.ts +674 -0
- package/src/web-plugin.ts +293 -26
- package/dist/chunk-action-menu-7v5fce2v.js +0 -1
- package/dist/chunk-agent-2rw4ydw2.js +0 -1
- package/dist/chunk-agent-sheet-2q24dkkf.js +0 -3
- package/dist/chunk-character-creator-8q6ympxj.js +0 -1
- package/dist/chunk-codeeditor-fgmq298t.js +0 -2
- package/dist/chunk-connectors-mr53r7fh.js +0 -1
- package/dist/chunk-conversations-4vy2z6w9.js +0 -1
- package/dist/chunk-entry-jg1eem82.js +0 -1
- package/dist/chunk-group-scene-na95b1te.js +0 -1
- package/dist/chunk-index-11rjg8a8.js +0 -1
- package/dist/chunk-index-1y50d6sj.js +0 -2
- package/dist/chunk-index-5y2r0eaj.js +0 -47
- package/dist/chunk-index-7fphxxzd.js +0 -14
- package/dist/chunk-index-80f1zkp6.css +0 -1
- package/dist/chunk-index-87h45frg.js +0 -2
- package/dist/chunk-index-bnwmssyn.js +0 -2
- package/dist/chunk-index-ckgbszwt.js +0 -51
- package/dist/chunk-index-fmjjekhd.js +0 -2
- package/dist/chunk-index-t1m65yn6.js +0 -13
- package/dist/chunk-index-t711t17m.js +0 -2
- package/dist/chunk-index-txv7qvyy.js +0 -2
- package/dist/chunk-index-wr3dg24d.js +0 -2
- package/dist/chunk-index-yx683d5n.js +0 -2
- package/dist/chunk-lang-css-fkdg3x8x.js +0 -1
- package/dist/chunk-lang-html-v8h9g977.js +0 -1
- package/dist/chunk-lang-javascript-ykb2vhaa.js +0 -1
- package/dist/chunk-lang-json-2rze3rpy.js +0 -1
- package/dist/chunk-lang-markdown-6w2htnfc.js +0 -6
- package/dist/chunk-lang-rust-24fggcqx.js +0 -1
- package/dist/chunk-lang-sql-gjywb9ke.js +0 -2
- package/dist/chunk-lang-typescript-qh6rebyb.js +0 -1
- package/dist/chunk-menu-items-zjpyj51b.js +0 -2
- package/dist/chunk-notes-sjba7kw3.js +0 -1
- package/dist/chunk-overview-26r77kb6.js +0 -1
- package/dist/chunk-room-confirms-851184ep.js +0 -1
- package/dist/chunk-room-menu-8y9c026y.js +0 -1
- package/dist/chunk-skills-56s4hdbn.js +0 -1
- package/dist/chunk-table-qykpvp32.js +0 -1
- package/dist/chunk-table2d-k60ressj.js +0 -1
- package/dist/chunk-table3d-bv2ajk39.js +0 -4220
- package/dist/chunk-transcript-vekz1mh6.js +0 -1
- package/dist/chunk-workspace-hevefd0d.js +0 -1
- package/dist/chunk-zh-tw-5r2wjre0.js +0 -1
package/src/app/routes.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
AgentChange,
|
|
3
2
|
AgentDirectory,
|
|
4
3
|
AgentTeam,
|
|
5
4
|
AvatarStudio,
|
|
@@ -9,23 +8,22 @@ import type {
|
|
|
9
8
|
Logger,
|
|
10
9
|
ModeChange,
|
|
11
10
|
ScheduleStore,
|
|
11
|
+
SkillRegistry,
|
|
12
12
|
Speaker,
|
|
13
13
|
} from "pi-roundtable";
|
|
14
|
-
import {
|
|
15
|
-
import { CharacterError
|
|
14
|
+
import { PluginError } from "pi-roundtable";
|
|
15
|
+
import { CharacterError } from "pi-roundtable/characters";
|
|
16
16
|
import { AgentError } from "pi-roundtable/kit";
|
|
17
17
|
import type {
|
|
18
|
-
AgentPatch,
|
|
19
|
-
AgentProfileView,
|
|
20
18
|
ApiError,
|
|
21
19
|
CharacterRefusal,
|
|
22
|
-
CharacterRequest,
|
|
23
20
|
MessagesView,
|
|
24
21
|
ModelsView,
|
|
25
22
|
RoomsView,
|
|
26
23
|
SchedulesView,
|
|
27
24
|
ScheduleView,
|
|
28
25
|
SessionView,
|
|
26
|
+
SkillView,
|
|
29
27
|
WorkspaceConflict,
|
|
30
28
|
WorkspaceNew,
|
|
31
29
|
WorkspaceRefusalBody,
|
|
@@ -34,14 +32,27 @@ import type {
|
|
|
34
32
|
WorkspaceSave,
|
|
35
33
|
} from "../api-types.ts";
|
|
36
34
|
import type { Authenticated } from "../auth/authenticator.ts";
|
|
35
|
+
import { type ConsolePresentation, message } from "../features.ts";
|
|
36
|
+
import type { Gate } from "../gate.ts";
|
|
37
|
+
import type { HostApp } from "../host/host-app.ts";
|
|
38
|
+
import type { RoomReads } from "../host/reads.ts";
|
|
39
|
+
import { readsOf, withExpiredFiles } from "../host/room-views.ts";
|
|
37
40
|
import { json } from "../http.ts";
|
|
41
|
+
import type { ResolvedOptions } from "../options.ts";
|
|
38
42
|
import type { PushMutes } from "../push/mutes.ts";
|
|
39
|
-
import {
|
|
43
|
+
import { viewerOf, type WebAppRegistry } from "../web-app.ts";
|
|
44
|
+
import { bad, bodyOf, HttpError, notFound } from "./api-http.ts";
|
|
40
45
|
import type { CardLog } from "./card-log.ts";
|
|
41
46
|
import { type FileDir, fileResponse, isFileId } from "./files.ts";
|
|
47
|
+
import type { LayoutStore } from "./layout.ts";
|
|
48
|
+
import { LayoutRoutes } from "./layout-routes.ts";
|
|
49
|
+
import { PromptApi, type PromptApiOptions } from "./prompt-routes.ts";
|
|
42
50
|
import { PAGE_LIMIT, type RoomLog } from "./room-log.ts";
|
|
43
51
|
import type { Rooms } from "./rooms.ts";
|
|
52
|
+
import { SkillDocApi } from "./skill-doc-routes.ts";
|
|
44
53
|
import type { AppSurface } from "./surface.ts";
|
|
54
|
+
import { TeamApi } from "./team-routes.ts";
|
|
55
|
+
import type { WebThreads } from "./threads.ts";
|
|
45
56
|
import { UploadRefusal, type Uploads } from "./uploads.ts";
|
|
46
57
|
import {
|
|
47
58
|
EDIT_MAX_BYTES,
|
|
@@ -50,38 +61,82 @@ import {
|
|
|
50
61
|
WorkspaceRefusal,
|
|
51
62
|
} from "./workspace-files.ts";
|
|
52
63
|
|
|
53
|
-
/** The largest JSON body a write reads: 64 KiB. */
|
|
54
|
-
const MAX_BODY_BYTES = 64 * 1024;
|
|
55
|
-
/** The largest body of a character write: 4 KiB. */
|
|
56
|
-
const MAX_CHARACTER_BYTES = 4 * 1024;
|
|
57
|
-
|
|
58
64
|
export interface AppApiOptions {
|
|
65
|
+
/** The host's wording: a refusal reaches a client of the API in this locale when the catalog has it. */
|
|
66
|
+
presentation?: ConsolePresentation;
|
|
59
67
|
surface: AppSurface;
|
|
60
68
|
rooms: Rooms;
|
|
61
|
-
log: RoomLog
|
|
62
|
-
cards: CardLog
|
|
69
|
+
log: Pick<RoomLog, "page" | "lastOf" | "removeRoom">;
|
|
70
|
+
cards: Pick<CardLog, "during" | "removeRoom">;
|
|
63
71
|
uploads: Uploads;
|
|
64
72
|
/** Where stored files are read from. */
|
|
65
73
|
files: FileDir;
|
|
66
74
|
modes: Pick<ConversationModes, "set" | "peek">;
|
|
67
75
|
/** The rooms whose push the owners muted. */
|
|
68
|
-
mutes: Pick<PushMutes, "set">;
|
|
76
|
+
mutes: Pick<PushMutes, "set" | "setMany">;
|
|
77
|
+
/** The dispatch threads, which go with the room they hang under. */
|
|
78
|
+
threads: Pick<WebThreads, "under" | "removeUnder">;
|
|
79
|
+
/** The owners' shared arrangement of the room list: pins and folders. */
|
|
80
|
+
layout: LayoutStore;
|
|
69
81
|
conversations: Pick<ConversationPort, "startFresh">;
|
|
70
|
-
identity: Pick<IdentityService, "
|
|
82
|
+
identity: Pick<IdentityService, "principal">;
|
|
71
83
|
/** Present when the schedules pane is served. */
|
|
72
|
-
schedules?: Pick<ScheduleStore, "all" | "remove">;
|
|
73
|
-
directory: Pick<AgentDirectory, "agent">;
|
|
84
|
+
schedules?: Pick<ScheduleStore, "all" | "get" | "remove">;
|
|
85
|
+
directory: Pick<AgentDirectory, "agent" | "agents" | "group" | "groups">;
|
|
74
86
|
team: Pick<
|
|
75
87
|
AgentTeam,
|
|
76
|
-
|
|
88
|
+
| "update"
|
|
89
|
+
| "promptHistory"
|
|
90
|
+
| "dress"
|
|
91
|
+
| "usableModels"
|
|
92
|
+
| "modelOf"
|
|
93
|
+
| "defaultModel"
|
|
94
|
+
| "createAgent"
|
|
95
|
+
| "archive"
|
|
96
|
+
| "restore"
|
|
97
|
+
| "remove"
|
|
98
|
+
| "createGroup"
|
|
99
|
+
| "updateGroup"
|
|
77
100
|
>;
|
|
101
|
+
/** Present when the host has the skills addon: the catalog an agent's skills are chosen from, and the change of what it carries. */
|
|
102
|
+
skills?: {
|
|
103
|
+
catalog(): SkillView[];
|
|
104
|
+
attach(
|
|
105
|
+
agent: string,
|
|
106
|
+
add: readonly string[],
|
|
107
|
+
remove: readonly string[],
|
|
108
|
+
): Promise<string[]>;
|
|
109
|
+
/** The registry's documents: a skill's SKILL.md read, saved and made, and its versions. */
|
|
110
|
+
documents: Pick<
|
|
111
|
+
SkillRegistry,
|
|
112
|
+
"document" | "saveDocument" | "createDocument" | "documentHistory"
|
|
113
|
+
>;
|
|
114
|
+
};
|
|
115
|
+
/** Present when the host has prompt documents (the prompts plugin): the main prompts the owner edits. */
|
|
116
|
+
prompts?: PromptApiOptions["prompts"];
|
|
78
117
|
avatars: Pick<AvatarStudio, "serve">;
|
|
118
|
+
/** The read cursors, for each room's unread count and who has read what. */
|
|
119
|
+
reads?: Pick<RoomReads, "unread" | "cursorOf" | "marks">;
|
|
120
|
+
/** Who may be served a file bound to a host room or a page. */
|
|
121
|
+
host?: Pick<HostApp, "mayRead">;
|
|
122
|
+
/** The name the app shows for a person. */
|
|
123
|
+
nameOf?(principalId: string): Promise<string>;
|
|
79
124
|
/** Present when the workspace pane is served. */
|
|
80
125
|
workspace?: WorkspaceFiles;
|
|
81
126
|
/** The mount, which a room's agent picture hangs under. */
|
|
82
127
|
mount: string;
|
|
83
128
|
/** How the console signs the person in, as the session reports it. */
|
|
84
129
|
authMode: SessionView["auth"];
|
|
130
|
+
/** Who the app admits; the session reports the person's tier only where it admits more than owners. */
|
|
131
|
+
admission?: ResolvedOptions["admission"];
|
|
132
|
+
/** The area handlers the pieces of the app plug in; without it the API is the owners' as in 0.25. */
|
|
133
|
+
registry?: Pick<WebAppRegistry, "areaFor">;
|
|
134
|
+
/** The onboarding gate; without it nobody is gated. */
|
|
135
|
+
gate?: Pick<Gate, "required">;
|
|
136
|
+
/** The tiers whose people may use push; default owners. */
|
|
137
|
+
pushTiers?: ResolvedOptions["push"]["tiers"];
|
|
138
|
+
/** Whether the yolo and long-running switches are served; `PUT rooms/<key>/mode` answers 404 without them. Default true. */
|
|
139
|
+
modeSwitches?: boolean;
|
|
85
140
|
/** The quiet hours of push, as the settings page shows them. */
|
|
86
141
|
quietHours?: NonNullable<SessionView["quietHours"]>;
|
|
87
142
|
/** Called after a change the other pages should refetch. */
|
|
@@ -89,19 +144,6 @@ export interface AppApiOptions {
|
|
|
89
144
|
logger: Logger;
|
|
90
145
|
}
|
|
91
146
|
|
|
92
|
-
class HttpError extends Error {
|
|
93
|
-
constructor(
|
|
94
|
-
readonly status: number,
|
|
95
|
-
message: string,
|
|
96
|
-
) {
|
|
97
|
-
super(message);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const bad = (message: string) => new HttpError(400, message);
|
|
102
|
-
const notFound = () =>
|
|
103
|
-
new HttpError(404, "There is no such room, agent or file.");
|
|
104
|
-
|
|
105
147
|
/** A refusal of the workspace pane as the HTTP error the owner reads. */
|
|
106
148
|
function refusalOf(refusal: WorkspaceRefusal): HttpError {
|
|
107
149
|
const { status } = refusal;
|
|
@@ -183,33 +225,6 @@ export function partsOf(path: string): string[] | undefined {
|
|
|
183
225
|
}
|
|
184
226
|
}
|
|
185
227
|
|
|
186
|
-
async function bodyOf(
|
|
187
|
-
request: Request,
|
|
188
|
-
limit = MAX_BODY_BYTES,
|
|
189
|
-
): Promise<Record<string, unknown>> {
|
|
190
|
-
const raw = await readLimited(request, limit);
|
|
191
|
-
if (!raw) throw new HttpError(413, "The body is too large.");
|
|
192
|
-
let value: unknown;
|
|
193
|
-
try {
|
|
194
|
-
value = JSON.parse(raw.toString("utf8"));
|
|
195
|
-
} catch {
|
|
196
|
-
throw bad("The body is not JSON.");
|
|
197
|
-
}
|
|
198
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
199
|
-
throw bad("The body is not an object.");
|
|
200
|
-
return value as Record<string, unknown>;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const optionalText = (
|
|
204
|
-
body: Record<string, unknown>,
|
|
205
|
-
key: string,
|
|
206
|
-
): string | undefined => {
|
|
207
|
-
const value = body[key];
|
|
208
|
-
if (value === undefined) return undefined;
|
|
209
|
-
if (typeof value !== "string") throw bad(`${key} must be text.`);
|
|
210
|
-
return value;
|
|
211
|
-
};
|
|
212
|
-
|
|
213
228
|
/**
|
|
214
229
|
* The owner app's REST under `<mount>/api/` and `<mount>/avatars/`. Every route is behind the
|
|
215
230
|
* authenticator, which also checks the `Origin` of a write; a write also reads the person's tier
|
|
@@ -218,9 +233,44 @@ const optionalText = (
|
|
|
218
233
|
*/
|
|
219
234
|
export class AppApi {
|
|
220
235
|
readonly #options: AppApiOptions;
|
|
236
|
+
readonly #team: TeamApi;
|
|
237
|
+
readonly #layout: LayoutRoutes;
|
|
238
|
+
readonly #prompts: PromptApi | undefined;
|
|
239
|
+
readonly #skillDocs: SkillDocApi | undefined;
|
|
221
240
|
|
|
222
241
|
constructor(options: AppApiOptions) {
|
|
223
242
|
this.#options = options;
|
|
243
|
+
this.#layout = new LayoutRoutes({
|
|
244
|
+
layout: options.layout,
|
|
245
|
+
mutes: options.mutes,
|
|
246
|
+
placeable: () => options.rooms.placeable(),
|
|
247
|
+
home: () => options.rooms.homeKey(),
|
|
248
|
+
owner: (admission) => this.#owner(admission),
|
|
249
|
+
changed: () => options.changed(),
|
|
250
|
+
});
|
|
251
|
+
this.#prompts = options.prompts
|
|
252
|
+
? new PromptApi({ prompts: options.prompts })
|
|
253
|
+
: undefined;
|
|
254
|
+
this.#skillDocs = options.skills
|
|
255
|
+
? new SkillDocApi({
|
|
256
|
+
skills: options.skills.documents,
|
|
257
|
+
catalog: () => options.skills?.catalog() ?? [],
|
|
258
|
+
})
|
|
259
|
+
: undefined;
|
|
260
|
+
this.#team = new TeamApi({
|
|
261
|
+
surface: options.surface.surface,
|
|
262
|
+
rooms: options.rooms,
|
|
263
|
+
directory: options.directory,
|
|
264
|
+
team: options.team,
|
|
265
|
+
skills: options.skills,
|
|
266
|
+
log: options.log,
|
|
267
|
+
cards: options.cards,
|
|
268
|
+
mutes: options.mutes,
|
|
269
|
+
modes: options.modes,
|
|
270
|
+
threads: options.threads,
|
|
271
|
+
layout: options.layout,
|
|
272
|
+
changed: () => options.changed(),
|
|
273
|
+
});
|
|
224
274
|
}
|
|
225
275
|
|
|
226
276
|
/** Answers a path of the app's, or undefined for any other. `rest` is what follows the mount. */
|
|
@@ -228,25 +278,76 @@ export class AppApi {
|
|
|
228
278
|
request: Request,
|
|
229
279
|
rest: string,
|
|
230
280
|
admission: Authenticated,
|
|
281
|
+
): Promise<Response | undefined> {
|
|
282
|
+
const response = await this.#answer(request, rest, admission);
|
|
283
|
+
return response && this.#options.presentation
|
|
284
|
+
? this.#worded(response, this.#options.presentation)
|
|
285
|
+
: response;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** A refusal's `error` in the host's wording; the page words it again from the same catalog and finds the wording already there. */
|
|
289
|
+
async #worded(
|
|
290
|
+
response: Response,
|
|
291
|
+
presentation: ConsolePresentation,
|
|
292
|
+
): Promise<Response> {
|
|
293
|
+
if (response.status < 400) return response;
|
|
294
|
+
const body: unknown = await response
|
|
295
|
+
.clone()
|
|
296
|
+
.json()
|
|
297
|
+
.catch(() => undefined);
|
|
298
|
+
const error = (body as { error?: unknown } | undefined)?.error;
|
|
299
|
+
if (typeof error !== "string") return response;
|
|
300
|
+
const worded = message(presentation, error);
|
|
301
|
+
if (worded === error) return response;
|
|
302
|
+
const headers = new Headers(response.headers);
|
|
303
|
+
headers.delete("content-length");
|
|
304
|
+
return new Response(
|
|
305
|
+
JSON.stringify({ ...(body as object), error: worded }),
|
|
306
|
+
{
|
|
307
|
+
status: response.status,
|
|
308
|
+
headers,
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async #answer(
|
|
314
|
+
request: Request,
|
|
315
|
+
rest: string,
|
|
316
|
+
admission: Authenticated,
|
|
231
317
|
): Promise<Response | undefined> {
|
|
232
318
|
const parts = partsOf(rest);
|
|
233
319
|
if (!parts) return undefined;
|
|
234
320
|
const [first, second] = parts;
|
|
321
|
+
const area =
|
|
322
|
+
first === "api" && second !== undefined
|
|
323
|
+
? this.#options.registry?.areaFor(second)
|
|
324
|
+
: undefined;
|
|
235
325
|
const mine =
|
|
326
|
+
area !== undefined ||
|
|
236
327
|
(first === "avatars" && parts.length === 2) ||
|
|
237
328
|
(first === "api" &&
|
|
238
329
|
[
|
|
239
330
|
"session",
|
|
240
331
|
"rooms",
|
|
332
|
+
"folders",
|
|
241
333
|
"uploads",
|
|
242
334
|
"files",
|
|
243
335
|
"schedules",
|
|
244
336
|
"agents",
|
|
337
|
+
"groups",
|
|
338
|
+
"archive",
|
|
245
339
|
"models",
|
|
246
340
|
"workspace",
|
|
341
|
+
"prompts",
|
|
342
|
+
"skill-docs",
|
|
247
343
|
].includes(second ?? ""));
|
|
248
344
|
if (!mine) return undefined;
|
|
249
345
|
try {
|
|
346
|
+
if (area)
|
|
347
|
+
return await area.handle(request, parts.slice(2), {
|
|
348
|
+
visitor: admission.visitor,
|
|
349
|
+
viewer: viewerOf(admission.visitor),
|
|
350
|
+
});
|
|
250
351
|
return await this.#route(request, parts, admission);
|
|
251
352
|
} catch (error) {
|
|
252
353
|
if (error instanceof HttpError || error instanceof UploadRefusal)
|
|
@@ -269,16 +370,13 @@ export class AppApi {
|
|
|
269
370
|
}
|
|
270
371
|
}
|
|
271
372
|
|
|
272
|
-
/** The owner who writes:
|
|
373
|
+
/** The owner who writes: the tier the request was admitted at, as a speaker for the calls that need one. */
|
|
273
374
|
async #owner(admission: Authenticated): Promise<Speaker> {
|
|
274
|
-
const { principal } = admission.visitor;
|
|
275
|
-
const tier = await this.#options.identity.tierOf(principal.id);
|
|
375
|
+
const { principal, tier, speakerId } = admission.visitor;
|
|
276
376
|
if (tier !== "owner")
|
|
277
377
|
throw new HttpError(403, "Only an owner may do this.");
|
|
278
378
|
return {
|
|
279
|
-
id:
|
|
280
|
-
? `${admission.actor.provider}:${admission.actor.subject}`
|
|
281
|
-
: principal.id,
|
|
379
|
+
id: speakerId,
|
|
282
380
|
name: principal.displayName,
|
|
283
381
|
tier,
|
|
284
382
|
principalId: principal.id,
|
|
@@ -299,40 +397,67 @@ export class AppApi {
|
|
|
299
397
|
new Response("Not found", { status: 404 })
|
|
300
398
|
);
|
|
301
399
|
}
|
|
302
|
-
if (area === "session" && method === "GET")
|
|
400
|
+
if (area === "session" && method === "GET")
|
|
401
|
+
return await this.#session(admission);
|
|
303
402
|
if (area === "models" && method === "GET")
|
|
304
403
|
return json({
|
|
305
404
|
models: await this.#options.team.usableModels(),
|
|
306
405
|
} satisfies ModelsView);
|
|
307
406
|
if (area === "rooms") return this.#rooms(request, parts, admission);
|
|
308
|
-
if (area === "
|
|
309
|
-
|
|
407
|
+
if (area === "folders")
|
|
408
|
+
return this.#layout.folders(request, parts, admission);
|
|
409
|
+
// An upload is the sender's own, whoever they are; a file answers by the room it is bound to.
|
|
410
|
+
if (area === "uploads" && method === "POST" && !id)
|
|
310
411
|
return json(
|
|
311
|
-
await this.#options.uploads.receive(
|
|
412
|
+
await this.#options.uploads.receive(
|
|
413
|
+
request,
|
|
414
|
+
admission.visitor.principal.id,
|
|
415
|
+
),
|
|
312
416
|
);
|
|
313
|
-
}
|
|
314
417
|
if (area === "uploads" && method === "DELETE" && id && !action) {
|
|
315
|
-
|
|
316
|
-
if (!this.#options.uploads.discard(id, owner.principalId))
|
|
418
|
+
if (!this.#options.uploads.discard(id, admission.visitor.principal.id))
|
|
317
419
|
throw notFound();
|
|
318
420
|
return json({ ok: true });
|
|
319
421
|
}
|
|
320
422
|
if (area === "files" && method === "GET" && id && !action)
|
|
321
|
-
return this.#file(id);
|
|
423
|
+
return await this.#file(id, admission);
|
|
322
424
|
if (area === "workspace") return this.#workspace(request, parts, admission);
|
|
323
425
|
if (area === "schedules") return this.#schedules(method, id, admission);
|
|
324
|
-
|
|
325
|
-
|
|
426
|
+
const owner = () => this.#owner(admission);
|
|
427
|
+
if (area === "agents") return this.#team.agents(request, parts, owner);
|
|
428
|
+
if (area === "groups") return this.#team.groups(request, parts, owner);
|
|
429
|
+
if (area === "archive" && method === "GET" && !id)
|
|
430
|
+
return this.#team.archived(owner);
|
|
431
|
+
if (area === "prompts") {
|
|
432
|
+
if (!this.#prompts)
|
|
433
|
+
throw new HttpError(404, "This host has no prompts to edit.");
|
|
434
|
+
return this.#prompts.handle(request, parts, owner);
|
|
435
|
+
}
|
|
436
|
+
if (area === "skill-docs") {
|
|
437
|
+
if (!this.#skillDocs)
|
|
438
|
+
throw new HttpError(404, "This host has no skills.");
|
|
439
|
+
return this.#skillDocs.handle(request, parts, owner);
|
|
440
|
+
}
|
|
326
441
|
throw new HttpError(404, "There is no such API.");
|
|
327
442
|
}
|
|
328
443
|
|
|
329
|
-
#session(admission: Authenticated): Response {
|
|
330
|
-
const { principal } = admission.visitor;
|
|
444
|
+
async #session(admission: Authenticated): Promise<Response> {
|
|
445
|
+
const { principal, tier } = admission.visitor;
|
|
446
|
+
const { admission: admits = "owners", gate } = this.#options;
|
|
447
|
+
const pushTiers = this.#options.pushTiers ?? ["owner"];
|
|
448
|
+
const owed =
|
|
449
|
+
admits === "access" && gate
|
|
450
|
+
? await gate.required(viewerOf(admission.visitor))
|
|
451
|
+
: [];
|
|
331
452
|
return json({
|
|
332
453
|
principal: { id: principal.id, name: principal.displayName },
|
|
333
454
|
surface: this.#options.surface.surface,
|
|
334
455
|
auth: this.#options.authMode,
|
|
335
|
-
|
|
456
|
+
...(admits === "access" ? { tier } : {}),
|
|
457
|
+
...(owed.length > 0 ? { gate: owed } : {}),
|
|
458
|
+
...(pushTiers.includes(tier) ? { push: true as const } : {}),
|
|
459
|
+
...(this.#options.prompts ? { prompts: true } : {}),
|
|
460
|
+
...(this.#options.skills ? { skillEdit: true } : {}),
|
|
336
461
|
...(this.#options.quietHours
|
|
337
462
|
? { quietHours: this.#options.quietHours }
|
|
338
463
|
: {}),
|
|
@@ -350,9 +475,24 @@ export class AppApi {
|
|
|
350
475
|
const { method } = request;
|
|
351
476
|
const [, , key, action] = parts;
|
|
352
477
|
const { rooms, log, cards, modes, mutes, conversations } = this.#options;
|
|
478
|
+
// The agent rooms are the owners'; to anyone else this API does not exist.
|
|
479
|
+
if (admission.visitor.tier !== "owner") throw notFound();
|
|
353
480
|
if (!key) {
|
|
354
481
|
if (method !== "GET") throw new HttpError(405, "Method not allowed.");
|
|
355
|
-
|
|
482
|
+
const listed = await rooms.list();
|
|
483
|
+
// What each room holds past this person's cursor; a room they never read starts from what it has now.
|
|
484
|
+
const unread = await this.#options.reads?.unread(
|
|
485
|
+
admission.visitor.principal.id,
|
|
486
|
+
listed.map((one) => one.key),
|
|
487
|
+
new Set(listed.map((one) => one.key)),
|
|
488
|
+
);
|
|
489
|
+
return json({
|
|
490
|
+
rooms: listed.map((one) => {
|
|
491
|
+
const count = unread?.get(one.key);
|
|
492
|
+
return count ? { ...one, unread: count } : one;
|
|
493
|
+
}),
|
|
494
|
+
...(await this.#layout.view()),
|
|
495
|
+
} satisfies RoomsView);
|
|
356
496
|
}
|
|
357
497
|
const room = await rooms.resolve(key);
|
|
358
498
|
if (!room) throw notFound();
|
|
@@ -372,17 +512,26 @@ export class AppApi {
|
|
|
372
512
|
// The cards stand among the messages by the time they opened; a page that has older
|
|
373
513
|
// messages leaves the cards before its first one to the older page.
|
|
374
514
|
const first = page.messages[0];
|
|
515
|
+
const { reads, nameOf } = this.#options;
|
|
516
|
+
const self = admission.visitor.principal.id;
|
|
517
|
+
const cursor = await reads?.cursorOf(room.key, self);
|
|
375
518
|
return json({
|
|
376
519
|
...page,
|
|
520
|
+
messages: withExpiredFiles(page.messages, this.#options.files),
|
|
377
521
|
cards: await cards.during(room.key, {
|
|
378
522
|
...(page.more && first ? { from: first.at } : {}),
|
|
379
523
|
...(beforeId === undefined ? {} : { before: beforeId }),
|
|
380
524
|
}),
|
|
525
|
+
...(reads && nameOf
|
|
526
|
+
? { reads: await readsOf(reads, room.key, self, nameOf) }
|
|
527
|
+
: {}),
|
|
528
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
381
529
|
} satisfies MessagesView);
|
|
382
530
|
}
|
|
383
531
|
if (action === "mode" && method === "PUT" && parts.length === 4) {
|
|
384
532
|
// Only an agent's or group's room has modes; a thread runs no turn of the owner's.
|
|
385
|
-
if (room.readOnly
|
|
533
|
+
if (room.readOnly || this.#options.modeSwitches === false)
|
|
534
|
+
throw notFound();
|
|
386
535
|
const owner = await this.#owner(admission);
|
|
387
536
|
const body = await bodyOf(request);
|
|
388
537
|
const change: ModeChange = {};
|
|
@@ -404,6 +553,12 @@ export class AppApi {
|
|
|
404
553
|
await conversations.startFresh(room.key);
|
|
405
554
|
return json({ ok: true });
|
|
406
555
|
}
|
|
556
|
+
if (
|
|
557
|
+
(action === "folder" || action === "pin") &&
|
|
558
|
+
method === "PUT" &&
|
|
559
|
+
parts.length === 4
|
|
560
|
+
)
|
|
561
|
+
return this.#layout.place(request, room, action, admission);
|
|
407
562
|
if (action === "mute" && method === "PUT" && parts.length === 4) {
|
|
408
563
|
// Only an agent's or group's room has a mute of its own; a thread's posts are its parent's business.
|
|
409
564
|
if (room.readOnly) throw notFound();
|
|
@@ -418,10 +573,20 @@ export class AppApi {
|
|
|
418
573
|
throw new HttpError(404, "There is no such API.");
|
|
419
574
|
}
|
|
420
575
|
|
|
421
|
-
#file(id: string): Response {
|
|
576
|
+
async #file(id: string, admission: Authenticated): Promise<Response> {
|
|
422
577
|
if (!isFileId(id)) throw notFound();
|
|
423
|
-
const
|
|
424
|
-
|
|
578
|
+
const { files, host } = this.#options;
|
|
579
|
+
// A file bound to a host room or page is for the people it is open to, owners too; every other stored file belongs to an agent room, which only owners read.
|
|
580
|
+
const bound = files.binding(id)?.room;
|
|
581
|
+
if (bound === undefined) {
|
|
582
|
+
if (admission.visitor.tier !== "owner") throw notFound();
|
|
583
|
+
} else if (
|
|
584
|
+
!host ||
|
|
585
|
+
!(await host.mayRead(viewerOf(admission.visitor), bound))
|
|
586
|
+
)
|
|
587
|
+
throw notFound();
|
|
588
|
+
const meta = files.meta(id);
|
|
589
|
+
const blob = files.blob(id);
|
|
425
590
|
if (!meta || !blob) throw notFound();
|
|
426
591
|
return fileResponse(meta, blob);
|
|
427
592
|
}
|
|
@@ -541,6 +706,18 @@ export class AppApi {
|
|
|
541
706
|
throw new HttpError(404, "There is no such API.");
|
|
542
707
|
}
|
|
543
708
|
|
|
709
|
+
/** Whether a schedule is the visitor's own: its creator id names their principal. */
|
|
710
|
+
async #ownSchedule(
|
|
711
|
+
createdById: string,
|
|
712
|
+
principalId: string,
|
|
713
|
+
): Promise<boolean> {
|
|
714
|
+
if (createdById === principalId) return true;
|
|
715
|
+
// A schedule from 0.8 names its creator by a user id that the backfill made a principal's id.
|
|
716
|
+
return (
|
|
717
|
+
(await this.#options.identity.principal(createdById))?.id === principalId
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
|
|
544
721
|
async #schedules(
|
|
545
722
|
method: string,
|
|
546
723
|
id: string | undefined,
|
|
@@ -548,11 +725,19 @@ export class AppApi {
|
|
|
548
725
|
): Promise<Response> {
|
|
549
726
|
const { schedules, rooms } = this.#options;
|
|
550
727
|
if (!schedules) throw new HttpError(404, "This pane is not served.");
|
|
728
|
+
const { tier, principal } = admission.visitor;
|
|
729
|
+
const owner = tier === "owner";
|
|
551
730
|
if (method === "GET" && !id) {
|
|
552
731
|
const all = await schedules.all();
|
|
553
732
|
const views: ScheduleView[] = [];
|
|
554
733
|
for (const schedule of all) {
|
|
555
|
-
|
|
734
|
+
// The owners see every schedule; anyone else sees their own, and no room of the owners.
|
|
735
|
+
if (
|
|
736
|
+
!owner &&
|
|
737
|
+
!(await this.#ownSchedule(schedule.createdById, principal.id))
|
|
738
|
+
)
|
|
739
|
+
continue;
|
|
740
|
+
const room = owner ? await rooms.resolve(schedule.channel) : undefined;
|
|
556
741
|
views.push({
|
|
557
742
|
id: schedule.id,
|
|
558
743
|
...(room ? { room: room.key } : {}),
|
|
@@ -572,9 +757,17 @@ export class AppApi {
|
|
|
572
757
|
return json({ schedules: views } satisfies SchedulesView);
|
|
573
758
|
}
|
|
574
759
|
if (method === "DELETE" && id) {
|
|
575
|
-
await this.#owner(admission);
|
|
576
760
|
const number = Number(id);
|
|
577
761
|
if (!Number.isInteger(number)) throw bad("The schedule id is a number.");
|
|
762
|
+
// Another person's schedule is not there to remove, for anyone but the owners.
|
|
763
|
+
if (!owner) {
|
|
764
|
+
const found = await schedules.get(number);
|
|
765
|
+
if (
|
|
766
|
+
!found ||
|
|
767
|
+
!(await this.#ownSchedule(found.createdById, principal.id))
|
|
768
|
+
)
|
|
769
|
+
throw new HttpError(404, "There is no such schedule.");
|
|
770
|
+
}
|
|
578
771
|
const removed = await schedules.remove(number);
|
|
579
772
|
if (!removed) throw new HttpError(404, "There is no such schedule.");
|
|
580
773
|
this.#options.changed();
|
|
@@ -582,75 +775,4 @@ export class AppApi {
|
|
|
582
775
|
}
|
|
583
776
|
throw new HttpError(405, "Method not allowed.");
|
|
584
777
|
}
|
|
585
|
-
|
|
586
|
-
async #agent(
|
|
587
|
-
request: Request,
|
|
588
|
-
name: string,
|
|
589
|
-
action: string | undefined,
|
|
590
|
-
admission: Authenticated,
|
|
591
|
-
): Promise<Response> {
|
|
592
|
-
const { directory, team } = this.#options;
|
|
593
|
-
const agent = directory.agent(name);
|
|
594
|
-
if (agent?.status !== "active") throw notFound();
|
|
595
|
-
if (!action && request.method === "GET") return json(this.#profile(name));
|
|
596
|
-
if (!action && request.method === "PATCH") {
|
|
597
|
-
await this.#owner(admission);
|
|
598
|
-
const body = await bodyOf(request);
|
|
599
|
-
const patch: AgentPatch = {};
|
|
600
|
-
for (const key of [
|
|
601
|
-
"displayName",
|
|
602
|
-
"prompt",
|
|
603
|
-
"model",
|
|
604
|
-
"thinking",
|
|
605
|
-
] as const) {
|
|
606
|
-
const value = optionalText(body, key);
|
|
607
|
-
if (value !== undefined) patch[key] = value;
|
|
608
|
-
}
|
|
609
|
-
const change: AgentChange = patch;
|
|
610
|
-
await team.update(name, change);
|
|
611
|
-
this.#options.changed();
|
|
612
|
-
return json(this.#profile(name));
|
|
613
|
-
}
|
|
614
|
-
if (action === "character" && request.method === "PUT") {
|
|
615
|
-
await this.#owner(admission);
|
|
616
|
-
const body = await bodyOf(request, MAX_CHARACTER_BYTES);
|
|
617
|
-
const { character } = body;
|
|
618
|
-
if (
|
|
619
|
-
typeof character !== "object" ||
|
|
620
|
-
character === null ||
|
|
621
|
-
Array.isArray(character)
|
|
622
|
-
)
|
|
623
|
-
throw bad("character must be an object.");
|
|
624
|
-
const asked: CharacterRequest = {
|
|
625
|
-
character: character as CharacterRequest["character"],
|
|
626
|
-
};
|
|
627
|
-
// `dress` words a refusal as an agent error; checking here first names the field.
|
|
628
|
-
resolveCharacter(asked.character, characterOf(agent));
|
|
629
|
-
await team.dress(name, asked.character);
|
|
630
|
-
this.#options.changed();
|
|
631
|
-
return json(this.#profile(name));
|
|
632
|
-
}
|
|
633
|
-
throw new HttpError(404, "There is no such API.");
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
#profile(name: string): AgentProfileView {
|
|
637
|
-
const { directory, team, rooms, surface } = this.#options;
|
|
638
|
-
const agent = directory.agent(name);
|
|
639
|
-
if (!agent) throw notFound();
|
|
640
|
-
const effective = team.modelOf(name);
|
|
641
|
-
return {
|
|
642
|
-
name: agent.name,
|
|
643
|
-
displayName: agent.displayName,
|
|
644
|
-
prompt: agent.prompt,
|
|
645
|
-
...(agent.model ? { model: agent.model } : {}),
|
|
646
|
-
...(agent.thinking ? { thinking: agent.thinking } : {}),
|
|
647
|
-
effective: {
|
|
648
|
-
model: effective.model,
|
|
649
|
-
thinking: String(effective.thinking),
|
|
650
|
-
},
|
|
651
|
-
avatar: rooms.avatarOf(agent),
|
|
652
|
-
character: characterOf(agent),
|
|
653
|
-
room: `${surface.surface}:agent.${agent.name}`,
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
778
|
}
|