pi-roundtable-web 0.26.3 → 0.27.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 (210) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +18 -11
  3. package/dist/chunk-account-1ecx5kt7.js +1 -0
  4. package/dist/chunk-action-menu-zh2n0ahf.js +1 -0
  5. package/dist/chunk-agent-ynm2tcbx.js +1 -0
  6. package/dist/chunk-arranged-rooms-qnz8vese.js +1 -0
  7. package/dist/chunk-bulk-actions-pr0mw0kc.js +1 -0
  8. package/dist/chunk-character-creator-caj4pmg4.js +1 -0
  9. package/dist/{chunk-codeeditor-6xavqntt.js → chunk-codeeditor-dga6s78p.js} +2 -2
  10. package/dist/chunk-confirm-g76v5d9r.js +1 -0
  11. package/dist/{chunk-connectors-7xqsg5q5.js → chunk-connectors-j79ze3j1.js} +1 -1
  12. package/dist/{chunk-conversations-zc4mv6vj.js → chunk-conversations-2farn72g.js} +1 -1
  13. package/dist/chunk-council-sheet-s9sz1bzf.js +2 -0
  14. package/dist/{chunk-drag-0mefcvp3.js → chunk-drag-45e8222r.js} +1 -1
  15. package/dist/chunk-entry-q11n47h9.js +1 -0
  16. package/dist/chunk-folder-list-4gcj7g7h.js +1 -0
  17. package/dist/chunk-folds-ten4ykfw.js +1 -0
  18. package/dist/{chunk-gate-86r8a2vf.js → chunk-gate-kcv1a4t0.js} +1 -1
  19. package/dist/chunk-group-scene-12ghcvzd.js +1 -0
  20. package/dist/chunk-group-table-t458kdgn.js +1 -0
  21. package/dist/chunk-host-icons-h1k9k96a.js +1 -0
  22. package/dist/chunk-host-picture-3p0wzn3v.js +1 -0
  23. package/dist/chunk-host-state-7mdve7cb.js +1 -0
  24. package/dist/chunk-host-ui-bdgwa6sf.js +1 -0
  25. package/dist/chunk-index-02vfshtt.js +2 -0
  26. package/dist/chunk-index-0a81da8z.js +2 -0
  27. package/dist/{chunk-index-8eexybcx.js → chunk-index-0w4bfjb5.js} +2 -2
  28. package/dist/{chunk-index-1fn8q4ps.js → chunk-index-12zyshae.js} +2 -2
  29. package/dist/chunk-index-18pgxxw3.js +2 -0
  30. package/dist/chunk-index-2e4am3hw.js +50 -0
  31. package/dist/{chunk-index-agrvqf5b.js → chunk-index-2fdns8tn.js} +2 -2
  32. package/dist/{chunk-index-84wbc80d.js → chunk-index-2t30xwed.js} +2 -2
  33. package/dist/{chunk-index-jq970hsd.js → chunk-index-31x6krbs.js} +3 -3
  34. package/dist/{chunk-index-eh0wb6g0.js → chunk-index-3d8s3565.js} +2 -2
  35. package/dist/chunk-index-5bfqek1d.js +2 -0
  36. package/dist/chunk-index-64rsz6pf.js +2 -0
  37. package/dist/chunk-index-6bkcq9ka.js +2 -0
  38. package/dist/{chunk-index-47yspxa4.js → chunk-index-8d81qy69.js} +1 -1
  39. package/dist/chunk-index-92zgb7my.js +2 -0
  40. package/dist/chunk-index-aamhjg5n.js +2 -0
  41. package/dist/{chunk-index-r1v895tj.js → chunk-index-ac1ktr56.js} +2 -2
  42. package/dist/{chunk-index-hc0dgmrs.js → chunk-index-ak6ecqy3.js} +2 -2
  43. package/dist/{chunk-index-eyc2rkq5.js → chunk-index-b73y2gc0.js} +2 -2
  44. package/dist/chunk-index-b8cpvr7y.js +2 -0
  45. package/dist/{chunk-index-3aa5czxj.js → chunk-index-bhqczxr3.js} +2 -2
  46. package/dist/{chunk-index-6c8mewwy.js → chunk-index-cbqhzf40.js} +12 -12
  47. package/dist/chunk-index-cdctnw71.js +2 -0
  48. package/dist/chunk-index-dj0tbfyb.js +2 -0
  49. package/dist/chunk-index-dnjftvtq.js +2 -0
  50. package/dist/chunk-index-e2dqjde8.js +1 -0
  51. package/dist/{chunk-index-tsxsy4qs.js → chunk-index-ebg48jgy.js} +2 -2
  52. package/dist/{chunk-index-rcxz5w5d.js → chunk-index-ek5mek4j.js} +3 -3
  53. package/dist/chunk-index-f6q0stqx.js +2 -0
  54. package/dist/{chunk-index-67p2sc14.js → chunk-index-ffbrm6tr.js} +3 -3
  55. package/dist/chunk-index-g1awv7rk.js +2 -0
  56. package/dist/chunk-index-g3wrs02w.js +1 -0
  57. package/dist/chunk-index-j0m16cj1.js +2 -0
  58. package/dist/chunk-index-j4kk1m59.js +2 -0
  59. package/dist/chunk-index-j4x9dq30.css +1 -0
  60. package/dist/{chunk-index-3wbyq10m.js → chunk-index-jgzygbt8.js} +1 -1
  61. package/dist/chunk-index-kb3w11mn.js +7 -0
  62. package/dist/{chunk-index-raf772z6.js → chunk-index-kbrg32sx.js} +2 -2
  63. package/dist/chunk-index-msqhgvfx.js +2 -0
  64. package/dist/chunk-index-pefwfm26.js +2 -0
  65. package/dist/chunk-index-pyfx77cj.js +2 -0
  66. package/dist/{chunk-index-30vw4nhh.js → chunk-index-q7g62sgm.js} +3 -3
  67. package/dist/chunk-index-qfgkfq86.js +2 -0
  68. package/dist/chunk-index-rh0x9rzf.js +2 -0
  69. package/dist/{chunk-index-m65cv329.js → chunk-index-sm7m4fej.js} +2 -2
  70. package/dist/chunk-index-sv9qpmhv.js +1 -0
  71. package/dist/chunk-index-t998yg8y.js +2 -0
  72. package/dist/{chunk-index-e90kztkx.js → chunk-index-tk4se135.js} +3 -3
  73. package/dist/chunk-index-v6smgw77.js +2 -0
  74. package/dist/chunk-index-w42rcbx1.js +2 -0
  75. package/dist/chunk-index-x4enmj72.js +2 -0
  76. package/dist/chunk-index-xq84gg8w.js +2 -0
  77. package/dist/{chunk-index-gt42wjby.js → chunk-index-xxeb2xp0.js} +3 -3
  78. package/dist/{chunk-index-hczrjnky.js → chunk-index-zyvwhxga.js} +1 -1
  79. package/dist/chunk-lang-css-y9133ktr.js +1 -0
  80. package/dist/{chunk-lang-dockerfile-tjxsnhr9.js → chunk-lang-dockerfile-cnwqn660.js} +1 -1
  81. package/dist/{chunk-lang-go-drmfa25k.js → chunk-lang-go-yy6kgsxv.js} +2 -2
  82. package/dist/chunk-lang-html-r2xmryek.js +1 -0
  83. package/dist/chunk-lang-javascript-1r6wd177.js +1 -0
  84. package/dist/{chunk-lang-json-790swysy.js → chunk-lang-json-dx56g11y.js} +1 -1
  85. package/dist/{chunk-lang-markdown-483dkxcq.js → chunk-lang-markdown-fpcf2dfq.js} +5 -5
  86. package/dist/{chunk-lang-python-v9s40bzj.js → chunk-lang-python-zzp95955.js} +2 -2
  87. package/dist/{chunk-lang-rust-z4gry1bf.js → chunk-lang-rust-214fh7sp.js} +1 -1
  88. package/dist/{chunk-lang-shell-egsvk8rj.js → chunk-lang-shell-1rpgtepw.js} +1 -1
  89. package/dist/{chunk-lang-sql-75j3denr.js → chunk-lang-sql-vvp46hpj.js} +2 -2
  90. package/dist/{chunk-lang-toml-7dbjvhex.js → chunk-lang-toml-tbj7467c.js} +1 -1
  91. package/dist/chunk-lang-typescript-y5w4tt4g.js +1 -0
  92. package/dist/{chunk-lang-xml-jg1sv4h2.js → chunk-lang-xml-tbcqmedk.js} +1 -1
  93. package/dist/{chunk-lang-yaml-g3v7s5je.js → chunk-lang-yaml-jh56t3sv.js} +1 -1
  94. package/dist/chunk-layout-actions-y73mpsh1.js +1 -0
  95. package/dist/chunk-menu-items-bza16bw6.js +2 -0
  96. package/dist/chunk-mode-toggle-nj9gnt3b.js +1 -0
  97. package/dist/chunk-note-row-6fmhrdng.js +75 -0
  98. package/dist/chunk-notes-wv0e6ssf.js +1 -0
  99. package/dist/{chunk-notice-bell-jehad6sx.js → chunk-notice-bell-9z27epd2.js} +1 -1
  100. package/dist/{chunk-notices-prqkv7ge.js → chunk-notices-fte9f196.js} +1 -1
  101. package/dist/chunk-onboarding-c5twahc3.js +1 -0
  102. package/dist/{chunk-overview-2j1s6kqg.js → chunk-overview-k4ccprax.js} +1 -1
  103. package/dist/{chunk-palette-4qt6ewem.js → chunk-palette-9rbp2fab.js} +1 -1
  104. package/dist/chunk-placement-items-me598wsb.js +1 -0
  105. package/dist/{chunk-profile-pfzyc04p.js → chunk-profile-s7r7fcvz.js} +1 -1
  106. package/dist/{chunk-prompt-history-2bcp2rky.js → chunk-prompt-history-hf296e7v.js} +1 -1
  107. package/dist/chunk-prompts-c8s0nhkm.js +1 -0
  108. package/dist/chunk-room-confirms-ga2sbvhc.js +1 -0
  109. package/dist/chunk-room-menu-7h4jym2b.js +1 -0
  110. package/dist/chunk-rooms-menu-button-pgtaanb6.js +1 -0
  111. package/dist/{chunk-schedules-64h9gfwn.js → chunk-schedules-mbnwcxpc.js} +1 -1
  112. package/dist/{chunk-seat-menu-vrpc40v9.js → chunk-seat-menu-r0k0eawr.js} +1 -1
  113. package/dist/chunk-sheet-hosts-a5dfevy9.js +1 -0
  114. package/dist/{chunk-shortcut-help-bdbkdcs9.js → chunk-shortcut-help-a6pkjcq7.js} +2 -2
  115. package/dist/{chunk-shortcuts-qft33r8t.js → chunk-shortcuts-cgt87jkz.js} +1 -1
  116. package/dist/chunk-skill-editor-67yk4x7t.js +8 -0
  117. package/dist/chunk-skills-rewemey4.js +1 -0
  118. package/dist/chunk-switcher-list-t8rbay2y.js +1 -0
  119. package/dist/{chunk-tab-dot-1p68ybf8.js → chunk-tab-dot-vr11dfrw.js} +1 -1
  120. package/dist/chunk-table-52nh0azk.js +1 -0
  121. package/dist/chunk-table2d-g57vr6h0.js +1 -0
  122. package/dist/chunk-table3d-rp0w6kt4.js +4220 -0
  123. package/dist/chunk-thread-actions-qcxt1y1g.js +1 -0
  124. package/dist/{chunk-thread-cards-t794fp4d.js → chunk-thread-cards-c2mrp2gj.js} +1 -1
  125. package/dist/chunk-thread-list-0071q455.js +217 -0
  126. package/dist/{chunk-transcript-km4e3aey.js → chunk-transcript-789yqysq.js} +1 -1
  127. package/dist/chunk-where-hmwmme9e.js +1 -0
  128. package/dist/{chunk-workspace-d064v56e.js → chunk-workspace-wr1qrtsh.js} +2 -2
  129. package/dist/chunk-zh-tw-z5p5mpph.js +1 -0
  130. package/dist/index.html +1 -1
  131. package/dist/sw.js +1 -1
  132. package/package.json +3 -3
  133. package/src/api-types.ts +51 -0
  134. package/src/app/app.ts +38 -4
  135. package/src/app/helper-routes.ts +106 -0
  136. package/src/app/rooms.ts +18 -3
  137. package/src/app/routes.ts +20 -0
  138. package/src/app/surface.ts +18 -1
  139. package/src/app/team-routes.ts +21 -1
  140. package/src/i18n/zh-tw.ts +46 -9
  141. package/dist/chunk-account-hck7mhf8.js +0 -1
  142. package/dist/chunk-action-menu-beqh40k6.js +0 -1
  143. package/dist/chunk-agent-znnth24t.js +0 -1
  144. package/dist/chunk-arranged-rooms-wfakrddk.js +0 -1
  145. package/dist/chunk-bulk-actions-c4tkvk9g.js +0 -1
  146. package/dist/chunk-character-creator-qwcsdytw.js +0 -1
  147. package/dist/chunk-confirm-z95j9jt3.js +0 -1
  148. package/dist/chunk-entry-hrt97qed.js +0 -1
  149. package/dist/chunk-folder-list-nemtqbnc.js +0 -1
  150. package/dist/chunk-folds-xpg9qcwf.js +0 -1
  151. package/dist/chunk-group-scene-g24kxz6f.js +0 -1
  152. package/dist/chunk-group-table-851jm8ef.js +0 -1
  153. package/dist/chunk-host-icons-23zhqpmc.js +0 -1
  154. package/dist/chunk-host-picture-8tnh2wam.js +0 -1
  155. package/dist/chunk-host-state-j41sp065.js +0 -1
  156. package/dist/chunk-host-ui-cje7a9t9.js +0 -1
  157. package/dist/chunk-index-3k7sdph2.js +0 -1
  158. package/dist/chunk-index-4jf4jvfd.css +0 -1
  159. package/dist/chunk-index-4s1qj9we.js +0 -2
  160. package/dist/chunk-index-5fz5e1ct.js +0 -2
  161. package/dist/chunk-index-azrnzgev.js +0 -2
  162. package/dist/chunk-index-c2ag3qw2.js +0 -2
  163. package/dist/chunk-index-efgm0066.js +0 -2
  164. package/dist/chunk-index-fe0m5mht.js +0 -2
  165. package/dist/chunk-index-hrtyanda.js +0 -2
  166. package/dist/chunk-index-j479p62w.js +0 -2
  167. package/dist/chunk-index-kankja6p.js +0 -50
  168. package/dist/chunk-index-kb44jp50.js +0 -2
  169. package/dist/chunk-index-mnwdbb4n.js +0 -2
  170. package/dist/chunk-index-n6zrc05a.js +0 -2
  171. package/dist/chunk-index-n93wqszt.js +0 -8
  172. package/dist/chunk-index-nrexxkar.js +0 -2
  173. package/dist/chunk-index-pgxgq61x.js +0 -2
  174. package/dist/chunk-index-q5vzjqr5.js +0 -2
  175. package/dist/chunk-index-qqwk157d.js +0 -2
  176. package/dist/chunk-index-r3tqhew8.js +0 -2
  177. package/dist/chunk-index-rg9vh59e.js +0 -1
  178. package/dist/chunk-index-sxq69p1f.js +0 -2
  179. package/dist/chunk-index-taq2psr6.js +0 -2
  180. package/dist/chunk-index-wc83vb0a.js +0 -2
  181. package/dist/chunk-index-xbs8q4kn.js +0 -2
  182. package/dist/chunk-index-xxbveevc.js +0 -2
  183. package/dist/chunk-index-zx6yka30.js +0 -2
  184. package/dist/chunk-lang-css-yrc25qk9.js +0 -1
  185. package/dist/chunk-lang-html-rwe8epvf.js +0 -1
  186. package/dist/chunk-lang-javascript-d6x6qjtg.js +0 -1
  187. package/dist/chunk-lang-typescript-1rv9vdcz.js +0 -1
  188. package/dist/chunk-layout-actions-6assr93n.js +0 -1
  189. package/dist/chunk-menu-items-9szcza00.js +0 -2
  190. package/dist/chunk-mode-toggle-bzbq7ycq.js +0 -1
  191. package/dist/chunk-note-row-g20h11pm.js +0 -75
  192. package/dist/chunk-notes-gc8bssxn.js +0 -1
  193. package/dist/chunk-onboarding-mdtc2mb7.js +0 -1
  194. package/dist/chunk-placement-items-2eect825.js +0 -1
  195. package/dist/chunk-prompts-052mb05y.js +0 -1
  196. package/dist/chunk-room-confirms-asvmjh5s.js +0 -1
  197. package/dist/chunk-room-menu-dm2r2hk6.js +0 -1
  198. package/dist/chunk-rooms-menu-button-32hemzxc.js +0 -1
  199. package/dist/chunk-sheet-hosts-jaqx490x.js +0 -1
  200. package/dist/chunk-skill-editor-z3dx2895.js +0 -8
  201. package/dist/chunk-skills-4jfcv39n.js +0 -1
  202. package/dist/chunk-switcher-list-xysjd4tw.js +0 -1
  203. package/dist/chunk-table-0v4q4dx3.js +0 -1
  204. package/dist/chunk-table-seats-f683hx8k.js +0 -1
  205. package/dist/chunk-table2d-1a6atjhj.js +0 -1
  206. package/dist/chunk-table3d-k0ffeffj.js +0 -4220
  207. package/dist/chunk-thread-actions-4rpfg4fq.js +0 -1
  208. package/dist/chunk-thread-list-1pcygpka.js +0 -1
  209. package/dist/chunk-where-phjwqggy.js +0 -1
  210. package/dist/chunk-zh-tw-qf18hcxc.js +0 -1
package/dist/index.html CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
 
11
11
 
12
- <link rel="stylesheet" crossorigin href="./chunk-index-4jf4jvfd.css"><script type="module" crossorigin src="./chunk-index-kankja6p.js"></script></head>
12
+ <link rel="stylesheet" crossorigin href="./chunk-index-j4x9dq30.css"><script type="module" crossorigin src="./chunk-index-2e4am3hw.js"></script></head>
13
13
  <body>
14
14
  <div id="root"></div>
15
15
 
package/dist/sw.js CHANGED
@@ -1 +1 @@
1
- var o=self,i=new URL(o.registration.scope);var d=new URL("index.html",i).href,h=new Set(["index.html","account-zk4efb87.css","chunk-account-hck7mhf8.js","chunk-action-menu-beqh40k6.js","chunk-agent-znnth24t.js","chunk-arranged-rooms-wfakrddk.js","chunk-bulk-actions-c4tkvk9g.js","chunk-character-creator-qwcsdytw.js","chunk-confirm-z95j9jt3.js","chunk-connectors-7xqsg5q5.js","chunk-conversations-zc4mv6vj.js","chunk-drag-0mefcvp3.js","chunk-entry-hrt97qed.js","chunk-folder-list-nemtqbnc.js","chunk-folds-xpg9qcwf.js","chunk-gate-86r8a2vf.js","chunk-group-scene-g24kxz6f.js","chunk-group-table-851jm8ef.js","chunk-host-icons-23zhqpmc.js","chunk-host-picture-8tnh2wam.js","chunk-host-state-j41sp065.js","chunk-host-ui-cje7a9t9.js","chunk-index-0q15ysrb.js","chunk-index-1fn8q4ps.js","chunk-index-30vw4nhh.js","chunk-index-3aa5czxj.js","chunk-index-3k7sdph2.js","chunk-index-3wbyq10m.js","chunk-index-47yspxa4.js","chunk-index-4jf4jvfd.css","chunk-index-4s1qj9we.js","chunk-index-5fz5e1ct.js","chunk-index-67p2sc14.js","chunk-index-84wbc80d.js","chunk-index-8eexybcx.js","chunk-index-agrvqf5b.js","chunk-index-azrnzgev.js","chunk-index-c2ag3qw2.js","chunk-index-e90kztkx.js","chunk-index-efgm0066.js","chunk-index-eh0wb6g0.js","chunk-index-eyc2rkq5.js","chunk-index-fe0m5mht.js","chunk-index-gt42wjby.js","chunk-index-hc0dgmrs.js","chunk-index-hczrjnky.js","chunk-index-hrtyanda.js","chunk-index-j479p62w.js","chunk-index-jq970hsd.js","chunk-index-kankja6p.js","chunk-index-kb44jp50.js","chunk-index-m65cv329.js","chunk-index-mnwdbb4n.js","chunk-index-n6zrc05a.js","chunk-index-n93wqszt.js","chunk-index-nrexxkar.js","chunk-index-pgxgq61x.js","chunk-index-q5vzjqr5.js","chunk-index-qqwk157d.js","chunk-index-r1v895tj.js","chunk-index-r3tqhew8.js","chunk-index-raf772z6.js","chunk-index-rcxz5w5d.js","chunk-index-rg9vh59e.js","chunk-index-sxq69p1f.js","chunk-index-taq2psr6.js","chunk-index-tsxsy4qs.js","chunk-index-wc83vb0a.js","chunk-index-xbs8q4kn.js","chunk-index-xxbveevc.js","chunk-index-zx6yka30.js","chunk-layout-actions-6assr93n.js","chunk-menu-items-9szcza00.js","chunk-mode-toggle-bzbq7ycq.js","chunk-note-row-g20h11pm.js","chunk-notes-gc8bssxn.js","chunk-notice-bell-jehad6sx.js","chunk-notices-prqkv7ge.js","chunk-onboarding-mdtc2mb7.js","chunk-overview-2j1s6kqg.js","chunk-palette-4qt6ewem.js","chunk-placement-items-2eect825.js","chunk-profile-pfzyc04p.js","chunk-prompt-history-2bcp2rky.js","chunk-prompts-052mb05y.js","chunk-room-confirms-asvmjh5s.js","chunk-room-menu-dm2r2hk6.js","chunk-rooms-menu-button-32hemzxc.js","chunk-schedules-64h9gfwn.js","chunk-seat-menu-vrpc40v9.js","chunk-sheet-hosts-jaqx490x.js","chunk-shortcut-help-bdbkdcs9.js","chunk-shortcuts-qft33r8t.js","chunk-skill-editor-z3dx2895.js","chunk-skills-4jfcv39n.js","chunk-switcher-list-xysjd4tw.js","chunk-tab-dot-1p68ybf8.js","chunk-table-0v4q4dx3.js","chunk-table-seats-f683hx8k.js","chunk-table2d-1a6atjhj.js","chunk-thread-actions-4rpfg4fq.js","chunk-thread-cards-t794fp4d.js","chunk-thread-list-1pcygpka.js","chunk-transcript-km4e3aey.js","chunk-where-phjwqggy.js","chunk-workspace-d064v56e.js","chunk-zh-tw-qf18hcxc.js"].map((e)=>new URL(e,i).href)),w=/\/chunk-table3d-[a-z0-9]+\.js$/,g=/\/chunk-(?:codeeditor|lang-[a-z]+)-[a-z0-9]+\.js$/,k=["api/","auth/","login","files/","avatars/"],m=(e)=>{let n=e.pathname.slice(i.pathname.length);return k.some((t)=>n.startsWith(t))};async function v(e){let n=await caches.open("roundtable-web-fc485222cec1");try{let t=await fetch(e);if(t.ok&&new URL(e.url).href===i.href)await n.put(d,t.clone());return t}catch{let t=await n.match(d);if(t)return t;throw Error("offline and the shell is not cached")}}async function W(e){let n=await caches.open("roundtable-web-fc485222cec1"),t=await n.match(e);if(t)return t;let a=await fetch(e);if(a.ok)await n.put(e,a.clone());return a}var r=(e,n)=>typeof e==="string"&&e?e.slice(0,n):void 0;function l(e){if(typeof e!=="string")return i.href;let n=URL.parse(e,i);return n&&n.origin===i.origin&&n.pathname.startsWith(i.pathname)?n.href:i.href}var E=new Set(["notice","test"]);async function u(e,n){if(!e?.tag||!e.kind||!E.has(e.kind))return;try{await fetch(new URL("api/push/receipts",i),{method:"POST",credentials:"include",headers:{"content-type":"application/json"},body:JSON.stringify({tag:e.tag,event:n})})}catch{}}async function y(e){if(typeof e!=="number"||!Number.isSafeInteger(e)||e<0)return;try{if(e>0)await o.navigator.setAppBadge?.(e);else await o.navigator.clearAppBadge?.()}catch{}}async function P(e){let n={};try{let s=e.data?.json();if(typeof s==="object"&&s!==null)n=s}catch{}let t=r(n.tag,200),a=r(n.kind,16);if(await y(n.badge),a==="clear"){if(t)for(let s of await o.registration.getNotifications({tag:t}))s.close();return}await o.registration.showNotification(r(n.title,120)??"Roundtable",{body:r(n.body,240),icon:new URL("icons/192.png",i).href,badge:new URL("icons/badge-96.png",i).href,...t?{tag:t,renotify:!0}:{},...n.urgency==="high"?{requireInteraction:!0}:{},data:{url:l(n.url),...t?{tag:t}:{},...a?{kind:a}:{}}}),await u({...t?{tag:t}:{},...a?{kind:a}:{}},"received")}async function b(e){e.notification.close();let n=e.notification.data,t=l(n?.url),a=r(n?.tag,200),s=r(n?.kind,16),f=u({...a?{tag:a}:{},...s?{kind:s}:{}},"opened"),c=(await o.clients.matchAll({type:"window",includeUncontrolled:!0})).find((p)=>p.url.startsWith(i.href));if(c)await c.focus(),c.postMessage({type:"roundtable:open",url:t});else await o.clients.openWindow(t);await f}o.addEventListener("install",(e)=>{e.waitUntil(caches.open("roundtable-web-fc485222cec1").then((n)=>n.addAll([...h])).then(()=>o.skipWaiting()))});o.addEventListener("activate",(e)=>{e.waitUntil(caches.keys().then((n)=>Promise.all(n.filter((t)=>t.startsWith("roundtable-web-")&&t!=="roundtable-web-fc485222cec1").map((t)=>caches.delete(t)))).then(()=>o.clients.claim()))});o.addEventListener("fetch",(e)=>{let{request:n}=e;if(n.method!=="GET")return;let t=new URL(n.url);if(t.origin!==i.origin||!t.pathname.startsWith(i.pathname))return;if(m(t))return;if(n.mode==="navigate")e.respondWith(v(n));else if(h.has(t.origin+t.pathname)||w.test(t.pathname)||g.test(t.pathname))e.respondWith(W(n))});o.addEventListener("push",(e)=>{e.waitUntil(P(e))});o.addEventListener("notificationclick",(e)=>{e.waitUntil(b(e))});
1
+ var o=self,i=new URL(o.registration.scope);var d=new URL("index.html",i).href,h=new Set(["index.html","account-zk4efb87.css","chunk-account-1ecx5kt7.js","chunk-action-menu-zh2n0ahf.js","chunk-agent-ynm2tcbx.js","chunk-arranged-rooms-qnz8vese.js","chunk-bulk-actions-pr0mw0kc.js","chunk-character-creator-caj4pmg4.js","chunk-confirm-g76v5d9r.js","chunk-connectors-j79ze3j1.js","chunk-conversations-2farn72g.js","chunk-council-sheet-s9sz1bzf.js","chunk-drag-45e8222r.js","chunk-entry-q11n47h9.js","chunk-folder-list-4gcj7g7h.js","chunk-folds-ten4ykfw.js","chunk-gate-kcv1a4t0.js","chunk-group-scene-12ghcvzd.js","chunk-group-table-t458kdgn.js","chunk-host-icons-h1k9k96a.js","chunk-host-picture-3p0wzn3v.js","chunk-host-state-7mdve7cb.js","chunk-host-ui-bdgwa6sf.js","chunk-index-02vfshtt.js","chunk-index-0a81da8z.js","chunk-index-0q15ysrb.js","chunk-index-0w4bfjb5.js","chunk-index-12zyshae.js","chunk-index-18pgxxw3.js","chunk-index-2e4am3hw.js","chunk-index-2fdns8tn.js","chunk-index-2t30xwed.js","chunk-index-31x6krbs.js","chunk-index-3d8s3565.js","chunk-index-5bfqek1d.js","chunk-index-64rsz6pf.js","chunk-index-6bkcq9ka.js","chunk-index-8d81qy69.js","chunk-index-92zgb7my.js","chunk-index-aamhjg5n.js","chunk-index-ac1ktr56.js","chunk-index-ak6ecqy3.js","chunk-index-b73y2gc0.js","chunk-index-b8cpvr7y.js","chunk-index-bhqczxr3.js","chunk-index-cdctnw71.js","chunk-index-dj0tbfyb.js","chunk-index-dnjftvtq.js","chunk-index-e2dqjde8.js","chunk-index-ebg48jgy.js","chunk-index-ek5mek4j.js","chunk-index-f6q0stqx.js","chunk-index-ffbrm6tr.js","chunk-index-g1awv7rk.js","chunk-index-g3wrs02w.js","chunk-index-j0m16cj1.js","chunk-index-j4kk1m59.js","chunk-index-j4x9dq30.css","chunk-index-jgzygbt8.js","chunk-index-kb3w11mn.js","chunk-index-kbrg32sx.js","chunk-index-msqhgvfx.js","chunk-index-pefwfm26.js","chunk-index-pyfx77cj.js","chunk-index-q7g62sgm.js","chunk-index-qfgkfq86.js","chunk-index-rh0x9rzf.js","chunk-index-sm7m4fej.js","chunk-index-sv9qpmhv.js","chunk-index-t998yg8y.js","chunk-index-tk4se135.js","chunk-index-v6smgw77.js","chunk-index-w42rcbx1.js","chunk-index-x4enmj72.js","chunk-index-xq84gg8w.js","chunk-index-xxeb2xp0.js","chunk-index-zyvwhxga.js","chunk-layout-actions-y73mpsh1.js","chunk-menu-items-bza16bw6.js","chunk-mode-toggle-nj9gnt3b.js","chunk-note-row-6fmhrdng.js","chunk-notes-wv0e6ssf.js","chunk-notice-bell-9z27epd2.js","chunk-notices-fte9f196.js","chunk-onboarding-c5twahc3.js","chunk-overview-k4ccprax.js","chunk-palette-9rbp2fab.js","chunk-placement-items-me598wsb.js","chunk-profile-s7r7fcvz.js","chunk-prompt-history-hf296e7v.js","chunk-prompts-c8s0nhkm.js","chunk-room-confirms-ga2sbvhc.js","chunk-room-menu-7h4jym2b.js","chunk-rooms-menu-button-pgtaanb6.js","chunk-schedules-mbnwcxpc.js","chunk-seat-menu-r0k0eawr.js","chunk-sheet-hosts-a5dfevy9.js","chunk-shortcut-help-a6pkjcq7.js","chunk-shortcuts-cgt87jkz.js","chunk-skill-editor-67yk4x7t.js","chunk-skills-rewemey4.js","chunk-switcher-list-t8rbay2y.js","chunk-tab-dot-vr11dfrw.js","chunk-table-52nh0azk.js","chunk-table2d-g57vr6h0.js","chunk-thread-actions-qcxt1y1g.js","chunk-thread-cards-c2mrp2gj.js","chunk-thread-list-0071q455.js","chunk-transcript-789yqysq.js","chunk-where-hmwmme9e.js","chunk-workspace-wr1qrtsh.js","chunk-zh-tw-z5p5mpph.js"].map((e)=>new URL(e,i).href)),w=/\/chunk-table3d-[a-z0-9]+\.js$/,g=/\/chunk-(?:codeeditor|lang-[a-z]+)-[a-z0-9]+\.js$/,k=["api/","auth/","login","files/","avatars/"],m=(e)=>{let n=e.pathname.slice(i.pathname.length);return k.some((t)=>n.startsWith(t))};async function v(e){let n=await caches.open("roundtable-web-7a28cdb79a14");try{let t=await fetch(e);if(t.ok&&new URL(e.url).href===i.href)await n.put(d,t.clone());return t}catch{let t=await n.match(d);if(t)return t;throw Error("offline and the shell is not cached")}}async function W(e){let n=await caches.open("roundtable-web-7a28cdb79a14"),t=await n.match(e);if(t)return t;let a=await fetch(e);if(a.ok)await n.put(e,a.clone());return a}var r=(e,n)=>typeof e==="string"&&e?e.slice(0,n):void 0;function l(e){if(typeof e!=="string")return i.href;let n=URL.parse(e,i);return n&&n.origin===i.origin&&n.pathname.startsWith(i.pathname)?n.href:i.href}var E=new Set(["notice","test"]);async function u(e,n){if(!e?.tag||!e.kind||!E.has(e.kind))return;try{await fetch(new URL("api/push/receipts",i),{method:"POST",credentials:"include",headers:{"content-type":"application/json"},body:JSON.stringify({tag:e.tag,event:n})})}catch{}}async function y(e){if(typeof e!=="number"||!Number.isSafeInteger(e)||e<0)return;try{if(e>0)await o.navigator.setAppBadge?.(e);else await o.navigator.clearAppBadge?.()}catch{}}async function P(e){let n={};try{let s=e.data?.json();if(typeof s==="object"&&s!==null)n=s}catch{}let t=r(n.tag,200),a=r(n.kind,16);if(await y(n.badge),a==="clear"){if(t)for(let s of await o.registration.getNotifications({tag:t}))s.close();return}await o.registration.showNotification(r(n.title,120)??"Roundtable",{body:r(n.body,240),icon:new URL("icons/192.png",i).href,badge:new URL("icons/badge-96.png",i).href,...t?{tag:t,renotify:!0}:{},...n.urgency==="high"?{requireInteraction:!0}:{},data:{url:l(n.url),...t?{tag:t}:{},...a?{kind:a}:{}}}),await u({...t?{tag:t}:{},...a?{kind:a}:{}},"received")}async function b(e){e.notification.close();let n=e.notification.data,t=l(n?.url),a=r(n?.tag,200),s=r(n?.kind,16),f=u({...a?{tag:a}:{},...s?{kind:s}:{}},"opened"),c=(await o.clients.matchAll({type:"window",includeUncontrolled:!0})).find((p)=>p.url.startsWith(i.href));if(c)await c.focus(),c.postMessage({type:"roundtable:open",url:t});else await o.clients.openWindow(t);await f}o.addEventListener("install",(e)=>{e.waitUntil(caches.open("roundtable-web-7a28cdb79a14").then((n)=>n.addAll([...h])).then(()=>o.skipWaiting()))});o.addEventListener("activate",(e)=>{e.waitUntil(caches.keys().then((n)=>Promise.all(n.filter((t)=>t.startsWith("roundtable-web-")&&t!=="roundtable-web-7a28cdb79a14").map((t)=>caches.delete(t)))).then(()=>o.clients.claim()))});o.addEventListener("fetch",(e)=>{let{request:n}=e;if(n.method!=="GET")return;let t=new URL(n.url);if(t.origin!==i.origin||!t.pathname.startsWith(i.pathname))return;if(m(t))return;if(n.mode==="navigate")e.respondWith(v(n));else if(h.has(t.origin+t.pathname)||w.test(t.pathname)||g.test(t.pathname))e.respondWith(W(n))});o.addEventListener("push",(e)=>{e.waitUntil(P(e))});o.addEventListener("notificationclick",(e)=>{e.waitUntil(b(e))});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-roundtable-web",
3
- "version": "0.26.3",
3
+ "version": "0.27.0",
4
4
  "description": "A web console for the owners of a pi-roundtable host: conversations, transcripts, and memory notes, with live updates",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "jose": "6.2.12"
44
44
  },
45
45
  "peerDependencies": {
46
- "pi-roundtable": ">=0.26.0 <0.27.0"
46
+ "pi-roundtable": ">=0.27.0 <0.28.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@biomejs/biome": "2.5.15",
@@ -70,7 +70,7 @@
70
70
  "@types/react": "19.3.0",
71
71
  "@types/react-dom": "19.3.0",
72
72
  "@types/three": "0.186.0",
73
- "pi-roundtable": "0.26.3",
73
+ "pi-roundtable": "0.27.0",
74
74
  "react": "19.3.0",
75
75
  "react-dom": "19.3.0",
76
76
  "react-markdown": "10.1.0",
package/src/api-types.ts CHANGED
@@ -507,6 +507,11 @@ export interface RoomView {
507
507
  members?: string[];
508
508
  /** A thread's parent room. */
509
509
  parent?: string;
510
+ /**
511
+ * A temp knight's room: `parent` is the room of the knight that made it, and `finished` is set
512
+ * when it has nothing running, queued or waiting. Absent on every permanent knight.
513
+ */
514
+ helper?: { parent: string; parentName: string; finished?: true };
510
515
  /** A thread's room takes no messages from the page. */
511
516
  readOnly: boolean;
512
517
  /**
@@ -528,6 +533,8 @@ export interface RoomView {
528
533
  agent?: string;
529
534
  /** A turn runs that Stop would end. */
530
535
  stoppable: boolean;
536
+ /** A group room: the round running now, who has spoken and who is next; absent when none runs. */
537
+ round?: RoundView;
531
538
  /** The newest stored message, if any. */
532
539
  last?: RoomMessage;
533
540
  /** Messages past this person's read cursor; absent when none. */
@@ -578,6 +585,22 @@ export interface ThreadsDismissedAll extends ThreadsDismissed {
578
585
  threads: Record<string, string[]>;
579
586
  }
580
587
 
588
+ /**
589
+ * What a system line of a room starts with when its knight cleared its temp knights: the rest is a
590
+ * JSON list of `{ name, displayName }`, which the page words and offers the undo of.
591
+ */
592
+ export const HELPERS_ENDED = "Temp knights archived: ";
593
+
594
+ /** `POST api/rooms/archive-finished-helpers`: the temp knights archived, by the room of the knight that made them. */
595
+ export interface HelpersArchived {
596
+ archived: { room: string; names: string[] }[];
597
+ }
598
+
599
+ /** `POST api/rooms/restore-helpers`: the temp knights that came back; a name no longer archived is passed over. */
600
+ export interface HelpersRestored {
601
+ restored: string[];
602
+ }
603
+
581
604
  /** A cursor that marking rooms read moved: where it was (0 for a room never read) and where it is. */
582
605
  export interface ReadMove {
583
606
  room: string;
@@ -783,6 +806,8 @@ export interface AgentProfileView {
783
806
  avatar: string;
784
807
  character: CharacterSpec;
785
808
  room: string;
809
+ /** A temp knight's profile: the room of the knight that made it, and its name. */
810
+ helper?: { parent: string; parentName: string };
786
811
  }
787
812
 
788
813
  export interface ModelsView {
@@ -977,6 +1002,8 @@ export interface ArchivedEntry {
977
1002
  character?: CharacterSpec;
978
1003
  /** A group's members that are still on the team. */
979
1004
  members?: string[];
1005
+ /** A temp knight's parent, by name; the archive page groups it under that knight. */
1006
+ parent?: string;
980
1007
  }
981
1008
 
982
1009
  /** `GET api/archive`. */
@@ -1181,6 +1208,28 @@ export type ClientFrame =
1181
1208
  | { type: "cancel"; prompt: string };
1182
1209
 
1183
1210
  /** What the server sends: one JSON object per WebSocket text message. */
1211
+ /**
1212
+ * Where a group's round stands: the members' answers to one owner message, one after another.
1213
+ * `id` is the group message that opened the round, so a new round is told from the last one.
1214
+ */
1215
+ export interface RoundView {
1216
+ id: number;
1217
+ state: "running" | "ended";
1218
+ /** The member speaking now, by name. */
1219
+ current?: string;
1220
+ /** The members still to speak, in order, a handoff appended. */
1221
+ next: string[];
1222
+ /** The members who have replied so far, in order. */
1223
+ spoken: string[];
1224
+ /** The replies made so far, and the most a round holds. */
1225
+ replies: number;
1226
+ limit: number;
1227
+ /** Why the round ended: every member spoke, it reached its limit, or the owner stopped it. */
1228
+ ended?: "done" | "limit" | "stopped";
1229
+ /** The owner's messages waiting in the room for the next round. */
1230
+ queued?: number;
1231
+ }
1232
+
1184
1233
  export type ServerFrame =
1185
1234
  /** The socket is authenticated; sent first, and followed by every open card. */
1186
1235
  | {
@@ -1215,6 +1264,8 @@ export type ServerFrame =
1215
1264
  /** Only on the frame where `typing` turns false. */
1216
1265
  outcome?: "done" | "failed";
1217
1266
  }
1267
+ /** Where a group room's round stands; sent at each step and when it ends. */
1268
+ | { type: "round"; room: string; round: RoundView }
1218
1269
  /** Which agents work now, anywhere (Discord included); sent after `ready` and when it changes. */
1219
1270
  | { type: "presence"; agents: Record<string, { busy: boolean }> }
1220
1271
  | { type: "prompt"; room: string; prompt: PromptFrame }
package/src/app/app.ts CHANGED
@@ -13,8 +13,8 @@ import type {
13
13
  ScheduleStore,
14
14
  WebSocketRoute,
15
15
  } from "pi-roundtable";
16
- import { channelKey } from "pi-roundtable";
17
- import { APP_CLOSE_EXPIRED } from "../api-types.ts";
16
+ import { agentRoomKey, channelKey, isHelper, roomOf } from "pi-roundtable";
17
+ import { APP_CLOSE_EXPIRED, HELPERS_ENDED } from "../api-types.ts";
18
18
  import type { Authenticated, Authenticator } from "../auth/authenticator.ts";
19
19
  import type { ConsolePresentation } from "../features.ts";
20
20
  import type { Gate } from "../gate.ts";
@@ -209,6 +209,11 @@ export class OwnerApp {
209
209
  const quiet = quietClock
210
210
  ? (at: number) => quietClock.quietAt(at)
211
211
  : undefined;
212
+ const held = roomMuted(
213
+ mutes,
214
+ threads,
215
+ channelKey(options.surface, "thread."),
216
+ );
212
217
  const mounted = mountNotices({
213
218
  database: options.database,
214
219
  logger,
@@ -246,7 +251,16 @@ export class OwnerApp {
246
251
  visible: (principalId) => sockets.anyVisible(principalId),
247
252
  preview: options.push.preview,
248
253
  ...(quiet ? { quiet } : {}),
249
- muted: roomMuted(mutes, threads, channelKey(options.surface, "thread.")),
254
+ muted: async (room) => {
255
+ const named = roomOf(room as ChannelKey, [options.surface]);
256
+ // A temp knight's answers reach its knight's room, which pushes; its own room stays quiet.
257
+ if (
258
+ named?.kind === "agent" &&
259
+ isHelper(agents.directory.agent(named.name))
260
+ )
261
+ return true;
262
+ return held(room);
263
+ },
250
264
  urlOf: options.push.urlOf,
251
265
  logger,
252
266
  });
@@ -334,7 +348,10 @@ export class OwnerApp {
334
348
  log,
335
349
  modes,
336
350
  mutes,
337
- activity: (room) => surface.activityOf(room),
351
+ activity: (room) => {
352
+ const round = surface.roundOf(room);
353
+ return { ...surface.activityOf(room), ...(round ? { round } : {}) };
354
+ },
338
355
  // The agent rooms are the owners'; where there are several, they can leave each other notes.
339
356
  readers: async () => (await identity.owners()).length > 1,
340
357
  });
@@ -398,6 +415,23 @@ export class OwnerApp {
398
415
  },
399
416
  push,
400
417
  });
418
+ // A knight clearing its own temp knights leaves a line in its room, with the undo.
419
+ agents.team.onHelpersEnded((event) => {
420
+ const named = event.names.map((name) => ({
421
+ name,
422
+ displayName: agents.directory.agent(name)?.displayName ?? name,
423
+ }));
424
+ void surface
425
+ .publish({
426
+ room: agentRoomKey(options.surface, event.parent),
427
+ kind: "system",
428
+ author: "Notice",
429
+ text: `${HELPERS_ENDED}${JSON.stringify(named)}`,
430
+ })
431
+ .catch((error) =>
432
+ logger.warn({ err: error }, "the temp knights' line was not posted"),
433
+ );
434
+ });
401
435
  this.#api = new AppApi({
402
436
  ...(options.presentation ? { presentation: options.presentation } : {}),
403
437
  surface,
@@ -0,0 +1,106 @@
1
+ import type {
2
+ AgentDirectory,
3
+ AgentTeam,
4
+ ChannelKey,
5
+ Speaker,
6
+ } from "pi-roundtable";
7
+ import { agentRoomKey, isHelper, roomOf } from "pi-roundtable";
8
+ import type { HelpersArchived, HelpersRestored } from "../api-types.ts";
9
+ import { json } from "../http.ts";
10
+ import { bad, bodyOf } from "./api-http.ts";
11
+
12
+ /** The most rooms or names one request of these routes names. */
13
+ const MAX_NAMED = 500;
14
+
15
+ export interface HelperRoutesOptions {
16
+ surface: string;
17
+ directory: Pick<AgentDirectory, "agent" | "agents">;
18
+ team: Pick<
19
+ AgentTeam,
20
+ "finishedHelpers" | "archiveHelpers" | "restoreHelpers"
21
+ >;
22
+ changed(): void;
23
+ }
24
+
25
+ /** A list of at most 500 strings, or undefined when the body leaves it out. */
26
+ function listOf(value: unknown, field: string): string[] | undefined {
27
+ if (value === undefined) return undefined;
28
+ if (
29
+ !Array.isArray(value) ||
30
+ value.length > MAX_NAMED ||
31
+ !value.every((one) => typeof one === "string" && one.length <= 300)
32
+ )
33
+ throw bad(`${field} must be a list of at most ${MAX_NAMED} strings.`);
34
+ return value;
35
+ }
36
+
37
+ /**
38
+ * The two bulk calls on temp knights, for owners: archiving the finished ones of some knights (or
39
+ * of every knight) and putting back the ones an archive took. They reach the same team operations
40
+ * as the knight's own `helper_end`.
41
+ */
42
+ export class HelperRoutes {
43
+ readonly #options: HelperRoutesOptions;
44
+
45
+ constructor(options: HelperRoutesOptions) {
46
+ this.#options = options;
47
+ }
48
+
49
+ /**
50
+ * `POST api/rooms/archive-finished-helpers`: archives the finished temp knights of the named
51
+ * knights' rooms, or of every knight when none is named. Answers what it archived by room.
52
+ */
53
+ async archiveFinished(request: Request, owner: Speaker): Promise<Response> {
54
+ const { surface, directory, team } = this.#options;
55
+ const keys = listOf((await bodyOf(request)).rooms, "rooms");
56
+ let parents: string[] | undefined;
57
+ if (keys) {
58
+ parents = [];
59
+ for (const key of new Set(keys)) {
60
+ const named = roomOf(key as ChannelKey, [surface]);
61
+ const agent =
62
+ named?.kind === "agent" ? directory.agent(named.name) : undefined;
63
+ if (!agent || isHelper(agent))
64
+ throw bad(`"${key}" is not the room of a knight.`);
65
+ parents.push(agent.name);
66
+ }
67
+ }
68
+ const finished = team.finishedHelpers(parents);
69
+ const byParent = new Map<string, string[]>();
70
+ for (const helper of finished)
71
+ byParent.set(helper.parent as string, [
72
+ ...(byParent.get(helper.parent as string) ?? []),
73
+ helper.name,
74
+ ]);
75
+ const done = new Set(
76
+ await team.archiveHelpers(
77
+ finished.map((helper) => helper.name),
78
+ owner.principalId ?? owner.name,
79
+ ),
80
+ );
81
+ const archived = [...byParent]
82
+ .map(([parent, names]) => ({
83
+ room: agentRoomKey(surface, parent),
84
+ names: names.filter((name) => done.has(name)),
85
+ }))
86
+ .filter((entry) => entry.names.length > 0);
87
+ if (archived.length > 0) this.#options.changed();
88
+ return json({ archived } satisfies HelpersArchived);
89
+ }
90
+
91
+ /**
92
+ * `POST api/rooms/restore-helpers`: puts back archived temp knights, the undo of an archive. A
93
+ * name that is not a temp knight is refused by name; one no longer archived is passed over.
94
+ */
95
+ async restore(request: Request): Promise<Response> {
96
+ const { directory, team } = this.#options;
97
+ const names = listOf((await bodyOf(request)).names, "names");
98
+ if (!names) throw bad("Give names, the temp knights to restore.");
99
+ for (const name of names)
100
+ if (!isHelper(directory.agent(name)))
101
+ throw bad(`"${name}" is not a temp knight.`);
102
+ const restored = await team.restoreHelpers([...new Set(names)]);
103
+ if (restored.length > 0) this.#options.changed();
104
+ return json({ restored } satisfies HelpersRestored);
105
+ }
106
+ }
package/src/app/rooms.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  channelKey,
13
13
  characterOf,
14
14
  groupRoomKey,
15
+ isHelper,
15
16
  roomOf,
16
17
  } from "pi-roundtable";
17
18
  import type { RoomKind, RoomView } from "../api-types.ts";
@@ -44,7 +45,7 @@ export interface RoomsOptions {
44
45
  mutes: Pick<PushMutes, "all">;
45
46
  activity(
46
47
  room: ChannelKey,
47
- ): Pick<RoomView, "typing" | "stoppable" | "phase" | "agent">;
48
+ ): Pick<RoomView, "typing" | "stoppable" | "phase" | "agent" | "round">;
48
49
  /**
49
50
  * Whether more than one person reads the agent and group rooms, so a message may be an
50
51
  * internal note. Absent: one person does, and a room takes only turns.
@@ -127,7 +128,9 @@ export class Rooms {
127
128
  const coordinator = team.coordinator();
128
129
  const home =
129
130
  (coordinator ? directory.agent(coordinator) : undefined) ??
130
- directory.agents().find((agent) => agent.status === "active");
131
+ directory
132
+ .agents()
133
+ .find((agent) => agent.status === "active" && !isHelper(agent));
131
134
  return home?.status === "active"
132
135
  ? agentRoomKey(surface, home.name)
133
136
  : undefined;
@@ -144,7 +147,7 @@ export class Rooms {
144
147
  [
145
148
  ...directory
146
149
  .agents()
147
- .filter((agent) => agent.status === "active")
150
+ .filter((agent) => agent.status === "active" && !isHelper(agent))
148
151
  .map((agent) => agentRoomKey(surface, agent.name)),
149
152
  ...directory
150
153
  .groups()
@@ -225,6 +228,18 @@ export class Rooms {
225
228
  avatar: this.avatarOf(agent),
226
229
  character: characterOf(agent),
227
230
  ...(agent.name === coordinator ? { coordinator: true as const } : {}),
231
+ ...(agent.parent
232
+ ? {
233
+ helper: {
234
+ parent: agentRoomKey(surface, agent.parent),
235
+ parentName:
236
+ directory.agent(agent.parent)?.displayName ?? agent.parent,
237
+ ...(agent.finishedAt ? { finished: true as const } : {}),
238
+ },
239
+ // A temp knight's answers reach its knight's room, which pushes; its cards still do.
240
+ muted: true as const,
241
+ }
242
+ : {}),
228
243
  }),
229
244
  ),
230
245
  ...groups.map((group: AgentGroup) =>
package/src/app/routes.ts CHANGED
@@ -51,6 +51,7 @@ import { viewerOf, type WebAppRegistry } from "../web-app.ts";
51
51
  import { bad, bodyOf, HttpError, notFound } from "./api-http.ts";
52
52
  import type { CardLog } from "./card-log.ts";
53
53
  import { type FileDir, fileResponse, isFileId } from "./files.ts";
54
+ import { HelperRoutes } from "./helper-routes.ts";
54
55
  import type { LayoutStore } from "./layout.ts";
55
56
  import { LayoutRoutes } from "./layout-routes.ts";
56
57
  import { PromptApi, type PromptApiOptions } from "./prompt-routes.ts";
@@ -118,6 +119,9 @@ export interface AppApiOptions {
118
119
  | "remove"
119
120
  | "createGroup"
120
121
  | "updateGroup"
122
+ | "finishedHelpers"
123
+ | "archiveHelpers"
124
+ | "restoreHelpers"
121
125
  >;
122
126
  /** Present when the host has the skills addon: the catalog an agent's skills are chosen from, and the change of what it carries. */
123
127
  skills?: {
@@ -272,6 +276,7 @@ export class AppApi {
272
276
  readonly #options: AppApiOptions;
273
277
  readonly #team: TeamApi;
274
278
  readonly #layout: LayoutRoutes;
279
+ readonly #helpers: HelperRoutes;
275
280
  readonly #prompts: PromptApi | undefined;
276
281
  readonly #skillDocs: SkillDocApi | undefined;
277
282
 
@@ -285,6 +290,12 @@ export class AppApi {
285
290
  owner: (admission) => this.#owner(admission),
286
291
  changed: () => options.changed(),
287
292
  });
293
+ this.#helpers = new HelperRoutes({
294
+ surface: options.surface.surface,
295
+ directory: options.directory,
296
+ team: options.team,
297
+ changed: () => options.changed(),
298
+ });
288
299
  this.#prompts = options.prompts
289
300
  ? new PromptApi({ prompts: options.prompts })
290
301
  : undefined;
@@ -536,6 +547,15 @@ export class AppApi {
536
547
  return this.#readAll(request, admission);
537
548
  if (key === "dismiss-finished-threads" && !action && method === "POST")
538
549
  return this.#dismissAllFinished(request, admission);
550
+ if (key === "archive-finished-helpers" && !action && method === "POST")
551
+ return this.#helpers.archiveFinished(
552
+ request,
553
+ await this.#owner(admission),
554
+ );
555
+ if (key === "restore-helpers" && !action && method === "POST") {
556
+ await this.#owner(admission);
557
+ return this.#helpers.restore(request);
558
+ }
539
559
  const room = await rooms.resolve(key);
540
560
  if (!room) throw notFound();
541
561
  if (!action && method === "GET") {
@@ -14,7 +14,7 @@ import {
14
14
  roomOf,
15
15
  type TurnProgress,
16
16
  } from "pi-roundtable";
17
- import type { RoomFile, RoomMessage } from "../api-types.ts";
17
+ import type { RoomFile, RoomMessage, RoundView } from "../api-types.ts";
18
18
  import type { OwnerDesk } from "./desk.ts";
19
19
  import { type FileBinding, type FileDir, typeByName } from "./files.ts";
20
20
  import type { NewRoomMessage, RoomLog } from "./room-log.ts";
@@ -115,6 +115,8 @@ export class AppSurface implements ChatSurface {
115
115
  readonly threads: AppSurfaceThreads;
116
116
  readonly #options: AppSurfaceOptions;
117
117
  readonly #activity = new Map<ChannelKey, Activity>();
118
+ /** The round each group room runs now; an ended round is dropped, since the page keeps what it was told. */
119
+ readonly #rounds = new Map<ChannelKey, RoundView>();
118
120
  /** When a page last sent a message to each room. */
119
121
  readonly #askedAt = new Map<ChannelKey, number>();
120
122
  /** The turns running in each room that a page's message started. */
@@ -196,6 +198,21 @@ export class AppSurface implements ChatSurface {
196
198
  };
197
199
  }
198
200
 
201
+ /** Where the group room's running round stands, for a page that opens mid-round. */
202
+ roundOf(room: ChannelKey): RoundView | undefined {
203
+ return this.#rounds.get(room);
204
+ }
205
+
206
+ /** Shows a group round's step to the sockets that may have the room. */
207
+ groupRound(channel: ChannelKey, round: RoundView): void {
208
+ if (round.state === "ended") this.#rounds.delete(channel);
209
+ else this.#rounds.set(channel, round);
210
+ this.#options.sockets.deliver(
211
+ { type: "round", room: channel, round },
212
+ this.#options.audienceOf(channel),
213
+ );
214
+ }
215
+
199
216
  /** The agents with a turn working in an app room now: a room's own agent, or a group's speaker. */
200
217
  workingAgents(): Set<string> {
201
218
  const names = new Set<string>();
@@ -133,7 +133,17 @@ export class TeamApi {
133
133
  // The app's part runs inside the delete, after its checks and before the row goes: a
134
134
  // cleanup that fails leaves the agent there to delete again.
135
135
  const room = agentRoomKey(this.#options.surface, name);
136
- await this.#options.team.remove(name, () => this.#forget(room, by));
136
+ await this.#options.team.remove(name, async () => {
137
+ // Its archived temp knights go with it, and so does what the app kept of their rooms.
138
+ for (const helper of this.#options.directory
139
+ .agents()
140
+ .filter((one) => one.parent === name))
141
+ await this.#forget(
142
+ agentRoomKey(this.#options.surface, helper.name),
143
+ by,
144
+ );
145
+ await this.#forget(room, by);
146
+ });
137
147
  this.#options.changed();
138
148
  return json({ ok: true });
139
149
  }
@@ -311,6 +321,15 @@ export class TeamApi {
311
321
  avatar: rooms.avatarOf(agent),
312
322
  character: characterOf(agent),
313
323
  room: agentRoomKey(surface, agent.name),
324
+ ...(agent.parent
325
+ ? {
326
+ helper: {
327
+ parent: agentRoomKey(surface, agent.parent),
328
+ parentName:
329
+ directory.agent(agent.parent)?.displayName ?? agent.parent,
330
+ },
331
+ }
332
+ : {}),
314
333
  };
315
334
  }
316
335
 
@@ -412,6 +431,7 @@ export class TeamApi {
412
431
  displayName: agent.displayName,
413
432
  avatar: rooms.avatarOf(agent),
414
433
  character: characterOf(agent),
434
+ ...(agent.parent ? { parent: agent.parent } : {}),
415
435
  }),
416
436
  ),
417
437
  ...directory
package/src/i18n/zh-tw.ts CHANGED
@@ -282,16 +282,38 @@ export const ZH_TW: Readonly<Record<string, string>> = {
282
282
  "Reply to the knight that started this thread.": "回覆開啟這個討論串的騎士。",
283
283
  "Yolo mode": "Yolo 模式",
284
284
  "No knights are seated yet.": "圓桌上還沒有騎士入座。",
285
- "Who's at the table": "誰上桌",
286
- "Off the table": "離席中",
287
- "Leave the table": "離席",
288
- "{name} left the table": "{name}已離席",
289
285
  "The table is empty": "圓桌空了",
290
286
  "There are no rooms to seat yet.": "還沒有可以入座的聊天室。",
291
- "Every knight has left the table. Choose who sits down.":
292
- "所有騎士都離席了。選擇誰要上桌。",
293
- "A knight who leaves the table is muted: its room stops pushing to you. A card that waits for you is always pushed.":
294
- "離席的騎士會被靜音:它的聊天室不再推播給你。等你回應的卡片仍會推播。",
287
+ "All rooms": "所有聊天室",
288
+ "Council: {name} ▾": "評議會:{name} ▾",
289
+ Council: "評議會",
290
+ Councils: "評議會",
291
+ "{count} knights": "{count} 位騎士",
292
+ "Pick who sits": "選誰上桌",
293
+ "No council yet": "還沒有評議會",
294
+ "The council is empty": "評議會沒有人了",
295
+ "Every member of this council is gone. Edit who sits.":
296
+ "這個評議會的成員都不在了。請重新選誰上桌。",
297
+ "A council is a group of up to {max} knights who answer in turn. Pick who sits.":
298
+ "評議會是最多 {max} 位騎士組成的群組,他們輪流發言。請選誰上桌。",
299
+ "Edit this council": "編輯這個評議會",
300
+ "{count} of {max} picked": "已選 {count}/{max}",
301
+ "At most {max} knights": "最多 {max} 位",
302
+ "A council needs at least {min} knights.": "評議會至少要有 {min} 位騎士。",
303
+ "Council name": "評議會名稱",
304
+ "Save as a new council": "另存為新評議會",
305
+ "Make council": "建立評議會",
306
+ "Private chat": "私下對話",
307
+ "Remove from the council": "移出評議會",
308
+ "{name} left the council": "{name}已移出評議會",
309
+ "Speaking order": "發言順序",
310
+ "has spoken": "已發言",
311
+ "Reply {n} of {limit}": "第 {n}/{limit} 則",
312
+ "{count} of {limit} replies": "{count}/{limit} 則回覆",
313
+ "Limit reached": "已達上限",
314
+ "Round stopped": "本輪已停止",
315
+ "Stop the round": "停止本輪",
316
+ "Your message waits for the next round": "你的訊息排在下一輪",
295
317
 
296
318
  // 通知
297
319
  "A file is no longer available. Attach it again.":
@@ -542,7 +564,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
542
564
  "Edit group": "編輯群組",
543
565
  "Name, members and host": "名稱、成員與主持人",
544
566
  Members: "成員",
545
- "Pick 2 to 6 knights. They take turns in the order picked.":
567
+ "Pick 2 to 8 knights. They take turns in the order picked.":
546
568
  "選 2 到 6 位騎士。他們依選取的順序輪流發言。",
547
569
  Host: "主持人",
548
570
  "It stops answering. The history is kept and it can be restored.":
@@ -598,6 +620,20 @@ export const ZH_TW: Readonly<Record<string, string>> = {
598
620
  "Clear finished": "清除已完成",
599
621
  Finished: "已完成",
600
622
  "In progress": "進行中",
623
+ Ended: "已結束",
624
+ "Temp knight": "臨時騎士",
625
+ "{count} temp knight": "{count} 位臨時騎士",
626
+ "{count} temp knights": "{count} 位臨時騎士",
627
+ "Made by {name}": "隸屬 {name}",
628
+ "Temp knight of {name}": "{name} 的臨時騎士",
629
+ "Clear finished temp knights": "清除已結束的臨時騎士",
630
+ "Archived {count} temp knight": "已封存 {count} 位臨時騎士",
631
+ "Archived {count} temp knights": "已封存 {count} 位臨時騎士",
632
+ "Archive this temp knight": "封存這位臨時騎士",
633
+ "{name} archived {count} temp knight: {names}":
634
+ "{name}封存了 {count} 位臨時騎士:{names}",
635
+ "{name} archived {count} temp knights: {names}":
636
+ "{name}封存了 {count} 位臨時騎士:{names}",
601
637
  "More actions for {name}": "{name} 的更多動作",
602
638
  "Cleared “{name}”": "已清除「{name}」",
603
639
  "Cleared {count} finished thread": "已清除 {count} 個已完成的討論串",
@@ -605,6 +641,7 @@ export const ZH_TW: Readonly<Record<string, string>> = {
605
641
  "The thread is still open.": "這個討論串還在進行中。",
606
642
  "threads must be a list of at most 100 keys.":
607
643
  "threads 必須是最多 100 個鍵的清單。",
644
+ "Give names, the temp knights to restore.": "請提供要復原的臨時騎士名稱。",
608
645
  "rooms must be a list of at most 500 keys.":
609
646
  "rooms 必須是最多 500 個鍵的清單。",
610
647
  "restore must be a list of at most 500 moves.":
@@ -1 +0,0 @@
1
- import{h,Q,S,e,w}from"./chunk-index-kankja6p.js";var a={profile:S(()=>(h("pfzyc04p"),import("./chunk-profile-pfzyc04p.js")).then((o)=>({default:o.ProfilePage}))),notices:S(()=>(h("prqkv7ge"),import("./chunk-notices-prqkv7ge.js")).then((o)=>({default:o.NoticesPage}))),onboarding:S(()=>(h("mdtc2mb7"),import("./chunk-onboarding-mdtc2mb7.js")).then((o)=>({default:o.OnboardingPage})))};function n({pane:o}){let t=a[o];return t?e(Q,{fallback:e(w,{}),children:e(t,{})}):null}export{n as default};
@@ -1 +0,0 @@
1
- import{s,Fe,a,r,g,e,n,o,kt,zo}from"./chunk-index-kankja6p.js";import{L}from"./chunk-index-47yspxa4.js";var k=()=>typeof matchMedia==="function"&&matchMedia("(max-width: 719px)").matches,x={rtMenu:!0},H=()=>Boolean(window.history.state?.rtMenu),w=8,M=(t)=>t?.isConnected?t:null;function y(t){return t.items.map((u)=>n("button",{type:"button",role:u.checked===void 0?"menuitem":"menuitemcheckbox",...u.checked===void 0?{}:{"aria-checked":u.checked},className:"menu-item menu-link","data-tone":u.tone,onClick:()=>t.onChoose(u),children:[u.icon?e("span",{className:"menu-icon","data-tone":u.tone,children:e(o,{name:u.icon,size:18})}):null,e("span",{className:"menu-text",children:e("strong",{children:u.label})}),u.checked?e(o,{name:"check",size:18,className:"menu-check"}):null]},u.id))}function T({menu:t}){let u=a(void 0);if(t)u.current=t;let c=u.current,m=a(!1),p=a(void 0),h=t?.serial;return s(()=>{if(h===void 0)return;return window.history.pushState(x,"",window.location.href),m.current=!0,()=>{if(m.current)m.current=!1,window.history.back()}},[h]),s(()=>{let f=()=>{if(!m.current||H())return;m.current=!1,kt();let E=p.current;p.current=void 0,E?.()};return window.addEventListener("popstate",f),()=>window.removeEventListener("popstate",f)},[]),e(L,{open:t!==void 0,title:c?.title??"",onClose:kt,restoreFocus:()=>M(c?.source??null),children:c?e("div",{className:"menu action-items",role:"menu","aria-label":c.title,children:e(y,{items:c.items,onChoose:(f)=>{if(!m.current){kt(),f.run();return}p.current=f.run,window.history.back()}})}):null})}function C({menu:t}){let u=a(null),[c,m]=r(t.at),{source:p}=t,h=(i)=>{M(p)?.focus(),kt(),i?.()},f=a(h);f.current=h,Fe(()=>{let i=u.current;if(!i)return;let d=i.getBoundingClientRect();m({x:Math.max(w,Math.min(t.at.x,window.innerWidth-d.width-w)),y:Math.max(w,Math.min(t.at.y,window.innerHeight-d.height-w))}),i.querySelector('[role^="menuitem"]')?.focus()},[t.serial]),s(()=>{let i=(l)=>{if(l.target instanceof Node&&u.current?.contains(l.target))return;kt()},d=()=>kt();return document.addEventListener("pointerdown",i,!0),document.addEventListener("wheel",i,{capture:!0,passive:!0}),document.addEventListener("touchmove",i,{capture:!0,passive:!0}),window.addEventListener("resize",d),()=>{document.removeEventListener("pointerdown",i,!0),document.removeEventListener("wheel",i,!0),document.removeEventListener("touchmove",i,!0),window.removeEventListener("resize",d)}},[]);let E=(i)=>{let d=[...u.current?.querySelectorAll('[role^="menuitem"]')??[]],l=d.indexOf(document.activeElement),v=(b)=>{i.preventDefault(),d[(b+d.length)%d.length]?.focus()};switch(i.key){case"Escape":i.preventDefault(),i.stopPropagation(),f.current();return;case"ArrowDown":return v(l+1);case"ArrowUp":return v(l<0?-1:l-1);case"Tab":return v(i.shiftKey?l<0?-1:l-1:l+1);case"Home":return v(0);case"End":return v(-1)}};return e("div",{ref:u,className:"action-popover",role:"menu","aria-label":t.title,tabIndex:-1,style:{left:`${c.x}px`,top:`${c.y}px`},onKeyDown:E,children:e(y,{items:t.items,onChoose:(i)=>h(i.run)})})}function O(){let t=zo(),u=a(void 0);if(t&&u.current?.serial!==t.serial)u.current={serial:t.serial,sheet:k()};let c=Boolean(t&&u.current?.sheet);return n(g,{children:[e(T,{menu:c?t:void 0}),t&&!c?e(C,{menu:t},t.serial):null]})}export{O as ActionMenuHost};
@@ -1 +0,0 @@
1
- import{s,e,F,d,w,tt}from"./chunk-index-kankja6p.js";function g({name:o}){let i=d((t)=>t.listed),r=d((t)=>Object.values(t.rooms).find((m)=>m.view?.kind==="agent"&&m.view.name===o)?.view?.key,o);return s(()=>{if(r)F("room",r,{replace:!0}),tt(o);else if(i)F("rooms",void 0,{replace:!0})},[r,i,o]),e("div",{className:"page",children:e(w,{})})}export{g as AgentPage};