pi-roundtable-web 0.28.0 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +4 -4
  3. package/dist/chunk-account-66e6ezdq.js +1 -0
  4. package/dist/{chunk-action-menu-qpp9bdxh.js → chunk-action-menu-ap5xn7bf.js} +1 -1
  5. package/dist/chunk-agent-sapt5qhn.js +1 -0
  6. package/dist/chunk-arranged-rooms-n6bk696f.js +1 -0
  7. package/dist/chunk-attachments-p5v9gqyt.js +102 -0
  8. package/dist/chunk-bulk-actions-aze4ewc4.js +1 -0
  9. package/dist/chunk-character-creator-m7tw20ef.js +225 -0
  10. package/dist/{chunk-codeeditor-jgj32247.js → chunk-codeeditor-yy6pq8mv.js} +2 -2
  11. package/dist/chunk-confirm-0cajsfhk.js +1 -0
  12. package/dist/chunk-connectors-tw0tbm21.js +37 -0
  13. package/dist/chunk-conversations-wkm23m5r.js +1 -0
  14. package/dist/chunk-council-sheet-mz7s23nq.js +2 -0
  15. package/dist/chunk-council-view-zgvzxtgn.js +1 -0
  16. package/dist/{chunk-drag-04qfef58.js → chunk-drag-d1bpy8w6.js} +1 -1
  17. package/dist/{chunk-entry-mws704mc.js → chunk-entry-7zr5b1fp.js} +1 -1
  18. package/dist/chunk-folder-list-bs4eh3g9.js +1 -0
  19. package/dist/chunk-folds-eez1q41c.js +1 -0
  20. package/dist/{chunk-gate-vxetvn8t.js → chunk-gate-dt91egcd.js} +1 -1
  21. package/dist/chunk-host-icons-nfe2t8tk.js +1 -0
  22. package/dist/chunk-host-picture-4rtswxwa.js +1 -0
  23. package/dist/chunk-host-state-1vxzmmq6.js +1 -0
  24. package/dist/chunk-host-ui-x38bf0et.js +1 -0
  25. package/dist/{chunk-index-zj0gjcvr.js → chunk-index-0h99xcs3.js} +2 -2
  26. package/dist/{chunk-index-e2gy763y.js → chunk-index-1kb8tv4v.js} +2 -2
  27. package/dist/chunk-index-24cggw6k.js +50 -0
  28. package/dist/chunk-index-2g8xtwqg.js +2 -0
  29. package/dist/chunk-index-3n38xe7m.js +2 -0
  30. package/dist/{chunk-index-n0cmnwd2.js → chunk-index-3tksdmx7.js} +2 -2
  31. package/dist/{chunk-index-2822z600.js → chunk-index-4a1tdadd.js} +3 -3
  32. package/dist/{chunk-index-2syyyjnq.js → chunk-index-53fwc7sc.js} +2 -2
  33. package/dist/chunk-index-55ps4x5n.js +2 -0
  34. package/dist/{chunk-index-q5fyrtx4.js → chunk-index-5j1v2q7w.js} +1 -1
  35. package/dist/chunk-index-5w6tb7p0.js +2 -0
  36. package/dist/chunk-index-6btdp13p.js +2 -0
  37. package/dist/chunk-index-6m7dwsmv.js +1 -0
  38. package/dist/{chunk-index-20xgx0k2.js → chunk-index-6rdywten.js} +2 -2
  39. package/dist/{chunk-index-3gfbhmpb.js → chunk-index-6s2pqwna.js} +2 -2
  40. package/dist/{chunk-index-fepsbhr6.js → chunk-index-7mrkq793.js} +2 -2
  41. package/dist/chunk-index-7xcbpeqz.js +2 -0
  42. package/dist/chunk-index-88wh5eyj.js +2 -0
  43. package/dist/{chunk-index-6cg8xp1c.js → chunk-index-8pbwr3rs.js} +1 -1
  44. package/dist/{chunk-index-jc3r4qam.js → chunk-index-8wpvj7k5.js} +2 -2
  45. package/dist/chunk-index-97dcp1vh.js +1 -0
  46. package/dist/chunk-index-a7e6rhxy.js +2 -0
  47. package/dist/{chunk-index-2tr41qht.js → chunk-index-bxf8eq1s.js} +2 -2
  48. package/dist/chunk-index-c8gm1crh.js +120 -0
  49. package/dist/{chunk-index-2y8r4s4f.js → chunk-index-ce7hsda9.js} +2 -2
  50. package/dist/chunk-index-cfesmk1f.js +2 -0
  51. package/dist/{chunk-index-m81362sc.js → chunk-index-cmzvq3nf.js} +1 -1
  52. package/dist/{chunk-index-7qd604rq.js → chunk-index-d4mx2vvf.js} +1 -1
  53. package/dist/chunk-index-d74tpnkd.js +2 -0
  54. package/dist/chunk-index-d941ae7c.js +2 -0
  55. package/dist/{chunk-index-e35v554x.js → chunk-index-dr0jr7d3.js} +3 -3
  56. package/dist/chunk-index-en728705.js +2 -0
  57. package/dist/chunk-index-fb3kzyyp.js +2 -0
  58. package/dist/{chunk-index-3yy10a5h.js → chunk-index-ffnd0jcj.js} +2 -2
  59. package/dist/chunk-index-fn6305vn.js +3 -0
  60. package/dist/{chunk-index-zcfr8jdv.js → chunk-index-gdh7wsd5.js} +12 -12
  61. package/dist/{chunk-index-2hvn2kte.js → chunk-index-m76gdwnk.js} +2 -2
  62. package/dist/chunk-index-mr391t49.js +2 -0
  63. package/dist/chunk-index-n3khm9fq.js +2 -0
  64. package/dist/{chunk-index-sm7kfb07.js → chunk-index-nn4w6a04.js} +2 -2
  65. package/dist/{chunk-index-gwkhs8s3.js → chunk-index-nr275sa7.js} +2 -2
  66. package/dist/{chunk-index-x60n1me6.js → chunk-index-pgxgq61x.js} +2 -2
  67. package/dist/chunk-index-prq011b6.js +2 -0
  68. package/dist/{chunk-index-v9gbt5tz.js → chunk-index-q4k9vk04.js} +19 -3
  69. package/dist/chunk-index-rgdaec7y.js +2 -0
  70. package/dist/chunk-index-rjs1can6.js +2 -0
  71. package/dist/chunk-index-s4a8e4qm.js +2 -0
  72. package/dist/{chunk-index-s9p27f9b.js → chunk-index-sz381vda.js} +3 -3
  73. package/dist/{chunk-index-5hbq6g2e.css → chunk-index-t7csmbpy.css} +1 -1
  74. package/dist/{chunk-index-rgf4rvns.js → chunk-index-v9jqyr1f.js} +3 -3
  75. package/dist/{chunk-index-rsd8rftk.js → chunk-index-vpq2cj14.js} +2 -2
  76. package/dist/chunk-index-vsaggfns.js +2 -0
  77. package/dist/{chunk-index-p7jkr7gv.js → chunk-index-w17wgjby.js} +5 -5
  78. package/dist/{chunk-index-c7dbvkrq.js → chunk-index-wgrx2ybn.js} +2 -2
  79. package/dist/chunk-index-wsv46y9p.js +2 -0
  80. package/dist/{chunk-index-4t6vnwap.js → chunk-index-xbj816p0.js} +2 -2
  81. package/dist/{chunk-index-s53bxwkk.js → chunk-index-yr758n6t.js} +2 -2
  82. package/dist/{chunk-index-5t2jkecy.js → chunk-index-ysv36nbk.js} +2 -2
  83. package/dist/{chunk-index-zt19fdmc.js → chunk-index-ze9p4npm.js} +2 -2
  84. package/dist/chunk-index-zfdvyrjt.js +2 -0
  85. package/dist/chunk-lang-css-3h22se37.js +1 -0
  86. package/dist/{chunk-lang-dockerfile-n4bt8jbt.js → chunk-lang-dockerfile-y24eejnh.js} +1 -1
  87. package/dist/{chunk-lang-go-9k4y107e.js → chunk-lang-go-qzgv1161.js} +3 -3
  88. package/dist/chunk-lang-html-9rvrmmpg.js +1 -0
  89. package/dist/chunk-lang-javascript-4kv8s3tk.js +1 -0
  90. package/dist/{chunk-lang-json-hr12bzs1.js → chunk-lang-json-99v80v88.js} +1 -1
  91. package/dist/{chunk-lang-markdown-ttzcpgkg.js → chunk-lang-markdown-gn8he877.js} +4 -4
  92. package/dist/{chunk-lang-python-ntj8maem.js → chunk-lang-python-prsw4p4k.js} +2 -2
  93. package/dist/{chunk-lang-rust-gv8y859d.js → chunk-lang-rust-g6xqta16.js} +1 -1
  94. package/dist/{chunk-lang-shell-5zzs09b7.js → chunk-lang-shell-dmpnzc6w.js} +1 -1
  95. package/dist/{chunk-lang-sql-6vmze4pg.js → chunk-lang-sql-wz7ca27b.js} +2 -2
  96. package/dist/{chunk-lang-toml-tt0d4rw9.js → chunk-lang-toml-160prtq4.js} +1 -1
  97. package/dist/chunk-lang-typescript-mp0yw903.js +1 -0
  98. package/dist/{chunk-lang-xml-r2sbqnjv.js → chunk-lang-xml-9htkz2dd.js} +1 -1
  99. package/dist/{chunk-lang-yaml-ex31qbph.js → chunk-lang-yaml-08749wd5.js} +1 -1
  100. package/dist/chunk-layout-actions-tdx896xf.js +1 -0
  101. package/dist/chunk-menu-items-trhybv1g.js +1 -0
  102. package/dist/chunk-mode-toggle-4dtfphq4.js +1 -0
  103. package/dist/{chunk-note-row-40ypghn9.js → chunk-note-row-28e5x7n8.js} +33 -4
  104. package/dist/chunk-notes-5nkayhw8.js +1 -0
  105. package/dist/chunk-notice-bell-xz8a04am.js +1 -0
  106. package/dist/{chunk-notices-tefx93s6.js → chunk-notices-d0cwvk27.js} +1 -1
  107. package/dist/chunk-onboarding-zs4rtea9.js +1 -0
  108. package/dist/{chunk-overview-zrx3bpwf.js → chunk-overview-9sb3ywa8.js} +1 -1
  109. package/dist/chunk-palette-5sfhrv4x.js +1 -0
  110. package/dist/chunk-placement-items-e2neja55.js +1 -0
  111. package/dist/{chunk-profile-tyb48b79.js → chunk-profile-jffhtpqf.js} +1 -1
  112. package/dist/chunk-prompt-history-1jz2gk3v.js +1 -0
  113. package/dist/chunk-prompts-0t30ttd0.js +1 -0
  114. package/dist/chunk-room-confirms-6595921h.js +1 -0
  115. package/dist/{chunk-room-menu-ha1evv71.js → chunk-room-menu-hwkjyxwe.js} +1 -1
  116. package/dist/chunk-rooms-menu-button-k0eq1tcr.js +1 -0
  117. package/dist/{chunk-schedules-a5pewkhd.js → chunk-schedules-e12y8fw2.js} +1 -1
  118. package/dist/{chunk-seat-menu-eh3ymmks.js → chunk-seat-menu-n2bcw0mf.js} +1 -1
  119. package/dist/chunk-sheet-hosts-myvm2vhd.js +29 -0
  120. package/dist/chunk-shortcut-help-434sqnxc.js +26 -0
  121. package/dist/chunk-shortcuts-mjxph0g7.js +1 -0
  122. package/dist/chunk-skill-editor-98492yrt.js +8 -0
  123. package/dist/chunk-skills-mfv8ppfh.js +1 -0
  124. package/dist/chunk-start-over-line-m526193z.js +16 -0
  125. package/dist/{chunk-switcher-list-dtfh87zy.js → chunk-switcher-list-5ngjar24.js} +1 -1
  126. package/dist/{chunk-tab-dot-qd640mjr.js → chunk-tab-dot-tzjkahj5.js} +1 -1
  127. package/dist/chunk-table-xbtbex2z.js +1 -0
  128. package/dist/{chunk-table2d-5nrhfwft.js → chunk-table2d-61bvrykm.js} +1 -1
  129. package/dist/{chunk-table3d-5cb66d99.js → chunk-table3d-5a7vbgyj.js} +172 -172
  130. package/dist/chunk-thread-actions-bf6vf2mt.js +1 -0
  131. package/dist/{chunk-thread-cards-kgt22sbp.js → chunk-thread-cards-n0n603vf.js} +1 -1
  132. package/dist/chunk-thread-list-7bkdn51x.js +227 -0
  133. package/dist/{chunk-transcript-a0p3vwh6.js → chunk-transcript-8ztrkt6g.js} +1 -1
  134. package/dist/chunk-waiting-chip-y83c90v3.js +32 -0
  135. package/dist/chunk-waiting-s273kcgz.js +1 -0
  136. package/dist/chunk-where-jr791gzj.js +1 -0
  137. package/dist/chunk-workspace-dw71mshw.js +586 -0
  138. package/dist/chunk-zh-tw-vm5d0w6j.js +1 -0
  139. package/dist/index.html +1 -1
  140. package/dist/sw.js +1 -1
  141. package/package.json +3 -3
  142. package/src/api-types.ts +51 -0
  143. package/src/app/app.ts +7 -0
  144. package/src/app/card-log.ts +10 -2
  145. package/src/app/protocol.ts +18 -0
  146. package/src/app/queued-delivery.ts +107 -0
  147. package/src/app/queued-frames.ts +115 -0
  148. package/src/app/room-log.ts +39 -7
  149. package/src/app/rooms.ts +30 -2
  150. package/src/app/routes.ts +123 -7
  151. package/src/app/skill-group-routes.ts +126 -0
  152. package/src/app/socket.ts +12 -2
  153. package/src/app/surface.ts +37 -6
  154. package/src/app/zip.ts +124 -0
  155. package/src/auth/sessions.ts +9 -1
  156. package/src/authz.ts +1 -0
  157. package/src/host/cards.ts +10 -3
  158. package/src/host/stored-json.ts +23 -0
  159. package/src/i18n/zh-tw.ts +45 -2
  160. package/src/notices/mount.ts +1 -0
  161. package/src/notices/store.ts +9 -1
  162. package/src/push/channel.ts +1 -0
  163. package/src/web-plugin.ts +5 -1
  164. package/dist/chunk-account-678dgbww.js +0 -1
  165. package/dist/chunk-agent-x0yvvdq2.js +0 -1
  166. package/dist/chunk-arranged-rooms-6rdkk8t1.js +0 -1
  167. package/dist/chunk-bulk-actions-zbwdaxtc.js +0 -1
  168. package/dist/chunk-character-creator-215psy17.js +0 -1
  169. package/dist/chunk-confirm-2pzrt272.js +0 -1
  170. package/dist/chunk-connectors-zha2d834.js +0 -37
  171. package/dist/chunk-conversations-fb071rez.js +0 -1
  172. package/dist/chunk-council-sheet-k5fycefd.js +0 -2
  173. package/dist/chunk-folder-list-qxhmczwv.js +0 -1
  174. package/dist/chunk-folds-b7asegfs.js +0 -1
  175. package/dist/chunk-group-scene-63yhh1mh.js +0 -1
  176. package/dist/chunk-group-table-zy4hmgk3.js +0 -1
  177. package/dist/chunk-host-icons-tyktazzz.js +0 -1
  178. package/dist/chunk-host-picture-ebhejn2k.js +0 -1
  179. package/dist/chunk-host-state-wajep0yr.js +0 -1
  180. package/dist/chunk-host-ui-pc9r24e3.js +0 -1
  181. package/dist/chunk-index-1af8vsyb.js +0 -2
  182. package/dist/chunk-index-254701sm.js +0 -3
  183. package/dist/chunk-index-31m1b2f5.js +0 -2
  184. package/dist/chunk-index-5vatmakn.js +0 -2
  185. package/dist/chunk-index-6yra9kdq.js +0 -2
  186. package/dist/chunk-index-9bdstf8t.js +0 -2
  187. package/dist/chunk-index-acj4xxgq.js +0 -100
  188. package/dist/chunk-index-aexy023z.js +0 -2
  189. package/dist/chunk-index-amxa2bn3.js +0 -2
  190. package/dist/chunk-index-bef0fc2m.js +0 -2
  191. package/dist/chunk-index-c6tphrjr.js +0 -1
  192. package/dist/chunk-index-cn49mawx.js +0 -50
  193. package/dist/chunk-index-gcmmwh8t.js +0 -2
  194. package/dist/chunk-index-h2vhv4kh.js +0 -2
  195. package/dist/chunk-index-h9ccz1r3.js +0 -2
  196. package/dist/chunk-index-n0ejy0wd.js +0 -2
  197. package/dist/chunk-index-nkdg1dfz.js +0 -7
  198. package/dist/chunk-index-qxynr1hf.js +0 -2
  199. package/dist/chunk-index-r7fdgzy1.js +0 -2
  200. package/dist/chunk-index-sdcpf5dp.js +0 -2
  201. package/dist/chunk-index-sw9r4aa4.js +0 -2
  202. package/dist/chunk-index-t5bmsqqd.js +0 -1
  203. package/dist/chunk-index-v8mma5np.js +0 -2
  204. package/dist/chunk-index-vsfsv8hd.js +0 -2
  205. package/dist/chunk-index-wxft0aqb.js +0 -2
  206. package/dist/chunk-index-zvvwxkev.js +0 -2
  207. package/dist/chunk-lang-css-7q1dtpgm.js +0 -1
  208. package/dist/chunk-lang-html-xgvba8rn.js +0 -1
  209. package/dist/chunk-lang-javascript-vgxmgd0a.js +0 -1
  210. package/dist/chunk-lang-typescript-2v652sh5.js +0 -1
  211. package/dist/chunk-layout-actions-bfqtg71d.js +0 -1
  212. package/dist/chunk-menu-items-rztgekfj.js +0 -1
  213. package/dist/chunk-mode-toggle-m5kgb7c7.js +0 -1
  214. package/dist/chunk-notes-a8nqffbf.js +0 -1
  215. package/dist/chunk-notice-bell-rdnfmtf2.js +0 -1
  216. package/dist/chunk-onboarding-wvh0s3vg.js +0 -1
  217. package/dist/chunk-palette-5xtdvy9a.js +0 -1
  218. package/dist/chunk-placement-items-rgyrktvy.js +0 -1
  219. package/dist/chunk-prompt-history-wv0vdsjv.js +0 -1
  220. package/dist/chunk-prompts-75hgb0t0.js +0 -1
  221. package/dist/chunk-room-confirms-bcvhpcry.js +0 -1
  222. package/dist/chunk-rooms-menu-button-ej3a2hyc.js +0 -1
  223. package/dist/chunk-sheet-hosts-d5zbwbbr.js +0 -29
  224. package/dist/chunk-shortcut-help-bh9ntk9s.js +0 -26
  225. package/dist/chunk-shortcuts-6em0atk9.js +0 -1
  226. package/dist/chunk-skill-editor-e43mwz01.js +0 -8
  227. package/dist/chunk-skills-vp107wrp.js +0 -1
  228. package/dist/chunk-start-over-line-xd1v2p0g.js +0 -16
  229. package/dist/chunk-table-svb2xhfd.js +0 -1
  230. package/dist/chunk-thread-actions-926eab0w.js +0 -1
  231. package/dist/chunk-thread-list-j2v1pqkz.js +0 -217
  232. package/dist/chunk-waiting-cdyz7r1g.js +0 -1
  233. package/dist/chunk-waiting-chip-58ttx9av.js +0 -28
  234. package/dist/chunk-where-9jp6g029.js +0 -1
  235. package/dist/chunk-workspace-d34njy4p.js +0 -578
  236. package/dist/chunk-zh-tw-y3jb20wc.js +0 -1
@@ -22,6 +22,7 @@ import {
22
22
  } from "../api-types.ts";
23
23
  import type { OwnerDesk } from "./desk.ts";
24
24
  import { type FileBinding, type FileDir, typeByName } from "./files.ts";
25
+ import { QueuedDelivery } from "./queued-delivery.ts";
25
26
  import type { NewRoomMessage, RoomLog } from "./room-log.ts";
26
27
  import type { AppSocket, Audience, Sockets } from "./sockets.ts";
27
28
  import { AppSurfaceThreads } from "./surface-threads.ts";
@@ -118,6 +119,8 @@ export class AppSurface implements ChatSurface {
118
119
  /** Present on a surface with host rooms: what was said in one since the assistant last posted. */
119
120
  channelContext?: ChatSurface["channelContext"];
120
121
  readonly threads: AppSurfaceThreads;
122
+ /** How the messages sent during a turn stand: steered, queued, cancelled. */
123
+ readonly delivery: QueuedDelivery;
121
124
  readonly #options: AppSurfaceOptions;
122
125
  readonly #activity = new Map<ChannelKey, Activity>();
123
126
  /** The round each group room runs now; an ended round is dropped, since the page keeps what it was told. */
@@ -132,6 +135,11 @@ export class AppSurface implements ChatSurface {
132
135
  this.surface = options.surface;
133
136
  this.maxAttachmentBytes = options.uploads.maxBytes;
134
137
  this.#options = options;
138
+ this.delivery = new QueuedDelivery({
139
+ log: options.log,
140
+ isHostRoom: options.isHostRoom,
141
+ show: (stored) => this.#showChange(stored),
142
+ });
135
143
  if (options.channelContext) this.channelContext = options.channelContext;
136
144
  this.threads = new AppSurfaceThreads({
137
145
  surface: options.surface,
@@ -168,7 +176,7 @@ export class AppSurface implements ChatSurface {
168
176
 
169
177
  /** Stores a message of a room and shows it to the sockets that may have the room, except `except`. */
170
178
  async publish(row: NewRoomMessage, except?: AppSocket): Promise<RoomMessage> {
171
- const message = await this.#options.log.append(row);
179
+ const message = this.delivery.shown(await this.#options.log.append(row));
172
180
  this.#options.sockets.deliver(
173
181
  this.#options.isHostRoom(row.room)
174
182
  ? { type: "host_message", room: row.room, message }
@@ -180,17 +188,40 @@ export class AppSurface implements ChatSurface {
180
188
  }
181
189
 
182
190
  /** Changes the text of a stored message and shows the change to the sockets that may have the room. */
183
- async edit(room: ChannelKey, id: number, text: string): Promise<void> {
184
- const message = await this.#options.log.edit(room, id, text);
185
- if (!message) return;
191
+ async edit(
192
+ room: ChannelKey,
193
+ id: number,
194
+ text: string,
195
+ mark = false,
196
+ ): Promise<void> {
197
+ const stored = await this.#options.log.edit(room, id, text, mark);
198
+ if (stored) this.#showChange(stored);
199
+ }
200
+
201
+ /** Shows a stored message as it is now to the sockets that may have its room. */
202
+ #showChange(stored: RoomMessage): void {
203
+ const { room } = stored;
204
+ const message = this.delivery.shown(stored);
186
205
  this.#options.sockets.deliver(
187
206
  this.#options.isHostRoom(room)
188
- ? { type: "host_edited", room: message.room, message }
189
- : { type: "edited", room: message.room, message },
207
+ ? { type: "host_edited", room, message }
208
+ : { type: "edited", room, message },
190
209
  this.#options.audienceOf(room),
191
210
  );
192
211
  }
193
212
 
213
+ react(channel: ChannelKey, messageId: string, emoji: string): Promise<void> {
214
+ return this.delivery.react(channel, messageId, emoji);
215
+ }
216
+
217
+ unreact(
218
+ channel: ChannelKey,
219
+ messageId: string,
220
+ emoji: string,
221
+ ): Promise<void> {
222
+ return this.delivery.unreact(channel, messageId, emoji);
223
+ }
224
+
194
225
  /** Whether a turn works in the room and whether Stop applies, as `activity` frames say. */
195
226
  activityOf(room: ChannelKey): RoomActivity {
196
227
  const found = this.#activity.get(room);
package/src/app/zip.ts ADDED
@@ -0,0 +1,124 @@
1
+ /** One file of a zip: the name it is stored under and its bytes. */
2
+ export interface ZipEntry {
3
+ name: string;
4
+ data: Uint8Array;
5
+ }
6
+
7
+ /** General purpose flag bit 11: the entry's name is UTF-8. */
8
+ const UTF8_NAME = 0x0800;
9
+ /** The version a store-only, UTF-8-named entry needs: 2.0. */
10
+ const VERSION = 20;
11
+ /** "Made by" Unix, spec 2.0: unzip then reads names as they are instead of as a DOS code page. */
12
+ const MADE_BY = (3 << 8) | VERSION;
13
+ /** A regular file readable by all, writable by its owner, in the Unix half of the attributes. */
14
+ const FILE_MODE = 0o100644 << 16;
15
+ /** The most bytes or entries a zip without the 64-bit extension can describe. */
16
+ const MAX_BYTES = 0xffff_ffff;
17
+ const MAX_ENTRIES = 0xffff;
18
+
19
+ /** The DOS date and time of `at`, as zip headers keep them, in UTC. */
20
+ function dosTime(at: Date): { time: number; date: number } {
21
+ const year = Math.max(at.getUTCFullYear(), 1980);
22
+ return {
23
+ time:
24
+ (at.getUTCHours() << 11) |
25
+ (at.getUTCMinutes() << 5) |
26
+ Math.floor(at.getUTCSeconds() / 2),
27
+ date:
28
+ ((year - 1980) << 9) | ((at.getUTCMonth() + 1) << 5) | at.getUTCDate(),
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Packs the entries into one zip, stored without compression: a local header and the bytes of
34
+ * each entry, then the central directory that lists them. Throws past what a zip without the
35
+ * 64-bit extension holds; the caller caps the total well below that.
36
+ */
37
+ export function zipStore(
38
+ entries: readonly ZipEntry[],
39
+ at = new Date(),
40
+ ): Uint8Array {
41
+ if (entries.length > MAX_ENTRIES)
42
+ throw new Error("too many files for one zip");
43
+ const encoder = new TextEncoder();
44
+ const { time, date } = dosTime(at);
45
+ const locals: Uint8Array[] = [];
46
+ const centrals: Uint8Array[] = [];
47
+ let offset = 0;
48
+ for (const entry of entries) {
49
+ const name = encoder.encode(entry.name);
50
+ const crc = Bun.hash.crc32(entry.data);
51
+ const size = entry.data.byteLength;
52
+ if (size > MAX_BYTES || offset > MAX_BYTES)
53
+ throw new Error("the files are too large for one zip");
54
+ const local = new Uint8Array(30 + name.byteLength);
55
+ const l = new DataView(local.buffer);
56
+ l.setUint32(0, 0x04034b50, true);
57
+ l.setUint16(4, VERSION, true);
58
+ l.setUint16(6, UTF8_NAME, true);
59
+ l.setUint16(8, 0, true);
60
+ l.setUint16(10, time, true);
61
+ l.setUint16(12, date, true);
62
+ l.setUint32(14, crc, true);
63
+ l.setUint32(18, size, true);
64
+ l.setUint32(22, size, true);
65
+ l.setUint16(26, name.byteLength, true);
66
+ l.setUint16(28, 0, true);
67
+ local.set(name, 30);
68
+ const central = new Uint8Array(46 + name.byteLength);
69
+ const c = new DataView(central.buffer);
70
+ c.setUint32(0, 0x02014b50, true);
71
+ c.setUint16(4, MADE_BY, true);
72
+ c.setUint16(6, VERSION, true);
73
+ c.setUint16(8, UTF8_NAME, true);
74
+ c.setUint16(10, 0, true);
75
+ c.setUint16(12, time, true);
76
+ c.setUint16(14, date, true);
77
+ c.setUint32(16, crc, true);
78
+ c.setUint32(20, size, true);
79
+ c.setUint32(24, size, true);
80
+ c.setUint16(28, name.byteLength, true);
81
+ // Extra and comment lengths, disk number and internal attributes stay 0.
82
+ c.setUint32(38, FILE_MODE >>> 0, true);
83
+ c.setUint32(42, offset, true);
84
+ central.set(name, 46);
85
+ locals.push(local, entry.data);
86
+ centrals.push(central);
87
+ offset += local.byteLength + size;
88
+ }
89
+ const directorySize = centrals.reduce(
90
+ (sum, part) => sum + part.byteLength,
91
+ 0,
92
+ );
93
+ if (offset + directorySize > MAX_BYTES)
94
+ throw new Error("the files are too large for one zip");
95
+ const end = new Uint8Array(22);
96
+ const e = new DataView(end.buffer);
97
+ e.setUint32(0, 0x06054b50, true);
98
+ e.setUint16(8, entries.length, true);
99
+ e.setUint16(10, entries.length, true);
100
+ e.setUint32(12, directorySize, true);
101
+ e.setUint32(16, offset, true);
102
+ const out = new Uint8Array(offset + directorySize + end.byteLength);
103
+ let cursor = 0;
104
+ for (const part of [...locals, ...centrals, end]) {
105
+ out.set(part, cursor);
106
+ cursor += part.byteLength;
107
+ }
108
+ return out;
109
+ }
110
+
111
+ /** The names made unique the way a download folder does: `a.pdf`, `a-2.pdf`, `a-3.pdf`. */
112
+ export function uniqueNames(names: readonly string[]): string[] {
113
+ const taken = new Set<string>();
114
+ return names.map((name) => {
115
+ const dot = name.lastIndexOf(".");
116
+ const [stem, ext] =
117
+ dot > 0 ? [name.slice(0, dot), name.slice(dot)] : [name, ""];
118
+ let candidate = name;
119
+ for (let n = 2; taken.has(candidate.toLowerCase()); n++)
120
+ candidate = `${stem}-${n}${ext}`;
121
+ taken.add(candidate.toLowerCase());
122
+ return candidate;
123
+ });
124
+ }
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
  import type { SQL } from "bun";
3
3
  import type { ActorFacts, Migration } from "pi-roundtable";
4
+ import { jsonValuesMigration } from "../host/stored-json.ts";
4
5
 
5
6
  /** A session's idle limit slides at most this often, so a busy page costs no write per request. */
6
7
  const SLIDE_AFTER_MS = 3_600_000;
@@ -84,6 +85,13 @@ export class WebSessions {
84
85
  this.#options = options;
85
86
  }
86
87
 
88
+ /** Its jsonb columns stored as the JSON values themselves, not as the text of them. */
89
+ static readonly jsonValuesMigration: Migration = jsonValuesMigration(
90
+ "web-sessions-json-values",
91
+ "web_sessions",
92
+ ["actor"],
93
+ );
94
+
87
95
  static readonly migration: Migration = {
88
96
  name: "web-sessions",
89
97
  up: async (sql) => {
@@ -120,7 +128,7 @@ export class WebSessions {
120
128
  const expires = this.#limit(now);
121
129
  await this.#sql`
122
130
  INSERT INTO web_sessions (id_hash, principal_id, actor, created_at, last_seen_at, verified_at, expires_at)
123
- VALUES (${hashOf(token)}, ${principalId}, ${JSON.stringify(actor)}::jsonb, ${now}, ${now}, ${now}, ${expires})`;
131
+ VALUES (${hashOf(token)}, ${principalId}, ${JSON.stringify(actor)}::text::jsonb, ${now}, ${now}, ${now}, ${expires})`;
124
132
  return {
125
133
  token,
126
134
  session: {
package/src/authz.ts CHANGED
@@ -61,6 +61,7 @@ export const ROUTES: readonly Rule[] = [
61
61
  row("*", "api/archive/**", "O"),
62
62
  row("*", "api/prompts/**", "O"),
63
63
  row("*", "api/skill-docs/**", "O"),
64
+ row("*", "api/skill-groups/**", "O"),
64
65
  row("*", "api/workspace/**", "O"),
65
66
  row(["GET", "HEAD"], "avatars/:file", "V"),
66
67
  // Uploads and files: the upload is the sender's own; a file answers by the room it is bound to.
package/src/host/cards.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { SQL } from "bun";
2
2
  import type { Migration } from "pi-roundtable";
3
3
  import type { HostCard, StoredHostCard } from "../web-app.ts";
4
- import { storedJson } from "./stored-json.ts";
4
+ import { jsonValuesMigration, storedJson } from "./stored-json.ts";
5
5
 
6
6
  interface Row {
7
7
  id: string;
@@ -33,6 +33,13 @@ export class HostCardLog {
33
33
  this.#sql = sql;
34
34
  }
35
35
 
36
+ /** Its jsonb columns stored as the JSON values themselves, not as the text of them. */
37
+ static readonly jsonValuesMigration: Migration = jsonValuesMigration(
38
+ "web-host-cards-json-values",
39
+ "web_host_cards",
40
+ ["card"],
41
+ );
42
+
36
43
  static readonly migration: Migration = {
37
44
  name: "web-host-cards",
38
45
  up: async (sql) => {
@@ -62,7 +69,7 @@ export class HostCardLog {
62
69
  const id = crypto.randomUUID();
63
70
  const [row]: Row[] = await this.#sql`
64
71
  INSERT INTO web_host_cards (id, room, card, state, keep)
65
- VALUES (${id}, ${room}, ${JSON.stringify(card)}::jsonb, ${card.state ?? null}, ${card.keep === true})
72
+ VALUES (${id}, ${room}, ${JSON.stringify(card)}::text::jsonb, ${card.state ?? null}, ${card.keep === true})
66
73
  RETURNING *`;
67
74
  if (!row) throw new Error("the card log stored nothing");
68
75
  return cardOf(row);
@@ -76,7 +83,7 @@ export class HostCardLog {
76
83
  ): Promise<StoredHostCard | undefined> {
77
84
  const [row]: Row[] = await this.#sql`
78
85
  UPDATE web_host_cards
79
- SET card = ${JSON.stringify(card)}::jsonb, state = ${card.state ?? null}, keep = ${card.keep === true}, updated_at = now()
86
+ SET card = ${JSON.stringify(card)}::text::jsonb, state = ${card.state ?? null}, keep = ${card.keep === true}, updated_at = now()
80
87
  WHERE id = ${id} AND room = ${room} RETURNING *`;
81
88
  return row && cardOf(row);
82
89
  }
@@ -1,3 +1,5 @@
1
+ import type { Migration } from "pi-roundtable";
2
+
1
3
  /**
2
4
  * A jsonb value as the driver returned it: already an object, or the text of one when the
3
5
  * driver left it as text. Anything else the database holds here is a stored value gone wrong,
@@ -11,3 +13,24 @@ export function storedJson<T>(value: T | string, what: string): T {
11
13
  throw new Error(`the stored ${what} is not valid JSON`);
12
14
  }
13
15
  }
16
+
17
+ /**
18
+ * Turns the jsonb columns that hold the text of a value, as a JSON string, into the value
19
+ * itself. Inserts once bound `${JSON.stringify(x)}::jsonb`, which Postgres stores as a jsonb
20
+ * string; none of these columns holds a string on purpose.
21
+ */
22
+ export function jsonValuesMigration(
23
+ name: string,
24
+ table: string,
25
+ columns: readonly string[],
26
+ ): Migration {
27
+ return {
28
+ name,
29
+ up: async (sql) => {
30
+ for (const column of columns)
31
+ await sql.unsafe(
32
+ `UPDATE ${table} SET ${column} = (${column} #>> '{}')::jsonb WHERE jsonb_typeof(${column}) = 'string'`,
33
+ );
34
+ },
35
+ };
36
+ }
package/src/i18n/zh-tw.ts CHANGED
@@ -234,7 +234,6 @@ export const ZH_TW: Readonly<Record<string, string>> = {
234
234
 
235
235
  // 群組圓桌
236
236
  Close: "關閉",
237
- Table: "圓桌",
238
237
 
239
238
  // 訊息列
240
239
  "Conversation started over": "對話已重新開始",
@@ -602,6 +601,10 @@ export const ZH_TW: Readonly<Record<string, string>> = {
602
601
  "Quote reply": "引用回覆",
603
602
  "Select text": "選取文字",
604
603
  "Copy all": "全部複製",
604
+ "Its turn has already started, so it can no longer be changed.":
605
+ "這一輪已經開始,無法再更改。",
606
+ "Added to this turn": "已插入這一輪",
607
+ Queued: "排隊中",
605
608
  Share: "分享",
606
609
  "Knight profile": "騎士資料",
607
610
  "Copy details": "複製細節",
@@ -676,6 +679,8 @@ export const ZH_TW: Readonly<Record<string, string>> = {
676
679
  "Next room with unread messages": "下一個有未讀訊息的聊天室",
677
680
  "Close the open menu or sheet, or leave the message box":
678
681
  "關閉開啟的選單或面板;都沒有時離開訊息輸入框",
682
+ "Edit your latest message that is still waiting, from an empty message box":
683
+ "在空的訊息輸入框編輯你最新一則還在排隊的訊息",
679
684
  "Show these shortcuts": "顯示這份快速鍵清單",
680
685
  "In a message box the arrow shortcuts switch rooms only when it is empty or the caret is at its very start (up) or very end (down).":
681
686
  "在訊息輸入框裡,方向鍵快速鍵只在框內沒有文字,或游標在最開頭(上)、最結尾(下)時才會切換聊天室。",
@@ -836,6 +841,10 @@ export const ZH_TW: Readonly<Record<string, string>> = {
836
841
  // 伺服器回應
837
842
  "There is no such room, knight or file.": "找不到這個聊天室、騎士或檔案。",
838
843
  "The body is too large.": "內容太大。",
844
+ "None of this message's files are kept any more.":
845
+ "這則訊息的檔案都已不再保留。",
846
+ "These files are too large to download together; download them one by one.":
847
+ "這些檔案太大,無法一起下載;請逐一下載。",
839
848
  "The body is not JSON.": "內容不是 JSON。",
840
849
  "The body is not an object.": "內容不是物件。",
841
850
  "Only an owner may do this.": "只有擁有者可以這麼做。",
@@ -1178,10 +1187,39 @@ export const ZH_TW: Readonly<Record<string, string>> = {
1178
1187
  "{name} 會被刪除,並從攜帶它的騎士身上拿掉:{carriers}。",
1179
1188
  "{name} will be deleted. No knight carries it.":
1180
1189
  "{name} 會被刪除,目前沒有騎士攜帶它。",
1181
- "It also leaves the skill groups {groups}.": "它也會離開技能群組 {groups}。",
1190
+ "It also leaves the skill groups {groups}.": "它也會離開技能組合 {groups}。",
1182
1191
  "This cannot be undone.": "這個動作無法復原。",
1183
1192
  "Skills ({count})": "技能({count})",
1184
1193
  "Choose the skills this knight carries.": "選擇這位騎士攜帶的技能。",
1194
+ "Skill groups": "技能組合",
1195
+ "A group lists skills to switch on for a knight in one step, from the knight's sheet. Knights carry the skills, not the group.":
1196
+ "技能組合列出一組技能,可在騎士的設定頁一次開啟。騎士攜帶的是技能,不是組合。",
1197
+ "No skill groups.": "還沒有技能組合。",
1198
+ "Edit {name}": "編輯 {name}",
1199
+ "New skill group": "新增技能組合",
1200
+ "Delete this skill group?": "要刪除這個技能組合嗎?",
1201
+ "Delete group": "刪除組合",
1202
+ "{name} will be deleted. Its skills stay, and so does every knight that carries them.":
1203
+ "{name} 會被刪除。其中的技能會保留,攜帶它們的騎士也不受影響。",
1204
+ "Saved {name}.": "已儲存 {name}。",
1205
+ "Lowercase letters, digits and single hyphens, up to 64.":
1206
+ "小寫字母、數字與單一連字號,最多 64 個字元。",
1207
+ Description: "描述",
1208
+ "Save group": "儲存組合",
1209
+ "Switching a group on switches on its skills this knight lacks; switching it off switches off its skills, built-in ones excepted.":
1210
+ "打開組合會開啟這位騎士還沒有的組合技能;關閉組合會關掉組合裡的技能,內建技能除外。",
1211
+ "Carries all of it.": "已攜帶全部技能。",
1212
+ "Lacks {count}: {skills}": "缺少 {count} 個:{skills}",
1213
+ "Skill group {name}": "技能組合 {name}",
1214
+ "Switch this skill group on?": "要打開這個技能組合嗎?",
1215
+ "Switch this skill group off?": "要關閉這個技能組合嗎?",
1216
+ "Switch on": "打開",
1217
+ "Switch off": "關閉",
1218
+ "Switched on: {skills}": "已開啟:{skills}",
1219
+ "Switched off: {skills}": "已關閉:{skills}",
1220
+ "Nothing changed for {name}.": "{name} 沒有變動。",
1221
+ "{agent} will also carry: {skills}.": "{agent} 將會再攜帶:{skills}。",
1222
+ "{agent} will no longer carry: {skills}.": "{agent} 將不再攜帶:{skills}。",
1185
1223
  // 主要提示詞與技能內容的編輯:伺服器的拒絕訊息
1186
1224
  "This host has no prompts to edit.": "這個主機沒有可編輯的提示詞。",
1187
1225
  "There is no such prompt.": "沒有這份提示詞。",
@@ -1195,6 +1233,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
1195
1233
  "baseHash 必須是讀取技能時的雜湊值。",
1196
1234
  "This skill changed while you were editing it.":
1197
1235
  "這個技能在你編輯時被更動了。",
1236
+ "There is no such skill group.": "沒有這個技能組合。",
1198
1237
  // 成員、登入與設定關卡
1199
1238
  Profile: "個人資料",
1200
1239
  Owner: "擁有者",
@@ -1236,6 +1275,10 @@ export const ZH_TW: Readonly<Record<string, string>> = {
1236
1275
  edited: "已編輯",
1237
1276
  "{count} file was removed": "已移除 {count} 個檔案",
1238
1277
  "{count} files were removed": "已移除 {count} 個檔案",
1278
+ "Attachments ({count})": "附件({count})",
1279
+ "Download all": "全部下載",
1280
+ "{count} attachment": "{count} 個附件",
1281
+ "{count} attachments": "{count} 個附件",
1239
1282
  "{count} unread message": "{count} 則未讀訊息",
1240
1283
  "{count} unread messages": "{count} 則未讀訊息",
1241
1284
  "New messages": "新訊息",
@@ -37,6 +37,7 @@ import { NoticeStore } from "./store.ts";
37
37
  /** The tables of notices, push delivery, preferences and waivers. */
38
38
  export const NOTICE_MIGRATIONS: readonly Migration[] = [
39
39
  NoticeStore.migration,
40
+ NoticeStore.jsonValuesMigration,
40
41
  PushOutbox.migration,
41
42
  PushPrefsStore.migration,
42
43
  OnboardingWaivers.migration,
@@ -1,5 +1,6 @@
1
1
  import type { SQL } from "bun";
2
2
  import type { Migration } from "pi-roundtable";
3
+ import { jsonValuesMigration } from "../host/stored-json.ts";
3
4
  import type { OpenTarget } from "../web-app.ts";
4
5
 
5
6
  /** Where a tap on a notice goes. */
@@ -85,6 +86,13 @@ export class NoticeStore {
85
86
  this.#sql = sql;
86
87
  }
87
88
 
89
+ /** Its jsonb columns stored as the JSON values themselves, not as the text of them. */
90
+ static readonly jsonValuesMigration: Migration = jsonValuesMigration(
91
+ "web-notices-json-values",
92
+ "web_notices",
93
+ ["open"],
94
+ );
95
+
88
96
  static readonly migration: Migration = {
89
97
  name: "web-notices",
90
98
  up: async (sql) => {
@@ -130,7 +138,7 @@ export class NoticeStore {
130
138
  await tx`
131
139
  INSERT INTO web_notices (tag, title, body, open, urgency, ackable)
132
140
  VALUES (${notice.tag}, ${notice.title}, ${notice.body ?? null},
133
- ${notice.open ? JSON.stringify(notice.open) : null}::jsonb,
141
+ ${notice.open ? JSON.stringify(notice.open) : null}::text::jsonb,
134
142
  ${notice.urgency}, ${notice.ackable})
135
143
  ON CONFLICT (tag) DO UPDATE SET
136
144
  title = EXCLUDED.title, body = EXCLUDED.body, open = EXCLUDED.open,
@@ -30,6 +30,7 @@ export function webPushChannel(
30
30
  (await identity.tierOf(principalId)) === "owner";
31
31
  return {
32
32
  name: `web:${options.surface}`,
33
+ surface: options.surface,
33
34
  label: "a notice in the web app",
34
35
  knows: async (principalId) =>
35
36
  (await isOwner(principalId)) && home() !== undefined,
package/src/web-plugin.ts CHANGED
@@ -66,7 +66,9 @@ export function webConsole(options: WebConsoleOptions): RoundtablePlugin {
66
66
  const schedules = settings.panes.includes("schedules");
67
67
  const migrations = [
68
68
  ...(rooms ? APP_MIGRATIONS : []),
69
- ...(auth.mode === "oidc" ? [WebSessions.migration] : []),
69
+ ...(auth.mode === "oidc"
70
+ ? [WebSessions.migration, WebSessions.jsonValuesMigration]
71
+ : []),
70
72
  ];
71
73
  return definePlugin({
72
74
  name: "web-console",
@@ -198,6 +200,7 @@ async function build(settings: ResolvedOptions, context: Context) {
198
200
  remove: readonly string[],
199
201
  ) => skillRegistry.attach(agent, add, remove),
200
202
  documents: skillRegistry,
203
+ groups: skillRegistry,
201
204
  }
202
205
  : undefined;
203
206
  // The prompts plugin is always on; a host that replaced it may leave it out.
@@ -367,6 +370,7 @@ async function build(settings: ResolvedOptions, context: Context) {
367
370
  const app =
368
371
  rooms && (vapid || external)
369
372
  ? new OwnerApp({
373
+ timeZone: env.timeZone,
370
374
  ...(settings.presentation
371
375
  ? { presentation: settings.presentation }
372
376
  : {}),
@@ -1 +0,0 @@
1
- import{p,Q,S,e,v}from"./chunk-index-cn49mawx.js";var a={profile:S(()=>(p("tyb48b79"),import("./chunk-profile-tyb48b79.js")).then((o)=>({default:o.ProfilePage}))),notices:S(()=>(p("tefx93s6"),import("./chunk-notices-tefx93s6.js")).then((o)=>({default:o.NoticesPage}))),onboarding:S(()=>(p("wvh0s3vg"),import("./chunk-onboarding-wvh0s3vg.js")).then((o)=>({default:o.OnboardingPage})))};function n({pane:o}){let t=a[o];return t?e(Q,{fallback:e(v,{}),children:e(t,{})}):null}export{n as default};
@@ -1 +0,0 @@
1
- import{o,e,I,l,v,He}from"./chunk-index-cn49mawx.js";function c({name:r}){let m=l((i)=>i.listed),t=l((i)=>Object.values(i.rooms).find((n)=>n.view?.kind==="agent"&&n.view.name===r)?.view?.key,r);return o(()=>{if(t)I("room",t,{replace:!0}),He(r);else if(m)I("rooms",void 0,{replace:!0})},[t,m,r]),e("div",{className:"page",children:e(v,{})})}export{c as AgentPage};
@@ -1 +0,0 @@
1
- import{p,Q,S,j,C,a,t,g,e,n,s,Re,l,k,_,me,Ht,bn,J,ar,wi,qr,Nt,ki,Br}from"./chunk-index-cn49mawx.js";import{ze}from"./chunk-index-6yra9kdq.js";import{ui}from"./chunk-index-sdcpf5dp.js";import"./chunk-index-q5fyrtx4.js";import"./chunk-index-gwkhs8s3.js";import{si}from"./chunk-index-31m1b2f5.js";import{ai,li}from"./chunk-index-s9p27f9b.js";import{Gt}from"./chunk-index-2tr41qht.js";import"./chunk-index-vsfsv8hd.js";import"./chunk-index-s53bxwkk.js";import{at}from"./chunk-index-20xgx0k2.js";ai();var M=S(()=>(p("qxhmczwv"),import("./chunk-folder-list-qxhmczwv.js")).then((o)=>({default:o.FolderList}))),O=j(function(d){let{view:r}=d,f=Gt(()=>Ht(r.key),r.home?void 0:li(r.key));return e(qr,{...d,press:f,item:{...r.home?{}:{"data-drop-item":"","data-key":r.key},...d.arranging?{"data-arranging":""}:{}},after:d.arranging?e("button",{type:"button",className:"icon-button row-more","aria-haspopup":"menu","aria-label":t("Room menu"),onClick:(c)=>{let u=c.currentTarget;Ht(r.key).then((i)=>{if(!i)return;let y=u.getBoundingClientRect();me({...i,at:{x:y.left,y:y.bottom},via:"key",source:u})},()=>{return})},children:e(s,{name:"more",size:18})}):null})});function P(o){let{now:d}=J(),{timeZone:r}=_(),f=Nt(o.rooms);return e("ul",{className:"room-list",children:f.map(({key:c,...u})=>{let i=o.threads[c];return e(O,{...u,active:c===o.active,now:d,timeZone:r,arranging:o.arranging,...i?{threads:e(ki,{open:i.open,closed:i.closed,active:o.active,parent:c})}:{}},c)})})}function v(o){let r=Nt(o.rooms).length===0;if(r&&o.zone?.kind!=="pinned")return null;return n("section",{className:"stack",...r?{"data-empty":""}:{},...o.zone?{"data-drop-zone":o.zone.kind,"data-zone-id":o.zone.id}:{},children:[e("h2",{className:"section-title",children:o.title}),r?null:e(P,{rooms:o.rooms,threads:o.threads,active:o.active,arranging:o.arranging})]})}function re(o){let d=Boolean(_().kinds?.length),r=l((m)=>JSON.stringify({listed:m.listed,...at(m,(F)=>Re(m,F))})),f=ui(),c=a(r);if(!f)c.current=r;let u=c.current,{listed:i,...y}=C(()=>JSON.parse(u),[u]),{home:w,pinned:z,folders:N,restAgents:T,restGroups:D,rest:A,threads:L,orphans:h}=y,R=ar(),K=w.length+z.length+A.length===0&&N.every((m)=>m.rooms.length===0)&&h.open.length+h.closed.length===0,b={threads:L,active:o.active,arranging:R};return n("div",{className:"page","data-column":o.active?"":void 0,"data-drop-root":"",children:[n("header",{className:"page-head",children:[e("h1",{children:t("Rooms")}),n("div",{className:"actions",children:[n("button",{type:"button",className:"header-button search-button",onClick:o.onSearch,children:[e(s,{name:"search",size:16}),e("span",{children:t("Find a room")}),e("kbd",{children:"⌘K"})]}),i?e("button",{type:"button",className:"header-button icon-button","aria-haspopup":"menu","aria-label":t("Folders and order"),onClick:(m)=>si(m.currentTarget),children:e(s,{name:"more",size:18})}):null,e("button",{type:"button",className:"header-button icon-button","aria-label":t("Add"),"aria-haspopup":"menu",onClick:(m)=>bn(m.currentTarget),children:e(s,{name:"plus"})})]})]}),R?n("div",{className:"arrange-bar",children:[n("button",{type:"button",className:"secondary",onClick:()=>ze({kind:"new"}),children:[e(s,{name:"plus",size:16}),e("span",{children:t("Add a folder")})]}),e("button",{type:"button",onClick:()=>wi(!1),children:t("Done")})]}):null,i&&K&&!d?e(k,{children:t("No rooms yet. Rooms appear when the team has a knight.")}):null,!i?n("div",{className:"stack","aria-busy":"true",children:[e("div",{className:"skeleton"}),e("div",{className:"skeleton"}),e("div",{className:"skeleton"})]}):null,e(v,{title:t("Home"),rooms:w,...b}),e(v,{title:t("Pinned"),rooms:z,zone:{kind:"pinned",id:"pinned"},...b}),N.length>0?e("div",{className:"stack folder-list","data-folder-list":"",children:e(Q,{fallback:e("div",{className:"skeleton","aria-busy":"true"}),children:e(M,{folders:N,active:o.active,listed:i,rooms:(m)=>e(P,{rooms:m,threads:L,active:o.active,arranging:R})})})}):null,N.length>0?e(v,{title:t("Ungrouped"),rooms:A,zone:{kind:"rest",id:"rest"},...b}):n(g,{children:[e(v,{title:t("Knights"),rooms:T,zone:{kind:"rest",id:"rest-agents"},...b}),e(v,{title:t("Groups"),rooms:D,zone:{kind:"rest",id:"rest-groups"},...b})]}),h.open.length+h.closed.length>0?n("section",{className:"stack",children:[e("h2",{className:"section-title",children:t("Dispatch threads")}),e(ki,{open:h.open,closed:h.closed,active:o.active})]}):null,e(Br,{active:o.active})]})}export{re as ArrangedRoomsPage};
@@ -1 +0,0 @@
1
- import{t,K,O,d,f}from"./chunk-index-cn49mawx.js";import{on}from"./chunk-index-gwkhs8s3.js";import"./chunk-index-q5fyrtx4.js";import{kn,Hr}from"./chunk-index-5t2jkecy.js";import{ne}from"./chunk-index-c7dbvkrq.js";import{uo}from"./chunk-index-vsfsv8hd.js";import"./chunk-index-s53bxwkk.js";var s=500,c=(n)=>{let r=[];for(let e=0;e<n.length;e+=s)r.push(n.slice(e,e+s));return r},i=()=>O.dispatch({type:"changed"});async function w(n){let r=uo(n),e=Object.keys(r);if(e.length===0)return;let a=[];try{for(let o of c(e))a.push(...(await kn.readAll(o)).moved)}catch(o){f(d(o),"danger"),i();return}on(Object.fromEntries(e.map((o)=>[o,0]))),f(K(e.length,"Marked {count} room as read","Marked {count} rooms as read"),"info",a.length?{label:t("Undo"),run:()=>void m(a,r)}:void 0)}async function m(n,r){try{for(let e of c(n)){let{restored:a}=await kn.unreadAll(e);on(Object.fromEntries(a.map((o)=>[o,r[o]??0])))}}catch(e){f(d(e),"danger")}i()}var P=(n)=>Hr(n);async function l(n){try{await kn.restoreHelpers(n)}catch(r){f(d(r),"danger")}i()}async function R(n){let r;try{r=(await kn.archiveFinishedHelpers(n)).archived.flatMap((a)=>a.names)}catch(e){f(d(e),"danger"),i();return}if(i(),r.length===0)return;f(K(r.length,"Archived {count} temp knight","Archived {count} temp knights"),"info",{label:t("Undo"),run:()=>void l(r)})}async function T(n,r){try{await ne.archiveAgent(n)}catch(e){f(d(e),"danger"),i();return}i(),f(t("Archived {name}",{name:r}),"info",{label:t("Undo"),run:()=>void l([n])})}export{T as archiveHelper,P as clearAllFinished,R as clearFinishedHelpers,w as markAllRead,l as putBackHelpers};
@@ -1 +0,0 @@
1
- import{Ke,Kt,Z,_i,wn}from"./chunk-index-fepsbhr6.js";import{j,$r,o,z,C,a,r,t,u,g,e,n,Oi,de,y,d,f,vn,fe,et,Ce}from"./chunk-index-cn49mawx.js";import{q}from"./chunk-index-qxynr1hf.js";import{R}from"./chunk-index-m81362sc.js";var ue={"hat.none":"No hat","hat.goggcap":"Leather cap","hat.tallcap":"Burgundian felt cap","hat.softcap":"Soft cap","hat.hardhat":"Iron skullcap","hat.fcap":"Hunter's cap","hat.winghelm":"Winged helm","hat.helm":"Helm","hat.mortar":"Biretta","hat.beret":"Chaperon","hat.goggles":"Pointed cap","hat.tricorn":"Pilgrim hat","hat.sailor":"Straw hat","hat.kettle":"Kettle hat","hat.cavalier":"Feathered hat","hat.toque":"Linen cook's cap","hair.none":"No hair","beard.none":"No beard","accessory.none":"No accessory","prop.beads":"Prayer beads","prop.marotte":"Marotte","prop.map":"Chart","prop.flask":"Alchemist's phial","prop.telescope":"Astrolabe","prop.wrench":"Dividers","outfit.surplice":"Surplice","outfit.overalls":"Work jerkin","outfit.armor":"Mail hauberk","outfit.apron":"Smith's apron","outfit.chef":"Cook's tunic","outfit.coat":"Doublet","outfit.vest":"Pourpoint","outfit.sailor":"Mariner's tunic","hair.bob":"Bob","hair.bun":"Bun","hair.ponytail":"Ponytail"},X=(i)=>i.charAt(0).toUpperCase()+i.slice(1);function P(i,l){let h=`character.${i}.${l}`,b=t(h);if(b!==h)return b;if(i==="class")return Z.classes[l]?.label??X(l);return ue[`${i}.${l}`]??X(l)}function Y(i){let l=`character.jobs.${i}`,h=t(l);return h!==l?h:Z.classes[i]?.jobs??""}var M=[{field:"hat",title:u("Hat"),ids:Z.hats},{field:"outfit",title:u("Outfit"),ids:Z.outfits},{field:"prop",title:u("Prop"),ids:Z.props},{field:"hair",title:u("Hair"),ids:Z.hair},{field:"beard",title:u("Beard"),ids:["none",...Z.beards]},{field:"accessory",title:u("Accessory"),ids:["none",...Z.accessories]}],me=new Set(["class","colors","palette","build","skin","hairColor",...M.map((i)=>i.field)]),re=Object.keys(Z.palettes),ee={idle:u("Idle"),thinking:u("Thinking"),working:u("Working"),waiting:u("Waiting for you"),done:u("Done"),error:u("Error"),sleepy:u("Sleepy")},he={slim:u("Slim"),regular:u("Regular"),stout:u("Stout")};function O(i){return{class:i.class,outfit:i.outfit,hat:i.hat,prop:i.prop,hair:i.hair,beard:i.beard,accessory:i.accessory,build:i.build,skin:i.skin,hairColor:i.hairColor,colors:{...i.colors}}}var te=(i)=>JSON.stringify(O(i)),v=(i)=>i[Math.floor(Math.random()*i.length)];function be(){return{class:v(Ke),outfit:v(Z.outfits),hat:v(Z.hats),prop:v(Z.props),hair:v(Z.hair),beard:v(["none",...Z.beards]),accessory:v(["none",...Z.accessories]),build:v(["slim","regular","stout"]),skin:v([0,1,2,3,4,5]),hairColor:v(Z.hairColors),palette:v(re)}}var ie=j(function(l){return n("button",{type:"button",className:"creator-tile","aria-pressed":l.pressed,title:l.hint?`${l.label} — ${l.hint}`:l.label,onClick:l.onPick,...l.data,children:[e(fe,{spec:l.spec,size:l.size,live:!1}),e("span",{children:l.label})]})});function ge(i,l,h){let b=JSON.stringify({...i,[l]:0});return C(()=>h.map((c)=>({...i,[l]:c})),[b,l,h])}function pe(){let i=a(null),[l,h]=r();return o(()=>{let b=i.current;if(!b)return;let c=()=>{let N=b.scrollLeft>1,w=b.scrollLeft+b.clientWidth<b.scrollWidth-1;h(N&&w?"both":w?"end":N?"start":void 0)};c(),b.addEventListener("scroll",c,{passive:!0});let p=typeof ResizeObserver>"u"?void 0:new ResizeObserver(c);return p?.observe(b),()=>{b.removeEventListener("scroll",c),p?.disconnect()}},[]),[i,l]}function ye(i){let{part:l}=i,h=ge(i.shown,l.field,l.ids),[b,c]=pe();return e("div",{className:"creator-strip",ref:b,"data-more":c,children:l.ids.map((p,N)=>e(ie,{data:{[`data-${l.field}`]:p},spec:h[N],size:44,label:P(l.field,p),pressed:i.draft[l.field]===p,onPick:()=>i.onPick(p)},p))})}function K(i){return e("button",{type:"button",className:"creator-swatch","aria-pressed":i.pressed,"aria-label":i.label,title:i.label,style:{background:i.background},onClick:i.onPick,...i.data})}function L(i){return n("div",{className:"creator-field","data-field":i.field,children:[i.title?e("h3",{children:i.title}):null,i.children,i.error?e("p",{className:"field-error",role:"alert",children:i.error}):null]})}function Oe(i){let[l,h]=r(i.character),b=$r(l),[c,p]=r({}),[N,w]=r(!1),[I,ne]=r("idle"),[W,T]=r(),[D,U]=r(),_=Ce("(max-width: 719px)"),G=a(null),F=z(),oe=et(l),se=vn(),A=te(l)!==te(i.character);o(()=>h(i.character),[i.character]),o(()=>{if(!A||i.onPick)return;let s=(k)=>k.preventDefault(),m=(k)=>{if(k.button!==0||k.metaKey||k.ctrlKey||k.shiftKey||k.altKey)return;let E=k.target instanceof Element?k.target.closest("a"):null;if(!E||E.target||E.hasAttribute("download"))return;if(G.current?.contains(E))return;k.preventDefault(),k.stopPropagation(),T({kind:"link",anchor:E})},H=Oi((k)=>T({kind:"navigate",proceed:k}));return window.addEventListener("beforeunload",s),document.addEventListener("click",m,!0),()=>{H(),window.removeEventListener("beforeunload",s),document.removeEventListener("click",m,!0)}},[A,i.onPick]);let S=(s)=>{p({}),h((m)=>_i(s,m))},le=async()=>{if(i.onPick){i.onPick(O(l),l);return}w(!0),p({});try{let s=await y.setCharacter(i.name,O(l));i.onSaved?.(s),f(t("Saved."))}catch(s){let m=s instanceof de&&s.field?s.field.split(".")[0]:void 0,H=m&&me.has(m)?m:"*";p({[H]:d(s)})}finally{w(!1)}},ce=async()=>{let s=W;if(h(i.character),p({}),s?.kind==="link")setTimeout(()=>s.anchor.click(),0);else if(s?.kind==="navigate")s.proceed()},J=e(L,{field:"class",title:t("Class"),error:c.class,children:e("div",{className:"creator-grid",children:Ke.map((s)=>e(ke,{id:s,base:b,pressed:l.class===s,onPick:()=>S({class:s,colors:l.colors})},s))})}),V=n(L,{field:"colors",title:t("Colors"),error:c.colors??c.palette,children:[n("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Palette")}),re.map((s)=>{let m=Z.palettes[s];return e(K,{data:{"data-palette":s},label:P("palette",s),pressed:l.palette===s,background:`linear-gradient(135deg, ${m.primary} 52%, ${m.trim} 52%)`,onPick:()=>S({palette:s})},s)})]}),e("div",{className:"creator-custom",children:["primary","secondary","trim"].map((s)=>n("label",{children:[e("span",{children:s==="primary"?t("Primary (the accent)"):s==="secondary"?t("Secondary"):t("Trim")}),e("input",{type:"color",value:l.colors[s],onChange:(m)=>S({colors:{[s]:m.target.value}})})]},s))})]}),x=M.map((s)=>e(L,{field:s.field,title:t(s.title),error:c[s.field],children:e(ye,{part:s,draft:l,shown:b,onPick:(m)=>S({[s.field]:m})})},s.field)),Q=n(g,{children:[e(L,{field:"build",title:t("Build"),error:c.build,children:n("fieldset",{className:"segmented",children:[e("legend",{className:"visually-hidden",children:t("Build")}),["slim","regular","stout"].map((s)=>n("label",{"data-checked":l.build===s,children:[e("input",{type:"radio",name:`${F}-build`,checked:l.build===s,onChange:()=>S({build:s})}),ae(s)]},s))]})}),e(L,{field:"skin",title:t("Skin tone"),error:c.skin,children:n("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Skin tone")}),(se??Z.skins).map((s,m)=>e(K,{data:{"data-skin":String(m)},label:t("Skin tone {n}",{n:m+1}),pressed:l.skin===m,background:s,onPick:()=>S({skin:m})},m))]})}),e(L,{field:"hairColor",title:t("Hair color"),error:c.hairColor,children:n("fieldset",{className:"creator-swatches",children:[e("legend",{className:"visually-hidden",children:t("Hair color")}),Z.hairColors.map((s,m)=>e(K,{data:{"data-hair-color":s},label:t("Hair color {n}",{n:m+1}),pressed:l.hairColor===s,background:s,onPick:()=>S({hairColor:s})},s)),e("input",{type:"color","aria-label":t("Hair color"),value:l.hairColor,onChange:(s)=>S({hairColor:s.target.value})})]})})]}),B=[{id:"class",title:t("Class"),summary:P("class",l.class),error:c.class,node:J},{id:"colors",title:t("Colors"),summary:l.palette?P("palette",l.palette):l.colors.primary,error:c.colors??c.palette,node:V},...M.map((s,m)=>({id:s.field,title:t(s.title),summary:P(s.field,l[s.field]),error:c[s.field],node:x[m]})),{id:"body",title:t("Body"),summary:ae(l.build),error:c.build??c.skin??c.hairColor,node:Q}];return n("form",{ref:G,className:"creator",style:oe,"aria-labelledby":F,onSubmit:(s)=>{if(s.preventDefault(),(A||i.onPick)&&!N)le()},children:[e("h2",{id:F,className:"visually-hidden",children:t("Character of {name}",{name:i.displayName})}),n("div",{className:"creator-preview",children:[e(fe,{spec:l,name:i.name,state:I,size:176,label:t("{name}, {state}",{name:i.displayName,state:t(ee[I])})}),e("button",{type:"button",className:"secondary","data-action":"cycle",onClick:()=>ne((s)=>Kt[(Kt.indexOf(s)+1)%Kt.length]??"idle"),children:t("Preview states")}),e("span",{className:"meta","aria-live":"polite",children:t(ee[I])})]}),n("div",{className:"creator-body",children:[c["*"]?e("p",{className:"field-error",role:"alert","data-field":"*",children:c["*"]}):null,_?e("ul",{className:"creator-sections",children:B.map((s)=>e("li",{children:n("button",{type:"button",className:"creator-section secondary",onClick:()=>U(s.id),children:[e("strong",{children:s.title}),e("span",{className:"meta",children:s.summary}),s.error?e("span",{className:"field-error",role:"alert",children:s.error}):null]})},s.id))}):n(g,{children:[J,V,x,Q]}),n("div",{className:"actions creator-actions",children:[e("button",{type:"button",className:"secondary","data-action":"suggest",onClick:()=>{p({}),h(wn({name:i.name,displayName:i.displayName,prompt:i.prompt}))},children:t("Suggest from role")}),e("button",{type:"button",className:"secondary","data-action":"randomize",onClick:()=>S(be()),children:t("Randomize")}),e("button",{type:"button",className:"secondary","data-action":"cancel",onClick:()=>{if(i.onCancel)i.onCancel();else if(A)T({kind:"cancel"})},children:t("Cancel")}),e("button",{type:"submit","data-action":"save",disabled:!A&&!i.onPick||N,children:N?t("Saving…"):i.onPick?t("Use this look"):t("Save")})]})]}),e(R,{open:_&&D!==void 0,title:B.find((s)=>s.id===D)?.title??"",onClose:()=>U(void 0),children:e("div",{className:"stack",children:B.find((s)=>s.id===D)?.node})}),e(q,{open:W!==void 0,title:t("Discard your changes?"),confirmLabel:t("Discard"),cancelLabel:t("Keep editing"),tone:"danger",onClose:()=>T(void 0),onConfirm:ce,children:e("p",{children:t("The new look for {name} has not been saved.",{name:i.displayName})})})]})}var ae=(i)=>t(he[i]);function ke(i){let l=i.base.colors,h=C(()=>_i({class:i.id,colors:l}),[i.id,l]);return e(ie,{data:{"data-class":i.id},spec:h,size:56,label:P("class",i.id),hint:Y(i.id),pressed:i.pressed,onPick:i.onPick})}export{Oe as CharacterCreator};
@@ -1 +0,0 @@
1
- import{q}from"./chunk-index-qxynr1hf.js";import"./chunk-index-m81362sc.js";export{q as Confirm};
@@ -1,37 +0,0 @@
1
- import{A,o,r,t,g,e,n,h,y,d,f,v,m,k,T,_,re,J}from"./chunk-index-cn49mawx.js";import{q}from"./chunk-index-qxynr1hf.js";import{ve}from"./chunk-index-gcmmwh8t.js";import{Te}from"./chunk-index-e35v554x.js";import{ie}from"./chunk-index-sw9r4aa4.js";import{se}from"./chunk-index-amxa2bn3.js";import"./chunk-index-m81362sc.js";var P=(a,s)=>({method:a,body:JSON.stringify(s)}),U=encodeURIComponent,L={list:()=>h("connectors/managed"),add:(a)=>h("connectors/managed",P("POST",a)),describe:(a,s)=>h(`connectors/managed/${U(a)}`,P("PATCH",{description:s})),remove:(a)=>h(`connectors/managed/${U(a)}`,{method:"DELETE"}),grants:()=>h("connectors/grants"),channels:()=>h("connectors/grants/channels"),authorize:(a)=>h("connectors/grants",P("POST",a)),describeGrant:(a,s,l)=>h(`connectors/grants/${U(a)}/${U(s)}`,P("PATCH",l)),revoke:(a,s)=>h(`connectors/grants/${U(a)}/${U(s)}`,{method:"DELETE"}),rotate:(a)=>h(`connectors/grants/${U(a)}/rotate`,{method:"POST"})};function V(a){let{version:s}=J(),[l,c]=r(0),[b,C]=r(),[w,u]=r();o(()=>{let i=!0;return a().then((N)=>{if(!i)return;C(N),u(void 0)},(N)=>{if(i)u(d(N))}),()=>{i=!1}},[l,s]);let R=A(()=>c((i)=>i+1),[]);return{data:b,error:w,reload:R}}var G={name:"",url:"",description:"",header:"",token:""};function K(a){let[s,l]=r(G),[c,b]=r(),[C,w]=r(!1),u=(i)=>(N)=>l((x)=>({...x,[i]:N}));return n("form",{className:"stack",onSubmit:async(i)=>{i.preventDefault(),w(!0),b(void 0);try{let{connector:N,skipped:x}=await L.add({name:s.name,url:s.url,description:s.description,...s.header.trim()?{header:s.header}:{},...s.token?{token:s.token}:{}});l(G),f(x.length?t("Added {name}. Left out tools whose names are too long: {tools}",{name:N.name,tools:x.join(", ")}):t("Added {name}.",{name:N.name})),a.onDone()}catch(N){b(d(N))}finally{w(!1)}},autoComplete:"off",children:[n("label",{children:[t("Name"),e("input",{value:s.name,maxLength:12,placeholder:"notion",required:!0,onChange:(i)=>u("name")(i.target.value)}),e("span",{className:"meta",children:t("Lowercase letters, digits and -, up to 12; it becomes the prefix of its tool names.")})]}),n("label",{children:[t("MCP URL"),e("input",{type:"url",value:s.url,maxLength:500,placeholder:"https://mcp.example.com/mcp",required:!0,onChange:(i)=>u("url")(i.target.value)}),e("span",{className:"meta",children:t("A Streamable HTTP endpoint; one ending in /sse uses SSE. A credential may be part of the URL.")})]}),n("label",{children:[t("Purpose"),e("textarea",{value:s.description,maxLength:1000,rows:3,required:!0,onChange:(i)=>u("description")(i.target.value)}),e("span",{className:"meta",children:t("The knights decide from this text when to use the connector.")})]}),n("div",{className:"form-row",children:[n("label",{children:[t("Header name (optional)"),e("input",{value:s.header,maxLength:100,placeholder:"X-API-Key",onChange:(i)=>u("header")(i.target.value)}),e("span",{className:"meta",children:t("Leave empty to send Authorization: Bearer <token>.")})]}),n("label",{children:[t("Token (optional)"),e("input",{type:"password",value:s.token,maxLength:4000,autoComplete:"new-password",onChange:(i)=>u("token")(i.target.value)}),e("span",{className:"meta",children:t("Stored encrypted by the gateway and never shown again; leave empty if the credential is in the URL.")})]})]}),c?e(m,{message:c}):null,n("div",{className:"actions",children:[e("button",{type:"button",className:"secondary",disabled:C,onClick:a.onCancel,children:t("Cancel")}),e("button",{type:"submit",disabled:C,children:t("Add connector")})]})]})}function X(a){let[s,l]=r(a.connector.description),[c,b]=r(),[C,w]=r(!1);return n("form",{className:"stack",onSubmit:async(R)=>{R.preventDefault(),w(!0),b(void 0);try{await L.describe(a.connector.name,s),f(t("Purpose updated.")),a.onDone()}catch(i){b(d(i))}finally{w(!1)}},children:[n("label",{children:[t("Purpose"),e("textarea",{value:s,maxLength:1000,rows:4,required:!0,onChange:(R)=>l(R.target.value)}),e("span",{className:"meta",children:t("From the next message on, the knights follow the new purpose.")})]}),c?e(m,{message:c}):null,n("div",{className:"actions",children:[e("button",{type:"button",className:"secondary",disabled:C,onClick:a.onCancel,children:t("Cancel")}),e("button",{type:"submit",disabled:C,children:t("Save")})]})]})}function H(){let{data:a,error:s,reload:l}=V(()=>L.list()),[c,b]=r(!1),[C,w]=r(),[u,R]=r();return n("section",{className:"stack","aria-labelledby":"connectors-mine",children:[n("div",{className:"card-head",children:[e("h3",{id:"connectors-mine",children:t("MCP connectors")}),e("button",{type:"button",onClick:()=>b(!0),children:t("Add connector")})]}),s?e(m,{message:s}):null,!a&&!s?e(v,{}):null,a?.length===0?e(k,{children:t("No connectors yet.")}):null,a?.map((i)=>n("article",{className:"card connector-card",children:[n("div",{className:"card-head",children:[e("strong",{children:i.name}),i.tools===void 0?e(T,{tone:"warn",children:t("Tools unreadable")}):e(T,{children:t("{count} tools",{count:i.tools})})]}),e("p",{className:"meta connector-url",children:i.displayUrl??t("(the URL cannot be parsed)")}),e("p",{children:i.description}),n("div",{className:"actions",children:[e("button",{type:"button",className:"secondary",onClick:()=>w(i),children:t("Edit purpose")}),e("button",{type:"button",className:"danger",onClick:()=>R(i),children:t("Remove")})]})]},i.name)),e(ie,{open:c,title:t("Add an MCP connector"),onClose:()=>b(!1),children:c?e(K,{onCancel:()=>b(!1),onDone:()=>{b(!1),l()}}):null}),e(ie,{open:C!==void 0,title:t("Edit purpose of {name}",{name:C?.name??""}),onClose:()=>w(void 0),children:C?e(X,{connector:C,onCancel:()=>w(void 0),onDone:()=>{w(void 0),l()}},C.name):null}),e(q,{open:u!==void 0,title:t("Remove connector"),confirmLabel:t("Remove"),tone:"danger",onClose:()=>R(void 0),onConfirm:async()=>{if(!u)return;await L.remove(u.name),f(t("Removed {name}.",{name:u.name})),l()},children:e("p",{children:t("Remove {name} and its token? From the next message on, the knights no longer use it.",{name:u?.name??""})})})]})}var I=" new",Z=(a,s)=>a.map((l)=>s.find((c)=>c.id===l)?.label??l).join(", ");function Q(a){return e(ie,{open:a.shown!==void 0,title:t("MCP URL of {bundle}",{bundle:a.shown?.bundle??""}),onClose:a.onClose,children:n("div",{className:"stack",children:[e("p",{children:t("This URL is the access credential and is shown only this once. Add a remote MCP server in the AI client and paste it; no separate token is needed.")}),e("input",{readOnly:!0,"aria-label":t("MCP URL"),className:"mcp-url",value:a.shown?.url??"",onFocus:(s)=>s.target.select()}),n("div",{className:"actions",children:[e("button",{type:"button",className:"secondary",onClick:()=>{navigator.clipboard?.writeText(a.shown?.url??"").then(()=>f(t("Copied")))},children:t("Copy")}),e("button",{type:"button",onClick:a.onClose,children:t("Done")})]})]})})}function ee(a){return n("fieldset",{className:"field",children:[e("legend",{children:t("Allowed operations")}),e("ul",{className:"menu",children:a.operations.map((s)=>{let l=a.chosen.includes(s.id);return e("li",{children:n("label",{className:"menu-item menu-toggle",children:[e("input",{type:"checkbox",checked:l,onChange:()=>a.onChange(l?a.chosen.filter((c)=>c!==s.id):[...a.chosen,s.id])}),e("span",{className:"menu-text",children:e("strong",{children:s.label})})]})},s.id)})})]})}function ne(a){let{view:s}=a,[l,c]=r(),[b,C]=r(s.bundles[0]?.name??I),[w,u]=r(""),[R,i]=r(""),[N,x]=r([]),[M,D]=r(""),[E,j]=r(""),[B,O]=r(),[z,F]=r(!1);return o(()=>{L.channels().then((p)=>{c(p),i((S)=>S||p[0]?.id||"")},(p)=>O(d(p)))},[]),n("form",{className:"stack",onSubmit:async(p)=>{p.preventDefault(),F(!0),O(void 0);try{let S=await L.authorize({bundle:b===I?w:b,channelId:R,operations:N,...M.trim()?{displayName:M}:{},...E.trim()?{description:E}:{}});f(t("Authorized the channel in {bundle}.",{bundle:S.bundle})),a.onDone(S.url?{bundle:S.bundle,url:S.url}:void 0)}catch(S){O(d(S))}finally{F(!1)}},children:[n("label",{htmlFor:"mcp-bundle",children:[t("Bundle"),n(Te,{id:"mcp-bundle",value:b,onChange:(p)=>C(p.target.value),children:[s.bundles.map((p)=>e("option",{value:p.name,children:p.name},p.name)),e("option",{value:I,children:t("New bundle…")})]})]}),b===I?n("label",{children:[t("New bundle name"),e("input",{value:w,maxLength:80,required:!0,onChange:(p)=>u(p.target.value)}),e("span",{className:"meta",children:t("Channels in one bundle share one MCP URL.")})]}):null,n("label",{htmlFor:"mcp-channel",children:[t("Channel"),l===void 0&&!B?e(v,{}):e(Te,{id:"mcp-channel",value:R,required:!0,onChange:(p)=>i(p.target.value),children:(l??[]).map((p)=>e("option",{value:p.id,children:`${p.guild} › #${p.name}`},p.id))})]}),e(ee,{operations:s.operations,chosen:N,onChange:x}),n("label",{children:[t("Name outside AI clients see (optional)"),e("input",{value:M,maxLength:100,onChange:(p)=>D(p.target.value)})]}),n("label",{children:[t("Purpose (optional)"),e("textarea",{value:E,maxLength:1000,rows:2,onChange:(p)=>j(p.target.value)})]}),B?e(m,{message:B}):null,n("div",{className:"actions",children:[e("button",{type:"button",className:"secondary",disabled:z,onClick:a.onCancel,children:t("Cancel")}),e("button",{type:"submit",disabled:z||!R||N.length===0,children:t("Authorize")})]})]})}function te(a){let{bundle:s,grant:l}=a.target,[c,b]=r(l.displayName),[C,w]=r(l.description),[u,R]=r(),[i,N]=r(!1);return n("form",{className:"stack",onSubmit:async(M)=>{M.preventDefault(),N(!0),R(void 0);try{await L.describeGrant(s,l.channelId,{displayName:c,description:C}),f(t("Updated what outside AI clients see.")),a.onDone()}catch(D){R(d(D))}finally{N(!1)}},children:[n("label",{children:[t("Name outside AI clients see"),e("input",{value:c,maxLength:100,required:!0,onChange:(M)=>b(M.target.value)})]}),n("label",{children:[t("Purpose"),e("textarea",{value:C,maxLength:1000,rows:3,onChange:(M)=>w(M.target.value)}),e("span",{className:"meta",children:t("The channel's name and topic on Discord stay as they are.")})]}),u?e(m,{message:u}):null,n("div",{className:"actions",children:[e("button",{type:"button",className:"secondary",disabled:i,onClick:a.onCancel,children:t("Cancel")}),e("button",{type:"submit",disabled:i,children:t("Save")})]})]})}function ae(a){let{grant:s}=a,{timeZone:l}=_();return n("li",{className:"grant",children:[n("div",{className:"card-head",children:[e("strong",{children:s.displayName}),s.visible?null:e(T,{tone:"warn",children:t("Not visible now")})]}),e("p",{className:"meta",children:`${s.guildName} › #${s.channelName}`}),e("p",{children:s.description||t("No purpose set.")}),e("p",{className:"meta",children:t("Allowed: {operations}",{operations:Z(s.operations,a.operations)})}),s.audit.length?n("details",{children:[e("summary",{children:t("Recent activity")}),e("ul",{className:"audit",children:s.audit.map((c)=>n("li",{children:[e("time",{dateTime:c.at,children:re(c.at,l)})," ",e("code",{children:c.tool})," ",c.status]},`${c.at}-${c.tool}`))})]}):null,n("div",{className:"actions",children:[e("button",{type:"button",className:"secondary",onClick:a.onDescribe,children:t("Rename / purpose")}),e("button",{type:"button",className:"danger",onClick:a.onRevoke,children:t("Revoke")})]})]})}function W(){let{data:a,error:s,reload:l}=V(()=>L.grants()),[c,b]=r(!1),[C,w]=r(),[u,R]=r(),[i,N]=r(),[x,M]=r();return n("section",{className:"stack","aria-labelledby":"mcp-grants",children:[n("div",{className:"card-head",children:[n("div",{children:[e("h3",{id:"mcp-grants",children:t("MCP authorizations")}),e("p",{className:"hint",children:t("Which channels outside AI clients may use through each bundle's MCP URL, and with which operations.")})]}),e("button",{type:"button",disabled:!a,onClick:()=>b(!0),children:t("Authorize a channel")})]}),s?e(m,{message:s}):null,!a&&!s?e(v,{}):null,a?.bundles.length===0?e(k,{children:t("There are no authorizations yet.")}):null,a?.bundles.map((D)=>n("article",{className:"card bundle-card",children:[n("div",{className:"card-head",children:[e("strong",{children:D.name}),e("button",{type:"button",className:"secondary",onClick:()=>N(D.name),children:t("New URL")})]}),D.grants.length===0?e(k,{children:t("No channels")}):null,e("ul",{className:"grants",children:D.grants.map((E)=>e(ae,{bundle:D.name,grant:E,operations:a.operations,onDescribe:()=>w({bundle:D.name,grant:E}),onRevoke:()=>R({bundle:D.name,grant:E})},E.channelId))})]},D.name)),e(ie,{open:c,title:t("Authorize a channel"),onClose:()=>b(!1),children:c&&a?e(ne,{view:a,onCancel:()=>b(!1),onDone:(D)=>{if(b(!1),D)M(D);l()}}):null}),e(ie,{open:C!==void 0,title:t("What outside AI clients see"),onClose:()=>w(void 0),children:C?e(te,{target:C,onCancel:()=>w(void 0),onDone:()=>{w(void 0),l()}},`${C.bundle}/${C.grant.channelId}`):null}),e(q,{open:u!==void 0,title:t("Revoke the channel"),confirmLabel:t("Revoke"),tone:"danger",onClose:()=>R(void 0),onConfirm:async()=>{if(!u)return;await L.revoke(u.bundle,u.grant.channelId),f(t("Revoked.")),l()},children:e("p",{children:t("Remove {channel} from bundle {bundle}? Its other channels and its URL keep working; operations already sent are not undone.",{channel:u?.grant.displayName??"",bundle:u?.bundle??""})})}),e(q,{open:i!==void 0,title:t("Replace the MCP URL"),confirmLabel:t("Replace URL"),tone:"danger",onClose:()=>N(void 0),onConfirm:async()=>{if(!i)return;let{bundle:D,url:E}=await L.rotate(i);if(E)M({bundle:D,url:E});l()},children:e("p",{children:t("Replace the URL of bundle {bundle}? The old URL stops working at once; every channel setting stays.",{bundle:i??""})})}),e(Q,{...x?{shown:x}:{},onClose:()=>M(void 0)})]})}var oe=`
2
- /* The Connectors page's management: an address never breaks the layout, a bundle's channels sit as rows. */
3
- .connector-url,
4
- .connectors-page .grant .meta {
5
- overflow-wrap: anywhere;
6
- }
7
-
8
- .connectors-page .grants,
9
- .connectors-page .audit {
10
- display: flex;
11
- flex-direction: column;
12
- margin: 0;
13
- padding: 0;
14
- list-style: none;
15
- }
16
-
17
- .connectors-page .grant {
18
- display: flex;
19
- flex-direction: column;
20
- gap: var(--space-1);
21
- padding: var(--space-3) 0;
22
- border-top: 1px solid var(--border);
23
- }
24
-
25
- .connectors-page .grant p {
26
- margin: 0;
27
- }
28
-
29
- .connectors-page .audit {
30
- gap: var(--space-1);
31
- font-size: var(--text-sm);
32
- }
33
-
34
- .mcp-url {
35
- font-family: var(--font-mono);
36
- }
37
- `,Y=()=>se("connectors",oe);function Qe(){Y();let{connectorAdminUrl:a,connectorParts:s}=_(),l=s??{gateways:!0};return n("section",{className:"stack connectors-page",children:[n("header",{className:"page-head",children:[e("h1",{children:t("Connectors")}),a?e("a",{href:a,target:"_blank",rel:"noreferrer",children:t("Open connector admin")}):null]}),e("p",{className:"hint",children:t("Upstream MCP gateways and virtual servers used by the knights.")}),l.manage?e(H,{}):null,l.grants?e(W,{}):null,l.gateways?e(le,{}):null]})}function le(){let{data:a,error:s}=ve(()=>y.connectors(),"connectors");return n("section",{className:"stack",children:[s?e(m,{message:s}):null,!a&&!s?e(v,{}):null,a?n(g,{children:[e("h3",{children:t("Upstream gateways")}),a.gateways.length===0?e(k,{children:t("No gateways.")}):null,a.gateways.map((l)=>e("article",{className:"card",children:n("div",{className:"card-head",children:[e("strong",{children:l.name}),e(T,{children:l.enabled?t("Enabled"):t("Disabled")}),e(T,{tone:l.reachable?"plain":"warn",children:l.reachable?t("Reachable"):t("Unreachable")}),e(T,{children:t("{count} tools",{count:l.tools})})]})},l.name)),e("h3",{children:t("Virtual servers")}),a.servers.length===0?e(k,{children:t("No virtual servers.")}):null,a.servers.map((l)=>n("article",{className:"card",children:[n("div",{className:"card-head",children:[e("strong",{children:l.name}),e(T,{children:t("{count} tools",{count:l.tools.length})})]}),n("p",{children:[t("Used by"),": ",l.usedBy.join(", ")||t("None")]}),n("details",{children:[e("summary",{children:t("Tool list")}),e("ul",{children:l.tools.map((c)=>e("li",{children:c},c))})]})]},l.name))]}):null]})}export{Qe as ConnectorsPage};
@@ -1 +0,0 @@
1
- import{t,u,g,e,n,x,y,v,m,k,T,_,xe,re,Ne,mn,vi,J}from"./chunk-index-cn49mawx.js";import{ve}from"./chunk-index-gcmmwh8t.js";var h=[{kind:"owner",title:u("Owner channels"),note:u("Direct messages and channels where the owner talks to a knight.")},{kind:"agent",title:u("Bot server channels"),note:u("The bot server's one-to-one channels.")},{kind:"group",title:u("Group channels"),note:u("The bot server's group channels.")},{kind:"outside",title:u("Outside AI clients"),note:u("Conversations an outside AI client holds over MCP.")},{kind:"plugin",title:u("Plugin conversations"),note:u("Conversations a plugin runs, such as a web chat, as the host recorded them.")}];function P(){let{timeZone:o}=_(),{now:i}=J(),{data:a,error:s}=ve(()=>y.conversations(),"conversations"),c=a?.conversations;if(!c)return s?e(m,{message:s}):e(v,{});return n(g,{children:[s?e(m,{message:s}):null,h.map((r)=>{let d=c.filter((l)=>l.kind===r.kind);return n("section",{className:"stack",children:[e("h2",{children:t(r.title)}),e("p",{className:"hint",children:t(r.note)}),d.length===0?e(k,{children:t("None stored.")}):null,d.map((l)=>e(p,{item:l,now:i,timeZone:o},l.key))]},r.kind)})]})}function p(o){let{item:i}=o,a=mn(i.channel,i.id),s=vi(i);return n("a",{className:"card card-link",href:x("conversations",i.key),children:[n("div",{className:"card-head",children:[e("strong",{children:s}),i.busy>0?e(T,{tone:"busy",children:t("running")}):null,i.liveBytes===0?e(T,{children:t("archived only")}):null]}),n("p",{className:"meta",children:[(i.kind==="outside"||i.kind==="plugin")&&i.startedAt?`${t("Started")} ${re(i.startedAt,o.timeZone)} · `:a.detail?`${a.detail} · `:"",i.principal?`${i.visibility==="private"?t("Private to {name}",{name:i.principal.name}):t("Shared · {name}",{name:i.principal.name})} · `:"",t("Last active")," ",xe(i.lastActive,o.now,o.timeZone)," ·"," ",Ne(i.liveBytes)," ·"," ",t("{count} archived",{count:i.archives})]})]})}export{P as ConversationsPage};