pi-roundtable-web 0.28.0 → 0.29.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 (205) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +4 -4
  3. package/dist/chunk-account-9616x405.js +1 -0
  4. package/dist/{chunk-action-menu-qpp9bdxh.js → chunk-action-menu-q5y7xpry.js} +1 -1
  5. package/dist/chunk-agent-x74xezyt.js +1 -0
  6. package/dist/{chunk-arranged-rooms-6rdkk8t1.js → chunk-arranged-rooms-9prq46vh.js} +1 -1
  7. package/dist/chunk-bulk-actions-5dc4m9ad.js +1 -0
  8. package/dist/chunk-character-creator-wf02eg0h.js +1 -0
  9. package/dist/{chunk-codeeditor-jgj32247.js → chunk-codeeditor-239ss6a2.js} +2 -2
  10. package/dist/chunk-confirm-bhrx9195.js +1 -0
  11. package/dist/chunk-connectors-k77pd9by.js +37 -0
  12. package/dist/{chunk-conversations-fb071rez.js → chunk-conversations-ftcpaqw1.js} +1 -1
  13. package/dist/chunk-council-sheet-a5e6tspr.js +2 -0
  14. package/dist/chunk-council-view-6593rqbr.js +1 -0
  15. package/dist/{chunk-drag-04qfef58.js → chunk-drag-7873fvzw.js} +1 -1
  16. package/dist/{chunk-entry-mws704mc.js → chunk-entry-5zmjxadx.js} +1 -1
  17. package/dist/chunk-folder-list-h9hmbm8e.js +1 -0
  18. package/dist/chunk-folds-vxq86t1s.js +1 -0
  19. package/dist/{chunk-gate-vxetvn8t.js → chunk-gate-fa6za28z.js} +1 -1
  20. package/dist/chunk-host-icons-y6frbzqt.js +1 -0
  21. package/dist/chunk-host-picture-fqg55rbr.js +1 -0
  22. package/dist/chunk-host-state-65tdhkyq.js +1 -0
  23. package/dist/{chunk-host-ui-pc9r24e3.js → chunk-host-ui-233kz7qp.js} +1 -1
  24. package/dist/{chunk-index-5hbq6g2e.css → chunk-index-1652v386.css} +1 -1
  25. package/dist/chunk-index-2c00zf3z.js +2 -0
  26. package/dist/chunk-index-2dhrqvba.js +2 -0
  27. package/dist/chunk-index-2s4bkrz3.js +2 -0
  28. package/dist/{chunk-index-v8mma5np.js → chunk-index-2ypaqwjy.js} +2 -2
  29. package/dist/{chunk-index-7qd604rq.js → chunk-index-35zqk1zr.js} +2 -2
  30. package/dist/chunk-index-39qq37j5.js +2 -0
  31. package/dist/{chunk-index-2hvn2kte.js → chunk-index-3tjmyd1h.js} +2 -2
  32. package/dist/{chunk-index-2tr41qht.js → chunk-index-4eag6s50.js} +2 -2
  33. package/dist/{chunk-index-2y8r4s4f.js → chunk-index-4jnn2g09.js} +2 -2
  34. package/dist/{chunk-index-q5fyrtx4.js → chunk-index-4krm48hm.js} +1 -1
  35. package/dist/{chunk-index-zj0gjcvr.js → chunk-index-4re5prfp.js} +2 -2
  36. package/dist/chunk-index-57teqgct.js +2 -0
  37. package/dist/{chunk-index-e2gy763y.js → chunk-index-5ssncptg.js} +2 -2
  38. package/dist/{chunk-index-20xgx0k2.js → chunk-index-7c9fn21n.js} +2 -2
  39. package/dist/{chunk-index-p7jkr7gv.js → chunk-index-7k5895sn.js} +5 -5
  40. package/dist/chunk-index-93f3e06v.js +2 -0
  41. package/dist/chunk-index-9m9v1p7s.js +2 -0
  42. package/dist/{chunk-index-3yy10a5h.js → chunk-index-a8c0ap8g.js} +2 -2
  43. package/dist/chunk-index-a8nqn3y0.js +3 -0
  44. package/dist/chunk-index-am7ghh75.js +120 -0
  45. package/dist/{chunk-index-rgf4rvns.js → chunk-index-amspdq2j.js} +3 -3
  46. package/dist/{chunk-index-s53bxwkk.js → chunk-index-aycxt14t.js} +2 -2
  47. package/dist/{chunk-index-jc3r4qam.js → chunk-index-bjeg3e2j.js} +2 -2
  48. package/dist/chunk-index-cttrqfm8.js +2 -0
  49. package/dist/chunk-index-dajanzxc.js +1 -0
  50. package/dist/chunk-index-e2eeckhh.js +2 -0
  51. package/dist/{chunk-index-rsd8rftk.js → chunk-index-e46652wf.js} +2 -2
  52. package/dist/chunk-index-ek98d2rn.js +2 -0
  53. package/dist/{chunk-index-n0cmnwd2.js → chunk-index-en5dm1zv.js} +2 -2
  54. package/dist/{chunk-index-zcfr8jdv.js → chunk-index-evhaaqzk.js} +12 -12
  55. package/dist/{chunk-index-2syyyjnq.js → chunk-index-f4gqzfkv.js} +2 -2
  56. package/dist/{chunk-index-3gfbhmpb.js → chunk-index-gj3n4dh5.js} +2 -2
  57. package/dist/chunk-index-gp4chnp3.js +2 -0
  58. package/dist/{chunk-index-v9gbt5tz.js → chunk-index-h2a42p6s.js} +3 -3
  59. package/dist/{chunk-index-zt19fdmc.js → chunk-index-haac6nbt.js} +2 -2
  60. package/dist/{chunk-index-gwkhs8s3.js → chunk-index-hef4gwtb.js} +2 -2
  61. package/dist/chunk-index-mg96sqpk.js +2 -0
  62. package/dist/chunk-index-nsya88fn.js +1 -0
  63. package/dist/{chunk-index-6cg8xp1c.js → chunk-index-ntt3em2k.js} +1 -1
  64. package/dist/chunk-index-pcbkp62w.js +2 -0
  65. package/dist/{chunk-index-5t2jkecy.js → chunk-index-pfs6rm6w.js} +2 -2
  66. package/dist/{chunk-index-x60n1me6.js → chunk-index-pgxgq61x.js} +2 -2
  67. package/dist/{chunk-index-s9p27f9b.js → chunk-index-q33ppq0j.js} +3 -3
  68. package/dist/{chunk-index-fepsbhr6.js → chunk-index-r0hk9kyd.js} +2 -2
  69. package/dist/{chunk-index-2822z600.js → chunk-index-rr73h4kc.js} +3 -3
  70. package/dist/{chunk-index-e35v554x.js → chunk-index-rtz4936v.js} +1 -1
  71. package/dist/chunk-index-smz90j6p.js +2 -0
  72. package/dist/{chunk-index-c7dbvkrq.js → chunk-index-teyx2hz7.js} +2 -2
  73. package/dist/{chunk-index-4t6vnwap.js → chunk-index-v6h43553.js} +2 -2
  74. package/dist/chunk-index-vzqwn3wp.js +2 -0
  75. package/dist/chunk-index-wt2asefx.js +50 -0
  76. package/dist/chunk-index-x4tq9btg.js +2 -0
  77. package/dist/chunk-index-y6p8j3zv.js +2 -0
  78. package/dist/chunk-index-yvcdg5hh.js +2 -0
  79. package/dist/{chunk-index-m81362sc.js → chunk-index-zdzzf3fp.js} +1 -1
  80. package/dist/chunk-index-zeqfnkty.js +2 -0
  81. package/dist/chunk-index-zpwb8mkk.js +2 -0
  82. package/dist/chunk-lang-css-avzhvm72.js +1 -0
  83. package/dist/{chunk-lang-dockerfile-n4bt8jbt.js → chunk-lang-dockerfile-2p7e3ssz.js} +1 -1
  84. package/dist/{chunk-lang-go-9k4y107e.js → chunk-lang-go-zxp9k517.js} +3 -3
  85. package/dist/chunk-lang-html-red02gep.js +1 -0
  86. package/dist/chunk-lang-javascript-n33kmjcx.js +1 -0
  87. package/dist/{chunk-lang-json-hr12bzs1.js → chunk-lang-json-zmym50rg.js} +1 -1
  88. package/dist/{chunk-lang-markdown-ttzcpgkg.js → chunk-lang-markdown-ww2kcs85.js} +4 -4
  89. package/dist/{chunk-lang-python-ntj8maem.js → chunk-lang-python-ng1zdtd4.js} +2 -2
  90. package/dist/{chunk-lang-rust-gv8y859d.js → chunk-lang-rust-6ynhxjdd.js} +1 -1
  91. package/dist/{chunk-lang-shell-5zzs09b7.js → chunk-lang-shell-39t9bz2s.js} +1 -1
  92. package/dist/{chunk-lang-sql-6vmze4pg.js → chunk-lang-sql-w875ejpg.js} +2 -2
  93. package/dist/{chunk-lang-toml-tt0d4rw9.js → chunk-lang-toml-b8ckgkg5.js} +1 -1
  94. package/dist/chunk-lang-typescript-qf8acbb2.js +1 -0
  95. package/dist/{chunk-lang-xml-r2sbqnjv.js → chunk-lang-xml-bz91cjp1.js} +1 -1
  96. package/dist/{chunk-lang-yaml-ex31qbph.js → chunk-lang-yaml-g9zy0tdv.js} +1 -1
  97. package/dist/chunk-layout-actions-fyzzzeyp.js +1 -0
  98. package/dist/chunk-menu-items-yvnk3vds.js +1 -0
  99. package/dist/chunk-mode-toggle-ccv271hm.js +1 -0
  100. package/dist/{chunk-note-row-40ypghn9.js → chunk-note-row-314py1pp.js} +3 -3
  101. package/dist/{chunk-notes-a8nqffbf.js → chunk-notes-4rnrddzw.js} +1 -1
  102. package/dist/{chunk-notice-bell-rdnfmtf2.js → chunk-notice-bell-z9wgamnz.js} +1 -1
  103. package/dist/{chunk-notices-tefx93s6.js → chunk-notices-mtn7pg1z.js} +1 -1
  104. package/dist/{chunk-onboarding-wvh0s3vg.js → chunk-onboarding-f3hhxgg4.js} +1 -1
  105. package/dist/{chunk-overview-zrx3bpwf.js → chunk-overview-qmepntrr.js} +1 -1
  106. package/dist/chunk-palette-edkn1ypq.js +1 -0
  107. package/dist/chunk-placement-items-trsa5t23.js +1 -0
  108. package/dist/{chunk-profile-tyb48b79.js → chunk-profile-pwfq0wns.js} +1 -1
  109. package/dist/chunk-prompt-history-xp9hmeqt.js +1 -0
  110. package/dist/chunk-prompts-a56qvytg.js +1 -0
  111. package/dist/chunk-room-confirms-gqpkz4h0.js +1 -0
  112. package/dist/{chunk-room-menu-ha1evv71.js → chunk-room-menu-1m2c2ekt.js} +1 -1
  113. package/dist/chunk-rooms-menu-button-tcvda1gq.js +1 -0
  114. package/dist/{chunk-schedules-a5pewkhd.js → chunk-schedules-kwnw7p8v.js} +1 -1
  115. package/dist/{chunk-seat-menu-eh3ymmks.js → chunk-seat-menu-m2n9nvv0.js} +1 -1
  116. package/dist/chunk-sheet-hosts-pr8xktyq.js +29 -0
  117. package/dist/{chunk-shortcut-help-bh9ntk9s.js → chunk-shortcut-help-sbvrds1m.js} +2 -2
  118. package/dist/{chunk-shortcuts-6em0atk9.js → chunk-shortcuts-8705p7q3.js} +1 -1
  119. package/dist/{chunk-skill-editor-e43mwz01.js → chunk-skill-editor-7xy09sq1.js} +2 -2
  120. package/dist/chunk-skills-cnc25x7q.js +1 -0
  121. package/dist/{chunk-start-over-line-xd1v2p0g.js → chunk-start-over-line-97zdd9pb.js} +2 -2
  122. package/dist/{chunk-switcher-list-dtfh87zy.js → chunk-switcher-list-ehsn2wsk.js} +1 -1
  123. package/dist/{chunk-tab-dot-qd640mjr.js → chunk-tab-dot-gkpe06q8.js} +1 -1
  124. package/dist/chunk-table-av3kh3tt.js +1 -0
  125. package/dist/{chunk-table2d-5nrhfwft.js → chunk-table2d-5x3jptp9.js} +1 -1
  126. package/dist/{chunk-table3d-5cb66d99.js → chunk-table3d-rsvajx7r.js} +172 -172
  127. package/dist/chunk-thread-actions-7xtkegwm.js +1 -0
  128. package/dist/{chunk-thread-cards-kgt22sbp.js → chunk-thread-cards-grzszfha.js} +1 -1
  129. package/dist/{chunk-thread-list-j2v1pqkz.js → chunk-thread-list-9bjjac3j.js} +2 -2
  130. package/dist/{chunk-transcript-a0p3vwh6.js → chunk-transcript-t5szb8r0.js} +1 -1
  131. package/dist/{chunk-waiting-chip-58ttx9av.js → chunk-waiting-chip-d8b6a0p8.js} +2 -2
  132. package/dist/chunk-waiting-ts3kxjvv.js +1 -0
  133. package/dist/chunk-where-3d3ncsa5.js +1 -0
  134. package/dist/{chunk-workspace-d34njy4p.js → chunk-workspace-ct6hvn0z.js} +2 -2
  135. package/dist/chunk-zh-tw-v9v1eerm.js +1 -0
  136. package/dist/index.html +1 -1
  137. package/dist/sw.js +1 -1
  138. package/package.json +3 -3
  139. package/src/api-types.ts +39 -0
  140. package/src/app/rooms.ts +30 -2
  141. package/src/app/routes.ts +26 -2
  142. package/src/app/skill-group-routes.ts +126 -0
  143. package/src/authz.ts +1 -0
  144. package/src/i18n/zh-tw.ts +31 -2
  145. package/src/push/channel.ts +1 -0
  146. package/src/web-plugin.ts +1 -0
  147. package/dist/chunk-account-678dgbww.js +0 -1
  148. package/dist/chunk-agent-x0yvvdq2.js +0 -1
  149. package/dist/chunk-bulk-actions-zbwdaxtc.js +0 -1
  150. package/dist/chunk-character-creator-215psy17.js +0 -1
  151. package/dist/chunk-confirm-2pzrt272.js +0 -1
  152. package/dist/chunk-connectors-zha2d834.js +0 -37
  153. package/dist/chunk-council-sheet-k5fycefd.js +0 -2
  154. package/dist/chunk-folder-list-qxhmczwv.js +0 -1
  155. package/dist/chunk-folds-b7asegfs.js +0 -1
  156. package/dist/chunk-group-scene-63yhh1mh.js +0 -1
  157. package/dist/chunk-group-table-zy4hmgk3.js +0 -1
  158. package/dist/chunk-host-icons-tyktazzz.js +0 -1
  159. package/dist/chunk-host-picture-ebhejn2k.js +0 -1
  160. package/dist/chunk-host-state-wajep0yr.js +0 -1
  161. package/dist/chunk-index-1af8vsyb.js +0 -2
  162. package/dist/chunk-index-254701sm.js +0 -3
  163. package/dist/chunk-index-31m1b2f5.js +0 -2
  164. package/dist/chunk-index-5vatmakn.js +0 -2
  165. package/dist/chunk-index-6yra9kdq.js +0 -2
  166. package/dist/chunk-index-9bdstf8t.js +0 -2
  167. package/dist/chunk-index-acj4xxgq.js +0 -100
  168. package/dist/chunk-index-aexy023z.js +0 -2
  169. package/dist/chunk-index-bef0fc2m.js +0 -2
  170. package/dist/chunk-index-c6tphrjr.js +0 -1
  171. package/dist/chunk-index-cn49mawx.js +0 -50
  172. package/dist/chunk-index-gcmmwh8t.js +0 -2
  173. package/dist/chunk-index-h2vhv4kh.js +0 -2
  174. package/dist/chunk-index-h9ccz1r3.js +0 -2
  175. package/dist/chunk-index-n0ejy0wd.js +0 -2
  176. package/dist/chunk-index-nkdg1dfz.js +0 -7
  177. package/dist/chunk-index-qxynr1hf.js +0 -2
  178. package/dist/chunk-index-r7fdgzy1.js +0 -2
  179. package/dist/chunk-index-sdcpf5dp.js +0 -2
  180. package/dist/chunk-index-sm7kfb07.js +0 -2
  181. package/dist/chunk-index-sw9r4aa4.js +0 -2
  182. package/dist/chunk-index-t5bmsqqd.js +0 -1
  183. package/dist/chunk-index-vsfsv8hd.js +0 -2
  184. package/dist/chunk-index-wxft0aqb.js +0 -2
  185. package/dist/chunk-index-zvvwxkev.js +0 -2
  186. package/dist/chunk-lang-css-7q1dtpgm.js +0 -1
  187. package/dist/chunk-lang-html-xgvba8rn.js +0 -1
  188. package/dist/chunk-lang-javascript-vgxmgd0a.js +0 -1
  189. package/dist/chunk-lang-typescript-2v652sh5.js +0 -1
  190. package/dist/chunk-layout-actions-bfqtg71d.js +0 -1
  191. package/dist/chunk-menu-items-rztgekfj.js +0 -1
  192. package/dist/chunk-mode-toggle-m5kgb7c7.js +0 -1
  193. package/dist/chunk-palette-5xtdvy9a.js +0 -1
  194. package/dist/chunk-placement-items-rgyrktvy.js +0 -1
  195. package/dist/chunk-prompt-history-wv0vdsjv.js +0 -1
  196. package/dist/chunk-prompts-75hgb0t0.js +0 -1
  197. package/dist/chunk-room-confirms-bcvhpcry.js +0 -1
  198. package/dist/chunk-rooms-menu-button-ej3a2hyc.js +0 -1
  199. package/dist/chunk-sheet-hosts-d5zbwbbr.js +0 -29
  200. package/dist/chunk-skills-vp107wrp.js +0 -1
  201. package/dist/chunk-table-svb2xhfd.js +0 -1
  202. package/dist/chunk-thread-actions-926eab0w.js +0 -1
  203. package/dist/chunk-waiting-cdyz7r1g.js +0 -1
  204. package/dist/chunk-where-9jp6g029.js +0 -1
  205. package/dist/chunk-zh-tw-y3jb20wc.js +0 -1
package/src/api-types.ts CHANGED
@@ -1003,6 +1003,45 @@ export interface SkillDeleted {
1003
1003
  message: string;
1004
1004
  }
1005
1005
 
1006
+ /** A skill group: a named list of skills to filter by and to apply to a knight once. A knight carries skills, not groups. */
1007
+ export interface SkillGroupView {
1008
+ name: string;
1009
+ description: string;
1010
+ skills: string[];
1011
+ }
1012
+
1013
+ /** The answer of `GET api/skill-groups`, by name. */
1014
+ export interface SkillGroupsView {
1015
+ groups: SkillGroupView[];
1016
+ }
1017
+
1018
+ /** The body of `PUT api/skill-groups/<name>`, which creates or replaces a group; `from` renames the group it names. */
1019
+ export interface SkillGroupSave {
1020
+ description: string;
1021
+ skills: string[];
1022
+ from?: string;
1023
+ }
1024
+
1025
+ /**
1026
+ * The body of `POST api/skill-groups/<name>/apply`. `on` (the default) switches on the group's skills
1027
+ * the knight lacks; `on: false` switches off the group's skills it carries, built-in ones excepted.
1028
+ */
1029
+ export interface SkillGroupApply {
1030
+ agent: string;
1031
+ on?: boolean;
1032
+ }
1033
+
1034
+ /** The answer of applying a group: the skills newly switched on and those switched off, in the group's order. */
1035
+ export interface SkillGroupApplied {
1036
+ added: string[];
1037
+ removed: string[];
1038
+ }
1039
+
1040
+ /** The answer of a group's save or delete: the registry's words. */
1041
+ export interface SkillGroupChanged {
1042
+ message: string;
1043
+ }
1044
+
1006
1045
  /** A 409: the skill is not as the writer saw it. */
1007
1046
  export interface SkillConflict extends ApiError {
1008
1047
  conflict: "changed";
package/src/app/rooms.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  channelKey,
14
14
  characterOf,
15
15
  groupRoomKey,
16
+ helperSurface,
16
17
  isHelper,
17
18
  roomOf,
18
19
  } from "pi-roundtable";
@@ -74,6 +75,8 @@ const THREAD_ID = /^thread\.[0-9a-f-]{36}$/;
74
75
  */
75
76
  export class Rooms {
76
77
  readonly #options: RoomsOptions;
78
+ /** The rooms of temp knights made on Discord that someone opened by their address here. */
79
+ readonly #opened = new Set<string>();
77
80
 
78
81
  constructor(options: RoomsOptions) {
79
82
  this.#options = options;
@@ -204,6 +207,19 @@ export class Rooms {
204
207
  };
205
208
  }
206
209
 
210
+ /**
211
+ * The view of a temp knight's room that the list leaves out, opened by its address: from then on
212
+ * the list shows it. Undefined for any other room.
213
+ */
214
+ async openHelper(key: ChannelKey): Promise<RoomView | undefined> {
215
+ const room = await this.resolve(key);
216
+ if (room?.kind !== "agent") return undefined;
217
+ const agent = this.#options.directory.agent(room.name);
218
+ if (helperSurface(agent) !== "discord") return undefined;
219
+ this.#opened.add(room.name);
220
+ return (await this.list()).find((one) => one.key === key);
221
+ }
222
+
207
223
  async list(): Promise<RoomView[]> {
208
224
  const { surface, directory, modes, mutes, activity, log, threads } =
209
225
  this.#options;
@@ -274,9 +290,21 @@ export class Rooms {
274
290
  // Home first; the others keep the team's order.
275
291
  rooms.sort((a, b) => Number(b.home) - Number(a.home));
276
292
  const last = await log.lastOf(rooms.map((room) => room.key));
277
- return rooms.map((room) => {
293
+ // A temp knight made on Discord works there: its room here shows once it has a message or
294
+ // someone opened it by its address.
295
+ const quiet = new Set<string>(
296
+ agents
297
+ .filter(
298
+ (agent) =>
299
+ helperSurface(agent) === "discord" && !this.#opened.has(agent.name),
300
+ )
301
+ .map((agent) => agentRoomKey(surface, agent.name))
302
+ .filter((key) => !last.has(key)),
303
+ );
304
+ return rooms.flatMap((room) => {
305
+ if (quiet.has(room.key)) return [];
278
306
  const message = last.get(room.key);
279
- return message ? { ...room, last: message } : room;
307
+ return [message ? { ...room, last: message } : room];
280
308
  });
281
309
  }
282
310
  }
package/src/app/routes.ts CHANGED
@@ -60,6 +60,7 @@ import { PromptApi, type PromptApiOptions } from "./prompt-routes.ts";
60
60
  import { PAGE_LIMIT, type RoomLog } from "./room-log.ts";
61
61
  import { type ResolvedRoom, type Rooms, roomModeOf } from "./rooms.ts";
62
62
  import { SkillDocApi } from "./skill-doc-routes.ts";
63
+ import { SkillGroupApi } from "./skill-group-routes.ts";
63
64
  import type { AppSurface } from "./surface.ts";
64
65
  import { TeamApi } from "./team-routes.ts";
65
66
  import type { WebThreads } from "./threads.ts";
@@ -142,6 +143,8 @@ export interface AppApiOptions {
142
143
  | "documentHistory"
143
144
  | "delete"
144
145
  >;
146
+ /** The registry's skill groups: listed, saved and deleted. */
147
+ groups: Pick<SkillRegistry, "groups" | "setGroup" | "deleteGroup">;
145
148
  };
146
149
  /** Present when the host has prompt documents (the prompts plugin): the main prompts the owner edits. */
147
150
  prompts?: PromptApiOptions["prompts"];
@@ -285,6 +288,7 @@ export class AppApi {
285
288
  readonly #helpers: HelperRoutes;
286
289
  readonly #prompts: PromptApi | undefined;
287
290
  readonly #skillDocs: SkillDocApi | undefined;
291
+ readonly #skillGroups: SkillGroupApi | undefined;
288
292
 
289
293
  constructor(options: AppApiOptions) {
290
294
  this.#options = options;
@@ -311,6 +315,17 @@ export class AppApi {
311
315
  catalog: () => options.skills?.catalog() ?? [],
312
316
  })
313
317
  : undefined;
318
+ const skills = options.skills;
319
+ this.#skillGroups = skills
320
+ ? new SkillGroupApi({
321
+ groups: skills.groups,
322
+ catalog: () => skills.catalog(),
323
+ attach: (agent, add, remove) => skills.attach(agent, add, remove),
324
+ active: (agent) =>
325
+ options.directory.agent(agent)?.status === "active",
326
+ changed: () => options.changed(),
327
+ })
328
+ : undefined;
314
329
  this.#team = new TeamApi({
315
330
  surface: options.surface.surface,
316
331
  rooms: options.rooms,
@@ -394,6 +409,7 @@ export class AppApi {
394
409
  "workspace",
395
410
  "prompts",
396
411
  "skill-docs",
412
+ "skill-groups",
397
413
  ].includes(second ?? ""));
398
414
  if (!mine) return undefined;
399
415
  try {
@@ -492,6 +508,11 @@ export class AppApi {
492
508
  throw new HttpError(404, "This host has no skills.");
493
509
  return this.#skillDocs.handle(request, parts, owner);
494
510
  }
511
+ if (area === "skill-groups") {
512
+ if (!this.#skillGroups)
513
+ throw new HttpError(404, "This host has no skills.");
514
+ return this.#skillGroups.handle(request, parts, owner);
515
+ }
495
516
  throw new HttpError(404, "There is no such API.");
496
517
  }
497
518
 
@@ -565,8 +586,11 @@ export class AppApi {
565
586
  const room = await rooms.resolve(key);
566
587
  if (!room) throw notFound();
567
588
  if (!action && method === "GET") {
568
- // A thread dismissed from the list still opens by its address; the page asks for its view.
569
- const view = await rooms.threadView(room.key);
589
+ // A thread dismissed from the list, or a temp knight's room the list leaves out, still opens
590
+ // by its address; the page asks for its view.
591
+ const view =
592
+ (await rooms.threadView(room.key)) ??
593
+ (await rooms.openHelper(room.key));
570
594
  if (!view) throw notFound();
571
595
  return json({ room: view } satisfies RoomOneView);
572
596
  }
@@ -0,0 +1,126 @@
1
+ import type { SkillRegistry } from "pi-roundtable";
2
+ import type {
3
+ SkillGroupApplied,
4
+ SkillGroupApply,
5
+ SkillGroupChanged,
6
+ SkillGroupSave,
7
+ SkillGroupsView,
8
+ SkillView,
9
+ } from "../api-types.ts";
10
+ import { json } from "../http.ts";
11
+ import {
12
+ bad,
13
+ bodyOf,
14
+ HttpError,
15
+ optionalList,
16
+ optionalText,
17
+ requiredText,
18
+ } from "./api-http.ts";
19
+ import type { OwnerOf } from "./team-routes.ts";
20
+
21
+ export interface SkillGroupApiOptions {
22
+ groups: Pick<SkillRegistry, "groups" | "setGroup" | "deleteGroup">;
23
+ /** The catalog the page lists: who carries each skill, and which are built in. */
24
+ catalog(): SkillView[];
25
+ /** The same change the per-skill toggle makes. */
26
+ attach(
27
+ agent: string,
28
+ add: readonly string[],
29
+ remove: readonly string[],
30
+ ): Promise<string[]>;
31
+ /** Whether a knight answers; an archived or unknown one is a 404. */
32
+ active(agent: string): boolean;
33
+ changed(): void;
34
+ }
35
+
36
+ /**
37
+ * The app's REST for skill groups: listing them, creating or replacing one, deleting one, and
38
+ * applying one to a knight. Applying switches on the group's skills the knight lacks through the
39
+ * per-skill toggle's path; applying it off switches off the group's skills the knight carries, built-in
40
+ * ones excepted. The knight carries skills, not the group.
41
+ * Every call is an owner's; the registry's refusals are a 400 in its words.
42
+ */
43
+ export class SkillGroupApi {
44
+ readonly #options: SkillGroupApiOptions;
45
+
46
+ constructor(options: SkillGroupApiOptions) {
47
+ this.#options = options;
48
+ }
49
+
50
+ async handle(
51
+ request: Request,
52
+ parts: string[],
53
+ owner: OwnerOf,
54
+ ): Promise<Response> {
55
+ const { method } = request;
56
+ const [, , name, action] = parts;
57
+ const { groups } = this.#options;
58
+ // The tier is read first, so a person below the owner learns of no group.
59
+ await owner();
60
+ if (!name) {
61
+ if (method !== "GET") throw new HttpError(405, "Method not allowed.");
62
+ return json({
63
+ groups: groups.groups().map(({ name, description, skills }) => ({
64
+ name,
65
+ description,
66
+ skills: [...skills],
67
+ })),
68
+ } satisfies SkillGroupsView);
69
+ }
70
+ const group = groups.groups().find((one) => one.name === name);
71
+ if (!action && method === "PUT") {
72
+ const body = await bodyOf(request);
73
+ const from = optionalText(body, "from");
74
+ const save: SkillGroupSave = {
75
+ description: requiredText(body, "description"),
76
+ skills: optionalList(body, "skills") ?? [],
77
+ ...(from ? { from } : {}),
78
+ };
79
+ const renamed = save.from !== undefined && save.from !== name;
80
+ if (renamed) {
81
+ if (!groups.groups().some((one) => one.name === save.from))
82
+ throw new HttpError(404, "There is no such skill group.");
83
+ if (group) throw bad(`There is already a skill group "${name}".`);
84
+ }
85
+ let message = await groups.setGroup(name, save.description, save.skills);
86
+ if (renamed && save.from)
87
+ message = `${message} ${await groups.deleteGroup(save.from)}`;
88
+ this.#options.changed();
89
+ return json({ message } satisfies SkillGroupChanged, group ? 200 : 201);
90
+ }
91
+ if (!group) throw new HttpError(404, "There is no such skill group.");
92
+ if (!action && method === "DELETE") {
93
+ const message = await groups.deleteGroup(name);
94
+ this.#options.changed();
95
+ return json({ message } satisfies SkillGroupChanged);
96
+ }
97
+ if (action === "apply" && method === "POST" && parts.length === 4) {
98
+ const body = await bodyOf(request);
99
+ const apply: SkillGroupApply = {
100
+ agent: requiredText(body, "agent"),
101
+ on: body.on !== false,
102
+ };
103
+ if (!this.#options.active(apply.agent))
104
+ throw new HttpError(404, "There is no such room, knight or file.");
105
+ const catalog = new Map(
106
+ this.#options.catalog().map((entry) => [entry.name, entry]),
107
+ );
108
+ // A built-in skill is carried always: it is never added nor switched off.
109
+ const switchable = group.skills.filter(
110
+ (skill) => catalog.get(skill)?.source.kind !== "builtin",
111
+ );
112
+ const carries = (skill: string) =>
113
+ catalog.get(skill)?.carriers.includes(apply.agent) ?? false;
114
+ const added = apply.on
115
+ ? switchable.filter((skill) => !carries(skill))
116
+ : [];
117
+ const removed = apply.on ? [] : switchable.filter(carries);
118
+ if (added.length > 0 || removed.length > 0) {
119
+ await this.#options.attach(apply.agent, added, removed);
120
+ this.#options.changed();
121
+ }
122
+ return json({ added, removed } satisfies SkillGroupApplied);
123
+ }
124
+ throw new HttpError(404, "There is no such API.");
125
+ }
126
+ }
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/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": "對話已重新開始",
@@ -1178,10 +1177,39 @@ export const ZH_TW: Readonly<Record<string, string>> = {
1178
1177
  "{name} 會被刪除,並從攜帶它的騎士身上拿掉:{carriers}。",
1179
1178
  "{name} will be deleted. No knight carries it.":
1180
1179
  "{name} 會被刪除,目前沒有騎士攜帶它。",
1181
- "It also leaves the skill groups {groups}.": "它也會離開技能群組 {groups}。",
1180
+ "It also leaves the skill groups {groups}.": "它也會離開技能組合 {groups}。",
1182
1181
  "This cannot be undone.": "這個動作無法復原。",
1183
1182
  "Skills ({count})": "技能({count})",
1184
1183
  "Choose the skills this knight carries.": "選擇這位騎士攜帶的技能。",
1184
+ "Skill groups": "技能組合",
1185
+ "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.":
1186
+ "技能組合列出一組技能,可在騎士的設定頁一次開啟。騎士攜帶的是技能,不是組合。",
1187
+ "No skill groups.": "還沒有技能組合。",
1188
+ "Edit {name}": "編輯 {name}",
1189
+ "New skill group": "新增技能組合",
1190
+ "Delete this skill group?": "要刪除這個技能組合嗎?",
1191
+ "Delete group": "刪除組合",
1192
+ "{name} will be deleted. Its skills stay, and so does every knight that carries them.":
1193
+ "{name} 會被刪除。其中的技能會保留,攜帶它們的騎士也不受影響。",
1194
+ "Saved {name}.": "已儲存 {name}。",
1195
+ "Lowercase letters, digits and single hyphens, up to 64.":
1196
+ "小寫字母、數字與單一連字號,最多 64 個字元。",
1197
+ Description: "描述",
1198
+ "Save group": "儲存組合",
1199
+ "Switching a group on switches on its skills this knight lacks; switching it off switches off its skills, built-in ones excepted.":
1200
+ "打開組合會開啟這位騎士還沒有的組合技能;關閉組合會關掉組合裡的技能,內建技能除外。",
1201
+ "Carries all of it.": "已攜帶全部技能。",
1202
+ "Lacks {count}: {skills}": "缺少 {count} 個:{skills}",
1203
+ "Skill group {name}": "技能組合 {name}",
1204
+ "Switch this skill group on?": "要打開這個技能組合嗎?",
1205
+ "Switch this skill group off?": "要關閉這個技能組合嗎?",
1206
+ "Switch on": "打開",
1207
+ "Switch off": "關閉",
1208
+ "Switched on: {skills}": "已開啟:{skills}",
1209
+ "Switched off: {skills}": "已關閉:{skills}",
1210
+ "Nothing changed for {name}.": "{name} 沒有變動。",
1211
+ "{agent} will also carry: {skills}.": "{agent} 將會再攜帶:{skills}。",
1212
+ "{agent} will no longer carry: {skills}.": "{agent} 將不再攜帶:{skills}。",
1185
1213
  // 主要提示詞與技能內容的編輯:伺服器的拒絕訊息
1186
1214
  "This host has no prompts to edit.": "這個主機沒有可編輯的提示詞。",
1187
1215
  "There is no such prompt.": "沒有這份提示詞。",
@@ -1195,6 +1223,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
1195
1223
  "baseHash 必須是讀取技能時的雜湊值。",
1196
1224
  "This skill changed while you were editing it.":
1197
1225
  "這個技能在你編輯時被更動了。",
1226
+ "There is no such skill group.": "沒有這個技能組合。",
1198
1227
  // 成員、登入與設定關卡
1199
1228
  Profile: "個人資料",
1200
1229
  Owner: "擁有者",
@@ -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
@@ -198,6 +198,7 @@ async function build(settings: ResolvedOptions, context: Context) {
198
198
  remove: readonly string[],
199
199
  ) => skillRegistry.attach(agent, add, remove),
200
200
  documents: skillRegistry,
201
+ groups: skillRegistry,
201
202
  }
202
203
  : undefined;
203
204
  // The prompts plugin is always on; a host that replaced it may leave it out.
@@ -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{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,2 +0,0 @@
1
- import{o,z,C,r,t,g,e,n,I,d,f,m,Ue}from"./chunk-index-cn49mawx.js";import"./chunk-index-r7fdgzy1.js";import"./chunk-index-zt19fdmc.js";import"./chunk-index-2y8r4s4f.js";import"./chunk-index-gwkhs8s3.js";import{oi}from"./chunk-index-nkdg1dfz.js";import{qt}from"./chunk-index-n0cmnwd2.js";import{R}from"./chunk-index-m81362sc.js";import{en,Fn,io,Ti,vs,xs,Ss,ws,ks}from"./chunk-index-9bdstf8t.js";import{ne,Ee}from"./chunk-index-c7dbvkrq.js";function Q(i){let{view:a}=i;return e("li",{children:n("button",{type:"button",className:"menu-item council-row","aria-current":i.current||void 0,onClick:i.onPick,children:[e(Ue,{view:a,size:40}),n("span",{className:"menu-text",children:[e("strong",{children:a.displayName}),e("span",{className:"meta",children:t("{count} knights",{count:a.members?.length??0})})]})]})})}function W(i){let{council:a}=i,w=Ti(),u=oi(),[v,M]=r([]),[y,O]=r(),[L,S]=r(""),[G,j]=r(!1),[P,E]=r(""),[p,k]=r(!1),[F,x]=r(),B=z(),V=a?.name,T=a?.members?.join(`
2
- `);o(()=>{if(!i.open)return;if(x(void 0),j(a===void 0),E(""),k(!1),O(void 0),S(""),M(a?[...a.members??[]]:vs(u)),!V)return;let s=!0;return ne.group(V).then((l)=>{if(!s)return;O(l),S(l.host)},()=>{return}),()=>{s=!1}},[i.open,V,T]);let U=C(()=>new Set(u.map((s)=>s.name)),[u]),_=u.find((s)=>s.home)?.name,c=Ss(v,(s)=>U.has(s)),b=ws(c,L||void 0,_),X=(s)=>u.find((l)=>l.name===s)?.displayName??s,D=c.length>=en,N=c.length>=Fn,q=a===void 0||c.join(",")!==(a.members??[]).join(",")||y!==void 0&&b!==y.host,H=async(s)=>{k(!0),x(void 0);try{await s(),await Ee()}catch(l){return x(d(l)),k(!1),!1}return k(!1),!0},J=async()=>{if(!a)return;if(!await H(async()=>{await ne.updateGroup(a.name,{...c.join(",")!==(a.members??[]).join(",")?{members:c}:{},...y&&b!==y.host?{host:b}:{}})}))return;f(t("Saved.")),i.onClose()},K=async()=>{let s=P.trim();if(s==="")return;let l=new Set([...u.map((A)=>A.name),...w.map((A)=>A.name)]),h;if(!await H(async()=>{h=await ne.createGroup({name:ks(s,l),displayName:s,members:c,host:b})})||!h)return;io(h.room),I("table",h.name,{replace:!0}),f(t("Made {name}.",{name:h.displayName})),i.onClose()};return n("div",{className:"stack council-sheet",children:[w.length>0?n("section",{className:"stack","aria-label":t("Councils"),children:[e("h3",{children:t("Councils")}),e("ul",{className:"menu",children:w.map((s)=>e(Q,{view:s,current:s.key===a?.key,onPick:()=>{io(s.key),I("table",s.name,{replace:!0}),i.onClose()}},s.key))})]}):null,n("fieldset",{className:"field council-pick",children:[e("legend",{children:a?t("Edit this council"):t("Pick who sits")}),n("span",{className:"meta council-count","aria-live":"polite",children:[t("{count} of {max} picked",{count:c.length,max:en}),D?` · ${t("At most {max} knights",{max:en})}`:""]}),e("ul",{className:"menu",children:u.map((s)=>{let l=v.includes(s.name);return e("li",{children:n("label",{className:"menu-item menu-toggle seat-row",children:[e("input",{type:"checkbox",checked:l,disabled:!l&&D,onChange:()=>M(xs(v,s.name,en))}),e(Ue,{view:s,size:32}),e("span",{className:"menu-text",children:e("strong",{children:s.displayName})})]})},s.key)})})]}),N?e(qt,{label:t("Host"),value:b,onChange:S,options:c.map((s)=>({value:s,label:X(s)}))}):e("span",{className:"meta",children:t("A council needs at least {min} knights.",{min:Fn})}),G?n("label",{htmlFor:B,children:[t("Council name"),e("input",{id:B,value:P,autoComplete:"off",onChange:(s)=>E(s.target.value)})]}):null,F?e(m,{message:F}):null,e("div",{className:"actions",children:a&&!G?n(g,{children:[e("button",{type:"button",className:"secondary",disabled:!N||p,onClick:()=>j(!0),children:t("Save as a new council")}),e("button",{type:"button",disabled:!N||!q||p,onClick:J,children:p?t("Saving…"):t("Done")})]}):e("button",{type:"button",disabled:!N||P.trim()===""||p,onClick:K,children:p?t("Saving…"):a?t("Save as a new council"):t("Make council")})})]})}function de(i){return e(R,{open:i.open,title:t("Council"),onClose:i.onClose,size:"default",children:e(W,{open:i.open,council:i.council,onClose:i.onClose})})}export{de as CouncilSheet};
@@ -1 +0,0 @@
1
- import{p,o,z,t,K,g,e,n,s,Je,l,me,Ue,qn,Sn}from"./chunk-index-cn49mawx.js";import{yr,vr,xr,Sr}from"./chunk-index-t5bmsqqd.js";import"./chunk-index-q5fyrtx4.js";import{Gt}from"./chunk-index-2tr41qht.js";import{Ci}from"./chunk-index-20xgx0k2.js";var v=()=>(p("bfqtg71d"),import("./chunk-layout-actions-bfqtg71d.js")),k=(a)=>(p("rgyrktvy"),import("./chunk-placement-items-rgyrktvy.js")).then((r)=>r.folderMenu(a)),F=(a)=>({mode:"folder",key:a,item:(r)=>r.closest("[data-folder-block]"),root:(r)=>r.closest("[data-folder-block]")?.parentElement??null,onDrop:(r)=>{if(r.type==="folder")v().then((i)=>i.placeFolderBefore(a,r.before))},open:xr});function b(a){let{id:r,name:i}=a,m=yr().has(r),d=a.holdsActive||!m,c=z(),h=Gt(()=>k(r),F(r)),N=(u)=>{k(r).then((f)=>{if(!f)return;let y=u.getBoundingClientRect();me({...f,at:{x:y.left,y:y.bottom},via:"key",source:u})},()=>{return})};return n("section",{className:"folder","aria-label":i,"data-folder-block":"","data-key":r,"data-drop-zone":"folder","data-zone-id":r,"data-folder":r,"data-collapsed":d?void 0:"",children:[n("header",{className:"folder-head","data-folder-head":"",...h,children:[n("button",{type:"button",className:"folder-toggle","aria-expanded":d,"aria-controls":c,"aria-label":d?t("Collapse {name}",{name:i}):t("Expand {name}",{name:i}),onClick:()=>vr(r),children:[e(s,{name:"chevron",size:16,className:"folder-chevron","data-open":d||void 0}),e("strong",{children:i}),e("span",{className:"meta",children:K(a.count,"{count} room","{count} rooms")}),d?null:a.stack]}),a.muted==="none"?null:e(s,{name:"bell-off",size:14,className:"muted-mark","data-partial":a.muted==="some"||void 0,role:"img","aria-hidden":!1,"aria-label":a.muted==="all"?t("Notifications are off"):t("Some notifications are off")}),d?null:a.summary,e("button",{type:"button",className:"icon-button folder-more","aria-haspopup":"menu","aria-label":t("Folder menu"),onClick:(u)=>N(u.currentTarget),children:e(s,{name:"more",size:18})})]}),e("div",{id:c,className:"folder-body",hidden:!d,children:d?a.count===0?e("p",{className:"meta folder-empty",children:t("No rooms in this folder yet.")}):a.children:null})]})}var R=4;function S(a){let r=a.rooms.slice(0,R),i=l((d)=>r.map((c)=>d.rooms[c]?.view),r.join(",")),m=a.rooms.length-r.length;return n("span",{className:"folder-stack","aria-hidden":"true",children:[i.map((d)=>d?e("span",{className:"folder-stack-item",children:e(Ue,{view:d,size:28})},d.key):null),m>0?n("span",{className:"folder-stack-more",children:["+",m]}):null]})}function M(a){let r=l((i)=>({waiting:a.rooms.reduce((m,d)=>m+Je(i,d),0),typing:a.rooms.some((m)=>i.rooms[m]?.typing)}),a.rooms.join(","));return n(g,{children:[r.typing?e(qn,{}):null,r.waiting?e(Sn,{count:r.waiting}):null]})}function w(a){return l((r)=>Ci(a,(i)=>r.rooms[i]?.view?.muted===!0),a.join(","))}function U(a){let r=a.folders.map((i)=>i.id).join(",");return o(()=>{if(a.listed)Sr(r?r.split(","):[])},[a.listed,r]),e(g,{children:a.folders.map((i)=>e(C,{folder:i,...a},i.id))})}function C(a){let{folder:r}=a,i=w(r.rooms);return e(b,{id:r.id,name:r.name,count:r.rooms.length,holdsActive:a.active!==void 0&&r.rooms.includes(a.active),muted:i,summary:e(M,{rooms:r.rooms}),stack:e(S,{rooms:r.rooms}),children:a.rooms(r.rooms)})}export{U as FolderList};
@@ -1 +0,0 @@
1
- import{yr,br,vr,xr,Sr}from"./chunk-index-t5bmsqqd.js";export{br as collapsedNow,xr as expandFolder,Sr as pruneCollapsed,vr as toggleCollapsed,yr as useCollapsed};
@@ -1 +0,0 @@
1
- import{Li,Xi,ra}from"./chunk-index-acj4xxgq.js";import"./chunk-index-nkdg1dfz.js";import"./chunk-index-2y8r4s4f.js";import"./chunk-index-zt19fdmc.js";import"./chunk-index-m81362sc.js";import"./chunk-index-r7fdgzy1.js";import"./chunk-index-gwkhs8s3.js";export{ra as GroupScene,Xi as GroupStage,Li as useGroupFocus};
@@ -1 +0,0 @@
1
- import{Nr,t,g,e,n,s,l,st}from"./chunk-index-cn49mawx.js";import"./chunk-index-zt19fdmc.js";import"./chunk-index-2y8r4s4f.js";import"./chunk-index-gwkhs8s3.js";import{Bt}from"./chunk-index-nkdg1dfz.js";import"./chunk-index-r7fdgzy1.js";var i=(o,a)=>a?Nr(o,a):o;function T(o){let a=l((c)=>c.rooms[o.roomKey]?.view),r=st();if(!a||a.kind!=="group")return null;let m=a.members??[];return n(g,{children:[n("button",{type:"button",className:"icon-button group-table-button","aria-label":t("Table"),"aria-pressed":o.scene,onClick:()=>o.onScene(!o.scene),children:[e(s,{name:"table"}),e("span",{className:"group-table-label",children:t("Table")})]}),r==="camelot"&&!o.scene?i(e("div",{className:"group-strip",children:e(Bt,{members:m,groupRoom:o.roomKey,variant:"compact"})}),o.slot):null]})}export{T as GroupTable};
@@ -1 +0,0 @@
1
- import"./chunk-index-6cg8xp1c.js";
@@ -1 +0,0 @@
1
- import{ct}from"./chunk-index-v9gbt5tz.js";export{ct as HostPicture};
@@ -1 +0,0 @@
1
- import{ho,Ie,po,mo,go,Oo,yo,on,ur,bo,Oa}from"./chunk-index-gwkhs8s3.js";export{bo as keepCards,ur as keepReads,yo as noCards,Oo as noReads,Oa as onFrame,on as seedUnread,ho as subscribeHost,mo as totalUnread,go as unreadCounts,po as unreadOf,Ie as useHost};
@@ -1,2 +0,0 @@
1
- var qs=40;class l extends Error{code;constructor(e){super(e);this.code=e;this.name="LayoutError"}}function Wn(e,n,r){if(r===n&&e.includes(n))return[...e];let t=e.filter((o)=>o!==n),i=r===null||r===void 0?-1:t.indexOf(r);if(i<0)return[...t,n];return[...t.slice(0,i),n,...t.slice(i)]}function tt(e,n,r){let t=e.indexOf(n);if(t<0)return;if(r==="up")return t===0?void 0:{before:e[t-1]??null};return t===e.length-1?void 0:{before:e[t+2]??null}}function xo(e){let n=e.trim(),r=Array.from(n).length;if(r<1||r>40||/\p{Cc}/u.test(n))throw new l("bad-name");return n}var c=(e,n)=>e.toLocaleLowerCase()===n.toLocaleLowerCase();function u(e,n){let r=e.folders.findIndex((t)=>t.id===n);if(r<0)throw new l("no-folder");return r}var f=(e,n)=>({pins:e.pins,folders:n});function Bs(e,n,r){let t=u(e,n),i=xo(r);if(e.folders.some((o)=>o.id!==n&&c(o.name,i)))throw new l("duplicate");return f(e,e.folders.map((o,s)=>s===t?{...o,name:i}:o))}function Fs(e,n,r){let t=u(e,n),i=e.folders[t],o=Wn(e.folders.map((s)=>s.id),n,r);return f(e,o.map((s)=>s===n?i:e.folders.find((a)=>a.id===s)))}function Gs(e,n){return u(e,n),f(e,e.folders.filter((r)=>r.id!==n))}var d=(e,n)=>({pins:e.pins.filter((r)=>r!==n),folders:e.folders.map((r)=>r.rooms.includes(n)?{...r,rooms:r.rooms.filter((t)=>t!==n)}:r)});function Zs(e,n,r,t){if(r!==null)u(e,r);let i=d(e,n);if(r===null)return i;return f(i,i.folders.map((o)=>o.id===r?{...o,rooms:Wn(o.rooms,n,t)}:o))}function Ws(e,n,r,t){let i=d(e,n);if(!r)return i;return{pins:Wn(e.pins.filter((o)=>o!==n),n,t),folders:i.folders}}
2
- export{qs,Wn,tt,xo,Bs,Fs,Gs,Zs,Ws};
@@ -1,3 +0,0 @@
1
- import{p,t,I,hn,O,d,f,es,me,He,At,Le,_t}from"./chunk-index-cn49mawx.js";import{Is}from"./chunk-index-wxft0aqb.js";import{ro}from"./chunk-index-sm7kfb07.js";import{Fn}from"./chunk-index-9bdstf8t.js";import{tn}from"./chunk-index-aexy023z.js";import{ne,Ee}from"./chunk-index-c7dbvkrq.js";import{fo}from"./chunk-index-vsfsv8hd.js";var m=(e)=>e();async function Bn(e){try{await navigator.clipboard.writeText(e),f(t("Copied"))}catch{f(t("Could not copy."),"danger")}}async function s(e){try{await navigator.share(e)}catch(o){if(o instanceof DOMException&&o.name==="AbortError")return;f(t("Could not share."),"danger")}}var b=()=>(p("zbwdaxtc"),import("./chunk-bulk-actions-zbwdaxtc.js")),h=()=>typeof navigator.share==="function";function ca(e,o=m){let i=O.getState().rooms[e],n=i?.view;if(!n||n.kind==="thread")return;let l=i.mode,a=n.home||n.helper?[]:ro(e,o);if(n.kind==="agent"){let r=n.name;a.push({id:"edit",label:t("Edit knight"),icon:"profile",run:()=>o(()=>He(r))},{id:"look",label:t("Change look"),icon:"sparkle",run:()=>o(()=>He(r,{look:!0}))},{id:"skills",label:t("Skills"),icon:"skills",run:()=>o(()=>He(r,{skills:!0}))})}if(n.kind==="group"){let r=n.name;a.push({id:"edit-group",label:t("Edit group"),icon:"profile",run:()=>o(()=>_t({kind:"group",name:r}))})}if(n.kind==="agent")a.push(...fo([e]));if(!n.helper)a.push({id:"notifications",label:t("Notifications"),icon:n.muted?"bell-off":"bell",checked:!n.muted,run:async()=>{let r=n.muted!==!0;if(!await tn(e,r))return;f(r?t("Notifications are off for {name}",{name:n.displayName}):t("Notifications are on for {name}",{name:n.displayName}))}});if(!n.readOnly)a.push({id:"yolo",label:t("Yolo mode"),icon:"bolt",tone:"yolo",checked:l.yolo,run:()=>{if(l.yolo)return At(e,{yolo:!1});Le({kind:"yolo",room:e,name:n.displayName})}},{id:"long",label:t("Long-running mode"),icon:"timer",tone:"long",checked:l.longRunning,run:()=>At(e,{longRunning:!l.longRunning})},{id:"start-over",label:t("Start over"),icon:"reset",tone:"danger",run:()=>Le({kind:"start-over",room:e,name:n.displayName})});if(n.helper){let{name:r,displayName:g}=n;a.push({id:"archive-helper",label:t("Archive this temp knight"),icon:"archive",tone:"danger",run:()=>b().then((v)=>v.archiveHelper(r,g))})}else if(!n.readOnly&&!n.coordinator)a.push({id:"archive",label:n.kind==="group"?t("Archive group"):t("Archive knight"),icon:"archive",tone:"danger",run:()=>Le({kind:"archive",room:e,name:n.displayName})});return{title:n.displayName,items:a}}function na(e=m){return{title:t("Add"),items:[{id:"new-agent",label:t("New knight"),icon:"profile",run:()=>e(()=>_t({kind:"new-agent"}))},{id:"new-group",label:t("New group"),icon:"rooms",run:()=>e(()=>_t({kind:"group"}))},{id:"archive-list",label:t("Archived"),icon:"archive",run:()=>e(()=>_t({kind:"archive"}))}]}}function ua(e,o){let i=e.getBoundingClientRect();me({...na(o),at:{x:i.left,y:i.bottom},via:"key",source:e})}function da(e,o){let{rooms:i}=O.getState(),n=i[e]?.view,l=i[o]?.view;if(n?.kind!=="agent"||l?.kind!=="group")return;return{title:n.displayName,items:[{id:"chat",label:t("Private chat"),icon:"conversations",run:()=>I("room",e)},{id:"leave",label:t("Remove from the council"),icon:"close",run:()=>y(l,n)}]}}async function y(e,o){let i=(e.members??[]).filter((n)=>n!==o.name);if(i.length<Fn){Le({kind:"archive",room:e.key,name:e.displayName});return}try{let n=await ne.group(e.name);await ne.updateGroup(e.name,{members:i,...n.host===o.name?{host:i[0]??""}:{}}),await Ee(),f(t("{name} left the council",{name:o.displayName}))}catch(n){f(d(n),"danger")}}function c(e,o){let i=e.querySelector(".bubble > .prose");if(!i)return o;return(i.innerText||i.textContent||o).trim()}function w(e){let o=window.getSelection();if(!o||o.isCollapsed)return"";return(o.anchorNode&&e.contains(o.anchorNode)?o.toString():"").trim()}function fa(e,o,i){let n=[{id:"copy",label:t("Copy"),icon:"copy",run:()=>Bn(w(o)||c(o,e.text))},{id:"copy-markdown",label:t("Copy as Markdown"),icon:"notes",run:()=>Bn(e.text)}];if(i)n.push({id:"quote",label:t("Quote reply"),icon:"conversations",run:()=>es(e.room,e.text)});if(n.push({id:"select",label:t("Select text"),icon:"list",run:()=>Is(e.author,e.text)}),h())n.push({id:"share",label:t("Share"),icon:"send",run:()=>s({text:c(o,e.text)})});return{title:e.author,items:n}}function ha(e,o){return{title:o,items:[{id:"profile",label:t("Knight profile"),icon:"profile",run:()=>He(e)}]}}var u=(e)=>[e.header?`${e.header}: ${e.question}`:e.question,...e.options.map((o)=>o.description?`- ${o.label}: ${o.description}`:`- ${o.label}`)];function ia(e){let o=[e.title];switch(e.kind){case"approval":{o.push("",e.message);let{approval:i}=e;if(i){if(o.push("",`${t("Tool")}: ${i.tool}`,`${t("Action")}: ${i.action}`),Object.keys(i.input).length)o.push(JSON.stringify(i.input,null,2));for(let n of i.files??[])o.push(n.path)}break}case"ask":o.push("",...u({question:e.question,options:e.options,multi:e.multi,allowOther:e.allowOther}));break;case"questionnaire":for(let i of e.questions)o.push("",...u(i));break}return o.join(`
2
- `)}function pa(e){return{title:e.title,items:[{id:"copy-details",label:t("Copy details"),icon:"copy",run:()=>Bn(ia(e))}]}}function k(e){let o=document.createElement("a");o.href=hn(e),o.download=e.name,o.hidden=!0,document.body.append(o),o.click(),o.remove()}async function M(e){let o=new URL(hn(e),window.location.href).href;if(typeof navigator.canShare==="function")try{let i=await fetch(o);if(i.ok){let n=new File([await i.blob()],e.name,{type:e.type});if(navigator.canShare({files:[n]})){await s({files:[n],title:e.name});return}}}catch{}await s({title:e.name,url:o})}function ma(e){let o=[{id:"download",label:t("Download"),icon:"download",run:()=>k(e)}];if(h())o.push({id:"share",label:t("Share"),icon:"send",run:()=>M(e)});return{title:e.name,items:o}}
3
- export{Bn,ca,na,ua,da,fa,ha,ia,pa,ma};
@@ -1,2 +0,0 @@
1
- import{t,me,ds,wi}from"./chunk-index-cn49mawx.js";import{ze}from"./chunk-index-6yra9kdq.js";import{Mi}from"./chunk-index-vsfsv8hd.js";function si(e){let r=e.getBoundingClientRect(),o=ds();me({title:t("Rooms"),items:[...Mi(),{id:"new-folder",label:t("Add a folder"),icon:"plus",run:()=>ze({kind:"new"})},{id:"arrange",label:o?t("Done"):t("Arrange"),icon:o?"check":"list",run:()=>wi(!o)}],at:{x:r.left,y:r.bottom},via:"key",source:e})}
2
- export{si};
@@ -1,2 +0,0 @@
1
- import{M}from"./chunk-index-cn49mawx.js";var o=!1,{emit:e,use:t}=M();function No(){if(o)return;o=!0,e()}function ii(){if(!o)return;o=!1,e()}var Lo=()=>t(()=>o),Xo=()=>o,zo=()=>typeof navigator<"u"&&/Mac|iPhone|iPad/.test(navigator.platform);
2
- export{No,ii,Lo,Xo,zo};
@@ -1,2 +0,0 @@
1
- import{M}from"./chunk-index-cn49mawx.js";var e,{emit:o,use:n}=M();function ze(i){e=i,o()}function Pi(){if(!e)return;e=void 0,o()}var _s=()=>n(()=>e);
2
- export{ze,Pi,_s};
@@ -1,2 +0,0 @@
1
- import{We,l}from"./chunk-index-cn49mawx.js";import{_n}from"./chunk-index-r7fdgzy1.js";var en=_n,Fn=2,d="rt-table-council",i,s=!1,u=new Set;function c(){try{return globalThis.localStorage?.getItem(d)??void 0}catch{return}}function g(){if(!s)i=c(),s=!0;return i}function io(n){i=n,s=!0;try{globalThis.localStorage?.setItem(d,n)}catch{}for(let e of u)e()}var a=(n)=>(u.add(n),()=>u.delete(n)),ys=()=>We(a,g,()=>{return});function Ti(){return l((n)=>n.order.map((e)=>n.rooms[e]?.view).filter((e)=>e!==void 0&&e.kind==="group"),"councils")}function bs(n,e,r){let t=(o)=>o===void 0?void 0:n.find((f)=>f.key===o||f.name===o);return t(r)??t(e)??n[0]}function vs(n){return[...n].filter((e)=>e.muted!==!0&&!e.helper).sort((e,r)=>Number(r.home)-Number(e.home)).slice(0,en).map((e)=>e.name)}function xs(n,e,r=en){if(n.includes(e))return n.filter((t)=>t!==e);return n.length>=r?[...n]:[...n,e]}function Ss(n,e){return n.filter(e).slice(0,en)}function ws(n,e,r){if(e!==void 0&&n.includes(e))return e;if(r!==void 0&&n.includes(r))return r;return n[0]??""}function ks(n,e){let r=n.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,28)||"council";if(!e.has(r))return r;for(let t=2;;t+=1){let o=`${r}-${t}`;if(!e.has(o))return o}}
2
- export{en,Fn,io,ys,Ti,bs,vs,xs,Ss,ws,ks};