pi-roundtable-web 0.26.2 → 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.
- package/CHANGELOG.md +42 -0
- package/README.md +34 -12
- package/dist/chunk-account-1ecx5kt7.js +1 -0
- package/dist/chunk-action-menu-zh2n0ahf.js +1 -0
- package/dist/chunk-agent-ynm2tcbx.js +1 -0
- package/dist/chunk-arranged-rooms-qnz8vese.js +1 -0
- package/dist/chunk-bulk-actions-pr0mw0kc.js +1 -0
- package/dist/chunk-character-creator-caj4pmg4.js +1 -0
- package/dist/{chunk-codeeditor-jm2sm333.js → chunk-codeeditor-dga6s78p.js} +2 -2
- package/dist/chunk-confirm-g76v5d9r.js +1 -0
- package/dist/{chunk-connectors-mhnw6d79.js → chunk-connectors-j79ze3j1.js} +1 -1
- package/dist/{chunk-conversations-yjyq4g1y.js → chunk-conversations-2farn72g.js} +1 -1
- package/dist/chunk-council-sheet-s9sz1bzf.js +2 -0
- package/dist/{chunk-drag-nyq52ck7.js → chunk-drag-45e8222r.js} +1 -1
- package/dist/chunk-entry-q11n47h9.js +1 -0
- package/dist/chunk-folder-list-4gcj7g7h.js +1 -0
- package/dist/chunk-folds-ten4ykfw.js +1 -0
- package/dist/chunk-gate-kcv1a4t0.js +1 -0
- package/dist/chunk-group-scene-12ghcvzd.js +1 -0
- package/dist/chunk-group-table-t458kdgn.js +1 -0
- package/dist/chunk-host-icons-h1k9k96a.js +1 -0
- package/dist/chunk-host-picture-3p0wzn3v.js +1 -0
- package/dist/chunk-host-state-7mdve7cb.js +1 -0
- package/dist/chunk-host-ui-bdgwa6sf.js +1 -0
- package/dist/chunk-index-02vfshtt.js +2 -0
- package/dist/{chunk-index-31ays46f.js → chunk-index-0a81da8z.js} +2 -2
- package/dist/{chunk-index-y00wke93.js → chunk-index-0w4bfjb5.js} +2 -2
- package/dist/chunk-index-12zyshae.js +2 -0
- package/dist/chunk-index-18pgxxw3.js +2 -0
- package/dist/chunk-index-2e4am3hw.js +50 -0
- package/dist/{chunk-index-1f0j335v.js → chunk-index-2fdns8tn.js} +2 -2
- package/dist/{chunk-index-934zgd18.js → chunk-index-2t30xwed.js} +2 -2
- package/dist/{chunk-index-cmxs5y22.js → chunk-index-31x6krbs.js} +3 -3
- package/dist/{chunk-index-b7jjca66.js → chunk-index-3d8s3565.js} +2 -2
- package/dist/chunk-index-5bfqek1d.js +2 -0
- package/dist/chunk-index-64rsz6pf.js +2 -0
- package/dist/chunk-index-6bkcq9ka.js +2 -0
- package/dist/{chunk-index-9nents16.js → chunk-index-8d81qy69.js} +2 -2
- package/dist/chunk-index-92zgb7my.js +2 -0
- package/dist/chunk-index-aamhjg5n.js +2 -0
- package/dist/{chunk-index-ewj52be9.js → chunk-index-ac1ktr56.js} +2 -2
- package/dist/{chunk-index-6tpr6r6d.js → chunk-index-ak6ecqy3.js} +2 -2
- package/dist/{chunk-index-yrhbhmxz.js → chunk-index-b73y2gc0.js} +2 -2
- package/dist/chunk-index-b8cpvr7y.js +2 -0
- package/dist/{chunk-index-tmx30r1w.js → chunk-index-bhqczxr3.js} +2 -2
- package/dist/{chunk-index-0xj30mzw.js → chunk-index-cbqhzf40.js} +12 -12
- package/dist/chunk-index-cdctnw71.js +2 -0
- package/dist/chunk-index-dj0tbfyb.js +2 -0
- package/dist/chunk-index-dnjftvtq.js +2 -0
- package/dist/chunk-index-e2dqjde8.js +1 -0
- package/dist/{chunk-index-frwa2fmc.js → chunk-index-ebg48jgy.js} +2 -2
- package/dist/{chunk-index-smw1n2pt.js → chunk-index-ek5mek4j.js} +5 -5
- package/dist/chunk-index-f6q0stqx.js +2 -0
- package/dist/{chunk-index-kw85ajap.js → chunk-index-ffbrm6tr.js} +3 -3
- package/dist/chunk-index-g1awv7rk.js +2 -0
- package/dist/chunk-index-g3wrs02w.js +1 -0
- package/dist/chunk-index-j0m16cj1.js +2 -0
- package/dist/chunk-index-j4kk1m59.js +2 -0
- package/dist/chunk-index-j4x9dq30.css +1 -0
- package/dist/{chunk-index-7bbqtdce.js → chunk-index-jgzygbt8.js} +2 -2
- package/dist/chunk-index-kb3w11mn.js +7 -0
- package/dist/chunk-index-kbrg32sx.js +2 -0
- package/dist/chunk-index-msqhgvfx.js +2 -0
- package/dist/chunk-index-pefwfm26.js +2 -0
- package/dist/chunk-index-pyfx77cj.js +2 -0
- package/dist/{chunk-index-kfv996qe.js → chunk-index-q7g62sgm.js} +35 -3
- package/dist/chunk-index-qfgkfq86.js +2 -0
- package/dist/chunk-index-rh0x9rzf.js +2 -0
- package/dist/{chunk-index-029209sy.js → chunk-index-sm7m4fej.js} +2 -2
- package/dist/chunk-index-sv9qpmhv.js +1 -0
- package/dist/chunk-index-t998yg8y.js +2 -0
- package/dist/chunk-index-tk4se135.js +4 -0
- package/dist/chunk-index-v6smgw77.js +2 -0
- package/dist/chunk-index-w42rcbx1.js +2 -0
- package/dist/chunk-index-x4enmj72.js +2 -0
- package/dist/chunk-index-xq84gg8w.js +2 -0
- package/dist/{chunk-index-f3b5nch2.js → chunk-index-xxeb2xp0.js} +3 -3
- package/dist/{chunk-index-0n6ja7my.js → chunk-index-zyvwhxga.js} +1 -1
- package/dist/chunk-lang-css-y9133ktr.js +1 -0
- package/dist/{chunk-lang-dockerfile-46rk929v.js → chunk-lang-dockerfile-cnwqn660.js} +1 -1
- package/dist/{chunk-lang-go-qb0qdpg7.js → chunk-lang-go-yy6kgsxv.js} +3 -3
- package/dist/chunk-lang-html-r2xmryek.js +1 -0
- package/dist/chunk-lang-javascript-1r6wd177.js +1 -0
- package/dist/{chunk-lang-json-1q4fp694.js → chunk-lang-json-dx56g11y.js} +1 -1
- package/dist/{chunk-lang-markdown-5w7t28wa.js → chunk-lang-markdown-fpcf2dfq.js} +5 -5
- package/dist/{chunk-lang-python-3dq0p4rf.js → chunk-lang-python-zzp95955.js} +2 -2
- package/dist/{chunk-lang-rust-70pa0agr.js → chunk-lang-rust-214fh7sp.js} +1 -1
- package/dist/{chunk-lang-shell-4pxt7hf4.js → chunk-lang-shell-1rpgtepw.js} +1 -1
- package/dist/{chunk-lang-sql-kgcbh2a9.js → chunk-lang-sql-vvp46hpj.js} +2 -2
- package/dist/{chunk-lang-toml-99t95pab.js → chunk-lang-toml-tbj7467c.js} +1 -1
- package/dist/chunk-lang-typescript-y5w4tt4g.js +1 -0
- package/dist/{chunk-lang-xml-mnbv9yfa.js → chunk-lang-xml-tbcqmedk.js} +1 -1
- package/dist/{chunk-lang-yaml-2by9g1dy.js → chunk-lang-yaml-jh56t3sv.js} +1 -1
- package/dist/chunk-layout-actions-y73mpsh1.js +1 -0
- package/dist/chunk-menu-items-bza16bw6.js +2 -0
- package/dist/chunk-mode-toggle-nj9gnt3b.js +1 -0
- package/dist/chunk-note-row-6fmhrdng.js +75 -0
- package/dist/chunk-notes-wv0e6ssf.js +1 -0
- package/dist/chunk-notice-bell-9z27epd2.js +1 -0
- package/dist/{chunk-notices-evny44be.js → chunk-notices-fte9f196.js} +1 -1
- package/dist/chunk-onboarding-c5twahc3.js +1 -0
- package/dist/{chunk-overview-42h45ehd.js → chunk-overview-k4ccprax.js} +1 -1
- package/dist/chunk-palette-9rbp2fab.js +1 -0
- package/dist/chunk-placement-items-me598wsb.js +1 -0
- package/dist/{chunk-profile-bqcgkgk4.js → chunk-profile-s7r7fcvz.js} +1 -1
- package/dist/chunk-prompt-history-hf296e7v.js +1 -0
- package/dist/chunk-prompts-c8s0nhkm.js +1 -0
- package/dist/chunk-room-confirms-ga2sbvhc.js +1 -0
- package/dist/chunk-room-menu-7h4jym2b.js +1 -0
- package/dist/chunk-rooms-menu-button-pgtaanb6.js +1 -0
- package/dist/chunk-schedules-mbnwcxpc.js +1 -0
- package/dist/{chunk-seat-menu-ca5p4e9q.js → chunk-seat-menu-r0k0eawr.js} +1 -1
- package/dist/chunk-sheet-hosts-a5dfevy9.js +1 -0
- package/dist/chunk-shortcut-help-a6pkjcq7.js +26 -0
- package/dist/chunk-shortcuts-cgt87jkz.js +1 -0
- package/dist/chunk-skill-editor-67yk4x7t.js +8 -0
- package/dist/chunk-skills-rewemey4.js +1 -0
- package/dist/chunk-switcher-list-t8rbay2y.js +1 -0
- package/dist/{chunk-tab-dot-4tkpxqeh.js → chunk-tab-dot-vr11dfrw.js} +1 -1
- package/dist/chunk-table-52nh0azk.js +1 -0
- package/dist/chunk-table2d-g57vr6h0.js +1 -0
- package/dist/chunk-table3d-rp0w6kt4.js +4220 -0
- package/dist/chunk-thread-actions-qcxt1y1g.js +1 -0
- package/dist/chunk-thread-cards-c2mrp2gj.js +1 -0
- package/dist/chunk-thread-list-0071q455.js +217 -0
- package/dist/chunk-transcript-789yqysq.js +1 -0
- package/dist/chunk-where-hmwmme9e.js +1 -0
- package/dist/{chunk-workspace-h0p32ygy.js → chunk-workspace-wr1qrtsh.js} +2 -2
- package/dist/chunk-zh-tw-z5p5mpph.js +1 -0
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +3 -3
- package/src/api-types.ts +73 -0
- package/src/app/app.ts +38 -4
- package/src/app/helper-routes.ts +106 -0
- package/src/app/rooms.ts +18 -3
- package/src/app/routes.ts +125 -2
- package/src/app/surface.ts +18 -1
- package/src/app/team-routes.ts +21 -1
- package/src/app/threads.ts +11 -0
- package/src/host/host-app.ts +15 -1
- package/src/host/read-all.ts +80 -0
- package/src/host/reads.ts +67 -0
- package/src/i18n/zh-tw.ts +64 -13
- package/dist/chunk-account-nhpd9j90.js +0 -1
- package/dist/chunk-action-menu-ec9jqtxq.js +0 -1
- package/dist/chunk-agent-d1xfrawq.js +0 -1
- package/dist/chunk-arranged-rooms-bga8yk2v.js +0 -1
- package/dist/chunk-character-creator-k67f77w5.js +0 -1
- package/dist/chunk-confirm-wc5xraev.js +0 -1
- package/dist/chunk-entry-red1597w.js +0 -1
- package/dist/chunk-folder-list-x56y26yq.js +0 -1
- package/dist/chunk-folds-3p7s2nvx.js +0 -1
- package/dist/chunk-gate-h41bv4y9.js +0 -1
- package/dist/chunk-group-scene-7eb6jyev.js +0 -1
- package/dist/chunk-group-table-rca48kt0.js +0 -1
- package/dist/chunk-host-icons-x318gjqp.js +0 -1
- package/dist/chunk-host-picture-xswcakgy.js +0 -1
- package/dist/chunk-host-state-bpahvk54.js +0 -1
- package/dist/chunk-host-ui-tsbwwk38.js +0 -1
- package/dist/chunk-index-066vzexw.js +0 -2
- package/dist/chunk-index-0h4h76kw.js +0 -2
- package/dist/chunk-index-2f5nfekb.js +0 -8
- package/dist/chunk-index-43k1rjmx.js +0 -2
- package/dist/chunk-index-4gk88a0x.js +0 -4
- package/dist/chunk-index-4jf4jvfd.css +0 -1
- package/dist/chunk-index-4mf29sbx.js +0 -2
- package/dist/chunk-index-51n20yhb.js +0 -2
- package/dist/chunk-index-84v7j6vh.js +0 -50
- package/dist/chunk-index-8f9jer8r.js +0 -2
- package/dist/chunk-index-91z3g774.js +0 -1
- package/dist/chunk-index-9w0sar6s.js +0 -2
- package/dist/chunk-index-a768zmp6.js +0 -2
- package/dist/chunk-index-c5x70d9g.js +0 -2
- package/dist/chunk-index-cwc1vvpw.js +0 -2
- package/dist/chunk-index-d85x96q0.js +0 -2
- package/dist/chunk-index-datrrs6b.js +0 -2
- package/dist/chunk-index-dep2e3f3.js +0 -2
- package/dist/chunk-index-dtste2ed.js +0 -2
- package/dist/chunk-index-grkczq2c.js +0 -2
- package/dist/chunk-index-gzvkpe3e.js +0 -2
- package/dist/chunk-index-satam151.js +0 -2
- package/dist/chunk-index-th43f690.js +0 -2
- package/dist/chunk-index-ths2hxpe.js +0 -1
- package/dist/chunk-index-y4sqmenv.js +0 -2
- package/dist/chunk-index-zd76pr2k.js +0 -2
- package/dist/chunk-lang-css-fa7c0zfj.js +0 -1
- package/dist/chunk-lang-html-47mf2y0v.js +0 -1
- package/dist/chunk-lang-javascript-d32qrvxz.js +0 -1
- package/dist/chunk-lang-typescript-4k1d8z7s.js +0 -1
- package/dist/chunk-layout-actions-cgghzj1w.js +0 -1
- package/dist/chunk-menu-items-24cfwawa.js +0 -2
- package/dist/chunk-mode-toggle-v4baz3r1.js +0 -1
- package/dist/chunk-note-row-72n6cqyk.js +0 -75
- package/dist/chunk-notes-83s3tsey.js +0 -1
- package/dist/chunk-notice-bell-a4ty5h1p.js +0 -1
- package/dist/chunk-onboarding-q1avjbrx.js +0 -1
- package/dist/chunk-palette-e44qepmh.js +0 -1
- package/dist/chunk-placement-items-v466t0sv.js +0 -1
- package/dist/chunk-prompt-history-hgbtcn4b.js +0 -1
- package/dist/chunk-prompts-czs8y6r1.js +0 -1
- package/dist/chunk-room-confirms-h8cdj3ng.js +0 -1
- package/dist/chunk-room-menu-7s0feajw.js +0 -1
- package/dist/chunk-rooms-menu-button-gawqvpsr.js +0 -1
- package/dist/chunk-schedules-zba7krac.js +0 -1
- package/dist/chunk-sheet-hosts-ssbz6hf2.js +0 -1
- package/dist/chunk-skill-editor-n0y9pd90.js +0 -8
- package/dist/chunk-skills-ea4vb2s4.js +0 -1
- package/dist/chunk-switcher-list-pj5csqr9.js +0 -1
- package/dist/chunk-table-j8nmtdnn.js +0 -1
- package/dist/chunk-table-seats-qeytqe0m.js +0 -1
- package/dist/chunk-table2d-18y029k9.js +0 -1
- package/dist/chunk-table3d-q2s31ae0.js +0 -4220
- package/dist/chunk-thread-actions-w3eadnh7.js +0 -1
- package/dist/chunk-thread-cards-fbpky3c9.js +0 -1
- package/dist/chunk-thread-list-pghe84be.js +0 -1
- package/dist/chunk-transcript-eqzxgqys.js +0 -1
- package/dist/chunk-where-ygyr4jdj.js +0 -1
- package/dist/chunk-zh-tw-k6xx1hp2.js +0 -1
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-
|
|
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.
|
|
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.
|
|
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.
|
|
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. */
|
|
@@ -573,6 +580,44 @@ export interface ThreadsDismissed {
|
|
|
573
580
|
dismissed: number;
|
|
574
581
|
}
|
|
575
582
|
|
|
583
|
+
/** What one clear-all across rooms dismissed: the finished threads' room keys, by the room they hang under. */
|
|
584
|
+
export interface ThreadsDismissedAll extends ThreadsDismissed {
|
|
585
|
+
threads: Record<string, string[]>;
|
|
586
|
+
}
|
|
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
|
+
|
|
604
|
+
/** A cursor that marking rooms read moved: where it was (0 for a room never read) and where it is. */
|
|
605
|
+
export interface ReadMove {
|
|
606
|
+
room: string;
|
|
607
|
+
from: number;
|
|
608
|
+
to: number;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** What marking rooms read moved; the page keeps it to undo the marking. */
|
|
612
|
+
export interface ReadAllView {
|
|
613
|
+
moved: ReadMove[];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** What undoing a marking put back: the rooms whose cursor went back. */
|
|
617
|
+
export interface ReadAllUndone {
|
|
618
|
+
restored: string[];
|
|
619
|
+
}
|
|
620
|
+
|
|
576
621
|
/** What taking dismissed threads back onto the room list restored. */
|
|
577
622
|
export interface ThreadsRestored {
|
|
578
623
|
restored: number;
|
|
@@ -761,6 +806,8 @@ export interface AgentProfileView {
|
|
|
761
806
|
avatar: string;
|
|
762
807
|
character: CharacterSpec;
|
|
763
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 };
|
|
764
811
|
}
|
|
765
812
|
|
|
766
813
|
export interface ModelsView {
|
|
@@ -955,6 +1002,8 @@ export interface ArchivedEntry {
|
|
|
955
1002
|
character?: CharacterSpec;
|
|
956
1003
|
/** A group's members that are still on the team. */
|
|
957
1004
|
members?: string[];
|
|
1005
|
+
/** A temp knight's parent, by name; the archive page groups it under that knight. */
|
|
1006
|
+
parent?: string;
|
|
958
1007
|
}
|
|
959
1008
|
|
|
960
1009
|
/** `GET api/archive`. */
|
|
@@ -1159,6 +1208,28 @@ export type ClientFrame =
|
|
|
1159
1208
|
| { type: "cancel"; prompt: string };
|
|
1160
1209
|
|
|
1161
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
|
+
|
|
1162
1233
|
export type ServerFrame =
|
|
1163
1234
|
/** The socket is authenticated; sent first, and followed by every open card. */
|
|
1164
1235
|
| {
|
|
@@ -1193,6 +1264,8 @@ export type ServerFrame =
|
|
|
1193
1264
|
/** Only on the frame where `typing` turns false. */
|
|
1194
1265
|
outcome?: "done" | "failed";
|
|
1195
1266
|
}
|
|
1267
|
+
/** Where a group room's round stands; sent at each step and when it ends. */
|
|
1268
|
+
| { type: "round"; room: string; round: RoundView }
|
|
1196
1269
|
/** Which agents work now, anywhere (Discord included); sent after `ready` and when it changes. */
|
|
1197
1270
|
| { type: "presence"; agents: Record<string, { busy: boolean }> }
|
|
1198
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:
|
|
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) =>
|
|
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
|
|
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
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
SkillRegistry,
|
|
12
12
|
Speaker,
|
|
13
13
|
} from "pi-roundtable";
|
|
14
|
-
import { PluginError } from "pi-roundtable";
|
|
14
|
+
import { channelKey, PluginError } from "pi-roundtable";
|
|
15
15
|
import { CharacterError } from "pi-roundtable/characters";
|
|
16
16
|
import { AgentError } from "pi-roundtable/kit";
|
|
17
17
|
import type {
|
|
@@ -19,6 +19,8 @@ import type {
|
|
|
19
19
|
CharacterRefusal,
|
|
20
20
|
MessagesView,
|
|
21
21
|
ModelsView,
|
|
22
|
+
ReadAllUndone,
|
|
23
|
+
ReadAllView,
|
|
22
24
|
RoomOneView,
|
|
23
25
|
RoomsView,
|
|
24
26
|
SchedulesView,
|
|
@@ -26,6 +28,7 @@ import type {
|
|
|
26
28
|
SessionView,
|
|
27
29
|
SkillView,
|
|
28
30
|
ThreadsDismissed,
|
|
31
|
+
ThreadsDismissedAll,
|
|
29
32
|
ThreadsRestored,
|
|
30
33
|
WorkspaceConflict,
|
|
31
34
|
WorkspaceNew,
|
|
@@ -38,6 +41,7 @@ import type { Authenticated } from "../auth/authenticator.ts";
|
|
|
38
41
|
import { type ConsolePresentation, message } from "../features.ts";
|
|
39
42
|
import type { Gate } from "../gate.ts";
|
|
40
43
|
import type { HostApp } from "../host/host-app.ts";
|
|
44
|
+
import { MAX_READ_ALL } from "../host/read-all.ts";
|
|
41
45
|
import type { RoomReads } from "../host/reads.ts";
|
|
42
46
|
import { readsOf, withExpiredFiles } from "../host/room-views.ts";
|
|
43
47
|
import { json } from "../http.ts";
|
|
@@ -47,6 +51,7 @@ import { viewerOf, type WebAppRegistry } from "../web-app.ts";
|
|
|
47
51
|
import { bad, bodyOf, HttpError, notFound } from "./api-http.ts";
|
|
48
52
|
import type { CardLog } from "./card-log.ts";
|
|
49
53
|
import { type FileDir, fileResponse, isFileId } from "./files.ts";
|
|
54
|
+
import { HelperRoutes } from "./helper-routes.ts";
|
|
50
55
|
import type { LayoutStore } from "./layout.ts";
|
|
51
56
|
import { LayoutRoutes } from "./layout-routes.ts";
|
|
52
57
|
import { PromptApi, type PromptApiOptions } from "./prompt-routes.ts";
|
|
@@ -66,6 +71,8 @@ import {
|
|
|
66
71
|
|
|
67
72
|
/** The most threads one request puts back on the room list: more than the list ever shows. */
|
|
68
73
|
const MAX_RESTORED = 100;
|
|
74
|
+
/** The most rooms one bulk request names. */
|
|
75
|
+
const MAX_BULK_ROOMS = MAX_READ_ALL;
|
|
69
76
|
|
|
70
77
|
export interface AppApiOptions {
|
|
71
78
|
/** The host's wording: a refusal reaches a client of the API in this locale when the catalog has it. */
|
|
@@ -87,6 +94,7 @@ export interface AppApiOptions {
|
|
|
87
94
|
| "removeUnder"
|
|
88
95
|
| "dismiss"
|
|
89
96
|
| "dismissFinished"
|
|
97
|
+
| "dismissFinishedUnder"
|
|
90
98
|
| "restore"
|
|
91
99
|
| "restoreFinished"
|
|
92
100
|
>;
|
|
@@ -111,6 +119,9 @@ export interface AppApiOptions {
|
|
|
111
119
|
| "remove"
|
|
112
120
|
| "createGroup"
|
|
113
121
|
| "updateGroup"
|
|
122
|
+
| "finishedHelpers"
|
|
123
|
+
| "archiveHelpers"
|
|
124
|
+
| "restoreHelpers"
|
|
114
125
|
>;
|
|
115
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. */
|
|
116
127
|
skills?: {
|
|
@@ -132,7 +143,7 @@ export interface AppApiOptions {
|
|
|
132
143
|
/** The read cursors, for each room's unread count and who has read what. */
|
|
133
144
|
reads?: Pick<RoomReads, "unread" | "cursorOf" | "marks">;
|
|
134
145
|
/** Who may be served a file bound to a host room or a page. */
|
|
135
|
-
host?: Pick<HostApp, "mayRead">;
|
|
146
|
+
host?: Pick<HostApp, "mayRead" | "readAll" | "unreadAll">;
|
|
136
147
|
/** The name the app shows for a person. */
|
|
137
148
|
nameOf?(principalId: string): Promise<string>;
|
|
138
149
|
/** Present when the workspace pane is served. */
|
|
@@ -158,6 +169,22 @@ export interface AppApiOptions {
|
|
|
158
169
|
logger: Logger;
|
|
159
170
|
}
|
|
160
171
|
|
|
172
|
+
/** A cursor move as the page sends it back to undo a marking. */
|
|
173
|
+
const isReadMove = (
|
|
174
|
+
value: unknown,
|
|
175
|
+
): value is { room: string; from: number; to: number } => {
|
|
176
|
+
if (typeof value !== "object" || value === null) return false;
|
|
177
|
+
const { room, from, to } = value as Record<string, unknown>;
|
|
178
|
+
return (
|
|
179
|
+
typeof room === "string" &&
|
|
180
|
+
room.length <= 300 &&
|
|
181
|
+
Number.isSafeInteger(from) &&
|
|
182
|
+
(from as number) >= 0 &&
|
|
183
|
+
Number.isSafeInteger(to) &&
|
|
184
|
+
(to as number) >= 0
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
161
188
|
/** A refusal of the workspace pane as the HTTP error the owner reads. */
|
|
162
189
|
function refusalOf(refusal: WorkspaceRefusal): HttpError {
|
|
163
190
|
const { status } = refusal;
|
|
@@ -249,6 +276,7 @@ export class AppApi {
|
|
|
249
276
|
readonly #options: AppApiOptions;
|
|
250
277
|
readonly #team: TeamApi;
|
|
251
278
|
readonly #layout: LayoutRoutes;
|
|
279
|
+
readonly #helpers: HelperRoutes;
|
|
252
280
|
readonly #prompts: PromptApi | undefined;
|
|
253
281
|
readonly #skillDocs: SkillDocApi | undefined;
|
|
254
282
|
|
|
@@ -262,6 +290,12 @@ export class AppApi {
|
|
|
262
290
|
owner: (admission) => this.#owner(admission),
|
|
263
291
|
changed: () => options.changed(),
|
|
264
292
|
});
|
|
293
|
+
this.#helpers = new HelperRoutes({
|
|
294
|
+
surface: options.surface.surface,
|
|
295
|
+
directory: options.directory,
|
|
296
|
+
team: options.team,
|
|
297
|
+
changed: () => options.changed(),
|
|
298
|
+
});
|
|
265
299
|
this.#prompts = options.prompts
|
|
266
300
|
? new PromptApi({ prompts: options.prompts })
|
|
267
301
|
: undefined;
|
|
@@ -508,6 +542,20 @@ export class AppApi {
|
|
|
508
542
|
...(await this.#layout.view()),
|
|
509
543
|
} satisfies RoomsView);
|
|
510
544
|
}
|
|
545
|
+
// No room key is a bare word: the bulk calls take that place.
|
|
546
|
+
if (key === "read-all" && !action && method === "POST")
|
|
547
|
+
return this.#readAll(request, admission);
|
|
548
|
+
if (key === "dismiss-finished-threads" && !action && method === "POST")
|
|
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
|
+
}
|
|
511
559
|
const room = await rooms.resolve(key);
|
|
512
560
|
if (!room) throw notFound();
|
|
513
561
|
if (!action && method === "GET") {
|
|
@@ -605,6 +653,81 @@ export class AppApi {
|
|
|
605
653
|
throw new HttpError(404, "There is no such API.");
|
|
606
654
|
}
|
|
607
655
|
|
|
656
|
+
/**
|
|
657
|
+
* Reads the person's rooms up to their newest message in one call: the named ones, or every room
|
|
658
|
+
* the list shows. A room they may not read is passed over as if it were not named. With `restore`
|
|
659
|
+
* instead, the cursors a call moved go back, where the person has not read on since.
|
|
660
|
+
*/
|
|
661
|
+
async #readAll(
|
|
662
|
+
request: Request,
|
|
663
|
+
admission: Authenticated,
|
|
664
|
+
): Promise<Response> {
|
|
665
|
+
const { host } = this.#options;
|
|
666
|
+
if (!host) throw new HttpError(404, "There is no such API.");
|
|
667
|
+
const viewer = viewerOf(admission.visitor);
|
|
668
|
+
const body = await bodyOf(request);
|
|
669
|
+
if (body.restore !== undefined) {
|
|
670
|
+
const { restore } = body;
|
|
671
|
+
if (
|
|
672
|
+
!Array.isArray(restore) ||
|
|
673
|
+
restore.length > MAX_BULK_ROOMS ||
|
|
674
|
+
!restore.every(isReadMove)
|
|
675
|
+
)
|
|
676
|
+
throw bad("restore must be a list of at most 500 moves.");
|
|
677
|
+
const restored = await host.unreadAll(viewer, restore);
|
|
678
|
+
if (restored.length) this.#options.changed();
|
|
679
|
+
return json({ restored } satisfies ReadAllUndone);
|
|
680
|
+
}
|
|
681
|
+
const named = await this.#named(body.rooms);
|
|
682
|
+
const keys =
|
|
683
|
+
named ?? (await this.#options.rooms.list()).map((one) => one.key);
|
|
684
|
+
const moved = await host.readAll(viewer, keys);
|
|
685
|
+
if (moved.length) this.#options.changed();
|
|
686
|
+
return json({
|
|
687
|
+
moved: moved.map(({ room, from, to }) => ({ room, from, to })),
|
|
688
|
+
} satisfies ReadAllView);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** Takes every finished thread under the named rooms, or under every room the list shows, off the list. */
|
|
692
|
+
async #dismissAllFinished(
|
|
693
|
+
request: Request,
|
|
694
|
+
admission: Authenticated,
|
|
695
|
+
): Promise<Response> {
|
|
696
|
+
await this.#owner(admission);
|
|
697
|
+
const { rooms, threads, surface } = this.#options;
|
|
698
|
+
const named = await this.#named((await bodyOf(request)).rooms);
|
|
699
|
+
const listed = named ?? (await rooms.list()).map((one) => one.key);
|
|
700
|
+
const parents: string[] = [];
|
|
701
|
+
for (const key of new Set(listed)) {
|
|
702
|
+
const one = await rooms.resolve(key);
|
|
703
|
+
if (one && one.kind !== "thread") parents.push(one.key);
|
|
704
|
+
}
|
|
705
|
+
const dismissed = await threads.dismissFinishedUnder(parents);
|
|
706
|
+
const byParent: Record<string, string[]> = {};
|
|
707
|
+
for (const { id, parent } of dismissed)
|
|
708
|
+
byParent[parent] = [
|
|
709
|
+
...(byParent[parent] ?? []),
|
|
710
|
+
channelKey(surface.surface, id),
|
|
711
|
+
];
|
|
712
|
+
if (dismissed.length) this.#options.changed();
|
|
713
|
+
return json({
|
|
714
|
+
dismissed: dismissed.length,
|
|
715
|
+
threads: byParent,
|
|
716
|
+
} satisfies ThreadsDismissedAll);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** The room keys a bulk body names; undefined when it names none, meaning every room. */
|
|
720
|
+
async #named(value: unknown): Promise<string[] | undefined> {
|
|
721
|
+
if (value === undefined) return undefined;
|
|
722
|
+
if (
|
|
723
|
+
!Array.isArray(value) ||
|
|
724
|
+
value.length > MAX_BULK_ROOMS ||
|
|
725
|
+
!value.every((one) => typeof one === "string" && one.length <= 300)
|
|
726
|
+
)
|
|
727
|
+
throw bad("rooms must be a list of at most 500 keys.");
|
|
728
|
+
return value;
|
|
729
|
+
}
|
|
730
|
+
|
|
608
731
|
/** Takes one finished thread off the room list for every owner; an open one is refused. */
|
|
609
732
|
async #dismiss(
|
|
610
733
|
room: ResolvedRoom,
|