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/sockets.ts
CHANGED
|
@@ -1,32 +1,79 @@
|
|
|
1
|
-
import type { ActorFacts, Logger, RouteSocket } from "pi-roundtable";
|
|
1
|
+
import type { ActorFacts, Logger, RouteSocket, Tier } from "pi-roundtable";
|
|
2
2
|
import type { ServerFrame } from "../api-types.ts";
|
|
3
3
|
|
|
4
|
-
/** One open WebSocket
|
|
4
|
+
/** One open WebSocket: who, what the page shows, and when its credential lapses. */
|
|
5
5
|
export interface AppSocket {
|
|
6
6
|
principal: { id: string; name: string };
|
|
7
7
|
/** The actor the credential vouched for, which the person's messages are written as. */
|
|
8
8
|
actor: ActorFacts | undefined;
|
|
9
9
|
expiresAt: Date | undefined;
|
|
10
|
+
/** The tier they hold, as of the upgrade or the latest frame or recheck. */
|
|
11
|
+
tier: Tier;
|
|
12
|
+
/** The identity they spoke as; the principal's id when the credential names none. */
|
|
13
|
+
speakerId: string;
|
|
14
|
+
/** The roles their sign-in carried. */
|
|
15
|
+
roles: readonly string[];
|
|
16
|
+
/** A required onboarding step stands undone: only the frames a gated socket may have reach it. */
|
|
17
|
+
gated: boolean;
|
|
18
|
+
/** The session the socket came in on, for the recheck that asks whether it still stands. */
|
|
19
|
+
session?: string | undefined;
|
|
10
20
|
/** The page is in view; a push goes out only when no socket is. */
|
|
11
21
|
visible: boolean;
|
|
12
22
|
/** The room the page shows, whose progress streams to it. */
|
|
13
23
|
focus: string | undefined;
|
|
24
|
+
/** The host rooms the socket follows. */
|
|
25
|
+
subscriptions: Set<string>;
|
|
26
|
+
/** Set once `ready` and the replay went out: until then nothing but a frame addressed to the socket reaches it. */
|
|
27
|
+
started?: boolean;
|
|
14
28
|
socket?: RouteSocket<AppSocket>;
|
|
15
29
|
/** Closes the socket when its credential lapses or the cap on its life is reached. */
|
|
16
30
|
expiry?: ReturnType<typeof setTimeout>;
|
|
17
|
-
/** Looks again at whether the person is still
|
|
31
|
+
/** Looks again at whether the person is still admitted, and at which tier. */
|
|
18
32
|
recheck?: ReturnType<typeof setInterval>;
|
|
19
33
|
}
|
|
20
34
|
|
|
21
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Who a frame is for. There is no unaddressed send: every frame names its audience, and the
|
|
37
|
+
* open-time replay goes through the same filter as a live frame.
|
|
38
|
+
*/
|
|
39
|
+
export type Audience =
|
|
40
|
+
/** Sockets whose tier is owner now; with `focus`, those showing that room. */
|
|
41
|
+
| { owners: true; focus?: string }
|
|
42
|
+
/** Every socket of one person. */
|
|
43
|
+
| { principal: string }
|
|
44
|
+
/** The sockets that follow a host room and may read it now; with `focus`, those also showing it. */
|
|
45
|
+
| { room: string; focus?: true }
|
|
46
|
+
/** One socket; with `audience`, only if it is also in that audience (the replay on open). */
|
|
47
|
+
| { connection: AppSocket; audience?: Audience };
|
|
48
|
+
|
|
49
|
+
/** The frames a gated socket still gets: the gate itself, the person's notices and badge, and refusals. */
|
|
50
|
+
const GATED_FRAMES: ReadonlySet<ServerFrame["type"]> = new Set([
|
|
51
|
+
"ready",
|
|
52
|
+
"refused",
|
|
53
|
+
"gate",
|
|
54
|
+
"notice",
|
|
55
|
+
"notice_resolved",
|
|
56
|
+
"badge",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
/** Whether a socket may read a host room now; answered from a memo, so it costs no lookup. */
|
|
60
|
+
export type RoomReader = (connection: AppSocket, room: string) => boolean;
|
|
61
|
+
|
|
62
|
+
/** The open sockets, and the one way to put a frame on them. */
|
|
22
63
|
export class Sockets {
|
|
23
64
|
readonly #open = new Set<AppSocket>();
|
|
24
65
|
readonly #logger: Logger;
|
|
66
|
+
#reader: RoomReader = () => false;
|
|
25
67
|
|
|
26
68
|
constructor(logger: Logger) {
|
|
27
69
|
this.#logger = logger;
|
|
28
70
|
}
|
|
29
71
|
|
|
72
|
+
/** Sets who may read which host room; until it is set, no host room frame reaches anyone. */
|
|
73
|
+
setRoomReader(reader: RoomReader): void {
|
|
74
|
+
this.#reader = reader;
|
|
75
|
+
}
|
|
76
|
+
|
|
30
77
|
add(connection: AppSocket, socket: RouteSocket<AppSocket>): void {
|
|
31
78
|
connection.socket = socket;
|
|
32
79
|
this.#open.add(connection);
|
|
@@ -40,8 +87,42 @@ export class Sockets {
|
|
|
40
87
|
this.#open.delete(connection);
|
|
41
88
|
}
|
|
42
89
|
|
|
43
|
-
/**
|
|
44
|
-
|
|
90
|
+
/** Whether the connection is in the audience now. */
|
|
91
|
+
reaches(connection: AppSocket, audience: Audience): boolean {
|
|
92
|
+
if ("connection" in audience)
|
|
93
|
+
return (
|
|
94
|
+
audience.connection === connection &&
|
|
95
|
+
(!audience.audience || this.reaches(connection, audience.audience))
|
|
96
|
+
);
|
|
97
|
+
if (!connection.started) return false;
|
|
98
|
+
if ("owners" in audience)
|
|
99
|
+
return (
|
|
100
|
+
connection.tier === "owner" &&
|
|
101
|
+
(audience.focus === undefined || connection.focus === audience.focus)
|
|
102
|
+
);
|
|
103
|
+
if ("principal" in audience)
|
|
104
|
+
return connection.principal.id === audience.principal;
|
|
105
|
+
return (
|
|
106
|
+
connection.subscriptions.has(audience.room) &&
|
|
107
|
+
this.#reader(connection, audience.room) &&
|
|
108
|
+
(!audience.focus || connection.focus === audience.room)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Puts a frame on the sockets in `audience`, except `except`. A gated socket gets only what the
|
|
114
|
+
* gate lets through. A dropped send is logged, never thrown.
|
|
115
|
+
*/
|
|
116
|
+
deliver(frame: ServerFrame, audience: Audience, except?: AppSocket): void {
|
|
117
|
+
for (const connection of this.#open) {
|
|
118
|
+
if (connection === except || !this.reaches(connection, audience))
|
|
119
|
+
continue;
|
|
120
|
+
if (connection.gated && !GATED_FRAMES.has(frame.type)) continue;
|
|
121
|
+
this.#send(connection, frame);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#send(connection: AppSocket, frame: ServerFrame): void {
|
|
45
126
|
const result = connection.socket?.send(JSON.stringify(frame));
|
|
46
127
|
if (result === "dropped")
|
|
47
128
|
this.#logger.warn(
|
|
@@ -50,18 +131,7 @@ export class Sockets {
|
|
|
50
131
|
);
|
|
51
132
|
}
|
|
52
133
|
|
|
53
|
-
/**
|
|
54
|
-
broadcast(
|
|
55
|
-
frame: ServerFrame,
|
|
56
|
-
where?: (connection: AppSocket) => boolean,
|
|
57
|
-
except?: AppSocket,
|
|
58
|
-
): void {
|
|
59
|
-
for (const connection of this.#open)
|
|
60
|
-
if (connection !== except && (!where || where(connection)))
|
|
61
|
-
this.send(connection, frame);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Whether a page of this person is in view now; of any owner when no one is named. */
|
|
134
|
+
/** Whether a page of this person is in view now; of any person when none is named. */
|
|
65
135
|
anyVisible(principalId?: string): boolean {
|
|
66
136
|
for (const connection of this.#open)
|
|
67
137
|
if (
|
|
@@ -72,10 +142,28 @@ export class Sockets {
|
|
|
72
142
|
return false;
|
|
73
143
|
}
|
|
74
144
|
|
|
145
|
+
/** Calls `visit` on each open socket `where` accepts. */
|
|
146
|
+
each(
|
|
147
|
+
where: (connection: AppSocket) => boolean,
|
|
148
|
+
visit: (connection: AppSocket) => void,
|
|
149
|
+
): void {
|
|
150
|
+
for (const connection of [...this.#open])
|
|
151
|
+
if (where(connection)) visit(connection);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Closes the sockets `where` accepts, such as those of a session that ended. */
|
|
155
|
+
closeWhere(
|
|
156
|
+
where: (connection: AppSocket) => boolean,
|
|
157
|
+
code: number,
|
|
158
|
+
reason: string,
|
|
159
|
+
): void {
|
|
160
|
+
for (const connection of [...this.#open])
|
|
161
|
+
if (where(connection)) connection.socket?.close(code, reason);
|
|
162
|
+
}
|
|
163
|
+
|
|
75
164
|
/** Closes every socket, when the app stops. */
|
|
76
165
|
closeAll(code: number, reason: string): void {
|
|
77
|
-
|
|
78
|
-
connection.socket?.close(code, reason);
|
|
166
|
+
this.closeWhere(() => true, code, reason);
|
|
79
167
|
}
|
|
80
168
|
|
|
81
169
|
get size(): number {
|
package/src/app/surface.ts
CHANGED
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
} from "pi-roundtable";
|
|
17
17
|
import type { RoomFile, RoomMessage } from "../api-types.ts";
|
|
18
18
|
import type { OwnerDesk } from "./desk.ts";
|
|
19
|
-
import { type FileDir, typeByName } from "./files.ts";
|
|
19
|
+
import { type FileBinding, type FileDir, typeByName } from "./files.ts";
|
|
20
20
|
import type { NewRoomMessage, RoomLog } from "./room-log.ts";
|
|
21
|
-
import type { AppSocket, Sockets } from "./sockets.ts";
|
|
21
|
+
import type { AppSocket, Audience, Sockets } from "./sockets.ts";
|
|
22
22
|
import { AppSurfaceThreads } from "./surface-threads.ts";
|
|
23
23
|
import type { WebThreads } from "./threads.ts";
|
|
24
24
|
import type { Uploads } from "./uploads.ts";
|
|
@@ -45,6 +45,12 @@ export interface AppSurfaceOptions {
|
|
|
45
45
|
threads: Pick<WebThreads, "open" | "close">;
|
|
46
46
|
/** Whether a room can host a thread: an agent's or group's room that exists. */
|
|
47
47
|
canHost(parent: ChannelKey): Promise<boolean>;
|
|
48
|
+
/** Who has a room's frames: the owners' sockets for an agent room, a host room's readers for a host room. */
|
|
49
|
+
audienceOf(room: string): Audience;
|
|
50
|
+
/** Whether the room is a host's (its frames are the `host_*` ones). */
|
|
51
|
+
isHostRoom(room: string): boolean;
|
|
52
|
+
/** What binds a file an agent attaches in a host room, so its readers are served it. */
|
|
53
|
+
fileBinding?(room: string): FileBinding | undefined;
|
|
48
54
|
/** Tells the sockets the room list differs, once changes settle. */
|
|
49
55
|
changed(): void;
|
|
50
56
|
/**
|
|
@@ -54,6 +60,8 @@ export interface AppSurfaceOptions {
|
|
|
54
60
|
unprompted?(room: ChannelKey, author: string, text: string): void;
|
|
55
61
|
/** Called when a room's activity changes, so the presence table can follow. */
|
|
56
62
|
activity?(): void;
|
|
63
|
+
/** What was said in a host room since the assistant last posted; the host rooms' reader. */
|
|
64
|
+
channelContext?: ChatSurface["channelContext"];
|
|
57
65
|
/** The clock, in milliseconds; default `Date.now`. */
|
|
58
66
|
now?: () => number;
|
|
59
67
|
logger: Logger;
|
|
@@ -88,8 +96,8 @@ export interface RoomActivity {
|
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
/**
|
|
91
|
-
* The owner app's rooms as a chat surface: what the host shows in a room reaches
|
|
92
|
-
*
|
|
99
|
+
* The owner app's rooms as a chat surface: what the host shows in a room reaches the sockets that
|
|
100
|
+
* may have it (the owners' for an agent room) as a frame, and what a room says is stored in the room log. The agent team runs
|
|
93
101
|
* in the rooms (`agentRooms`) as it runs in its Discord channels, but each room is a
|
|
94
102
|
* conversation of its own. A reply arrives whole, in Markdown, with its files stored, and an
|
|
95
103
|
* agent's interim text is shown live until the final reply replaces it.
|
|
@@ -101,6 +109,8 @@ export class AppSurface implements ChatSurface {
|
|
|
101
109
|
readonly formatting = APP_FORMATTING;
|
|
102
110
|
/** The turn takes an upload up to the most the app lets a person upload. */
|
|
103
111
|
readonly maxAttachmentBytes: number;
|
|
112
|
+
/** Present on a surface with host rooms: what was said in one since the assistant last posted. */
|
|
113
|
+
channelContext?: ChatSurface["channelContext"];
|
|
104
114
|
readonly threads: AppSurfaceThreads;
|
|
105
115
|
readonly #options: AppSurfaceOptions;
|
|
106
116
|
readonly #activity = new Map<ChannelKey, Activity>();
|
|
@@ -114,6 +124,7 @@ export class AppSurface implements ChatSurface {
|
|
|
114
124
|
this.surface = options.surface;
|
|
115
125
|
this.maxAttachmentBytes = options.uploads.maxBytes;
|
|
116
126
|
this.#options = options;
|
|
127
|
+
if (options.channelContext) this.channelContext = options.channelContext;
|
|
117
128
|
this.threads = new AppSurfaceThreads({
|
|
118
129
|
surface: options.surface,
|
|
119
130
|
threads: options.threads,
|
|
@@ -147,26 +158,29 @@ export class AppSurface implements ChatSurface {
|
|
|
147
158
|
return this.#deliver !== undefined;
|
|
148
159
|
}
|
|
149
160
|
|
|
150
|
-
/** Stores a message of a room and shows it to
|
|
161
|
+
/** Stores a message of a room and shows it to the sockets that may have the room, except `except`. */
|
|
151
162
|
async publish(row: NewRoomMessage, except?: AppSocket): Promise<RoomMessage> {
|
|
152
163
|
const message = await this.#options.log.append(row);
|
|
153
|
-
this.#options.sockets.
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
this.#options.sockets.deliver(
|
|
165
|
+
this.#options.isHostRoom(row.room)
|
|
166
|
+
? { type: "host_message", room: row.room, message }
|
|
167
|
+
: { type: "message", room: row.room, message },
|
|
168
|
+
this.#options.audienceOf(row.room),
|
|
156
169
|
except,
|
|
157
170
|
);
|
|
158
171
|
return message;
|
|
159
172
|
}
|
|
160
173
|
|
|
161
|
-
/** Changes the text of a stored message and shows the change to
|
|
174
|
+
/** Changes the text of a stored message and shows the change to the sockets that may have the room. */
|
|
162
175
|
async edit(room: ChannelKey, id: number, text: string): Promise<void> {
|
|
163
176
|
const message = await this.#options.log.edit(room, id, text);
|
|
164
177
|
if (!message) return;
|
|
165
|
-
this.#options.sockets.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
this.#options.sockets.deliver(
|
|
179
|
+
this.#options.isHostRoom(room)
|
|
180
|
+
? { type: "host_edited", room: message.room, message }
|
|
181
|
+
: { type: "edited", room: message.room, message },
|
|
182
|
+
this.#options.audienceOf(room),
|
|
183
|
+
);
|
|
170
184
|
}
|
|
171
185
|
|
|
172
186
|
/** Whether a turn works in the room and whether Stop applies, as `activity` frames say. */
|
|
@@ -225,12 +239,15 @@ export class AppSurface implements ChatSurface {
|
|
|
225
239
|
}
|
|
226
240
|
|
|
227
241
|
#announce(room: ChannelKey, outcome?: "done" | "failed"): void {
|
|
228
|
-
this.#options.sockets.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
242
|
+
this.#options.sockets.deliver(
|
|
243
|
+
{
|
|
244
|
+
type: "activity",
|
|
245
|
+
room,
|
|
246
|
+
...this.activityOf(room),
|
|
247
|
+
...(outcome ? { outcome } : {}),
|
|
248
|
+
},
|
|
249
|
+
this.#options.audienceOf(room),
|
|
250
|
+
);
|
|
234
251
|
this.#options.activity?.();
|
|
235
252
|
}
|
|
236
253
|
|
|
@@ -283,7 +300,15 @@ export class AppSurface implements ChatSurface {
|
|
|
283
300
|
const entry = this.#activity.get(channel);
|
|
284
301
|
if (entry) entry.outcome = reply.failed ? "failed" : "done";
|
|
285
302
|
const files: RoomFile[] = (reply.files ?? []).map((file) =>
|
|
286
|
-
this.#options.files.put(
|
|
303
|
+
this.#options.files.put(
|
|
304
|
+
file.name,
|
|
305
|
+
typeByName(file.name),
|
|
306
|
+
file.data,
|
|
307
|
+
undefined,
|
|
308
|
+
this.#options.isHostRoom(channel)
|
|
309
|
+
? this.#options.fileBinding?.(channel)
|
|
310
|
+
: undefined,
|
|
311
|
+
),
|
|
287
312
|
);
|
|
288
313
|
await this.publish({
|
|
289
314
|
room: channel,
|
|
@@ -328,21 +353,26 @@ export class AppSurface implements ChatSurface {
|
|
|
328
353
|
this.#speaks(channel, as?.agent);
|
|
329
354
|
const live = crypto.randomUUID();
|
|
330
355
|
const show = (current: string) =>
|
|
331
|
-
this.#options.sockets.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
356
|
+
this.#options.sockets.deliver(
|
|
357
|
+
{
|
|
358
|
+
type: this.#options.isHostRoom(channel)
|
|
359
|
+
? "host_message"
|
|
360
|
+
: "message",
|
|
336
361
|
room: channel,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
362
|
+
message: {
|
|
363
|
+
id: 0,
|
|
364
|
+
room: channel,
|
|
365
|
+
kind: "agent",
|
|
366
|
+
...(as?.agent ? { agent: as.agent } : {}),
|
|
367
|
+
author: as?.name ?? ASSISTANT,
|
|
368
|
+
text: current,
|
|
369
|
+
files: [],
|
|
370
|
+
at: new Date().toISOString(),
|
|
371
|
+
live,
|
|
372
|
+
},
|
|
344
373
|
},
|
|
345
|
-
|
|
374
|
+
this.#options.audienceOf(channel),
|
|
375
|
+
);
|
|
346
376
|
show(text);
|
|
347
377
|
return {
|
|
348
378
|
edit: async (edited) => {
|
|
@@ -362,9 +392,15 @@ export class AppSurface implements ChatSurface {
|
|
|
362
392
|
|
|
363
393
|
progress(channel: ChannelKey, event: TurnProgress): void {
|
|
364
394
|
this.#toolEvent(channel, event);
|
|
365
|
-
|
|
395
|
+
// Only the sockets showing the room get its progress, and only those that may have the room.
|
|
396
|
+
const audience = this.#options.audienceOf(channel);
|
|
397
|
+
this.#options.sockets.deliver(
|
|
366
398
|
{ type: "progress", room: channel, event },
|
|
367
|
-
|
|
399
|
+
"owners" in audience
|
|
400
|
+
? { owners: true, focus: channel }
|
|
401
|
+
: "room" in audience
|
|
402
|
+
? { room: audience.room, focus: true }
|
|
403
|
+
: audience,
|
|
368
404
|
);
|
|
369
405
|
}
|
|
370
406
|
|