pi-roundtable-web 0.22.5 → 0.23.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/README.md +427 -104
  3. package/assets/icons/192.png +0 -0
  4. package/assets/icons/512.png +0 -0
  5. package/assets/icons/apple-180.png +0 -0
  6. package/assets/icons/badge-96.png +0 -0
  7. package/assets/icons/mark-maskable.svg +1 -0
  8. package/assets/icons/mark.svg +1 -0
  9. package/assets/icons/maskable-512.png +0 -0
  10. package/dist/chunk-character-creator-6vdshaj9.js +1 -0
  11. package/dist/chunk-connectors-s33pc7b8.js +1 -0
  12. package/dist/chunk-conversations-zpztvesk.js +1 -0
  13. package/dist/chunk-entry-th44mrfe.js +1 -0
  14. package/dist/chunk-index-4w1rf52n.js +47 -0
  15. package/dist/chunk-index-5pnpf9v5.js +41 -0
  16. package/dist/chunk-index-dkvf85c7.css +1 -0
  17. package/dist/chunk-index-kkydgsrs.js +2 -0
  18. package/dist/chunk-index-mtcervy3.js +2 -0
  19. package/dist/chunk-index-x3cf4dsz.js +1 -0
  20. package/dist/chunk-notes-ytw8qcfk.js +1 -0
  21. package/dist/chunk-overview-fbea32dq.js +1 -0
  22. package/dist/chunk-skills-58f4qz3b.js +1 -0
  23. package/dist/chunk-table-ypqdgf66.js +1 -0
  24. package/dist/chunk-table2d-bk1khqzq.js +1 -0
  25. package/dist/chunk-table3d-5vpp4bzh.js +4229 -0
  26. package/dist/chunk-transcript-nabrrayp.js +1 -0
  27. package/dist/index.html +6 -2
  28. package/dist/sw.js +1 -0
  29. package/package.json +8 -3
  30. package/src/api-types.ts +448 -1
  31. package/src/app/app.ts +351 -0
  32. package/src/app/card-log.ts +119 -0
  33. package/src/app/desk.ts +533 -0
  34. package/src/app/files.ts +262 -0
  35. package/src/app/frames.ts +132 -0
  36. package/src/app/multipart.ts +172 -0
  37. package/src/app/presence.ts +73 -0
  38. package/src/app/protocol.ts +144 -0
  39. package/src/app/room-log.ts +137 -0
  40. package/src/app/rooms.ts +189 -0
  41. package/src/app/routes.ts +434 -0
  42. package/src/app/socket.ts +406 -0
  43. package/src/app/sockets.ts +84 -0
  44. package/src/app/surface-threads.ts +92 -0
  45. package/src/app/surface.ts +384 -0
  46. package/src/app/threads.ts +99 -0
  47. package/src/app/uploads.ts +363 -0
  48. package/src/assets.ts +1 -3
  49. package/src/auth/authenticator.ts +167 -0
  50. package/src/auth/login-page.ts +44 -0
  51. package/src/auth/none.ts +38 -0
  52. package/src/auth/password.ts +250 -0
  53. package/src/auth/session-key.ts +65 -0
  54. package/src/cleanup-api.ts +1 -1
  55. package/src/cloudflare-access.ts +5 -2
  56. package/src/console-api.ts +13 -15
  57. package/src/console-server.ts +77 -49
  58. package/src/http.ts +8 -0
  59. package/src/index.ts +1 -1
  60. package/src/options.ts +308 -12
  61. package/src/page-colors.ts +9 -0
  62. package/src/page-presentation.ts +17 -12
  63. package/src/push/api.ts +190 -0
  64. package/src/push/channel.ts +50 -0
  65. package/src/push/encrypt.ts +136 -0
  66. package/src/push/endpoint.ts +54 -0
  67. package/src/push/sender.ts +109 -0
  68. package/src/push/subscriptions.ts +140 -0
  69. package/src/push/test-fetch.ts +17 -0
  70. package/src/push/triggers.ts +155 -0
  71. package/src/push/vapid.ts +124 -0
  72. package/src/pwa.ts +130 -0
  73. package/src/request-body.ts +20 -0
  74. package/src/verifier.ts +7 -1
  75. package/src/web-plugin.ts +123 -11
  76. package/dist/chunk-82frzte9.js +0 -36
  77. package/dist/chunk-e8z9q554.css +0 -1
@@ -0,0 +1 @@
1
+ import{r,s,i,t,f,e,n,c,h,o,p,d,a,m,w,H,x}from"./chunk-index-5pnpf9v5.js";function q({conversationKey:l}){let[y,E]=i(),g=s(!1),{data:u,error:N}=x(()=>a.transcript(l,y),`${l}|${y??""}`);r(()=>{if(u&&!g.current)g.current=!0,window.scrollTo(0,document.body.scrollHeight)},[u]);let b=n("a",{className:"back",href:c("conversations"),children:["← ",t("Conversations")]});if(!u)return n(f,{children:[b,N?e(o,{message:N}):e(h,{})]});let{conversation:T}=u,k=T.kind==="outside"?t("Outside agent · {id}",{id:T.id.slice(0,8)}):H(T);return n(f,{children:[b,n("section",{className:"stack",children:[n("div",{className:"card-head",children:[e("h2",{children:k}),T.busy>0?e(d,{tone:"busy",children:t("running")}):null]}),u.archives.length>0?n("label",{className:"inline",children:[t("Showing")," ",n("select",{value:y??"",onChange:(v)=>{g.current=!1,E(v.target.value||void 0)},children:[e("option",{value:"",children:t("Current conversation")}),u.archives.map((v)=>e("option",{value:v,children:t("Archived {name}",{name:v})},v))]})]}):null,N?e(o,{message:N}):null,u.truncated?e("p",{className:"hint",children:t("This conversation is longer than the console reads; the oldest entries are not shown.")}):null,u.entries.length===0?e(p,{children:t("Nothing to show.")}):null,e("ol",{className:"transcript",children:u.entries.map((v,A)=>e(C,{entry:v},A))})]})]})}function C({entry:l}){let{timeZone:y}=m(),E=l.at?w(l.at,y):"";if(l.role==="tool")return e("li",{className:"entry entry-tool",children:n("details",{children:[n("summary",{children:[l.failed?t("Tool failed"):t("Tool result"),l.tool?` · ${l.tool}`:""]}),e("pre",{children:l.text||t("(empty)")})]})});return n("li",{className:`entry entry-${l.role}`,children:[n("div",{className:"entry-head",children:[e("span",{className:"role",children:t(S[l.role])}),e("span",{className:"meta",children:E})]}),l.text?e("p",{className:"text",children:l.text}):null,l.calls?.map((g,u)=>n("code",{className:"call",children:[g.name," ",g.preview]},u))]})}var S={user:"User",assistant:"Assistant",tool:"Tool",compaction:"Summary of earlier conversation"};export{q as TranscriptPage};
package/dist/index.html CHANGED
@@ -2,10 +2,14 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
6
+ <meta name="color-scheme" content="light dark" />
6
7
  <title>Console</title>
7
8
 
8
- <link rel="stylesheet" crossorigin href="./chunk-e8z9q554.css"><script type="module" crossorigin src="./chunk-82frzte9.js"></script></head>
9
+
10
+
11
+
12
+ <link rel="stylesheet" crossorigin href="./chunk-index-dkvf85c7.css"><script type="module" crossorigin src="./chunk-index-5pnpf9v5.js"></script></head>
9
13
  <body>
10
14
  <div id="root"></div>
11
15
 
package/dist/sw.js ADDED
@@ -0,0 +1 @@
1
+ var i=self,o=new URL(i.registration.scope);var c=new URL("index.html",o).href,h=new Set(["index.html","chunk-character-creator-6vdshaj9.js","chunk-connectors-s33pc7b8.js","chunk-conversations-zpztvesk.js","chunk-entry-th44mrfe.js","chunk-index-4w1rf52n.js","chunk-index-5pnpf9v5.js","chunk-index-dkvf85c7.css","chunk-index-kkydgsrs.js","chunk-index-mtcervy3.js","chunk-index-x3cf4dsz.js","chunk-notes-ytw8qcfk.js","chunk-overview-fbea32dq.js","chunk-skills-58f4qz3b.js","chunk-table-ypqdgf66.js","chunk-table2d-bk1khqzq.js","chunk-transcript-nabrrayp.js"].map((e)=>new URL(e,o).href)),u=/\/chunk-table3d-[a-z0-9]+\.js$/,f=["api/","auth/","login","files/","avatars/"],w=(e)=>{let n=e.pathname.slice(o.pathname.length);return f.some((t)=>n.startsWith(t))};async function p(e){let n=await caches.open("roundtable-web-9fc0b00f8a22");try{let t=await fetch(e);if(t.ok&&new URL(e.url).href===o.href)await n.put(c,t.clone());return t}catch{let t=await n.match(c);if(t)return t;throw Error("offline and the shell is not cached")}}async function m(e){let n=await caches.open("roundtable-web-9fc0b00f8a22"),t=await n.match(e);if(t)return t;let s=await fetch(e);if(s.ok)await n.put(e,s.clone());return s}var r=(e,n)=>typeof e==="string"&&e?e.slice(0,n):void 0;function d(e){if(typeof e!=="string")return o.href;let n=URL.parse(e,o);return n&&n.origin===o.origin&&n.pathname.startsWith(o.pathname)?n.href:o.href}async function k(e){let n={};try{let s=e.data?.json();if(typeof s==="object"&&s!==null)n=s}catch{}let t=r(n.tag,200);await i.registration.showNotification(r(n.title,120)??"Roundtable",{body:r(n.body,240),icon:new URL("icons/192.png",o).href,badge:new URL("icons/badge-96.png",o).href,...t?{tag:t,renotify:!0}:{},data:{url:d(n.url)}})}async function g(e){e.notification.close();let n=e.notification.data,t=d(n?.url),a=(await i.clients.matchAll({type:"window",includeUncontrolled:!0})).find((l)=>l.url.startsWith(o.href));if(a){await a.focus(),a.postMessage({type:"roundtable:open",url:t});return}await i.clients.openWindow(t)}i.addEventListener("install",(e)=>{e.waitUntil(caches.open("roundtable-web-9fc0b00f8a22").then((n)=>n.addAll([...h])).then(()=>i.skipWaiting()))});i.addEventListener("activate",(e)=>{e.waitUntil(caches.keys().then((n)=>Promise.all(n.filter((t)=>t.startsWith("roundtable-web-")&&t!=="roundtable-web-9fc0b00f8a22").map((t)=>caches.delete(t)))).then(()=>i.clients.claim()))});i.addEventListener("fetch",(e)=>{let{request:n}=e;if(n.method!=="GET")return;let t=new URL(n.url);if(t.origin!==o.origin||!t.pathname.startsWith(o.pathname))return;if(w(t))return;if(n.mode==="navigate")e.respondWith(p(n));else if(h.has(t.origin+t.pathname)||u.test(t.pathname))e.respondWith(m(n))});i.addEventListener("push",(e)=>{e.waitUntil(k(e))});i.addEventListener("notificationclick",(e)=>{e.waitUntil(g(e))});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-roundtable-web",
3
- "version": "0.22.5",
3
+ "version": "0.23.0",
4
4
  "description": "A web console for the owners of a pi-roundtable host: conversations, transcripts, and memory notes, with live updates",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,6 +15,7 @@
15
15
  "!src/**/*.test.ts",
16
16
  "!src/testing",
17
17
  "dist",
18
+ "assets",
18
19
  "LICENSE",
19
20
  "README.md",
20
21
  "CHANGELOG.md"
@@ -32,6 +33,7 @@
32
33
  "build": "bun scripts/build.ts",
33
34
  "prepack": "bun scripts/build.ts",
34
35
  "fixture": "bun scripts/fixture-host.ts",
36
+ "fixture:app": "bun scripts/fixture-app.ts",
35
37
  "typecheck": "tsc --noEmit && tsc --noEmit -p web",
36
38
  "lint": "biome check .",
37
39
  "test": "bun test"
@@ -40,18 +42,21 @@
40
42
  "jose": "6.2.12"
41
43
  },
42
44
  "peerDependencies": {
43
- "pi-roundtable": ">=0.22.0 <0.23.0"
45
+ "pi-roundtable": ">=0.23.0 <0.24.0"
44
46
  },
45
47
  "devDependencies": {
46
48
  "@biomejs/biome": "2.5.15",
49
+ "@happy-dom/global-registrator": "21.0.4",
47
50
  "@types/bun": "1.4.3",
48
51
  "@types/react": "19.3.0",
49
52
  "@types/react-dom": "19.3.0",
50
- "pi-roundtable": "0.22.5",
53
+ "@types/three": "0.186.0",
54
+ "pi-roundtable": "0.23.0",
51
55
  "react": "19.3.0",
52
56
  "react-dom": "19.3.0",
53
57
  "react-markdown": "10.1.0",
54
58
  "remark-gfm": "4.0.1",
59
+ "three": "0.186.1",
55
60
  "typescript": "7.0.2"
56
61
  }
57
62
  }
package/src/api-types.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  // The console's API shapes, shared by the server (`src/`) and the page (`web/`). Times are ISO strings.
2
2
 
3
+ import type { CharacterInput, CharacterSpec } from "pi-roundtable/characters";
4
+
5
+ /** What an agent is doing in a room, while it works. */
6
+ export type ActivityPhase = "thinking" | "working";
7
+
3
8
  /** The panes the console can show; the options choose which ones a host serves. */
4
9
  export const PANES = [
5
10
  "overview",
@@ -7,6 +12,9 @@ export const PANES = [
7
12
  "notes",
8
13
  "skills",
9
14
  "connectors",
15
+ "rooms",
16
+ "schedules",
17
+ "table",
10
18
  ] as const;
11
19
  export type PaneName = (typeof PANES)[number];
12
20
 
@@ -16,12 +24,21 @@ export interface ConfigView {
16
24
  panes: PaneName[];
17
25
  /** The IANA zone the page formats times and event dates in: the host's `env.timeZone`. */
18
26
  timeZone: string;
27
+ /** The page's look unless the owner picked one in the app; the server always sends it. */
28
+ look?: "modern" | "camelot";
19
29
  locale?: string;
20
30
  messages?: Readonly<Record<string, string>>;
21
31
  cleanup?: boolean;
22
32
  connectorAdminUrl?: string;
23
33
  /** Only supplied for path-based routing; also the base for page/API URLs. */
24
34
  mountPath?: string;
35
+ /** Present when the `rooms` pane is served: how the page opens the owner app's socket. */
36
+ app?: {
37
+ /** The key prefix of the app's rooms, such as `app`. */
38
+ surface: string;
39
+ /** The WebSocket subprotocol to offer. */
40
+ protocol: string;
41
+ };
25
42
  }
26
43
 
27
44
  export type ChannelName =
@@ -54,7 +71,8 @@ export interface AgentView {
54
71
  export interface GroupView {
55
72
  name: string;
56
73
  displayName: string;
57
- channelId: string;
74
+ /** The group's Discord channel; a team without Discord has none. */
75
+ channelId?: string;
58
76
  members: string[];
59
77
  host: string;
60
78
  busy: number;
@@ -222,3 +240,432 @@ export interface ConnectorsView {
222
240
  export interface ApiError {
223
241
  error: string;
224
242
  }
243
+
244
+ // ── The owner app: rooms, messages, cards, and the socket's frames ───────────────────────────────
245
+
246
+ /** The WebSocket subprotocol of `<mount>/api/socket`; a later incompatible version gets a new name. */
247
+ export const APP_PROTOCOL = "roundtable.app.v1";
248
+ /** The version `ready` reports. */
249
+ export const APP_PROTOCOL_VERSION = 1;
250
+ /** The close code that says the credential lapsed: reconnect, and reload the page when the reconnect is refused. */
251
+ export const APP_CLOSE_EXPIRED = 4401;
252
+ /** The close code that says the person is no owner (any more): do not reconnect. */
253
+ export const APP_CLOSE_FORBIDDEN = 4403;
254
+ /** The most bytes (UTF-8, as JSON) an approval frame's `approval` carries before its long values are cut: 256 KiB. */
255
+ export const APPROVAL_MAX_BYTES = 256 * 1024;
256
+ /** The most files one message carries. */
257
+ export const MAX_MESSAGE_FILES = 4;
258
+ /** The longest message text in characters. */
259
+ export const MAX_MESSAGE_CHARS = 32_000;
260
+
261
+ /** What a room is: an agent's, a group's, or a dispatch thread (read only). */
262
+ export type RoomKind = "agent" | "group" | "thread";
263
+
264
+ /** The conversation's modes, per room. */
265
+ export interface RoomMode {
266
+ yolo: boolean;
267
+ longRunning: boolean;
268
+ }
269
+
270
+ /** A file in a message, served at `<mount>/api/files/<id>`. */
271
+ export interface RoomFile {
272
+ id: string;
273
+ name: string;
274
+ type: string;
275
+ size: number;
276
+ }
277
+
278
+ /**
279
+ * One message of a room. `id` is the room log's own number, ascending; a live message (an
280
+ * agent's interim text, shown until its final reply arrives) has `id` 0 and a `live` token,
281
+ * and a later frame with the same token replaces it. It is never stored: the client drops a
282
+ * room's live messages when a stored message of that agent arrives or the agent stops working.
283
+ */
284
+ export interface RoomMessage {
285
+ id: number;
286
+ room: string;
287
+ /** `owner` wrote it, `agent` is a reply, `system` is a line the server wrote: a divider, a yolo approval. */
288
+ kind: "owner" | "agent" | "system";
289
+ /** The principal of an `owner` message. */
290
+ principalId?: string;
291
+ /** The agent name of an `agent` message. */
292
+ agent?: string;
293
+ /** The name to show with it. */
294
+ author: string;
295
+ text: string;
296
+ files: RoomFile[];
297
+ /** ISO time. */
298
+ at: string;
299
+ live?: string;
300
+ }
301
+
302
+ /** One entry of the room list. */
303
+ export interface RoomView {
304
+ /** The channel key: `<surface>:agent.<name>`, `<surface>:group.<name>` or `<surface>:thread.<id>`. */
305
+ key: string;
306
+ kind: RoomKind;
307
+ /** The agent's or group's name; a thread's id. */
308
+ name: string;
309
+ displayName: string;
310
+ /** The coordinator's room, where the page opens. */
311
+ home: boolean;
312
+ /** The agent's picture, a path under the mount, for notification icons; absent for groups and threads. */
313
+ avatar?: string;
314
+ /** An agent room's character; a group page looks its members' up in the agent rooms. */
315
+ character?: CharacterSpec;
316
+ /** A group's members, by agent name. */
317
+ members?: string[];
318
+ /** A thread's parent room. */
319
+ parent?: string;
320
+ /** A thread's room takes no messages from the page. */
321
+ readOnly: boolean;
322
+ /** A thread whose dispatch has ended; its room stays readable. */
323
+ closed?: boolean;
324
+ mode: RoomMode;
325
+ /** The room's agent is working now. */
326
+ typing: boolean;
327
+ /** While typing: `working` during a tool call, `thinking` otherwise. */
328
+ phase?: ActivityPhase;
329
+ /** While typing: the member speaking, in a group round. */
330
+ agent?: string;
331
+ /** A turn runs that Stop would end. */
332
+ stoppable: boolean;
333
+ /** The newest stored message, if any. */
334
+ last?: RoomMessage;
335
+ }
336
+
337
+ export interface RoomsView {
338
+ rooms: RoomView[];
339
+ }
340
+
341
+ /** A page of a room's stored messages, oldest first, and the cards that ended among them. */
342
+ export interface MessagesView {
343
+ messages: RoomMessage[];
344
+ /** There are older messages than the first one here. */
345
+ more: boolean;
346
+ /**
347
+ * The cards of the room that opened within this page's span (from the first message's time,
348
+ * or from the beginning when `more` is false, up to the next page's first message), oldest
349
+ * first; the page places each among the messages by `frame.at`.
350
+ */
351
+ cards: ClosedCard[];
352
+ }
353
+
354
+ /** Who the page is signed in as. */
355
+ export interface SessionView {
356
+ principal: PrincipalName;
357
+ surface: string;
358
+ /** How the console signs the person in; the page offers Log out under `password`. */
359
+ auth: "none" | "password" | "verifier";
360
+ /** When the credential lapses, if it does. */
361
+ expiresAt?: string;
362
+ /** Web Push is available: the app serves the rooms and a key for it. */
363
+ push?: true;
364
+ }
365
+
366
+ /** The key a browser subscribes to push against. */
367
+ export interface PushKeyView {
368
+ key: string;
369
+ }
370
+
371
+ /** One device of the person that gets push; the id is not the endpoint. */
372
+ export interface PushDeviceView {
373
+ id: string;
374
+ label?: string;
375
+ createdAt: string;
376
+ lastOkAt?: string;
377
+ }
378
+
379
+ export interface PushDevicesView {
380
+ devices: PushDeviceView[];
381
+ }
382
+
383
+ /** A schedule, labelled by the room it belongs to or else its channel. */
384
+ export interface ScheduleView {
385
+ id: number;
386
+ /** The room it runs in, when it belongs to one of the app's. */
387
+ room?: string;
388
+ /** The channel key it runs in. */
389
+ channel: string;
390
+ /** What to call that place: the room's name or the channel key. */
391
+ label: string;
392
+ title: string;
393
+ prompt: string;
394
+ /** The schedule's recurrence as the core stores it. */
395
+ recurrence: unknown;
396
+ nextRun: string;
397
+ lastRun?: string;
398
+ lastStatus?: string;
399
+ createdBy: string;
400
+ }
401
+
402
+ export interface SchedulesView {
403
+ schedules: ScheduleView[];
404
+ }
405
+
406
+ /** An agent's profile as its page edits it. */
407
+ export interface AgentProfileView {
408
+ name: string;
409
+ displayName: string;
410
+ prompt: string;
411
+ /** Its own model; absent when it follows the assistant's. */
412
+ model?: string;
413
+ /** Its own thinking level; absent when it follows the assistant's. */
414
+ thinking?: string;
415
+ /** What its next turn runs on. */
416
+ effective: { model: string; thinking: string };
417
+ avatar: string;
418
+ character: CharacterSpec;
419
+ room: string;
420
+ }
421
+
422
+ export interface ModelsView {
423
+ models: string[];
424
+ }
425
+
426
+ /** What changes an agent: the same fields `AgentTeam.update` takes. */
427
+ export interface AgentPatch {
428
+ displayName?: string;
429
+ prompt?: string;
430
+ model?: string;
431
+ thinking?: string;
432
+ }
433
+
434
+ /** The body of `PUT api/agents/<name>/character`: at most 4 KiB. */
435
+ export interface CharacterRequest {
436
+ character: CharacterInput;
437
+ }
438
+
439
+ /** The 400 of `PUT api/agents/<name>/character`: `field` is the path of the offending field. */
440
+ export interface CharacterRefusal extends ApiError {
441
+ field: string;
442
+ }
443
+
444
+ /** A file waiting for the message that uses it. */
445
+ export interface UploadView {
446
+ id: string;
447
+ name: string;
448
+ type: string;
449
+ size: number;
450
+ }
451
+
452
+ /** An option of a question, as the core writes it. */
453
+ export interface PromptOption {
454
+ label: string;
455
+ description?: string;
456
+ preview?: string;
457
+ }
458
+
459
+ /** One question of a card. */
460
+ export interface PromptQuestion {
461
+ question: string;
462
+ header?: string;
463
+ /** None means a free-text answer. */
464
+ options: readonly PromptOption[];
465
+ multi: boolean;
466
+ allowOther: boolean;
467
+ }
468
+
469
+ /** What a held call is, as data beside its markdown text. */
470
+ export interface PromptApproval {
471
+ action: string;
472
+ tool: string;
473
+ input: Record<string, unknown>;
474
+ files?: { path: string; bytes?: number }[];
475
+ copy?: { label?: string; text: string };
476
+ /** Set when long values of `input` were cut to fit `APPROVAL_MAX_BYTES`. */
477
+ truncated?: true;
478
+ }
479
+
480
+ /** The card's own words in place of an approval's. */
481
+ export interface PromptWording {
482
+ approve: string;
483
+ decline: string;
484
+ footer: string;
485
+ approved: string;
486
+ declined: string;
487
+ }
488
+
489
+ /** An answer to a question: the chosen options' labels and the person's own text. */
490
+ export interface PromptAnswer {
491
+ choices: string[];
492
+ text?: string;
493
+ }
494
+
495
+ /** A card the server asks the owner: an approval, a question, or several questions. */
496
+ export type PromptFrame =
497
+ | {
498
+ id: string;
499
+ /** When the card opened (ISO 8601, the server's clock), which places it among the room's messages. */
500
+ at?: string;
501
+ kind: "approval";
502
+ title: string;
503
+ /** The card as markdown text. */
504
+ message: string;
505
+ approval?: PromptApproval;
506
+ /** `turn-timeout` is a turn's time-limit card: continuing it lets the turn go on. */
507
+ purpose: "action" | "turn-timeout";
508
+ wording?: PromptWording;
509
+ /** Only the speaker whose turn asks may answer. */
510
+ speakerOnly?: true;
511
+ /** Display names of the people the call names to approve it; owners may still. */
512
+ approvers?: string[];
513
+ /** The turn stopped waiting; an answer starts a new turn in the room. */
514
+ late?: true;
515
+ }
516
+ | {
517
+ id: string;
518
+ at?: string;
519
+ kind: "ask";
520
+ title: string;
521
+ question: string;
522
+ options: readonly PromptOption[];
523
+ multi: boolean;
524
+ allowOther: boolean;
525
+ late?: true;
526
+ }
527
+ | {
528
+ id: string;
529
+ at?: string;
530
+ kind: "questionnaire";
531
+ title: string;
532
+ questions: readonly PromptQuestion[];
533
+ late?: true;
534
+ };
535
+
536
+ /** How a card ended. `withdrawn` is a card the plugin that asked for it took back. */
537
+ export type PromptOutcome =
538
+ | "approved"
539
+ | "declined"
540
+ | "answered"
541
+ | "expired"
542
+ | "cancelled"
543
+ | "withdrawn";
544
+
545
+ /** What a person gave a question card: one answer, or one per question with the note beside them. */
546
+ export type CardAnswer =
547
+ | { kind: "ask"; answer: PromptAnswer }
548
+ | { kind: "questionnaire"; answers: (PromptAnswer | null)[]; note?: string };
549
+
550
+ /** A card that ended and is kept in its room's history, with how it ended. */
551
+ export interface ClosedCard {
552
+ room: string;
553
+ /** The card as it was shown, `at` being when it opened. */
554
+ frame: PromptFrame;
555
+ outcome: PromptOutcome;
556
+ /** Why a `withdrawn` card was taken back. */
557
+ note?: string;
558
+ /** Who answered it, by display name; absent for a card nobody answered. */
559
+ by?: string;
560
+ /** What was answered, for the question cards. */
561
+ answer?: CardAnswer;
562
+ /** When it ended (ISO 8601, the server's clock). */
563
+ closedAt: string;
564
+ }
565
+
566
+ /** Why the server refused a frame. */
567
+ export type RefusalCode =
568
+ | "bad_frame"
569
+ | "unknown_room"
570
+ | "read_only"
571
+ | "forbidden"
572
+ | "unknown_prompt"
573
+ | "unknown_upload"
574
+ | "empty"
575
+ | "too_long"
576
+ | "not_accepted";
577
+
578
+ /** What the page sends: one JSON object per WebSocket text message. */
579
+ export type ClientFrame =
580
+ /** Declares whether the page is in view and which room it shows. Sent after each `ready`. */
581
+ | { type: "hello"; visible: boolean; focus?: string }
582
+ | { type: "visibility"; visible: boolean }
583
+ /** The room the page shows now, or none: progress streams only for it. */
584
+ | { type: "focus"; room?: string }
585
+ | {
586
+ type: "send";
587
+ /** The page's own reference, echoed by `accepted` or `refused`. */
588
+ id: string;
589
+ room: string;
590
+ text: string;
591
+ /** Files uploaded with `POST api/uploads`, by the `id` each returned. */
592
+ uploads?: string[];
593
+ }
594
+ | { type: "stop"; room: string }
595
+ | { type: "approval"; prompt: string; approved: boolean }
596
+ | { type: "answer"; prompt: string; answer: PromptAnswer }
597
+ | {
598
+ type: "answers";
599
+ prompt: string;
600
+ answers: (PromptAnswer | null)[];
601
+ note?: string;
602
+ }
603
+ /** Cancels a question card instead of answering it. */
604
+ | { type: "cancel"; prompt: string };
605
+
606
+ /** What the server sends: one JSON object per WebSocket text message. */
607
+ export type ServerFrame =
608
+ /** The socket is authenticated; sent first, and followed by every open card. */
609
+ | {
610
+ type: "ready";
611
+ protocol: typeof APP_PROTOCOL_VERSION;
612
+ principal: string;
613
+ name: string;
614
+ /** The most files one message carries and the most bytes of one. */
615
+ uploads: { perMessage: number; maxBytes: number };
616
+ }
617
+ /** The message `id` was taken into `room`; `message` is how it was stored. */
618
+ | { type: "accepted"; id: string; room: string; message: RoomMessage }
619
+ /** `id` names the refused `send`; `prompt` names the card whose answer was refused. */
620
+ | { type: "refused"; id?: string; prompt?: string; code: RefusalCode }
621
+ /** A message of a room, stored or live; see `RoomMessage`. */
622
+ | { type: "message"; room: string; message: RoomMessage }
623
+ /** A stored message changed its text in place, such as a thread's start line once the thread exists. */
624
+ | { type: "edited"; room: string; message: RoomMessage }
625
+ /** What the running turn writes and which tools it runs; sent to the sockets that focus the room. */
626
+ | { type: "progress"; room: string; event: AppProgress }
627
+ /** The room's agent is, or is no longer, working, and whether Stop applies. */
628
+ | {
629
+ type: "activity";
630
+ room: string;
631
+ typing: boolean;
632
+ stoppable: boolean;
633
+ phase?: ActivityPhase;
634
+ /** The member speaking, in a group round. */
635
+ agent?: string;
636
+ /** Only on the frame where `typing` turns false. */
637
+ outcome?: "done" | "failed";
638
+ }
639
+ /** Which agents work now, anywhere (Discord included); sent after `ready` and when it changes. */
640
+ | { type: "presence"; agents: Record<string, { busy: boolean }> }
641
+ | { type: "prompt"; room: string; prompt: PromptFrame }
642
+ | {
643
+ type: "prompt_closed";
644
+ prompt: string;
645
+ outcome: PromptOutcome;
646
+ /** Why a `withdrawn` card was taken back. */
647
+ note?: string;
648
+ /** Who answered the card, by display name. */
649
+ by?: string;
650
+ /** What was answered, for the question cards. */
651
+ answer?: CardAnswer;
652
+ /** When it ended (ISO 8601, the server's clock). */
653
+ closedAt?: string;
654
+ }
655
+ | { type: "mode"; room: string; yolo: boolean; longRunning: boolean }
656
+ /** Yolo mode approved a held call by itself. */
657
+ | { type: "auto_approved"; room: string; tool: string; action: string }
658
+ /** The team or the room list may differ: refetch. */
659
+ | { type: "changed" };
660
+
661
+ /** A turn's live view, as the core reports it. */
662
+ export type AppProgress =
663
+ | { type: "text"; delta: string }
664
+ | { type: "tool_start"; id: string; tool: string; preview?: string }
665
+ | {
666
+ type: "tool_end";
667
+ id: string;
668
+ tool: string;
669
+ ok: boolean;
670
+ refused?: "declined" | "expired" | "pending" | "held" | "withdrawn";
671
+ };