pi-roundtable-web 0.25.0 → 0.26.1
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 +54 -0
- package/README.md +165 -12
- package/dist/account-zk4efb87.css +102 -0
- package/dist/chunk-account-ghq105t4.js +1 -0
- package/dist/chunk-action-menu-me0g9qsc.js +1 -0
- package/dist/{chunk-agent-0pm9p3nn.js → chunk-agent-rdv7ekrt.js} +1 -1
- package/dist/chunk-arranged-rooms-81rqhx9x.js +1 -0
- package/dist/chunk-character-creator-bd426yjw.js +1 -0
- package/dist/chunk-codeeditor-ajhgd33t.js +2 -0
- package/dist/chunk-confirm-p3mts8mx.js +1 -0
- package/dist/{chunk-connectors-anhghy3k.js → chunk-connectors-bkv3xn83.js} +1 -1
- package/dist/chunk-conversations-0e0vbxwr.js +1 -0
- package/dist/{chunk-drag-q4fx9b22.js → chunk-drag-peyb8t0k.js} +1 -1
- package/dist/{chunk-entry-he1embgp.js → chunk-entry-mv7q8br0.js} +1 -1
- package/dist/chunk-folder-list-era3tksh.js +1 -0
- package/dist/chunk-folds-hvws0bd5.js +1 -0
- package/dist/chunk-gate-hc7dd0fx.js +1 -0
- package/dist/chunk-group-scene-dkbpmbh5.js +1 -0
- package/dist/chunk-group-table-fqmxgdfw.js +1 -0
- package/dist/chunk-host-icons-4xde9ntk.js +1 -0
- package/dist/chunk-host-picture-014yzk20.js +1 -0
- package/dist/chunk-host-state-t56e5sdy.js +1 -0
- package/dist/chunk-host-ui-p1zy9nmp.js +1 -0
- package/dist/chunk-index-0fmmvgee.js +2 -0
- package/dist/chunk-index-0q15ysrb.js +1 -0
- package/dist/chunk-index-0xh24y25.js +2 -0
- package/dist/chunk-index-2yebd9tb.js +426 -0
- package/dist/chunk-index-4t0r3t3b.js +2 -0
- package/dist/chunk-index-5yzfrf7b.js +138 -0
- package/dist/{chunk-index-d5abvq7v.js → chunk-index-6twb7ccd.js} +2 -2
- package/dist/chunk-index-73gh7kgr.js +920 -0
- package/dist/chunk-index-7xffskfa.js +8 -0
- package/dist/chunk-index-aqw7cy5j.js +1 -0
- package/dist/chunk-index-b6ca02e8.js +2 -0
- package/dist/chunk-index-b8nvhcx5.js +2 -0
- package/dist/{chunk-index-bvfqjh77.js → chunk-index-cvbdf2ca.js} +2 -2
- package/dist/chunk-index-czdketze.js +2 -0
- package/dist/{chunk-index-9jqcskb4.js → chunk-index-dt9775ka.js} +5 -5
- package/dist/chunk-index-eg55mqqe.js +1 -0
- package/dist/chunk-index-gr6e0hse.js +2 -0
- package/dist/chunk-index-gyary7np.js +13 -0
- package/dist/chunk-index-j214tczc.js +1 -0
- package/dist/chunk-index-jd6632ef.js +2 -0
- package/dist/chunk-index-jw7dtshr.js +2 -0
- package/dist/{chunk-index-krtk6nn2.js → chunk-index-k7t14rwa.js} +2 -2
- package/dist/chunk-index-kwcx62wb.js +4 -0
- package/dist/chunk-index-m7fvw92f.js +2 -0
- package/dist/chunk-index-mmg788a1.js +2 -0
- package/dist/chunk-index-mq0dacwr.js +2 -0
- package/dist/chunk-index-p53ak25v.js +2 -0
- package/dist/{chunk-index-27qca4mr.js → chunk-index-pk1yxkv4.js} +2 -2
- package/dist/{chunk-index-9b1kt7qw.css → chunk-index-rbcz9jd4.css} +1 -1
- package/dist/{chunk-index-s681nny9.js → chunk-index-rmxbdmn2.js} +2 -2
- package/dist/chunk-index-rnxj3z8k.js +2 -0
- package/dist/chunk-index-snzzfhk3.js +2 -0
- package/dist/chunk-index-v3bmr0nw.js +2 -0
- package/dist/chunk-index-vcyzwtk2.js +2 -0
- package/dist/{chunk-index-zfd00hwb.js → chunk-index-w2j3njb6.js} +2 -2
- package/dist/chunk-index-w31c4yjv.js +2 -0
- package/dist/chunk-index-wrm8w165.js +2 -0
- package/dist/chunk-index-xh0mg9cw.js +2 -0
- package/dist/chunk-index-ybtvvx85.js +2 -0
- package/dist/chunk-index-yzsgsvnp.js +2 -0
- package/dist/{chunk-index-18gxy79n.js → chunk-index-z9x4cnzf.js} +2 -2
- package/dist/chunk-index-zrvg23jd.js +2 -0
- package/dist/chunk-index-zynq0b0w.js +50 -0
- package/dist/chunk-lang-css-88v3hp3d.js +1 -0
- package/dist/{chunk-lang-dockerfile-d5mzxs60.js → chunk-lang-dockerfile-k728ewsf.js} +1 -1
- package/dist/{chunk-lang-go-830fmhba.js → chunk-lang-go-wjwgay4v.js} +3 -3
- package/dist/chunk-lang-html-07d47es4.js +1 -0
- package/dist/chunk-lang-javascript-fmhhgzwd.js +1 -0
- package/dist/{chunk-lang-json-5eaarz7j.js → chunk-lang-json-7gd89mvs.js} +1 -1
- package/dist/chunk-lang-markdown-b6qbe8bt.js +6 -0
- package/dist/{chunk-lang-python-fjw84w10.js → chunk-lang-python-3v56y31b.js} +2 -2
- package/dist/{chunk-lang-rust-5rbp2zdk.js → chunk-lang-rust-kh0z74d1.js} +1 -1
- package/dist/{chunk-lang-shell-vzagpym3.js → chunk-lang-shell-9zv0zvbx.js} +1 -1
- package/dist/{chunk-lang-sql-0c2w9c2y.js → chunk-lang-sql-yw6bf3t7.js} +2 -2
- package/dist/{chunk-lang-toml-yr9fv6eb.js → chunk-lang-toml-tg1r96nq.js} +1 -1
- package/dist/chunk-lang-typescript-etjjvr2g.js +1 -0
- package/dist/{chunk-lang-xml-eze08p42.js → chunk-lang-xml-ev1mndd4.js} +1 -1
- package/dist/{chunk-lang-yaml-c4b6ft3y.js → chunk-lang-yaml-fz5qpb3r.js} +1 -1
- package/dist/chunk-layout-actions-48ecgtkg.js +1 -0
- package/dist/{chunk-menu-items-jrwxycr1.js → chunk-menu-items-1epy3h0t.js} +2 -2
- package/dist/chunk-mode-toggle-fxmfxsa4.js +1 -0
- package/dist/chunk-note-row-kd0fy7ee.js +75 -0
- package/dist/chunk-notes-yb8f057c.js +1 -0
- package/dist/chunk-notice-bell-wr4f1rph.js +1 -0
- package/dist/chunk-notices-x07da5bb.js +1 -0
- package/dist/chunk-onboarding-csrkbh9z.js +1 -0
- package/dist/chunk-overview-avc2d30t.js +1 -0
- package/dist/chunk-palette-9nhp95bz.js +1 -0
- package/dist/chunk-placement-items-q694dpgk.js +1 -0
- package/dist/chunk-profile-6qemf3m7.js +1 -0
- package/dist/chunk-prompt-history-7enzp2p3.js +1 -0
- package/dist/chunk-prompts-nv6mrn0q.js +1 -0
- package/dist/chunk-room-confirms-zz7n61y7.js +1 -0
- package/dist/chunk-room-menu-rr6nzap1.js +1 -0
- package/dist/chunk-rooms-menu-button-zbtwj47d.js +1 -0
- package/dist/chunk-schedules-rw57nr0m.js +1 -0
- package/dist/{chunk-seat-menu-jv4s7hmj.js → chunk-seat-menu-ys5v9abz.js} +1 -1
- package/dist/chunk-sheet-hosts-jhvaacst.js +1 -0
- package/dist/chunk-skill-editor-sz8an8qr.js +8 -0
- package/dist/chunk-skills-tnr1h16h.js +1 -0
- package/dist/chunk-switcher-list-17enhrse.js +1 -0
- package/dist/chunk-tab-dot-r6brdbmw.js +1 -0
- package/dist/chunk-table-r12cja6p.js +1 -0
- package/dist/chunk-table-seats-g3t392zz.js +1 -0
- package/dist/{chunk-table2d-4sje1fr0.js → chunk-table2d-p3ezsdcj.js} +1 -1
- package/dist/{chunk-table3d-h64skazp.js → chunk-table3d-4x2n2b00.js} +23 -23
- package/dist/chunk-thread-actions-qc6ezkha.js +1 -0
- package/dist/chunk-transcript-n2s6dher.js +1 -0
- package/dist/chunk-where-f3scdydc.js +1 -0
- package/dist/chunk-workspace-xs023bxm.js +1 -0
- package/dist/chunk-zh-tw-efvmk3fd.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 +356 -4
- package/src/app/app.ts +201 -32
- package/src/app/desk.ts +47 -22
- package/src/app/files.ts +73 -7
- package/src/app/presence.ts +2 -2
- package/src/app/protocol.ts +63 -1
- package/src/app/room-log.ts +153 -14
- package/src/app/rooms.ts +39 -0
- package/src/app/routes.ts +168 -28
- package/src/app/socket.ts +349 -81
- package/src/app/sockets.ts +108 -20
- package/src/app/surface.ts +72 -36
- package/src/app/threads.ts +31 -2
- package/src/app/uploads.ts +33 -7
- package/src/auth/authenticator.ts +77 -8
- package/src/auth/login-page.ts +4 -2
- package/src/auth/next.ts +52 -0
- package/src/auth/oidc-pages.ts +62 -0
- package/src/auth/oidc.ts +649 -0
- package/src/auth/sessions.ts +193 -0
- package/src/authz.ts +173 -0
- package/src/console-api.ts +122 -25
- package/src/console-server.ts +102 -12
- package/src/gate.ts +90 -0
- package/src/host/access.ts +161 -0
- package/src/host/cards.ts +112 -0
- package/src/host/context.ts +188 -0
- package/src/host/forms.ts +240 -0
- package/src/host/frames.ts +349 -0
- package/src/host/host-app.ts +178 -0
- package/src/host/host-context.ts +49 -0
- package/src/host/host-routes.ts +396 -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 +429 -0
- package/src/host/stored-json.ts +13 -0
- package/src/host/views.ts +106 -0
- package/src/i18n/zh-tw.ts +139 -0
- package/src/index.ts +15 -1
- 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 +534 -34
- package/src/page-presentation.ts +47 -2
- 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/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/pwa.ts +32 -4
- package/src/visitors.ts +94 -6
- package/src/web-app.ts +718 -0
- package/src/web-plugin.ts +279 -26
- package/dist/chunk-action-menu-qe839g4b.js +0 -1
- package/dist/chunk-arranged-rooms-enpfswmg.js +0 -1
- package/dist/chunk-character-creator-6yf29xwx.js +0 -1
- package/dist/chunk-codeeditor-gdbdg7rp.js +0 -2
- package/dist/chunk-confirm-xrw7d732.js +0 -1
- package/dist/chunk-conversations-ry228aes.js +0 -1
- package/dist/chunk-folder-list-5cp8stna.js +0 -1
- package/dist/chunk-folds-hff104tk.js +0 -1
- package/dist/chunk-group-scene-h228jdat.js +0 -1
- package/dist/chunk-index-0p5h2kdz.js +0 -2
- package/dist/chunk-index-0pt08yd9.js +0 -1
- package/dist/chunk-index-12bw9yj8.js +0 -2
- package/dist/chunk-index-2ad17eh4.js +0 -2
- package/dist/chunk-index-31wh5twj.js +0 -52
- package/dist/chunk-index-4dy94pbz.js +0 -2
- package/dist/chunk-index-4ecjtdy8.js +0 -13
- package/dist/chunk-index-4v1b7ks9.js +0 -2
- package/dist/chunk-index-5d8yjj4m.js +0 -2
- package/dist/chunk-index-89ajmxh7.js +0 -2
- package/dist/chunk-index-b9ny4623.js +0 -2
- package/dist/chunk-index-e1dr6g10.js +0 -2
- package/dist/chunk-index-gpkzhq1g.js +0 -2
- package/dist/chunk-index-hk3xyy20.js +0 -2
- package/dist/chunk-index-jm23vn03.js +0 -4
- package/dist/chunk-index-k7gh0jvz.js +0 -2
- package/dist/chunk-index-ky42ev0b.js +0 -2
- package/dist/chunk-index-pa2yyqh0.js +0 -2
- package/dist/chunk-index-pcvt668q.js +0 -2
- package/dist/chunk-index-pqw27eye.js +0 -1
- package/dist/chunk-index-wmbmmrd4.js +0 -302
- package/dist/chunk-index-ymbjrsp6.js +0 -2
- package/dist/chunk-lang-css-t85r8rz0.js +0 -1
- package/dist/chunk-lang-html-n6jparge.js +0 -1
- package/dist/chunk-lang-javascript-xj00cp97.js +0 -1
- package/dist/chunk-lang-markdown-zxx8eke8.js +0 -6
- package/dist/chunk-lang-typescript-8f6yywt3.js +0 -1
- package/dist/chunk-layout-actions-bv0chj41.js +0 -1
- package/dist/chunk-notes-9b7pgqfe.js +0 -1
- package/dist/chunk-overview-8yw71esz.js +0 -1
- package/dist/chunk-palette-frms7axe.js +0 -1
- package/dist/chunk-placement-items-fvfy9m0y.js +0 -1
- package/dist/chunk-prompt-history-yrqehads.js +0 -1
- package/dist/chunk-prompts-g6609tqx.js +0 -1
- package/dist/chunk-room-confirms-0a2y0925.js +0 -1
- package/dist/chunk-room-menu-g11q97br.js +0 -1
- package/dist/chunk-rooms-menu-button-h9qge48n.js +0 -1
- package/dist/chunk-schedules-ezmacrbj.js +0 -1
- package/dist/chunk-sheet-hosts-b0azyfhb.js +0 -1
- package/dist/chunk-skill-editor-h6k6whwg.js +0 -8
- package/dist/chunk-skills-gqcfhq56.js +0 -1
- package/dist/chunk-switcher-list-mcpamqnj.js +0 -1
- package/dist/chunk-table-s8cde1xz.js +0 -1
- package/dist/chunk-table-seats-wqkjn8bc.js +0 -1
- package/dist/chunk-transcript-c59c6mpm.js +0 -1
- package/dist/chunk-workspace-0cyspkk4.js +0 -1
- package/dist/chunk-zh-tw-nc93jm10.js +0 -1
package/src/app/routes.ts
CHANGED
|
@@ -19,11 +19,13 @@ import type {
|
|
|
19
19
|
CharacterRefusal,
|
|
20
20
|
MessagesView,
|
|
21
21
|
ModelsView,
|
|
22
|
+
RoomOneView,
|
|
22
23
|
RoomsView,
|
|
23
24
|
SchedulesView,
|
|
24
25
|
ScheduleView,
|
|
25
26
|
SessionView,
|
|
26
27
|
SkillView,
|
|
28
|
+
ThreadsDismissed,
|
|
27
29
|
WorkspaceConflict,
|
|
28
30
|
WorkspaceNew,
|
|
29
31
|
WorkspaceRefusalBody,
|
|
@@ -33,8 +35,14 @@ import type {
|
|
|
33
35
|
} from "../api-types.ts";
|
|
34
36
|
import type { Authenticated } from "../auth/authenticator.ts";
|
|
35
37
|
import { type ConsolePresentation, message } from "../features.ts";
|
|
38
|
+
import type { Gate } from "../gate.ts";
|
|
39
|
+
import type { HostApp } from "../host/host-app.ts";
|
|
40
|
+
import type { RoomReads } from "../host/reads.ts";
|
|
41
|
+
import { readsOf, withExpiredFiles } from "../host/room-views.ts";
|
|
36
42
|
import { json } from "../http.ts";
|
|
43
|
+
import type { ResolvedOptions } from "../options.ts";
|
|
37
44
|
import type { PushMutes } from "../push/mutes.ts";
|
|
45
|
+
import { viewerOf, type WebAppRegistry } from "../web-app.ts";
|
|
38
46
|
import { bad, bodyOf, HttpError, notFound } from "./api-http.ts";
|
|
39
47
|
import type { CardLog } from "./card-log.ts";
|
|
40
48
|
import { type FileDir, fileResponse, isFileId } from "./files.ts";
|
|
@@ -42,7 +50,7 @@ import type { LayoutStore } from "./layout.ts";
|
|
|
42
50
|
import { LayoutRoutes } from "./layout-routes.ts";
|
|
43
51
|
import { PromptApi, type PromptApiOptions } from "./prompt-routes.ts";
|
|
44
52
|
import { PAGE_LIMIT, type RoomLog } from "./room-log.ts";
|
|
45
|
-
import type { Rooms } from "./rooms.ts";
|
|
53
|
+
import type { ResolvedRoom, Rooms } from "./rooms.ts";
|
|
46
54
|
import { SkillDocApi } from "./skill-doc-routes.ts";
|
|
47
55
|
import type { AppSurface } from "./surface.ts";
|
|
48
56
|
import { TeamApi } from "./team-routes.ts";
|
|
@@ -69,13 +77,16 @@ export interface AppApiOptions {
|
|
|
69
77
|
/** The rooms whose push the owners muted. */
|
|
70
78
|
mutes: Pick<PushMutes, "set" | "setMany">;
|
|
71
79
|
/** The dispatch threads, which go with the room they hang under. */
|
|
72
|
-
threads: Pick<
|
|
80
|
+
threads: Pick<
|
|
81
|
+
WebThreads,
|
|
82
|
+
"under" | "removeUnder" | "dismiss" | "dismissFinished"
|
|
83
|
+
>;
|
|
73
84
|
/** The owners' shared arrangement of the room list: pins and folders. */
|
|
74
85
|
layout: LayoutStore;
|
|
75
86
|
conversations: Pick<ConversationPort, "startFresh">;
|
|
76
|
-
identity: Pick<IdentityService, "
|
|
87
|
+
identity: Pick<IdentityService, "principal">;
|
|
77
88
|
/** Present when the schedules pane is served. */
|
|
78
|
-
schedules?: Pick<ScheduleStore, "all" | "remove">;
|
|
89
|
+
schedules?: Pick<ScheduleStore, "all" | "get" | "remove">;
|
|
79
90
|
directory: Pick<AgentDirectory, "agent" | "agents" | "group" | "groups">;
|
|
80
91
|
team: Pick<
|
|
81
92
|
AgentTeam,
|
|
@@ -109,12 +120,28 @@ export interface AppApiOptions {
|
|
|
109
120
|
/** Present when the host has prompt documents (the prompts plugin): the main prompts the owner edits. */
|
|
110
121
|
prompts?: PromptApiOptions["prompts"];
|
|
111
122
|
avatars: Pick<AvatarStudio, "serve">;
|
|
123
|
+
/** The read cursors, for each room's unread count and who has read what. */
|
|
124
|
+
reads?: Pick<RoomReads, "unread" | "cursorOf" | "marks">;
|
|
125
|
+
/** Who may be served a file bound to a host room or a page. */
|
|
126
|
+
host?: Pick<HostApp, "mayRead">;
|
|
127
|
+
/** The name the app shows for a person. */
|
|
128
|
+
nameOf?(principalId: string): Promise<string>;
|
|
112
129
|
/** Present when the workspace pane is served. */
|
|
113
130
|
workspace?: WorkspaceFiles;
|
|
114
131
|
/** The mount, which a room's agent picture hangs under. */
|
|
115
132
|
mount: string;
|
|
116
133
|
/** How the console signs the person in, as the session reports it. */
|
|
117
134
|
authMode: SessionView["auth"];
|
|
135
|
+
/** Who the app admits; the session reports the person's tier only where it admits more than owners. */
|
|
136
|
+
admission?: ResolvedOptions["admission"];
|
|
137
|
+
/** The area handlers the pieces of the app plug in; without it the API is the owners' as in 0.25. */
|
|
138
|
+
registry?: Pick<WebAppRegistry, "areaFor">;
|
|
139
|
+
/** The onboarding gate; without it nobody is gated. */
|
|
140
|
+
gate?: Pick<Gate, "required">;
|
|
141
|
+
/** The tiers whose people may use push; default owners. */
|
|
142
|
+
pushTiers?: ResolvedOptions["push"]["tiers"];
|
|
143
|
+
/** Whether the yolo and long-running switches are served; `PUT rooms/<key>/mode` answers 404 without them. Default true. */
|
|
144
|
+
modeSwitches?: boolean;
|
|
118
145
|
/** The quiet hours of push, as the settings page shows them. */
|
|
119
146
|
quietHours?: NonNullable<SessionView["quietHours"]>;
|
|
120
147
|
/** Called after a change the other pages should refetch. */
|
|
@@ -296,7 +323,12 @@ export class AppApi {
|
|
|
296
323
|
const parts = partsOf(rest);
|
|
297
324
|
if (!parts) return undefined;
|
|
298
325
|
const [first, second] = parts;
|
|
326
|
+
const area =
|
|
327
|
+
first === "api" && second !== undefined
|
|
328
|
+
? this.#options.registry?.areaFor(second)
|
|
329
|
+
: undefined;
|
|
299
330
|
const mine =
|
|
331
|
+
area !== undefined ||
|
|
300
332
|
(first === "avatars" && parts.length === 2) ||
|
|
301
333
|
(first === "api" &&
|
|
302
334
|
[
|
|
@@ -316,6 +348,11 @@ export class AppApi {
|
|
|
316
348
|
].includes(second ?? ""));
|
|
317
349
|
if (!mine) return undefined;
|
|
318
350
|
try {
|
|
351
|
+
if (area)
|
|
352
|
+
return await area.handle(request, parts.slice(2), {
|
|
353
|
+
visitor: admission.visitor,
|
|
354
|
+
viewer: viewerOf(admission.visitor),
|
|
355
|
+
});
|
|
319
356
|
return await this.#route(request, parts, admission);
|
|
320
357
|
} catch (error) {
|
|
321
358
|
if (error instanceof HttpError || error instanceof UploadRefusal)
|
|
@@ -338,16 +375,13 @@ export class AppApi {
|
|
|
338
375
|
}
|
|
339
376
|
}
|
|
340
377
|
|
|
341
|
-
/** The owner who writes:
|
|
378
|
+
/** The owner who writes: the tier the request was admitted at, as a speaker for the calls that need one. */
|
|
342
379
|
async #owner(admission: Authenticated): Promise<Speaker> {
|
|
343
|
-
const { principal } = admission.visitor;
|
|
344
|
-
const tier = await this.#options.identity.tierOf(principal.id);
|
|
380
|
+
const { principal, tier, speakerId } = admission.visitor;
|
|
345
381
|
if (tier !== "owner")
|
|
346
382
|
throw new HttpError(403, "Only an owner may do this.");
|
|
347
383
|
return {
|
|
348
|
-
id:
|
|
349
|
-
? `${admission.actor.provider}:${admission.actor.subject}`
|
|
350
|
-
: principal.id,
|
|
384
|
+
id: speakerId,
|
|
351
385
|
name: principal.displayName,
|
|
352
386
|
tier,
|
|
353
387
|
principalId: principal.id,
|
|
@@ -368,7 +402,8 @@ export class AppApi {
|
|
|
368
402
|
new Response("Not found", { status: 404 })
|
|
369
403
|
);
|
|
370
404
|
}
|
|
371
|
-
if (area === "session" && method === "GET")
|
|
405
|
+
if (area === "session" && method === "GET")
|
|
406
|
+
return await this.#session(admission);
|
|
372
407
|
if (area === "models" && method === "GET")
|
|
373
408
|
return json({
|
|
374
409
|
models: await this.#options.team.usableModels(),
|
|
@@ -376,20 +411,21 @@ export class AppApi {
|
|
|
376
411
|
if (area === "rooms") return this.#rooms(request, parts, admission);
|
|
377
412
|
if (area === "folders")
|
|
378
413
|
return this.#layout.folders(request, parts, admission);
|
|
379
|
-
|
|
380
|
-
|
|
414
|
+
// An upload is the sender's own, whoever they are; a file answers by the room it is bound to.
|
|
415
|
+
if (area === "uploads" && method === "POST" && !id)
|
|
381
416
|
return json(
|
|
382
|
-
await this.#options.uploads.receive(
|
|
417
|
+
await this.#options.uploads.receive(
|
|
418
|
+
request,
|
|
419
|
+
admission.visitor.principal.id,
|
|
420
|
+
),
|
|
383
421
|
);
|
|
384
|
-
}
|
|
385
422
|
if (area === "uploads" && method === "DELETE" && id && !action) {
|
|
386
|
-
|
|
387
|
-
if (!this.#options.uploads.discard(id, owner.principalId))
|
|
423
|
+
if (!this.#options.uploads.discard(id, admission.visitor.principal.id))
|
|
388
424
|
throw notFound();
|
|
389
425
|
return json({ ok: true });
|
|
390
426
|
}
|
|
391
427
|
if (area === "files" && method === "GET" && id && !action)
|
|
392
|
-
return this.#file(id);
|
|
428
|
+
return await this.#file(id, admission);
|
|
393
429
|
if (area === "workspace") return this.#workspace(request, parts, admission);
|
|
394
430
|
if (area === "schedules") return this.#schedules(method, id, admission);
|
|
395
431
|
const owner = () => this.#owner(admission);
|
|
@@ -410,13 +446,21 @@ export class AppApi {
|
|
|
410
446
|
throw new HttpError(404, "There is no such API.");
|
|
411
447
|
}
|
|
412
448
|
|
|
413
|
-
#session(admission: Authenticated): Response {
|
|
414
|
-
const { principal } = admission.visitor;
|
|
449
|
+
async #session(admission: Authenticated): Promise<Response> {
|
|
450
|
+
const { principal, tier } = admission.visitor;
|
|
451
|
+
const { admission: admits = "owners", gate } = this.#options;
|
|
452
|
+
const pushTiers = this.#options.pushTiers ?? ["owner"];
|
|
453
|
+
const owed =
|
|
454
|
+
admits === "access" && gate
|
|
455
|
+
? await gate.required(viewerOf(admission.visitor))
|
|
456
|
+
: [];
|
|
415
457
|
return json({
|
|
416
458
|
principal: { id: principal.id, name: principal.displayName },
|
|
417
459
|
surface: this.#options.surface.surface,
|
|
418
460
|
auth: this.#options.authMode,
|
|
419
|
-
|
|
461
|
+
...(admits === "access" ? { tier } : {}),
|
|
462
|
+
...(owed.length > 0 ? { gate: owed } : {}),
|
|
463
|
+
...(pushTiers.includes(tier) ? { push: true as const } : {}),
|
|
420
464
|
...(this.#options.prompts ? { prompts: true } : {}),
|
|
421
465
|
...(this.#options.skills ? { skillEdit: true } : {}),
|
|
422
466
|
...(this.#options.quietHours
|
|
@@ -436,15 +480,37 @@ export class AppApi {
|
|
|
436
480
|
const { method } = request;
|
|
437
481
|
const [, , key, action] = parts;
|
|
438
482
|
const { rooms, log, cards, modes, mutes, conversations } = this.#options;
|
|
483
|
+
// The agent rooms are the owners'; to anyone else this API does not exist.
|
|
484
|
+
if (admission.visitor.tier !== "owner") throw notFound();
|
|
439
485
|
if (!key) {
|
|
440
486
|
if (method !== "GET") throw new HttpError(405, "Method not allowed.");
|
|
487
|
+
const listed = await rooms.list();
|
|
488
|
+
// What each room holds past this person's cursor; a room they never read starts from what it has now.
|
|
489
|
+
const unread = await this.#options.reads?.unread(
|
|
490
|
+
admission.visitor.principal.id,
|
|
491
|
+
listed.map((one) => one.key),
|
|
492
|
+
new Set(listed.map((one) => one.key)),
|
|
493
|
+
);
|
|
441
494
|
return json({
|
|
442
|
-
rooms:
|
|
495
|
+
rooms: listed.map((one) => {
|
|
496
|
+
const count = unread?.get(one.key);
|
|
497
|
+
return count ? { ...one, unread: count } : one;
|
|
498
|
+
}),
|
|
443
499
|
...(await this.#layout.view()),
|
|
444
500
|
} satisfies RoomsView);
|
|
445
501
|
}
|
|
446
502
|
const room = await rooms.resolve(key);
|
|
447
503
|
if (!room) throw notFound();
|
|
504
|
+
if (!action && method === "GET") {
|
|
505
|
+
// A thread dismissed from the list still opens by its address; the page asks for its view.
|
|
506
|
+
const view = await rooms.threadView(room.key);
|
|
507
|
+
if (!view) throw notFound();
|
|
508
|
+
return json({ room: view } satisfies RoomOneView);
|
|
509
|
+
}
|
|
510
|
+
if (action === "dismiss" && method === "POST" && parts.length === 4)
|
|
511
|
+
return this.#dismiss(room, admission);
|
|
512
|
+
if (action === "dismiss-threads" && method === "POST" && parts.length === 4)
|
|
513
|
+
return this.#dismissFinished(room, admission);
|
|
448
514
|
if (action === "messages" && method === "GET" && parts.length === 4) {
|
|
449
515
|
const params = new URL(request.url).searchParams;
|
|
450
516
|
const before = params.get("before");
|
|
@@ -461,17 +527,26 @@ export class AppApi {
|
|
|
461
527
|
// The cards stand among the messages by the time they opened; a page that has older
|
|
462
528
|
// messages leaves the cards before its first one to the older page.
|
|
463
529
|
const first = page.messages[0];
|
|
530
|
+
const { reads, nameOf } = this.#options;
|
|
531
|
+
const self = admission.visitor.principal.id;
|
|
532
|
+
const cursor = await reads?.cursorOf(room.key, self);
|
|
464
533
|
return json({
|
|
465
534
|
...page,
|
|
535
|
+
messages: withExpiredFiles(page.messages, this.#options.files),
|
|
466
536
|
cards: await cards.during(room.key, {
|
|
467
537
|
...(page.more && first ? { from: first.at } : {}),
|
|
468
538
|
...(beforeId === undefined ? {} : { before: beforeId }),
|
|
469
539
|
}),
|
|
540
|
+
...(reads && nameOf
|
|
541
|
+
? { reads: await readsOf(reads, room.key, self, nameOf) }
|
|
542
|
+
: {}),
|
|
543
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
470
544
|
} satisfies MessagesView);
|
|
471
545
|
}
|
|
472
546
|
if (action === "mode" && method === "PUT" && parts.length === 4) {
|
|
473
547
|
// Only an agent's or group's room has modes; a thread runs no turn of the owner's.
|
|
474
|
-
if (room.readOnly
|
|
548
|
+
if (room.readOnly || this.#options.modeSwitches === false)
|
|
549
|
+
throw notFound();
|
|
475
550
|
const owner = await this.#owner(admission);
|
|
476
551
|
const body = await bodyOf(request);
|
|
477
552
|
const change: ModeChange = {};
|
|
@@ -513,10 +588,47 @@ export class AppApi {
|
|
|
513
588
|
throw new HttpError(404, "There is no such API.");
|
|
514
589
|
}
|
|
515
590
|
|
|
516
|
-
|
|
591
|
+
/** Takes one finished thread off the room list for every owner; an open one is refused. */
|
|
592
|
+
async #dismiss(
|
|
593
|
+
room: ResolvedRoom,
|
|
594
|
+
admission: Authenticated,
|
|
595
|
+
): Promise<Response> {
|
|
596
|
+
if (room.kind !== "thread") throw notFound();
|
|
597
|
+
await this.#owner(admission);
|
|
598
|
+
const outcome = await this.#options.threads.dismiss(room.name);
|
|
599
|
+
if (outcome === "missing") throw notFound();
|
|
600
|
+
if (outcome === "open")
|
|
601
|
+
throw new HttpError(409, "The thread is still open.");
|
|
602
|
+
this.#options.changed();
|
|
603
|
+
return json({ dismissed: 1 } satisfies ThreadsDismissed);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** Takes every finished thread of a room off the room list; the open ones stay. */
|
|
607
|
+
async #dismissFinished(
|
|
608
|
+
room: ResolvedRoom,
|
|
609
|
+
admission: Authenticated,
|
|
610
|
+
): Promise<Response> {
|
|
611
|
+
if (room.kind === "thread") throw notFound();
|
|
612
|
+
await this.#owner(admission);
|
|
613
|
+
const dismissed = await this.#options.threads.dismissFinished(room.key);
|
|
614
|
+
if (dismissed) this.#options.changed();
|
|
615
|
+
return json({ dismissed } satisfies ThreadsDismissed);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async #file(id: string, admission: Authenticated): Promise<Response> {
|
|
517
619
|
if (!isFileId(id)) throw notFound();
|
|
518
|
-
const
|
|
519
|
-
|
|
620
|
+
const { files, host } = this.#options;
|
|
621
|
+
// 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.
|
|
622
|
+
const bound = files.binding(id)?.room;
|
|
623
|
+
if (bound === undefined) {
|
|
624
|
+
if (admission.visitor.tier !== "owner") throw notFound();
|
|
625
|
+
} else if (
|
|
626
|
+
!host ||
|
|
627
|
+
!(await host.mayRead(viewerOf(admission.visitor), bound))
|
|
628
|
+
)
|
|
629
|
+
throw notFound();
|
|
630
|
+
const meta = files.meta(id);
|
|
631
|
+
const blob = files.blob(id);
|
|
520
632
|
if (!meta || !blob) throw notFound();
|
|
521
633
|
return fileResponse(meta, blob);
|
|
522
634
|
}
|
|
@@ -636,6 +748,18 @@ export class AppApi {
|
|
|
636
748
|
throw new HttpError(404, "There is no such API.");
|
|
637
749
|
}
|
|
638
750
|
|
|
751
|
+
/** Whether a schedule is the visitor's own: its creator id names their principal. */
|
|
752
|
+
async #ownSchedule(
|
|
753
|
+
createdById: string,
|
|
754
|
+
principalId: string,
|
|
755
|
+
): Promise<boolean> {
|
|
756
|
+
if (createdById === principalId) return true;
|
|
757
|
+
// A schedule from 0.8 names its creator by a user id that the backfill made a principal's id.
|
|
758
|
+
return (
|
|
759
|
+
(await this.#options.identity.principal(createdById))?.id === principalId
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
639
763
|
async #schedules(
|
|
640
764
|
method: string,
|
|
641
765
|
id: string | undefined,
|
|
@@ -643,11 +767,19 @@ export class AppApi {
|
|
|
643
767
|
): Promise<Response> {
|
|
644
768
|
const { schedules, rooms } = this.#options;
|
|
645
769
|
if (!schedules) throw new HttpError(404, "This pane is not served.");
|
|
770
|
+
const { tier, principal } = admission.visitor;
|
|
771
|
+
const owner = tier === "owner";
|
|
646
772
|
if (method === "GET" && !id) {
|
|
647
773
|
const all = await schedules.all();
|
|
648
774
|
const views: ScheduleView[] = [];
|
|
649
775
|
for (const schedule of all) {
|
|
650
|
-
|
|
776
|
+
// The owners see every schedule; anyone else sees their own, and no room of the owners.
|
|
777
|
+
if (
|
|
778
|
+
!owner &&
|
|
779
|
+
!(await this.#ownSchedule(schedule.createdById, principal.id))
|
|
780
|
+
)
|
|
781
|
+
continue;
|
|
782
|
+
const room = owner ? await rooms.resolve(schedule.channel) : undefined;
|
|
651
783
|
views.push({
|
|
652
784
|
id: schedule.id,
|
|
653
785
|
...(room ? { room: room.key } : {}),
|
|
@@ -667,9 +799,17 @@ export class AppApi {
|
|
|
667
799
|
return json({ schedules: views } satisfies SchedulesView);
|
|
668
800
|
}
|
|
669
801
|
if (method === "DELETE" && id) {
|
|
670
|
-
await this.#owner(admission);
|
|
671
802
|
const number = Number(id);
|
|
672
803
|
if (!Number.isInteger(number)) throw bad("The schedule id is a number.");
|
|
804
|
+
// Another person's schedule is not there to remove, for anyone but the owners.
|
|
805
|
+
if (!owner) {
|
|
806
|
+
const found = await schedules.get(number);
|
|
807
|
+
if (
|
|
808
|
+
!found ||
|
|
809
|
+
!(await this.#ownSchedule(found.createdById, principal.id))
|
|
810
|
+
)
|
|
811
|
+
throw new HttpError(404, "There is no such schedule.");
|
|
812
|
+
}
|
|
673
813
|
const removed = await schedules.remove(number);
|
|
674
814
|
if (!removed) throw new HttpError(404, "There is no such schedule.");
|
|
675
815
|
this.#options.changed();
|