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/app.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ConversationModes,
|
|
7
7
|
ConversationPort,
|
|
8
8
|
DirectChannelProvider,
|
|
9
|
+
EventSink,
|
|
9
10
|
IdentityService,
|
|
10
11
|
Logger,
|
|
11
12
|
Migration,
|
|
@@ -13,8 +14,21 @@ import type {
|
|
|
13
14
|
WebSocketRoute,
|
|
14
15
|
} from "pi-roundtable";
|
|
15
16
|
import { channelKey } from "pi-roundtable";
|
|
17
|
+
import { APP_CLOSE_EXPIRED } from "../api-types.ts";
|
|
16
18
|
import type { Authenticated, Authenticator } from "../auth/authenticator.ts";
|
|
17
|
-
import {
|
|
19
|
+
import type { ConsolePresentation } from "../features.ts";
|
|
20
|
+
import type { Gate } from "../gate.ts";
|
|
21
|
+
import { HostCardLog } from "../host/cards.ts";
|
|
22
|
+
import { HostApp } from "../host/host-app.ts";
|
|
23
|
+
import { RoomReads } from "../host/reads.ts";
|
|
24
|
+
import {
|
|
25
|
+
mountNotices,
|
|
26
|
+
NOTICE_MIGRATIONS,
|
|
27
|
+
type NoticesMount,
|
|
28
|
+
type NoticesMountOptions,
|
|
29
|
+
} from "../notices/mount.ts";
|
|
30
|
+
import type { ResolvedOptions } from "../options.ts";
|
|
31
|
+
import type { PushApi } from "../push/api.ts";
|
|
18
32
|
import { webPushChannel } from "../push/channel.ts";
|
|
19
33
|
import { EndpointPolicy } from "../push/endpoint.ts";
|
|
20
34
|
import { PushMutes, roomMuted } from "../push/mutes.ts";
|
|
@@ -23,15 +37,17 @@ import { PushSender } from "../push/sender.ts";
|
|
|
23
37
|
import { PushSubscriptions } from "../push/subscriptions.ts";
|
|
24
38
|
import { PushService } from "../push/triggers.ts";
|
|
25
39
|
import type { Vapid } from "../push/vapid.ts";
|
|
40
|
+
import type { WebAppRegistry } from "../web-app.ts";
|
|
26
41
|
import { CardLog } from "./card-log.ts";
|
|
27
42
|
import { OwnerDesk } from "./desk.ts";
|
|
28
43
|
import { FileDir } from "./files.ts";
|
|
44
|
+
import { RoomLayout } from "./layout.ts";
|
|
29
45
|
import { Presence } from "./presence.ts";
|
|
30
46
|
import { RoomLog } from "./room-log.ts";
|
|
31
47
|
import { Rooms } from "./rooms.ts";
|
|
32
|
-
import { AppApi, partsOf } from "./routes.ts";
|
|
48
|
+
import { AppApi, type AppApiOptions, partsOf } from "./routes.ts";
|
|
33
49
|
import { SocketRoute } from "./socket.ts";
|
|
34
|
-
import { type AppSocket, Sockets } from "./sockets.ts";
|
|
50
|
+
import { type AppSocket, type Audience, Sockets } from "./sockets.ts";
|
|
35
51
|
import { AppSurface } from "./surface.ts";
|
|
36
52
|
import { WebThreads } from "./threads.ts";
|
|
37
53
|
import { Uploads } from "./uploads.ts";
|
|
@@ -45,13 +61,20 @@ const SWEEP_EVERY_MS = 3_600_000;
|
|
|
45
61
|
/** The tables the app keeps; the plugin lists them as its own migrations. */
|
|
46
62
|
export const APP_MIGRATIONS: readonly Migration[] = [
|
|
47
63
|
RoomLog.migration,
|
|
64
|
+
RoomLog.hostMigration,
|
|
65
|
+
HostCardLog.migration,
|
|
66
|
+
RoomReads.migration,
|
|
48
67
|
CardLog.migration,
|
|
49
68
|
WebThreads.migration,
|
|
50
69
|
PushSubscriptions.migration,
|
|
51
70
|
PushMutes.migration,
|
|
71
|
+
RoomLayout.migration,
|
|
72
|
+
...NOTICE_MIGRATIONS,
|
|
52
73
|
];
|
|
53
74
|
|
|
54
75
|
export interface OwnerAppOptions {
|
|
76
|
+
/** The host's wording, for the refusals the API answers with. */
|
|
77
|
+
presentation?: ConsolePresentation;
|
|
55
78
|
/** The key prefix of the rooms, such as `app`. */
|
|
56
79
|
surface: string;
|
|
57
80
|
/** The path the console is served under, without a trailing slash. */
|
|
@@ -64,14 +87,36 @@ export interface OwnerAppOptions {
|
|
|
64
87
|
cardGraceSeconds: number;
|
|
65
88
|
/** The most bytes of one uploaded file. */
|
|
66
89
|
uploadMaxBytes: number;
|
|
67
|
-
|
|
90
|
+
/** How many days a stored file of an agent room is kept; default 30. */
|
|
91
|
+
uploadKeepDays?: number;
|
|
92
|
+
/** The name the app shows for a person; default the principal's own. */
|
|
93
|
+
nameOf?(principalId: string): Promise<string>;
|
|
94
|
+
/** The same-origin URL of a room, and of one of its messages, for a host's links. */
|
|
95
|
+
roomUrl(room: ChannelKey, message?: number): string;
|
|
96
|
+
/** Where the app reports what people do in it (a press on a host's button). */
|
|
97
|
+
events?: Pick<EventSink, "action">;
|
|
98
|
+
authenticator: Pick<Authenticator, "authenticate" | "recheck">;
|
|
68
99
|
/** How the console signs the person in. */
|
|
69
|
-
authMode: "
|
|
100
|
+
authMode: ResolvedOptions["auth"]["mode"];
|
|
101
|
+
/** Who the app admits; the page and the session report the tier only where it admits more than owners. */
|
|
102
|
+
admission: ResolvedOptions["admission"];
|
|
103
|
+
/** What the pieces of the app plug in: the kinds of host room, their handlers, who reads them. */
|
|
104
|
+
registry: WebAppRegistry;
|
|
105
|
+
/** The onboarding gate. */
|
|
106
|
+
gate: Gate;
|
|
107
|
+
/** The tiers whose people may use push. */
|
|
108
|
+
pushTiers: ResolvedOptions["push"]["tiers"];
|
|
109
|
+
/** Whether the yolo and long-running switches are served. */
|
|
110
|
+
modeSwitches: boolean;
|
|
70
111
|
/** The longest an open socket lives; for a credential that logout or a new password does not reach. */
|
|
71
112
|
socketLifeMs?: number;
|
|
72
|
-
/**
|
|
113
|
+
/** The notices, onboarding and profile pieces: what they read of the options. */
|
|
114
|
+
extras: Pick<NoticesMountOptions, "profiles" | "routing" | "onboarding"> & {
|
|
115
|
+
push: NoticesMountOptions["push"];
|
|
116
|
+
};
|
|
117
|
+
/** Web Push: the server's key (absent with an external sender, which holds it), and how notifications read. */
|
|
73
118
|
push: {
|
|
74
|
-
vapid
|
|
119
|
+
vapid?: Vapid;
|
|
75
120
|
/** The `sub` of the VAPID token; absent = push is not set up and nothing is sent. */
|
|
76
121
|
subject: string | undefined;
|
|
77
122
|
preview: boolean;
|
|
@@ -87,6 +132,13 @@ export interface OwnerAppOptions {
|
|
|
87
132
|
};
|
|
88
133
|
database: SQL;
|
|
89
134
|
agents: AgentServer;
|
|
135
|
+
/**
|
|
136
|
+
* Present when the host has the skills addon: the catalog an agent's skills are chosen from,
|
|
137
|
+
* how they change, and the registry's documents.
|
|
138
|
+
*/
|
|
139
|
+
skills?: AppApiOptions["skills"];
|
|
140
|
+
/** Present when the host has prompt documents: the main prompts the owner edits. */
|
|
141
|
+
prompts?: AppApiOptions["prompts"];
|
|
90
142
|
/** Present when the workspace pane is served. */
|
|
91
143
|
workspace?: WorkspaceFiles;
|
|
92
144
|
modes: ConversationModes;
|
|
@@ -112,10 +164,12 @@ export class OwnerApp {
|
|
|
112
164
|
readonly directChannel: DirectChannelProvider;
|
|
113
165
|
readonly #api: AppApi;
|
|
114
166
|
readonly #push: PushApi;
|
|
167
|
+
readonly #mounted: NoticesMount;
|
|
115
168
|
readonly #sockets: Sockets;
|
|
116
169
|
readonly #desk: OwnerDesk;
|
|
117
170
|
readonly #presence: Presence;
|
|
118
171
|
readonly #uploads: Uploads;
|
|
172
|
+
readonly #host: HostApp;
|
|
119
173
|
readonly #options: OwnerAppOptions;
|
|
120
174
|
readonly #unsubscribe: (() => void)[] = [];
|
|
121
175
|
#sweeper: ReturnType<typeof setInterval> | undefined;
|
|
@@ -131,7 +185,17 @@ export class OwnerApp {
|
|
|
131
185
|
const waiting = new FileDir(join(options.dataDir, "web", "uploads"));
|
|
132
186
|
const uploads = new Uploads(waiting, stored, {
|
|
133
187
|
maxBytes: options.uploadMaxBytes,
|
|
188
|
+
...(options.uploadKeepDays === undefined
|
|
189
|
+
? {}
|
|
190
|
+
: { keepDays: options.uploadKeepDays }),
|
|
134
191
|
});
|
|
192
|
+
const reads = new RoomReads(options.database);
|
|
193
|
+
const hostCards = new HostCardLog(options.database);
|
|
194
|
+
const nameOf =
|
|
195
|
+
options.nameOf ??
|
|
196
|
+
(async (id: string) => (await identity.principal(id))?.displayName ?? id);
|
|
197
|
+
// The host rooms read their context once the surface exists; the surface asks for it per turn.
|
|
198
|
+
let hostApp: HostApp | undefined;
|
|
135
199
|
const threads = new WebThreads(options.database);
|
|
136
200
|
const subscriptions = new PushSubscriptions(options.database);
|
|
137
201
|
const mutes = new PushMutes(options.database);
|
|
@@ -139,34 +203,63 @@ export class OwnerApp {
|
|
|
139
203
|
const quietClock = options.push.quietHours
|
|
140
204
|
? new QuietClock(options.push.quietHours, options.push.timeZone)
|
|
141
205
|
: undefined;
|
|
206
|
+
const quiet = quietClock
|
|
207
|
+
? (at: number) => quietClock.quietAt(at)
|
|
208
|
+
: undefined;
|
|
209
|
+
const mounted = mountNotices({
|
|
210
|
+
database: options.database,
|
|
211
|
+
logger,
|
|
212
|
+
registry: options.registry,
|
|
213
|
+
gate: options.gate,
|
|
214
|
+
sockets,
|
|
215
|
+
identity,
|
|
216
|
+
...(options.extras.profiles ? { profiles: options.extras.profiles } : {}),
|
|
217
|
+
subscriptions,
|
|
218
|
+
policy,
|
|
219
|
+
mount: options.mount,
|
|
220
|
+
routing: options.extras.routing,
|
|
221
|
+
push: options.extras.push,
|
|
222
|
+
...(quiet ? { quiet } : {}),
|
|
223
|
+
...(options.push.vapid ? { vapid: options.push.vapid } : {}),
|
|
224
|
+
onboarding: options.extras.onboarding,
|
|
225
|
+
});
|
|
142
226
|
const push = new PushService({
|
|
227
|
+
hostRoom: (room) => options.registry.isHostRoom(room),
|
|
143
228
|
subscriptions,
|
|
144
|
-
sender
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
229
|
+
// The app signs only with its own key; with an external sender it queues for it instead.
|
|
230
|
+
...(options.push.vapid
|
|
231
|
+
? {
|
|
232
|
+
sender: new PushSender({
|
|
233
|
+
vapid: options.push.vapid,
|
|
234
|
+
subject: options.push.subject,
|
|
235
|
+
policy,
|
|
236
|
+
...(options.push.fetch ? { fetch: options.push.fetch } : {}),
|
|
237
|
+
logger,
|
|
238
|
+
}),
|
|
239
|
+
}
|
|
240
|
+
: {}),
|
|
241
|
+
...(mounted.route ? { route: mounted.route } : {}),
|
|
151
242
|
identity,
|
|
152
243
|
visible: (principalId) => sockets.anyVisible(principalId),
|
|
153
244
|
preview: options.push.preview,
|
|
154
|
-
...(
|
|
245
|
+
...(quiet ? { quiet } : {}),
|
|
155
246
|
muted: roomMuted(mutes, threads, channelKey(options.surface, "thread.")),
|
|
156
247
|
urlOf: options.push.urlOf,
|
|
157
248
|
logger,
|
|
158
249
|
});
|
|
159
|
-
this.#
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
250
|
+
this.#mounted = mounted;
|
|
251
|
+
this.#push = mounted.pushApi;
|
|
252
|
+
const { registry } = options;
|
|
253
|
+
sockets.setRoomReader((connection, room) =>
|
|
254
|
+
registry.access.readNow(connection, room),
|
|
255
|
+
);
|
|
256
|
+
// An agent room's frames are the owners'; a host room's are its readers'.
|
|
257
|
+
const audienceOf = (room: string): Audience =>
|
|
258
|
+
registry.isHostRoom(room) ? { room } : { owners: true };
|
|
166
259
|
// The desk starts a late answer's turn through the socket route, which is made after it.
|
|
167
260
|
let route: SocketRoute | undefined;
|
|
168
261
|
const desk = new OwnerDesk({
|
|
169
|
-
broadcast: (frame) => sockets.
|
|
262
|
+
broadcast: (frame, room) => sockets.deliver(frame, audienceOf(room)),
|
|
170
263
|
graceMs: options.cardGraceSeconds * 1000,
|
|
171
264
|
closed: (card) => cards.record(card),
|
|
172
265
|
opened: (room, frame) => {
|
|
@@ -201,6 +294,20 @@ export class OwnerApp {
|
|
|
201
294
|
const room = await rooms.resolve(parent);
|
|
202
295
|
return room !== undefined && room.kind !== "thread";
|
|
203
296
|
},
|
|
297
|
+
audienceOf,
|
|
298
|
+
isHostRoom: (room) => registry.isHostRoom(room),
|
|
299
|
+
fileBinding: (room) => {
|
|
300
|
+
const kind = registry.hostKindOf(room)?.kind;
|
|
301
|
+
return {
|
|
302
|
+
room,
|
|
303
|
+
...(kind?.retention
|
|
304
|
+
? { expiresAt: Date.now() + kind.retention.fileDays * 86_400_000 }
|
|
305
|
+
: {}),
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
channelContext: (message, contextOptions) =>
|
|
309
|
+
hostApp?.channelContext(message, contextOptions) ??
|
|
310
|
+
Promise.resolve(undefined),
|
|
204
311
|
changed: () => this.changed(),
|
|
205
312
|
unprompted: (room, author, text) => {
|
|
206
313
|
push.reply(room, author, text).catch(() => undefined);
|
|
@@ -226,6 +333,27 @@ export class OwnerApp {
|
|
|
226
333
|
mutes,
|
|
227
334
|
activity: (room) => surface.activityOf(room),
|
|
228
335
|
});
|
|
336
|
+
hostApp = new HostApp({
|
|
337
|
+
surface: options.surface,
|
|
338
|
+
registry,
|
|
339
|
+
sockets,
|
|
340
|
+
app: surface,
|
|
341
|
+
desk,
|
|
342
|
+
log,
|
|
343
|
+
cards: hostCards,
|
|
344
|
+
reads,
|
|
345
|
+
files: stored,
|
|
346
|
+
uploads,
|
|
347
|
+
rooms,
|
|
348
|
+
conversations: options.conversations,
|
|
349
|
+
nameOf,
|
|
350
|
+
roomUrl: options.roomUrl,
|
|
351
|
+
maxFileBytes: options.uploadMaxBytes,
|
|
352
|
+
...(options.events ? { events: options.events } : {}),
|
|
353
|
+
logger,
|
|
354
|
+
});
|
|
355
|
+
hostApp.install();
|
|
356
|
+
this.#host = hostApp;
|
|
229
357
|
route = new SocketRoute({
|
|
230
358
|
path: `${options.mount}/api/socket`,
|
|
231
359
|
origin: options.origin,
|
|
@@ -239,10 +367,17 @@ export class OwnerApp {
|
|
|
239
367
|
rooms,
|
|
240
368
|
uploads,
|
|
241
369
|
presence,
|
|
242
|
-
|
|
370
|
+
registry,
|
|
371
|
+
gate: options.gate,
|
|
372
|
+
audienceOf,
|
|
373
|
+
opened: (connection) => mounted.opened(connection),
|
|
243
374
|
conversations: options.conversations,
|
|
244
375
|
logger,
|
|
245
376
|
});
|
|
377
|
+
const socketRoute = route;
|
|
378
|
+
this.#unsubscribe.push(
|
|
379
|
+
options.gate.onChanged((principalId) => socketRoute.refresh(principalId)),
|
|
380
|
+
);
|
|
246
381
|
this.websocket = route.route();
|
|
247
382
|
this.directChannel = webPushChannel({
|
|
248
383
|
surface: options.surface,
|
|
@@ -259,6 +394,7 @@ export class OwnerApp {
|
|
|
259
394
|
push,
|
|
260
395
|
});
|
|
261
396
|
this.#api = new AppApi({
|
|
397
|
+
...(options.presentation ? { presentation: options.presentation } : {}),
|
|
262
398
|
surface,
|
|
263
399
|
rooms,
|
|
264
400
|
log,
|
|
@@ -267,15 +403,27 @@ export class OwnerApp {
|
|
|
267
403
|
files: stored,
|
|
268
404
|
modes,
|
|
269
405
|
mutes,
|
|
406
|
+
threads,
|
|
407
|
+
layout: new RoomLayout(options.database),
|
|
270
408
|
conversations: options.conversations,
|
|
271
409
|
identity,
|
|
272
410
|
...(options.schedules ? { schedules: options.schedules } : {}),
|
|
273
411
|
...(options.workspace ? { workspace: options.workspace } : {}),
|
|
274
412
|
directory: agents.directory,
|
|
275
413
|
team: agents.team,
|
|
414
|
+
...(options.skills ? { skills: options.skills } : {}),
|
|
415
|
+
...(options.prompts ? { prompts: options.prompts } : {}),
|
|
276
416
|
avatars: agents.avatars,
|
|
417
|
+
reads,
|
|
418
|
+
host: hostApp,
|
|
419
|
+
nameOf,
|
|
277
420
|
mount: options.mount,
|
|
278
421
|
authMode: options.authMode,
|
|
422
|
+
admission: options.admission,
|
|
423
|
+
registry,
|
|
424
|
+
gate: options.gate,
|
|
425
|
+
pushTiers: options.pushTiers,
|
|
426
|
+
modeSwitches: options.modeSwitches,
|
|
279
427
|
...(options.push.quietHours
|
|
280
428
|
? {
|
|
281
429
|
quietHours: {
|
|
@@ -295,12 +443,15 @@ export class OwnerApp {
|
|
|
295
443
|
this.#unsubscribe.push(
|
|
296
444
|
modes.onChange((channel, mode) => {
|
|
297
445
|
if (!rooms.ownsKey(channel)) return;
|
|
298
|
-
sockets.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
446
|
+
sockets.deliver(
|
|
447
|
+
{
|
|
448
|
+
type: "mode",
|
|
449
|
+
room: channel,
|
|
450
|
+
yolo: mode.yolo,
|
|
451
|
+
longRunning: mode.longRunning,
|
|
452
|
+
},
|
|
453
|
+
{ owners: true },
|
|
454
|
+
);
|
|
304
455
|
}),
|
|
305
456
|
modes.onAutoApproved((entry) => {
|
|
306
457
|
if (!rooms.ownsKey(entry.channel)) return;
|
|
@@ -325,17 +476,28 @@ export class OwnerApp {
|
|
|
325
476
|
return pushed ?? this.#api.handle(request, rest, admission);
|
|
326
477
|
}
|
|
327
478
|
|
|
328
|
-
/**
|
|
479
|
+
/** Closes the sockets that came in on a session that has ended. */
|
|
480
|
+
sessionEnded(id: string): void {
|
|
481
|
+
this.#sockets.closeWhere(
|
|
482
|
+
(connection) => connection.session === id,
|
|
483
|
+
APP_CLOSE_EXPIRED,
|
|
484
|
+
"expired",
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** Tells the owners' sockets the team or the room list may differ, once changes settle. */
|
|
329
489
|
changed(): void {
|
|
330
490
|
if (this.#pending) return;
|
|
331
491
|
this.#pending = setTimeout(() => {
|
|
332
492
|
this.#pending = undefined;
|
|
333
|
-
this.#sockets.
|
|
493
|
+
this.#sockets.deliver({ type: "changed" }, { owners: true });
|
|
334
494
|
}, CHANGED_COALESCE_MS);
|
|
335
495
|
}
|
|
336
496
|
|
|
337
497
|
start(): void {
|
|
338
498
|
this.#uploads.adopt();
|
|
499
|
+
this.#host.start();
|
|
500
|
+
this.#mounted.start();
|
|
339
501
|
this.#sweep();
|
|
340
502
|
this.#sweeper = setInterval(() => this.#sweep(), SWEEP_EVERY_MS);
|
|
341
503
|
}
|
|
@@ -347,6 +509,8 @@ export class OwnerApp {
|
|
|
347
509
|
clearTimeout(this.#pending);
|
|
348
510
|
this.#pending = undefined;
|
|
349
511
|
for (const unsubscribe of this.#unsubscribe.splice(0)) unsubscribe();
|
|
512
|
+
this.#host.stop();
|
|
513
|
+
this.#mounted.stop().catch(() => undefined);
|
|
350
514
|
this.#desk.stop();
|
|
351
515
|
this.#presence.stop();
|
|
352
516
|
this.#sockets.closeAll(1001, "stopping");
|
|
@@ -360,6 +524,20 @@ export class OwnerApp {
|
|
|
360
524
|
} catch (error) {
|
|
361
525
|
this.#options.logger.warn({ err: error }, "old app files not deleted");
|
|
362
526
|
}
|
|
527
|
+
this.#host.sweep().then(
|
|
528
|
+
(gone) => {
|
|
529
|
+
if (gone.messages + gone.cards + gone.files > 0)
|
|
530
|
+
this.#options.logger.info(
|
|
531
|
+
gone,
|
|
532
|
+
"host room history past retention deleted",
|
|
533
|
+
);
|
|
534
|
+
},
|
|
535
|
+
(error: unknown) =>
|
|
536
|
+
this.#options.logger.warn(
|
|
537
|
+
{ err: error },
|
|
538
|
+
"host room history not swept",
|
|
539
|
+
),
|
|
540
|
+
);
|
|
363
541
|
}
|
|
364
542
|
|
|
365
543
|
/** Records and shows that yolo mode approved a held call in a room. */
|
|
@@ -374,6 +552,9 @@ export class OwnerApp {
|
|
|
374
552
|
author: "Yolo mode",
|
|
375
553
|
text: `Yolo mode approved: ${action}`,
|
|
376
554
|
});
|
|
377
|
-
this.#sockets.
|
|
555
|
+
this.#sockets.deliver(
|
|
556
|
+
{ type: "auto_approved", room, tool, action },
|
|
557
|
+
{ owners: true },
|
|
558
|
+
);
|
|
378
559
|
}
|
|
379
560
|
}
|
package/src/app/card-log.ts
CHANGED
|
@@ -95,6 +95,12 @@ export class CardLog {
|
|
|
95
95
|
ON CONFLICT (prompt_id) DO NOTHING`;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/** Deletes every ended card of a room that is gone for good, once the writes under way are in. */
|
|
99
|
+
async removeRoom(room: string): Promise<void> {
|
|
100
|
+
await Promise.allSettled([...this.#writing]);
|
|
101
|
+
await this.#sql`DELETE FROM web_room_cards WHERE room = ${room}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
98
104
|
/**
|
|
99
105
|
* The room's cards that opened at or after `from` (all of them when absent) and before the
|
|
100
106
|
* stored message `before` of the same room was written (all of them when absent), oldest
|
package/src/app/desk.ts
CHANGED
|
@@ -50,13 +50,15 @@ export interface Answerer {
|
|
|
50
50
|
/** The identity they spoke as, `<provider>:<subject>`. */
|
|
51
51
|
speakerId: string;
|
|
52
52
|
name: string;
|
|
53
|
+
/** The roles their sign-in carried. */
|
|
54
|
+
roles?: readonly string[];
|
|
53
55
|
/** The actor their credential vouched for, which a message they start is written as. */
|
|
54
56
|
actor?: ActorFacts;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
export interface OwnerDeskOptions {
|
|
58
|
-
/**
|
|
59
|
-
broadcast(frame: ServerFrame): void;
|
|
60
|
+
/** Puts a frame about a room on the sockets that may have it: the owners' for an agent room, the room's readers' for a host room. */
|
|
61
|
+
broadcast(frame: ServerFrame, room: ChannelKey): void;
|
|
60
62
|
/** How long a turn that can be resumed waits on its card before it goes on without the answer. */
|
|
61
63
|
graceMs: number;
|
|
62
64
|
/** A card nobody answers closes after this long; default 24 hours. */
|
|
@@ -143,9 +145,14 @@ function answerOf(ended: Ended): CardAnswer | undefined {
|
|
|
143
145
|
* after a day. A turn that can be resumed stops waiting after the grace period while the card
|
|
144
146
|
* stays open (`late`); answered then, the card starts a turn in its room with the answer.
|
|
145
147
|
*
|
|
146
|
-
* Who answers
|
|
147
|
-
*
|
|
148
|
-
*
|
|
148
|
+
* Who answers (the same rule as Discord's `CardAudiences`, so a room's readers cannot widen it):
|
|
149
|
+
* - an approval that names approvers: a named approver who holds its tier now, or an owner;
|
|
150
|
+
* - an approval that names none: the turn's speaker at its tier, and the owners when the scope
|
|
151
|
+
* escalates;
|
|
152
|
+
* - a question: the speaker, and the owners when the scope escalates;
|
|
153
|
+
* - a card that is the speaker's alone (`speakerOnly`): only the speaker's principal;
|
|
154
|
+
* - without a scope: the owners.
|
|
155
|
+
* It applies to approve, decline, answer and cancel alike, and the tier is the answerer's now.
|
|
149
156
|
*/
|
|
150
157
|
export class OwnerDesk {
|
|
151
158
|
readonly #open = new Map<string, Open>();
|
|
@@ -273,15 +280,19 @@ export class OwnerDesk {
|
|
|
273
280
|
};
|
|
274
281
|
}
|
|
275
282
|
|
|
276
|
-
/** The open cards as frames, to send again when a socket opens. */
|
|
277
|
-
openFrames(): ServerFrame[] {
|
|
283
|
+
/** The open cards as frames with their rooms, to send again when a socket opens; each goes only to those who may have its room. */
|
|
284
|
+
openFrames(): { room: ChannelKey; frame: ServerFrame }[] {
|
|
278
285
|
return [...this.#open.values()].map((open) => ({
|
|
279
|
-
type: "prompt",
|
|
280
286
|
room: open.room,
|
|
281
|
-
prompt: open.frame,
|
|
287
|
+
frame: { type: "prompt", room: open.room, prompt: open.frame },
|
|
282
288
|
}));
|
|
283
289
|
}
|
|
284
290
|
|
|
291
|
+
/** The room a card belongs to, while it is open. */
|
|
292
|
+
roomOf(prompt: string): ChannelKey | undefined {
|
|
293
|
+
return this.#open.get(prompt)?.room;
|
|
294
|
+
}
|
|
295
|
+
|
|
285
296
|
/** Whether the card is open and past its grace, so an answer to it starts a turn. */
|
|
286
297
|
isLate(prompt: string): boolean {
|
|
287
298
|
const frame = this.#open.get(prompt)?.frame;
|
|
@@ -357,7 +368,7 @@ export class OwnerDesk {
|
|
|
357
368
|
|
|
358
369
|
/**
|
|
359
370
|
* Who may approve a call held at `minTier`, as a test of the person who answers; undefined when
|
|
360
|
-
* no one may, so the call is unavailable at once.
|
|
371
|
+
* no one may, so the call is unavailable at once.
|
|
361
372
|
*/
|
|
362
373
|
#approvalAudience(
|
|
363
374
|
scope: PromptScope | undefined,
|
|
@@ -366,6 +377,7 @@ export class OwnerDesk {
|
|
|
366
377
|
speakerOnly: boolean,
|
|
367
378
|
): ((from: Answerer) => boolean) | undefined {
|
|
368
379
|
const tierOk = (from: Answerer) => atLeast(from.tier, minTier);
|
|
380
|
+
const owner = (from: Answerer) => from.tier === "owner";
|
|
369
381
|
if (speakerOnly) {
|
|
370
382
|
// The speaker's alone: below the tier, or a speaker who is no person, no one answers.
|
|
371
383
|
if (
|
|
@@ -377,28 +389,38 @@ export class OwnerDesk {
|
|
|
377
389
|
const speaker = scope.principalId;
|
|
378
390
|
return (from) => from.principalId === speaker && tierOk(from);
|
|
379
391
|
}
|
|
380
|
-
|
|
392
|
+
// Without a scope a card is the owners'.
|
|
393
|
+
if (!scope) return owner;
|
|
394
|
+
const person = scope.principalId;
|
|
395
|
+
if (scope.escalate === "none") {
|
|
381
396
|
// A private conversation's person is the only one who answers, and a call naming others is theirs only if named or an owner.
|
|
382
397
|
if (!atLeast(scope.tier, minTier)) return undefined;
|
|
383
398
|
if (
|
|
384
399
|
approvers?.length &&
|
|
385
|
-
!approvers.includes(
|
|
400
|
+
!approvers.includes(person) &&
|
|
386
401
|
scope.tier !== "owner"
|
|
387
402
|
)
|
|
388
403
|
return undefined;
|
|
389
|
-
const person = scope.principalId;
|
|
390
404
|
return (from) => from.principalId === person && tierOk(from);
|
|
391
405
|
}
|
|
392
|
-
|
|
406
|
+
if (approvers?.length) {
|
|
407
|
+
// Named approvers answer it, at the tier it needs now, and so do the owners; the speaker does not unless named.
|
|
408
|
+
const named = new Set(approvers);
|
|
409
|
+
return (from) =>
|
|
410
|
+
(named.has(from.principalId) && tierOk(from)) || owner(from);
|
|
411
|
+
}
|
|
412
|
+
return (from) =>
|
|
413
|
+
(from.principalId === person && tierOk(from)) || owner(from);
|
|
393
414
|
}
|
|
394
415
|
|
|
395
|
-
/** Who may answer a question:
|
|
416
|
+
/** Who may answer a question: the speaker, and the owners where the scope escalates; without a scope, the owners. */
|
|
396
417
|
#questionAudience(scope: PromptScope | undefined) {
|
|
397
|
-
if (scope
|
|
398
|
-
|
|
418
|
+
if (!scope) return (from: Answerer) => from.tier === "owner";
|
|
419
|
+
const person = scope.principalId;
|
|
420
|
+
if (scope.escalate === "none")
|
|
399
421
|
return (from: Answerer) => from.principalId === person;
|
|
400
|
-
|
|
401
|
-
|
|
422
|
+
return (from: Answerer) =>
|
|
423
|
+
from.principalId === person || from.tier === "owner";
|
|
402
424
|
}
|
|
403
425
|
|
|
404
426
|
/**
|
|
@@ -463,7 +485,10 @@ export class OwnerDesk {
|
|
|
463
485
|
};
|
|
464
486
|
// Kept before the sockets are told, so a page that reloads on the news finds it.
|
|
465
487
|
this.#keep({ room, frame: open.frame, ...ended, closedAt });
|
|
466
|
-
broadcast(
|
|
488
|
+
broadcast(
|
|
489
|
+
{ type: "prompt_closed", prompt: id, ...ended, closedAt },
|
|
490
|
+
room,
|
|
491
|
+
);
|
|
467
492
|
};
|
|
468
493
|
const onAbort = () => {
|
|
469
494
|
if (!this.#open.has(id)) return;
|
|
@@ -488,13 +513,13 @@ export class OwnerDesk {
|
|
|
488
513
|
late = true;
|
|
489
514
|
signal?.removeEventListener("abort", onAbort);
|
|
490
515
|
open.frame = { ...open.frame, late: true } as PromptFrame;
|
|
491
|
-
broadcast({ type: "prompt", room, prompt: open.frame });
|
|
516
|
+
broadcast({ type: "prompt", room, prompt: open.frame }, room);
|
|
492
517
|
resolve(card.pending as T);
|
|
493
518
|
}, graceMs);
|
|
494
519
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
495
520
|
supersede?.addEventListener("abort", onWithdraw, { once: true });
|
|
496
521
|
this.#open.set(id, open);
|
|
497
|
-
broadcast({ type: "prompt", room, prompt: open.frame });
|
|
522
|
+
broadcast({ type: "prompt", room, prompt: open.frame }, room);
|
|
498
523
|
try {
|
|
499
524
|
this.#options.opened?.(room, open.frame);
|
|
500
525
|
} catch (error) {
|