@hienlh/ppm 0.18.2 → 0.18.4

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 (517) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CLAUDE.md +1 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +6 -1
  5. package/dist/web/assets/{ai-resource-editor-B6CODu6u.js → ai-resource-editor-Bb-NNlp1.js} +1 -1
  6. package/dist/web/assets/{angular-html-C-Kjr43S.js → angular-html-jfMqUuRd.js} +1 -1
  7. package/dist/web/assets/{angular-ts-CaDDbZqm.js → angular-ts-wytn9PMm.js} +1 -1
  8. package/dist/web/assets/api-client-CzABTreM.js +1 -0
  9. package/dist/web/assets/{api-settings-CAhcNpj5.js → api-settings-DfNRPr1q.js} +1 -1
  10. package/dist/web/assets/api-settings-DlcoR5QT.js +1 -0
  11. package/dist/web/assets/{api-themes-CAjg_Bt9.js → api-themes-CEyYK7AK.js} +1 -1
  12. package/dist/web/assets/{apl-B1WsQFAb.js → apl-i-ahKx6O.js} +1 -1
  13. package/dist/web/assets/architecture-PBZL5I3N-Bs7wzj-W.js +1 -0
  14. package/dist/web/assets/arrow-right-DubGFkN8.js +1 -0
  15. package/dist/web/assets/{astro-Bdnz7nVp.js → astro-DVBCJfG6.js} +1 -1
  16. package/dist/web/assets/audio-preview-DG2Tw0cO.js +1 -0
  17. package/dist/web/assets/{blade-B2euh8sS.js → blade-BHlL8SqU.js} +1 -1
  18. package/dist/web/assets/{bsl-GNeMaxd0.js → bsl-D-UOfR1F.js} +1 -1
  19. package/dist/web/assets/button-B4MWPjWj.js +1 -0
  20. package/dist/web/assets/{cairo-D7G0W7-s.js → cairo-Bn6rucli.js} +1 -1
  21. package/dist/web/assets/chat-tab-CgAv-crh.js +15 -0
  22. package/dist/web/assets/{cobol-Q6YgysnI.js → cobol-Byn9FYKu.js} +1 -1
  23. package/dist/web/assets/code-editor-bX4jlgMy.js +10 -0
  24. package/dist/web/assets/{coffee-BjufWxVc.js → coffee-DjBl4gm-.js} +1 -1
  25. package/dist/web/assets/{conflict-editor-DnSltcyC.js → conflict-editor-B6hVffX3.js} +3 -3
  26. package/dist/web/assets/{context-menu-q-RHHAWz.js → context-menu-DkQdt6YA.js} +1 -1
  27. package/dist/web/assets/{cpp-CqWU0gCR.js → cpp-CKzic-3E.js} +1 -1
  28. package/dist/web/assets/{crystal-ylayVajF.js → crystal-Bkg-Ly_g.js} +1 -1
  29. package/dist/web/assets/{csv-preview-DnaFdgOB.js → csv-preview-hL4Z9HS5.js} +1 -1
  30. package/dist/web/assets/{data-grid-overlay-editor-BzPfsCfd.js → data-grid-overlay-editor-B1mTl3KS.js} +1 -1
  31. package/dist/web/assets/{database-viewer-C7uLy8Z1.js → database-viewer-CTK5kMaA.js} +1 -1
  32. package/dist/web/assets/diff-viewer-DZxnANY4.js +4 -0
  33. package/dist/web/assets/{docx-preview-Dd2nTW2v.js → docx-preview-CrAc7Qjv.js} +1 -1
  34. package/dist/web/assets/download-D9UYwY0-.js +1 -0
  35. package/dist/web/assets/{edge-DX4NxQfh.js → edge-DVbZnd3Y.js} +1 -1
  36. package/dist/web/assets/{edit-diff-preview-DfX-9om9.js → edit-diff-preview-HTZPC9VP.js} +1 -1
  37. package/dist/web/assets/{elixir-CSh3NjbV.js → elixir-Dwi8Wu7W.js} +1 -1
  38. package/dist/web/assets/{elm-DBzwFYq9.js → elm-DtIv-HXJ.js} +1 -1
  39. package/dist/web/assets/{erb-yxVOb8aB.js → erb-D3HEDz1x.js} +1 -1
  40. package/dist/web/assets/{erlang-5XIDkYrM.js → erlang-2MjTCzGz.js} +1 -1
  41. package/dist/web/assets/{esm-DO4el2OJ.js → esm-CZ8B8Ba2.js} +1 -1
  42. package/dist/web/assets/explorer-body-DykJXCTw.js +2 -0
  43. package/dist/web/assets/explorer-window-content-NS8Y0v8c.js +1 -0
  44. package/dist/web/assets/{extension-webview-Dk_zyoXZ.js → extension-webview-D6eywKPN.js} +1 -1
  45. package/dist/web/assets/{fortran-fixed-form-D4tE5VWY.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
  46. package/dist/web/assets/{fsharp-By8soj6-.js → fsharp-Bxbn_gbr.js} +1 -1
  47. package/dist/web/assets/{gdresource-DoSwM7Hv.js → gdresource-sFMY4D2Q.js} +1 -1
  48. package/dist/web/assets/{git-commit-_ZiciHIx.js → git-commit-BzB9Lio5.js} +1 -1
  49. package/dist/web/assets/{git-log-panel-BLQSy6rC.js → git-log-panel-DAogZ9QV.js} +1 -1
  50. package/dist/web/assets/{git-rebase-C3Y5N9-s.js → git-rebase-CKSVd848.js} +1 -1
  51. package/dist/web/assets/gitGraph-HDMCJU4V-D7i54kbM.js +1 -0
  52. package/dist/web/assets/glide-data-grid-DxfJRN-r.js +138 -0
  53. package/dist/web/assets/{glimmer-js-BPBTXVlC.js → glimmer-js-BY8aExAJ.js} +1 -1
  54. package/dist/web/assets/{glimmer-ts-BQSZdy_D.js → glimmer-ts-Bak54dBX.js} +1 -1
  55. package/dist/web/assets/{glsl-CbQdxpU-.js → glsl-nR2eaJQe.js} +1 -1
  56. package/dist/web/assets/{graphql-GMlKikpY.js → graphql-CGPPFAU6.js} +1 -1
  57. package/dist/web/assets/group-chat-tab-DkVi6qL2.js +1 -0
  58. package/dist/web/assets/{hack-DyshcLec.js → hack-CXIMX-Ol.js} +1 -1
  59. package/dist/web/assets/{haml-rqCqWzsq.js → haml-tPY-9Pfm.js} +1 -1
  60. package/dist/web/assets/{handlebars-BCKwSWjR.js → handlebars-BuW1LT7T.js} +1 -1
  61. package/dist/web/assets/{html-Cg29xPR1.js → html-bgVcwZ-t.js} +1 -1
  62. package/dist/web/assets/{html-derivative-geltIVg8.js → html-derivative-D6-IxTgn.js} +1 -1
  63. package/dist/web/assets/{http-pG7Vp237.js → http-CJ1zyB-h.js} +1 -1
  64. package/dist/web/assets/{hurl-Di3Uu_Mc.js → hurl-CmoT7l3T.js} +1 -1
  65. package/dist/web/assets/{hxml-CRy4lbv8.js → hxml-CztOsL8o.js} +1 -1
  66. package/dist/web/assets/{image-preview-KubLSLNO.js → image-preview-Byjx8MoL.js} +1 -1
  67. package/dist/web/assets/index-Bucbpby-.css +2 -0
  68. package/dist/web/assets/index-CcN0TBm4.js +52 -0
  69. package/dist/web/assets/info-3K5VOQVL-BV41AzR9.js +1 -0
  70. package/dist/web/assets/{jinja-C8GyVnvd.js → jinja-B1pq3jJq.js} +1 -1
  71. package/dist/web/assets/{jison-BywnqPn6.js → jison-D8B5pCwA.js} +1 -1
  72. package/dist/web/assets/{julia-DTrO3T3B.js → julia-D0K3bwpU.js} +1 -1
  73. package/dist/web/assets/{just-CA3KSHPi.js → just-B9Jlddu-.js} +1 -1
  74. package/dist/web/assets/keybindings-store-DJIHqbLM.js +1 -0
  75. package/dist/web/assets/{latex-BC5xUMpc.js → latex-BPJSSE6j.js} +1 -1
  76. package/dist/web/assets/{liquid-Chd8tSyQ.js → liquid-lv10fo4U.js} +1 -1
  77. package/dist/web/assets/{lua-DHaEtwLv.js → lua-PNuaIBEF.js} +1 -1
  78. package/dist/web/assets/{markdown-renderer-B4uL6GVA.js → markdown-renderer-CXlEe3ma.js} +3 -3
  79. package/dist/web/assets/markdown-renderer-pYznJDbh.js +1 -0
  80. package/dist/web/assets/{marko-Desx_bC-.js → marko-Du3frD6X.js} +1 -1
  81. package/dist/web/assets/{mdc-DahWHc1t.js → mdc-BeDVcFRl.js} +1 -1
  82. package/dist/web/assets/media-url-C5vA0ha9.js +1 -0
  83. package/dist/web/assets/{mobile-bottom-sheet-BQt45q9T.js → mobile-bottom-sheet-CS3XMwFw.js} +1 -1
  84. package/dist/web/assets/mobile-explorer-sheet-BuqBlP3o.js +1 -0
  85. package/dist/web/assets/{nextflow-3A7lutCA.js → nextflow-DkcXP71s.js} +1 -1
  86. package/dist/web/assets/{nginx-XQda6Hdy.js → nginx-C5n8N_bt.js} +1 -1
  87. package/dist/web/assets/{nim-DHqlpwP1.js → nim-CIhyfs-A.js} +1 -1
  88. package/dist/web/assets/notification-store-Bcl5efY2.js +1 -0
  89. package/dist/web/assets/{number-overlay-editor-DgZtViPO.js → number-overlay-editor-NgOsWJ7x.js} +1 -1
  90. package/dist/web/assets/packet-RMMSAZCW-CaJoP44m.js +1 -0
  91. package/dist/web/assets/{panel-store-s6Qx49RK.js → panel-store-DCD82zc1.js} +1 -1
  92. package/dist/web/assets/{pdf-preview-xZQVUtaV.js → pdf-preview-F2DWVB85.js} +1 -1
  93. package/dist/web/assets/{perl-M-ncjACb.js → perl-Cf31sZYE.js} +1 -1
  94. package/dist/web/assets/{php-CKIajrZi.js → php-Dzl_MDIW.js} +1 -1
  95. package/dist/web/assets/pie-UPGHQEXC-DSGZmNw2.js +1 -0
  96. package/dist/web/assets/plus-DmjC2iRB.js +1 -0
  97. package/dist/web/assets/{postgres-viewer-Ds0FxBel.js → postgres-viewer-KZqPYfOB.js} +3 -3
  98. package/dist/web/assets/{project-store-DyaEpHVG.js → project-store-B8BkHXy5.js} +1 -1
  99. package/dist/web/assets/{pug-DO0u_gO4.js → pug-CxSYHrB3.js} +1 -1
  100. package/dist/web/assets/{qml-UEje1PAA.js → qml-DlEjGhwN.js} +1 -1
  101. package/dist/web/assets/radar-KQ55EAFF-Be4VUTs0.js +1 -0
  102. package/dist/web/assets/{razor-lCs5T1Ib.js → razor-KVt5uW14.js} +1 -1
  103. package/dist/web/assets/{rst-CZX_O0z4.js → rst-D5BwNTnw.js} +1 -1
  104. package/dist/web/assets/{ruby-idycVAyE.js → ruby-D5fejQwd.js} +1 -1
  105. package/dist/web/assets/run-in-terminal-B8TDtaM_.js +1 -0
  106. package/dist/web/assets/{sas-o5gTfUwp.js → sas-Bj7B1CB2.js} +1 -1
  107. package/dist/web/assets/{scss-CRTk3iBQ.js → scss-BB0CUoCL.js} +1 -1
  108. package/dist/web/assets/send-to-chat-CuBAVC8l.js +1 -0
  109. package/dist/web/assets/{settings-store-ChqHdT2V.js → settings-store-CY-4_U3H.js} +2 -2
  110. package/dist/web/assets/settings-tab-Khj-w6Sr.js +1 -0
  111. package/dist/web/assets/{shaderlab-3e5ln8ci.js → shaderlab-BdjEZUMc.js} +1 -1
  112. package/dist/web/assets/{shellsession-CGQjQ75I.js → shellsession-Bp1OA1rR.js} +1 -1
  113. package/dist/web/assets/{soy-CxY0VwX2.js → soy-DlYz3wsC.js} +1 -1
  114. package/dist/web/assets/{sparql-CBAGAFuq.js → sparql-CdfII5Jd.js} +1 -1
  115. package/dist/web/assets/sql-query-editor-Cp0_e2P1.js +1 -0
  116. package/dist/web/assets/{sqlite-viewer-CZYDknNa.js → sqlite-viewer-CIv3sagZ.js} +1 -1
  117. package/dist/web/assets/{stata-BoezhOvk.js → stata-BvUA6GUW.js} +1 -1
  118. package/dist/web/assets/{surrealql-CHEzAhu7.js → surrealql--DLNbTyI.js} +1 -1
  119. package/dist/web/assets/{svelte-y6xtu2zX.js → svelte-CBfSsG4n.js} +1 -1
  120. package/dist/web/assets/system-monitor-tab-CrssTOf4.js +1 -0
  121. package/dist/web/assets/{tab-store-C42sktPo.js → tab-store-DAsvpx4t.js} +1 -1
  122. package/dist/web/assets/team-member-window-content-D-hXax3c.js +1 -0
  123. package/dist/web/assets/{templ-DbG2y5yD.js → templ-Dv8zpKZW.js} +1 -1
  124. package/dist/web/assets/terminal-tab-BIQDG-Ti.js +3 -0
  125. package/dist/web/assets/{tex-CeKQhrQG.js → tex-CsrOiqh1.js} +1 -1
  126. package/dist/web/assets/tool-cards-Dx-Rrpri.js +8 -0
  127. package/dist/web/assets/treemap-KZPCXAKY-Cu2zgbxU.js +1 -0
  128. package/dist/web/assets/{ts-tags-iKKOXJjG.js → ts-tags-3T98k4EJ.js} +1 -1
  129. package/dist/web/assets/{twig-DK8Vrp-G.js → twig-u6QZ45FH.js} +1 -1
  130. package/dist/web/assets/{use-blob-url-DYtOHK7u.js → use-blob-url-BmlYYZ8L.js} +1 -1
  131. package/dist/web/assets/use-is-mobile-B8FA4kQf.js +1 -0
  132. package/dist/web/assets/use-monaco-theme-Djp1_fKi.js +1 -0
  133. package/dist/web/assets/use-websocket-CCYA9-a2.js +1 -0
  134. package/dist/web/assets/{vendor-mermaid-C977xNeE.js → vendor-mermaid-Bg4vE8QA.js} +1 -1
  135. package/dist/web/assets/video-preview-cvjNMjWA.js +1 -0
  136. package/dist/web/assets/{vue-JkIEObMV.js → vue-CxIsxtwn.js} +1 -1
  137. package/dist/web/assets/{vue-html-D8SqnU86.js → vue-html-BGL5qnuH.js} +1 -1
  138. package/dist/web/assets/{vue-vine-DJj7N4WD.js → vue-vine-DHPh_gMr.js} +1 -1
  139. package/dist/web/assets/{xml-C-00w9Rv.js → xml-C-vM2m7J.js} +1 -1
  140. package/dist/web/assets/{xsl-CVdCwPHV.js → xsl-DvTXNwuO.js} +1 -1
  141. package/dist/web/index.html +21 -20
  142. package/dist/web/sw.js +1 -1
  143. package/docs/codebase-summary.md +1 -0
  144. package/package.json +1 -1
  145. package/scripts/dev-quick-tunnel-retry.mjs +51 -0
  146. package/src/providers/claude-agent-sdk.ts +6 -0
  147. package/src/server/helpers/media-route-handlers.ts +66 -0
  148. package/src/server/helpers/range-file-response.ts +86 -0
  149. package/src/server/index.ts +24 -17
  150. package/src/server/middleware/auth.ts +3 -2
  151. package/src/server/routes/files.ts +40 -7
  152. package/src/server/routes/fs-browse.ts +43 -5
  153. package/src/server/routes/fs-upload.ts +32 -0
  154. package/src/server/routes/teams.ts +34 -0
  155. package/src/server/ws/chat.ts +64 -16
  156. package/src/server/ws/team-inbox-watcher.ts +61 -22
  157. package/src/server/ws/terminal.ts +24 -6
  158. package/src/services/fs-ops/fs-ops-upload.service.ts +120 -0
  159. package/src/services/media-transcode/ffmpeg-capabilities.ts +89 -0
  160. package/src/services/media-transcode/media-probe.ts +63 -0
  161. package/src/services/media-transcode/transcode-stream.ts +129 -0
  162. package/src/services/subagent-transcript-merger.ts +1 -1
  163. package/src/services/team-member-activity/member-activity.service.ts +117 -0
  164. package/src/services/team-member-activity/outbound-message-scanner.ts +121 -0
  165. package/src/services/team-member-activity/subagent-transcript-index.ts +95 -0
  166. package/src/services/team-member-activity/transcript-tail-summary.ts +125 -0
  167. package/src/shared/background-agent-status.ts +79 -0
  168. package/src/types/api.ts +1 -0
  169. package/src/types/chat.ts +6 -1
  170. package/src/web/components/chat/chat-history-bar.tsx +4 -2
  171. package/src/web/components/chat/chat-tab.tsx +1 -0
  172. package/src/web/components/chat/message-input.tsx +13 -6
  173. package/src/web/components/chat/mini-markdown.tsx +17 -0
  174. package/src/web/components/chat/send-message-card.tsx +134 -0
  175. package/src/web/components/chat/send-message-parse.ts +159 -0
  176. package/src/web/components/chat/team-activity-panel.tsx +107 -121
  177. package/src/web/components/chat/team-member-activity-row.tsx +92 -0
  178. package/src/web/components/chat/team-member-list.tsx +43 -0
  179. package/src/web/components/chat/team-member-window-content.tsx +97 -0
  180. package/src/web/components/chat/team-message-badges.ts +18 -0
  181. package/src/web/components/chat/team-message-list.tsx +88 -0
  182. package/src/web/components/chat/team-message-preview.ts +87 -0
  183. package/src/web/components/chat/tool-cards.tsx +100 -23
  184. package/src/web/components/editor/audio-preview.tsx +6 -7
  185. package/src/web/components/editor/video-player/use-video-keyboard-shortcuts.ts +62 -0
  186. package/src/web/components/editor/video-player/video-player-controls.tsx +103 -0
  187. package/src/web/components/editor/video-player/video-player.tsx +190 -0
  188. package/src/web/components/editor/video-player/video-settings-menu.tsx +92 -0
  189. package/src/web/components/editor/video-player/video-transform.ts +49 -0
  190. package/src/web/components/editor/video-preview.tsx +75 -14
  191. package/src/web/components/explorer/file-tree.tsx +6 -0
  192. package/src/web/components/explorer/tree-node-context-menu.tsx +5 -1
  193. package/src/web/components/floating-window/window-content-registry.ts +5 -0
  194. package/src/web/components/floating-window/window-store-types.ts +1 -1
  195. package/src/web/components/os-explorer/actions/explorer-actions-upload.ts +96 -0
  196. package/src/web/components/os-explorer/actions/use-explorer-actions.ts +23 -2
  197. package/src/web/components/os-explorer/actions/use-explorer-upload-actions.tsx +41 -0
  198. package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +10 -1
  199. package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +8 -1
  200. package/src/web/components/os-explorer/dnd/use-external-file-drop.ts +84 -0
  201. package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +46 -3
  202. package/src/web/components/os-explorer/explorer-body.tsx +1 -1
  203. package/src/web/components/os-explorer/explorer-context-menu.tsx +10 -0
  204. package/src/web/components/os-explorer/explorer-dialogs.tsx +2 -0
  205. package/src/web/components/os-explorer/explorer-sidebar.tsx +10 -5
  206. package/src/web/components/os-explorer/explorer-toolbar.tsx +32 -2
  207. package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +5 -1
  208. package/src/web/components/os-explorer/upload/collect-dropped-entries.ts +81 -0
  209. package/src/web/components/os-explorer/upload/upload-queue.ts +79 -0
  210. package/src/web/components/os-explorer/upload/use-upload-picker.tsx +74 -0
  211. package/src/web/components/os-explorer/views/column-row.tsx +3 -1
  212. package/src/web/components/os-explorer/views/column-view-column.tsx +6 -1
  213. package/src/web/components/os-explorer/views/icons-view-tile.tsx +1 -1
  214. package/src/web/components/os-explorer/views/icons-view.tsx +5 -1
  215. package/src/web/components/os-explorer/views/list-row.tsx +1 -1
  216. package/src/web/components/os-explorer/views/list-view.tsx +5 -1
  217. package/src/web/components/shared/markdown-code-block.tsx +3 -7
  218. package/src/web/components/terminal/terminal-tab.tsx +19 -25
  219. package/src/web/hooks/use-chat.ts +77 -15
  220. package/src/web/hooks/use-team-activity-feed.ts +92 -0
  221. package/src/web/hooks/use-terminal-command-queue.ts +108 -0
  222. package/src/web/hooks/use-terminal.ts +45 -3
  223. package/src/web/lib/fs-api.ts +9 -0
  224. package/src/web/lib/fs-upload-xhr.ts +62 -0
  225. package/src/web/lib/media-url.ts +51 -0
  226. package/src/web/lib/open-terminal-at.ts +22 -0
  227. package/src/web/lib/run-in-terminal.ts +55 -0
  228. package/src/web/lib/send-to-chat.ts +118 -0
  229. package/src/web/lib/ws-auth.ts +13 -0
  230. package/src/web/lib/ws-client.ts +10 -5
  231. package/.release-run.sh +0 -5
  232. package/dist/web/assets/api-client-CaRE4HYh.js +0 -1
  233. package/dist/web/assets/api-settings-DBcXfeJ8.js +0 -1
  234. package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +0 -1
  235. package/dist/web/assets/audio-preview-BkNAEqcB.js +0 -1
  236. package/dist/web/assets/chat-tab-B-MEsHYp.js +0 -13
  237. package/dist/web/assets/code-editor-bC1KAEUA.js +0 -10
  238. package/dist/web/assets/dialog-CkI7rwrh.js +0 -1
  239. package/dist/web/assets/diff-viewer-BwElk4q9.js +0 -4
  240. package/dist/web/assets/explorer-body-DzdXBlWw.js +0 -2
  241. package/dist/web/assets/explorer-window-content-Dgl1WTNs.js +0 -1
  242. package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +0 -1
  243. package/dist/web/assets/glide-data-grid-QZ_fgjIa.js +0 -138
  244. package/dist/web/assets/group-chat-tab-BbdVlKFv.js +0 -1
  245. package/dist/web/assets/index-D-xwjNyX.css +0 -2
  246. package/dist/web/assets/index-wd3FSHHn.js +0 -52
  247. package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +0 -1
  248. package/dist/web/assets/keybindings-store-Bp_c_C3z.js +0 -1
  249. package/dist/web/assets/markdown-renderer-2asl-3Kk.js +0 -1
  250. package/dist/web/assets/mobile-explorer-sheet-LWUDaNBV.js +0 -1
  251. package/dist/web/assets/notification-store-BasUnpt7.js +0 -1
  252. package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +0 -1
  253. package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +0 -1
  254. package/dist/web/assets/plus-9LatwJMx.js +0 -1
  255. package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +0 -1
  256. package/dist/web/assets/settings-tab-vhatQ51-.js +0 -1
  257. package/dist/web/assets/sql-query-editor-BblB2Imt.js +0 -1
  258. package/dist/web/assets/system-monitor-tab-D8dy-RT_.js +0 -1
  259. package/dist/web/assets/terminal-tab-CfD22q5x.js +0 -3
  260. package/dist/web/assets/tool-cards-DNgw1yI6.js +0 -7
  261. package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +0 -1
  262. package/dist/web/assets/use-is-mobile-DnFSkUG2.js +0 -1
  263. package/dist/web/assets/use-monaco-theme-DEbn-QT3.js +0 -1
  264. package/dist/web/assets/video-preview-DcUaFYyv.js +0 -1
  265. package/src/web/components/chat/team-activity-popover.tsx +0 -202
  266. /package/dist/web/assets/{abap-DrMyiWmK.js → abap-C1K0-wzc.js} +0 -0
  267. /package/dist/web/assets/{actionscript-3-BlSOyi0y.js → actionscript-3-CJhQVjLA.js} +0 -0
  268. /package/dist/web/assets/{ada-B_m96icB.js → ada-Bt8r9fME.js} +0 -0
  269. /package/dist/web/assets/{andromeeda-Ch3vOiZB.js → andromeeda-DF1I8qi3.js} +0 -0
  270. /package/dist/web/assets/{apache-CdsaXc1n.js → apache-CnlleE6P.js} +0 -0
  271. /package/dist/web/assets/{apex-0AnsBevk.js → apex-j0C6Xmk6.js} +0 -0
  272. /package/dist/web/assets/{api-client-rTAGdEKo.js → api-client-BSB14J4w.js} +0 -0
  273. /package/dist/web/assets/{applescript-CSxLolzy.js → applescript-DO7AnO8U.js} +0 -0
  274. /package/dist/web/assets/{apply-theme-CMQXnUAa.js → apply-theme-CNj1iA-v.js} +0 -0
  275. /package/dist/web/assets/{ara-DXZS0jA5.js → ara-i83AydZj.js} +0 -0
  276. /package/dist/web/assets/{asciidoc-DDy3pH2A.js → asciidoc-DbBA6-sF.js} +0 -0
  277. /package/dist/web/assets/{asm-Bd17hhqg.js → asm-B5S47Rgr.js} +0 -0
  278. /package/dist/web/assets/{aurora-x-C62HJRs-.js → aurora-x-De_IaSKw.js} +0 -0
  279. /package/dist/web/assets/{awk-Cc-nazni.js → awk-CfyLHXqS.js} +0 -0
  280. /package/dist/web/assets/{ayu-dark-BsfmNWCU.js → ayu-dark-DTLdZ1Si.js} +0 -0
  281. /package/dist/web/assets/{ayu-light-BZ4HLa4X.js → ayu-light-DTOfkzhu.js} +0 -0
  282. /package/dist/web/assets/{ayu-mirage-Bhyjyiqr.js → ayu-mirage-DsAECCMv.js} +0 -0
  283. /package/dist/web/assets/{ballerina-QiWgir3v.js → ballerina-CumGp9A7.js} +0 -0
  284. /package/dist/web/assets/{bat-DxtlkwN3.js → bat-Ca2SBbYN.js} +0 -0
  285. /package/dist/web/assets/{beancount-Bt21X7M_.js → beancount-CqyXBwwF.js} +0 -0
  286. /package/dist/web/assets/{berry-Cksre7lO.js → berry-yl-kJr_H.js} +0 -0
  287. /package/dist/web/assets/{bibtex-DkakgDlR.js → bibtex-DoFFszjV.js} +0 -0
  288. /package/dist/web/assets/{bicep-8crmcyXU.js → bicep-CWPi4nfK.js} +0 -0
  289. /package/dist/web/assets/{bird2-CewQ2I_Z.js → bird2-DXew-ut0.js} +0 -0
  290. /package/dist/web/assets/{bot-nk1k3ELu.js → bot-CunPuEdW.js} +0 -0
  291. /package/dist/web/assets/{c-C_6zE8Ha.js → c-clbySDvd.js} +0 -0
  292. /package/dist/web/assets/{c3-DLWB9-dp.js → c3-B65jvSM1.js} +0 -0
  293. /package/dist/web/assets/{cadence-D8qYS6pR.js → cadence-Cm8tQTHN.js} +0 -0
  294. /package/dist/web/assets/{can-open-in-ppm-BzVXoV-d.js → can-open-in-ppm-Cu-XYIgu.js} +0 -0
  295. /package/dist/web/assets/{catppuccin-frappe-Bh95lNRy.js → catppuccin-frappe-DLeGslh_.js} +0 -0
  296. /package/dist/web/assets/{catppuccin-latte-C1PlN3G0.js → catppuccin-latte-CR5GeHws.js} +0 -0
  297. /package/dist/web/assets/{catppuccin-macchiato-CldtgdK4.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
  298. /package/dist/web/assets/{catppuccin-mocha-BiwqxX9A.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
  299. /package/dist/web/assets/{clarity-CGiU22jY.js → clarity-DPulbvKH.js} +0 -0
  300. /package/dist/web/assets/{clojure-COIC3doh.js → clojure-DFuWS20u.js} +0 -0
  301. /package/dist/web/assets/{cmake-DWUEQfHv.js → cmake-DejCqcu4.js} +0 -0
  302. /package/dist/web/assets/{codeowners-BNHiAQea.js → codeowners-BwAyqWIH.js} +0 -0
  303. /package/dist/web/assets/{codeql-BuKaoS0j.js → codeql-DgSwFjqI.js} +0 -0
  304. /package/dist/web/assets/{common-lisp-D5IKbxsd.js → common-lisp-CxIVJCtf.js} +0 -0
  305. /package/dist/web/assets/{coq-BMVS-p94.js → coq-DLSjSnmf.js} +0 -0
  306. /package/dist/web/assets/{csharp-DMlbXbM4.js → csharp-C1HHzXBp.js} +0 -0
  307. /package/dist/web/assets/{css-CjkJC89X.js → css-VXsMY--y.js} +0 -0
  308. /package/dist/web/assets/{csv-DoNVBBf0.js → csv-BEbcJdHz.js} +0 -0
  309. /package/dist/web/assets/{csv-parser-DHvoTjMF.js → csv-parser-r-Nk1oHT.js} +0 -0
  310. /package/dist/web/assets/{cue-DoTRbBg-.js → cue-BkDnFqUD.js} +0 -0
  311. /package/dist/web/assets/{cypher-DVsNdTT3.js → cypher-1cMqojSQ.js} +0 -0
  312. /package/dist/web/assets/{d-EX7BBSrv.js → d-BnYjRXtH.js} +0 -0
  313. /package/dist/web/assets/{dark-plus-P_YTHwlh.js → dark-plus-XfbaH7kZ.js} +0 -0
  314. /package/dist/web/assets/{dart-Do-rgU4S.js → dart-Ca7s-8hK.js} +0 -0
  315. /package/dist/web/assets/{data-grid-types-CUx8cusN.js → data-grid-types-BXwlFMzs.js} +0 -0
  316. /package/dist/web/assets/{dax-DxOieacu.js → dax-CmuOw-mY.js} +0 -0
  317. /package/dist/web/assets/{desktop-CvdV7SBf.js → desktop-C2gJ6Ovb.js} +0 -0
  318. /package/dist/web/assets/{diff-CNOetGen.js → diff-BCEo1Ej7.js} +0 -0
  319. /package/dist/web/assets/{dist-CMfLpPW_.js → dist-CUPbqNnL.js} +0 -0
  320. /package/dist/web/assets/{dist-DduBIVkD.js → dist-DOlBnxFX.js} +0 -0
  321. /package/dist/web/assets/{docker-DAlG4_q-.js → docker-DN2jlyAh.js} +0 -0
  322. /package/dist/web/assets/{dotenv-Dh3qf6yx.js → dotenv-NExMY2kS.js} +0 -0
  323. /package/dist/web/assets/{dracula-baAGmJYG.js → dracula-B9lbE01y.js} +0 -0
  324. /package/dist/web/assets/{dracula-soft-l7ZIyums.js → dracula-soft-CMBBfKF_.js} +0 -0
  325. /package/dist/web/assets/{dream-maker-DeRjdBn8.js → dream-maker-DEyDwFsd.js} +0 -0
  326. /package/dist/web/assets/{emacs-lisp-CB05-jQA.js → emacs-lisp-D_qP2zS6.js} +0 -0
  327. /package/dist/web/assets/{everforest-dark-CT-llQBT.js → everforest-dark-DbHa2U-p.js} +0 -0
  328. /package/dist/web/assets/{everforest-light-ByPJkTT7.js → everforest-light-BISkJchP.js} +0 -0
  329. /package/dist/web/assets/{fennel-CeMarbvn.js → fennel-6B22SfJR.js} +0 -0
  330. /package/dist/web/assets/{file-exclamation-point-COAwgNGw.js → file-exclamation-point-a8f3_w7a.js} +0 -0
  331. /package/dist/web/assets/{fish-BYrt6Xtb.js → fish-CubIIvJD.js} +0 -0
  332. /package/dist/web/assets/{fluent-Cr5VcAwh.js → fluent-DF55zQ09.js} +0 -0
  333. /package/dist/web/assets/{fortran-free-form-DQmKtrLa.js → fortran-free-form-DvOu8jAt.js} +0 -0
  334. /package/dist/web/assets/{gdscript-QTDKjzhY.js → gdscript-DCPxdLpq.js} +0 -0
  335. /package/dist/web/assets/{gdshader-Chr-5UYt.js → gdshader-CTZ381FO.js} +0 -0
  336. /package/dist/web/assets/{genie-kWHMWnt0.js → genie-C_QDtvgU.js} +0 -0
  337. /package/dist/web/assets/{gherkin-DtEBQFUo.js → gherkin-BcvK42tF.js} +0 -0
  338. /package/dist/web/assets/{github-dark-B5aiUta2.js → github-dark-C9afs_4w.js} +0 -0
  339. /package/dist/web/assets/{github-dark-default-BkxtsTqR.js → github-dark-default-CKGVrl3Z.js} +0 -0
  340. /package/dist/web/assets/{github-dark-dimmed-B9QjR_0K.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
  341. /package/dist/web/assets/{github-dark-high-contrast-D6bO-5c0.js → github-dark-high-contrast-DW78QukY.js} +0 -0
  342. /package/dist/web/assets/{github-light-Cg2ZW_CJ.js → github-light-BnP2dznS.js} +0 -0
  343. /package/dist/web/assets/{github-light-default-eD7qUVlQ.js → github-light-default-C2fPZzPG.js} +0 -0
  344. /package/dist/web/assets/{github-light-high-contrast-CFnlSVjd.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
  345. /package/dist/web/assets/{gleam-BpYoOqIT.js → gleam-BpeKwEIO.js} +0 -0
  346. /package/dist/web/assets/{gn-BxwtZPau.js → gn-nUb_Mit0.js} +0 -0
  347. /package/dist/web/assets/{gnuplot-CRyiZDfl.js → gnuplot-D7I0v20p.js} +0 -0
  348. /package/dist/web/assets/{go-Dw1Qx94W.js → go-D7Qqska7.js} +0 -0
  349. /package/dist/web/assets/{groovy-zq_SbWk-.js → groovy-DS-pne5-.js} +0 -0
  350. /package/dist/web/assets/{gruvbox-dark-hard-DbbNy8-Q.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
  351. /package/dist/web/assets/{gruvbox-dark-medium-BQZ7Kj6C.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
  352. /package/dist/web/assets/{gruvbox-dark-soft-vNavErXi.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
  353. /package/dist/web/assets/{gruvbox-light-hard-AOFbCd7i.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
  354. /package/dist/web/assets/{gruvbox-light-medium-DqtxND6H.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
  355. /package/dist/web/assets/{gruvbox-light-soft-Cg0basu6.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
  356. /package/dist/web/assets/{haskell-CcsmcOez.js → haskell-uQcywkDH.js} +0 -0
  357. /package/dist/web/assets/{haxe-BimYY9X4.js → haxe-c6bI4_GT.js} +0 -0
  358. /package/dist/web/assets/{hcl-CTrXCK40.js → hcl-CQXTiEXq.js} +0 -0
  359. /package/dist/web/assets/{hjson-CmqqU6Qg.js → hjson-ChETf2bz.js} +0 -0
  360. /package/dist/web/assets/{hlsl-CuJtX_uf.js → hlsl-BMC4JGoE.js} +0 -0
  361. /package/dist/web/assets/{horizon-BGeSjDYJ.js → horizon-BusFlhLW.js} +0 -0
  362. /package/dist/web/assets/{horizon-bright-GYMgd3R0.js → horizon-bright-2oD0Mz7W.js} +0 -0
  363. /package/dist/web/assets/{houston--JD_5r2q.js → houston-DdbbjqFg.js} +0 -0
  364. /package/dist/web/assets/{hy-C9ILOiqk.js → hy-D_SOuqw9.js} +0 -0
  365. /package/dist/web/assets/{image-gallery-BWFpZAAP.js → image-gallery-CrM5fHKy.js} +0 -0
  366. /package/dist/web/assets/{imba-B6F3yus5.js → imba-BxLOHO5I.js} +0 -0
  367. /package/dist/web/assets/{ini-BfkQswMZ.js → ini-ox-CFcg0.js} +0 -0
  368. /package/dist/web/assets/{java-DEH5avQ1.js → java-Bzo8yIal.js} +0 -0
  369. /package/dist/web/assets/{javascript-Dr4yHuV2.js → javascript-DHuz9VXn.js} +0 -0
  370. /package/dist/web/assets/{json-BuR35ewf.js → json-0_y80FD5.js} +0 -0
  371. /package/dist/web/assets/{json5-Ny9ZiJAC.js → json5-CEQ50pGM.js} +0 -0
  372. /package/dist/web/assets/{jsonc-zYVuoZkR.js → jsonc-CfrLEAKA.js} +0 -0
  373. /package/dist/web/assets/{jsonl-B9xthb_H.js → jsonl-fKbrANC2.js} +0 -0
  374. /package/dist/web/assets/{jsonnet-CCoLJbMP.js → jsonnet-DKbkPaQ2.js} +0 -0
  375. /package/dist/web/assets/{jssm-W0GDlQXi.js → jssm-Dxxx1Hqu.js} +0 -0
  376. /package/dist/web/assets/{jsx-BrLJpWC-.js → jsx-DlJL-Oj3.js} +0 -0
  377. /package/dist/web/assets/{kanagawa-dragon-CuVwt_MO.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
  378. /package/dist/web/assets/{kanagawa-lotus-B3W_617P.js → kanagawa-lotus-BHltayiE.js} +0 -0
  379. /package/dist/web/assets/{kanagawa-wave-CEx4MP5W.js → kanagawa-wave-D-KXPjw6.js} +0 -0
  380. /package/dist/web/assets/{kdl-DilRejnB.js → kdl-DvtoJdFo.js} +0 -0
  381. /package/dist/web/assets/{kotlin-Bq5JtKaa.js → kotlin-CTS4MLcx.js} +0 -0
  382. /package/dist/web/assets/{kusto-Bhp2KYog.js → kusto-HtSE_sKm.js} +0 -0
  383. /package/dist/web/assets/{laserwave-WA_7c5hu.js → laserwave-DaXZG9ge.js} +0 -0
  384. /package/dist/web/assets/{lean-AEGcXwS-.js → lean-BYtGUfWx.js} +0 -0
  385. /package/dist/web/assets/{less-NPqH-26u.js → less-BoxmU_Mk.js} +0 -0
  386. /package/dist/web/assets/{lib-BFetOULu.js → lib-BzxL1ARZ.js} +0 -0
  387. /package/dist/web/assets/{libesm-tR4XlhMj.js → libesm-ah-mr-sh.js} +0 -0
  388. /package/dist/web/assets/{light-plus-B4xW7cgY.js → light-plus-Bt5CyUuo.js} +0 -0
  389. /package/dist/web/assets/{llvm-CPyWNcDE.js → llvm-CSdk9q9V.js} +0 -0
  390. /package/dist/web/assets/{log-CeQmiQSz.js → log-B-kp_3CL.js} +0 -0
  391. /package/dist/web/assets/{logo-BO0VrjBm.js → logo-DdIsXFDO.js} +0 -0
  392. /package/dist/web/assets/{luau-B6GT9kA1.js → luau-DUBpLlzQ.js} +0 -0
  393. /package/dist/web/assets/{make-CwfmN7e_.js → make-B3JVjoW_.js} +0 -0
  394. /package/dist/web/assets/{markdown-Bbe4FlLp.js → markdown-Seon0vHs.js} +0 -0
  395. /package/dist/web/assets/{material-theme-iD9XbfsB.js → material-theme-Nq-ChX1P.js} +0 -0
  396. /package/dist/web/assets/{material-theme-darker-u7M3XGe7.js → material-theme-darker-CPV4ayXJ.js} +0 -0
  397. /package/dist/web/assets/{material-theme-lighter-BGdI48zn.js → material-theme-lighter-0kyjYqEd.js} +0 -0
  398. /package/dist/web/assets/{material-theme-ocean-_mk62F5q.js → material-theme-ocean-DGpAp5co.js} +0 -0
  399. /package/dist/web/assets/{material-theme-palenight-CxFbDyAu.js → material-theme-palenight-B0AUn0NG.js} +0 -0
  400. /package/dist/web/assets/{matlab-Z0RUE3V_.js → matlab-C2waytxs.js} +0 -0
  401. /package/dist/web/assets/{mdx-BZ5wPYzV.js → mdx-DAis6R6I.js} +0 -0
  402. /package/dist/web/assets/{mermaid-CY1_aP-h.js → mermaid-_rnT2YGD.js} +0 -0
  403. /package/dist/web/assets/{min-dark-DKWtxOre.js → min-dark-BSSjchUB.js} +0 -0
  404. /package/dist/web/assets/{min-light-e1iwHDfP.js → min-light-DomAFQsa.js} +0 -0
  405. /package/dist/web/assets/{mipsasm-BiDfQ4ir.js → mipsasm-bBgtvPyG.js} +0 -0
  406. /package/dist/web/assets/{mojo-DteFEHsj.js → mojo-Cp9BayW0.js} +0 -0
  407. /package/dist/web/assets/{monokai-BgPbE9xZ.js → monokai-CL_9gbmn.js} +0 -0
  408. /package/dist/web/assets/{moonbit-mEbRp-mA.js → moonbit-BS_fcr-P.js} +0 -0
  409. /package/dist/web/assets/{move-CogqZw7A.js → move-C79AH45U.js} +0 -0
  410. /package/dist/web/assets/{narrat-BD2qjmy1.js → narrat-CJ5s3BDf.js} +0 -0
  411. /package/dist/web/assets/{nextflow-groovy-BNX8GOD_.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
  412. /package/dist/web/assets/{night-owl-ed3GbziD.js → night-owl-Jkf9zFQK.js} +0 -0
  413. /package/dist/web/assets/{night-owl-light-B0j7O0Wn.js → night-owl-light-DRPeaG5J.js} +0 -0
  414. /package/dist/web/assets/{nix-DFDbcQQm.js → nix-D2Jet9DN.js} +0 -0
  415. /package/dist/web/assets/{nord-D93TxEQi.js → nord-CREVs22j.js} +0 -0
  416. /package/dist/web/assets/{nushell-3_H_XKja.js → nushell-CAiDi_mR.js} +0 -0
  417. /package/dist/web/assets/{objective-c-BM3u6nlm.js → objective-c-CVkmJkC1.js} +0 -0
  418. /package/dist/web/assets/{objective-cpp-DZ73h42g.js → objective-cpp-DIKDO1Ly.js} +0 -0
  419. /package/dist/web/assets/{ocaml-B1PU5gmh.js → ocaml-R9qtGQhs.js} +0 -0
  420. /package/dist/web/assets/{odin-D14L6aRL.js → odin-DTYNQqVr.js} +0 -0
  421. /package/dist/web/assets/{one-dark-pro-Bj-94Z8v.js → one-dark-pro-BSLWUJn5.js} +0 -0
  422. /package/dist/web/assets/{one-light-DTdGWyOZ.js → one-light-DbkRXG3t.js} +0 -0
  423. /package/dist/web/assets/{openscad-Ciq554Sj.js → openscad-BlPcSnmT.js} +0 -0
  424. /package/dist/web/assets/{pascal-Bfd4qdGi.js → pascal-B1Y46ZZB.js} +0 -0
  425. /package/dist/web/assets/{pkl-CTurDUfj.js → pkl-BhJSJbFv.js} +0 -0
  426. /package/dist/web/assets/{plastic-BLQfZXQp.js → plastic-BzxsCTpi.js} +0 -0
  427. /package/dist/web/assets/{plsql-DyYfP9kQ.js → plsql-DBba7dyP.js} +0 -0
  428. /package/dist/web/assets/{po-CgXdT8vQ.js → po-BvgLlnNo.js} +0 -0
  429. /package/dist/web/assets/{poimandres-DOg5fK6s.js → poimandres-DUaI_1JP.js} +0 -0
  430. /package/dist/web/assets/{polar-Dv4aoL6X.js → polar-C7uILwJn.js} +0 -0
  431. /package/dist/web/assets/{postcss-DxugHnJD.js → postcss-BgJlpFdw.js} +0 -0
  432. /package/dist/web/assets/{powerquery-CR3kzRyX.js → powerquery-Dn1MIpZu.js} +0 -0
  433. /package/dist/web/assets/{powershell-BE91xJQB.js → powershell-Bo-DYzOS.js} +0 -0
  434. /package/dist/web/assets/{prisma-B_3yUlRQ.js → prisma-CTLs5IdT.js} +0 -0
  435. /package/dist/web/assets/{prolog-DcT781Ht.js → prolog-QRGun3eG.js} +0 -0
  436. /package/dist/web/assets/{proto-CSf-w5k2.js → proto-B7PI7apn.js} +0 -0
  437. /package/dist/web/assets/{puppet-CLGO6QWl.js → puppet-CBq0tLrP.js} +0 -0
  438. /package/dist/web/assets/{purescript-Bah2McKd.js → purescript-DFewOQb2.js} +0 -0
  439. /package/dist/web/assets/{python-DUH8qy7N.js → python-iWuxKXvT.js} +0 -0
  440. /package/dist/web/assets/{qmldir-YIsHnjKq.js → qmldir-cXiW3AsF.js} +0 -0
  441. /package/dist/web/assets/{qss-CLmoVNA4.js → qss-DASwAbBL.js} +0 -0
  442. /package/dist/web/assets/{r-Pon-b-vz.js → r-TU2eaIPw.js} +0 -0
  443. /package/dist/web/assets/{racket-BA09P5Kg.js → racket-SkRKqrLY.js} +0 -0
  444. /package/dist/web/assets/{raku-DlpzU7Fe.js → raku-D31lthkZ.js} +0 -0
  445. /package/dist/web/assets/{react-w6wINGnd.js → react-I_EwPIZi.js} +0 -0
  446. /package/dist/web/assets/{red-l7VuCR6d.js → red-CTcQZevb.js} +0 -0
  447. /package/dist/web/assets/{reg-g_k7gQEr.js → reg-D8YAsTGf.js} +0 -0
  448. /package/dist/web/assets/{regexp-CT17B7I7.js → regexp-CD2uvAWx.js} +0 -0
  449. /package/dist/web/assets/{rel-DkPCS7Bp.js → rel-CAaI-1fg.js} +0 -0
  450. /package/dist/web/assets/{resolve-theme-DdB7Yo5Z.js → resolve-theme-CJvkL8hU.js} +0 -0
  451. /package/dist/web/assets/{riscv-CYuFTw30.js → riscv-DR-hJVIj.js} +0 -0
  452. /package/dist/web/assets/{ron-cY7u_yEu.js → ron-CuUCJ0n4.js} +0 -0
  453. /package/dist/web/assets/{rose-pine-Cr_h_Ubo.js → rose-pine-aFHxW48i.js} +0 -0
  454. /package/dist/web/assets/{rose-pine-dawn-rRewaSZg.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
  455. /package/dist/web/assets/{rose-pine-moon-3VX5Xwca.js → rose-pine-moon-wqyh0an4.js} +0 -0
  456. /package/dist/web/assets/{rosmsg-Du4ie5PU.js → rosmsg-CI1ywimO.js} +0 -0
  457. /package/dist/web/assets/{rust-B7xWL-Eh.js → rust-p7Imk4G_.js} +0 -0
  458. /package/dist/web/assets/{sass-oXbOG8de.js → sass-C8JfaqCV.js} +0 -0
  459. /package/dist/web/assets/{save-C4z1p51e.js → save-DRPrDfTt.js} +0 -0
  460. /package/dist/web/assets/{scala-nlZJhKHf.js → scala-DNxCW1By.js} +0 -0
  461. /package/dist/web/assets/{scheme-FZ9_Zy8D.js → scheme-BYuUPJoL.js} +0 -0
  462. /package/dist/web/assets/{sdbl-D_lyvXiu.js → sdbl-DbPeJPUr.js} +0 -0
  463. /package/dist/web/assets/{shellscript-DRWRboAd.js → shellscript-DMiHlR3q.js} +0 -0
  464. /package/dist/web/assets/{shield-check-C5laObJv.js → shield-check-8GObuUAn.js} +0 -0
  465. /package/dist/web/assets/{shield-off-DULFtyLS.js → shield-off-egKAZEwR.js} +0 -0
  466. /package/dist/web/assets/{slack-dark-BGk2uVHu.js → slack-dark-C4xGcQXI.js} +0 -0
  467. /package/dist/web/assets/{slack-ochin-PAoSX041.js → slack-ochin-BzgRt_wm.js} +0 -0
  468. /package/dist/web/assets/{smalltalk-qP0zvICo.js → smalltalk-LDeL9PYH.js} +0 -0
  469. /package/dist/web/assets/{snazzy-light-B7uccpH6.js → snazzy-light-CXE7a5j_.js} +0 -0
  470. /package/dist/web/assets/{solarized-dark-CbICLZs3.js → solarized-dark-DgUhY74x.js} +0 -0
  471. /package/dist/web/assets/{solarized-light-CprWhyjj.js → solarized-light-DD9C-H-9.js} +0 -0
  472. /package/dist/web/assets/{solidity-B7pcm2K6.js → solidity-CAbDB4D4.js} +0 -0
  473. /package/dist/web/assets/{splunk-Co2LL4J_.js → splunk-kQREPfIc.js} +0 -0
  474. /package/dist/web/assets/{sql-cETIdGnT.js → sql-CBzeNAD8.js} +0 -0
  475. /package/dist/web/assets/{ssh-config-DETG6JCD.js → ssh-config-L1euxrsM.js} +0 -0
  476. /package/dist/web/assets/{stylus-tmTre2-O.js → stylus-DvBnYniP.js} +0 -0
  477. /package/dist/web/assets/{swift-BeKQbULV.js → swift-DF5NDQp6.js} +0 -0
  478. /package/dist/web/assets/{synthwave-84-eCL4-Xdn.js → synthwave-84-DbBhB_Hq.js} +0 -0
  479. /package/dist/web/assets/{system-verilog-CviwWvEx.js → system-verilog-D8s2ZW_k.js} +0 -0
  480. /package/dist/web/assets/{systemd-WuoFIbBS.js → systemd-a5-XZJsG.js} +0 -0
  481. /package/dist/web/assets/{table-CHmxuh6M.js → table-BX2ZrJfM.js} +0 -0
  482. /package/dist/web/assets/{talonscript-B9VBu7VI.js → talonscript-BLa0URTW.js} +0 -0
  483. /package/dist/web/assets/{tasl-CN7r11ky.js → tasl-DIoV4XcC.js} +0 -0
  484. /package/dist/web/assets/{tcl-e1cdhe3h.js → tcl-ztYAdXfQ.js} +0 -0
  485. /package/dist/web/assets/{terraform-BEP0mfa_.js → terraform-DvpCCiOZ.js} +0 -0
  486. /package/dist/web/assets/{tokyo-night-BfA_dA37.js → tokyo-night-BkHlYUv0.js} +0 -0
  487. /package/dist/web/assets/{toml-BrDIfNnt.js → toml-acIrxos6.js} +0 -0
  488. /package/dist/web/assets/{tsv-CyfGyE4i.js → tsv-BJB5EVCT.js} +0 -0
  489. /package/dist/web/assets/{tsx-BrFA8Iwf.js → tsx-B1MmXGT3.js} +0 -0
  490. /package/dist/web/assets/{turtle-9GBtR0mP.js → turtle-DFceTiqd.js} +0 -0
  491. /package/dist/web/assets/{typescript-BuTRZ65V.js → typescript-Dz9wU5Y1.js} +0 -0
  492. /package/dist/web/assets/{typespec-CRiNNRXF.js → typespec-CPuYZijj.js} +0 -0
  493. /package/dist/web/assets/{typst-CmpTnnkl.js → typst-CfsCIYv8.js} +0 -0
  494. /package/dist/web/assets/{utils-B9H0d3Fh.js → utils-DoY4yTP-.js} +0 -0
  495. /package/dist/web/assets/{v-DnIi2aab.js → v-CGo0jcXA.js} +0 -0
  496. /package/dist/web/assets/{vala-B7VxCNZP.js → vala-AsSR0hrb.js} +0 -0
  497. /package/dist/web/assets/{vb-DGoQ3pBT.js → vb-C7tvbXv1.js} +0 -0
  498. /package/dist/web/assets/{vendor-xterm-ClRVIJ-k.js → vendor-xterm-BeaIjRrv.js} +0 -0
  499. /package/dist/web/assets/{verilog-zGOC4qwV.js → verilog--a33ND-p.js} +0 -0
  500. /package/dist/web/assets/{vesper-DXR9-06q.js → vesper-B_Z0h4pn.js} +0 -0
  501. /package/dist/web/assets/{vhdl-CCO0oz0l.js → vhdl-CWL5dZPN.js} +0 -0
  502. /package/dist/web/assets/{viml-Dxwr-QNN.js → viml-BglmT14Y.js} +0 -0
  503. /package/dist/web/assets/{vitesse-black-Bhah7RxE.js → vitesse-black-RKFsSqQI.js} +0 -0
  504. /package/dist/web/assets/{vitesse-dark-CGK6XVwc.js → vitesse-dark-D7zDWNmc.js} +0 -0
  505. /package/dist/web/assets/{vitesse-light-ne5kfw8h.js → vitesse-light-MAp87W5b.js} +0 -0
  506. /package/dist/web/assets/{vyper-ErDFqVoP.js → vyper-CgCBYg1G.js} +0 -0
  507. /package/dist/web/assets/{wasm-DtWvUWGr.js → wasm-BOG7ZeTT.js} +0 -0
  508. /package/dist/web/assets/{wasm-Bd258nS1.js → wasm-Bwl4txwp.js} +0 -0
  509. /package/dist/web/assets/{wenyan-EZe1IEYT.js → wenyan-DpShPKXZ.js} +0 -0
  510. /package/dist/web/assets/{wgsl-B1o5_I_9.js → wgsl-BakcCblK.js} +0 -0
  511. /package/dist/web/assets/{wikitext-C_rwp8An.js → wikitext-NroRWVAO.js} +0 -0
  512. /package/dist/web/assets/{wit-gbYtF3rY.js → wit-B5CM2pg6.js} +0 -0
  513. /package/dist/web/assets/{wolfram-DrcQFe2n.js → wolfram-7MTFBJpR.js} +0 -0
  514. /package/dist/web/assets/{x-B1qHmLdR.js → x-BcqfTY_7.js} +0 -0
  515. /package/dist/web/assets/{yaml-CzDAObLX.js → yaml-Bwr01G2K.js} +0 -0
  516. /package/dist/web/assets/{zenscript-B-J1phtg.js → zenscript-Dj8GATvi.js} +0 -0
  517. /package/dist/web/assets/{zig-BlzNia0O.js → zig-LAE-fbS-.js} +0 -0
@@ -21,17 +21,28 @@ interface WatcherCallbacks {
21
21
  onConfigUpdate: (teamName: string, config: unknown) => void;
22
22
  }
23
23
 
24
- /** Start watching a team's inboxes directory + config.json for changes */
25
- export function startTeamInboxWatcher(
24
+ /** Start watching a team's inboxes directory + config.json for changes.
25
+ * Snapshots are seeded from what is already on disk: a watcher can attach to a
26
+ * team that has been running for hours, and an unseeded snapshot would replay
27
+ * every historical message as "new" on the first file change. */
28
+ export async function startTeamInboxWatcher(
26
29
  teamName: string,
27
30
  callbacks: WatcherCallbacks,
28
- ): { watchers: FSWatcher[]; cleanup: () => void } {
31
+ ): Promise<{ watchers: FSWatcher[]; cleanup: () => void }> {
29
32
  const inboxDir = join(TEAMS_DIR, teamName, "inboxes");
30
33
  const configPath = join(TEAMS_DIR, teamName, "config.json");
31
34
  const watchers: FSWatcher[] = [];
32
35
  const inboxSnapshots = new Map<string, number>(); // filename → last known msg count
33
36
  const debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
34
37
 
38
+ // Seed from current on-disk state before the first change event lands.
39
+ for (const file of await readInboxFiles(teamName)) {
40
+ try {
41
+ const parsed = JSON.parse(await Bun.file(join(inboxDir, file)).text());
42
+ if (Array.isArray(parsed)) inboxSnapshots.set(file, parsed.length);
43
+ } catch { /* mid-write or malformed — treat as empty */ }
44
+ }
45
+
35
46
  // Watch inboxes directory
36
47
  try {
37
48
  const inboxWatcher = watch(inboxDir, (_event, filename) => {
@@ -92,6 +103,27 @@ export async function readTeamConfig(teamName: string): Promise<unknown | null>
92
103
  } catch { return null; }
93
104
  }
94
105
 
106
+ /** Inbox filenames (`<agent>.json`) for a team, empty when the dir is absent. */
107
+ async function readInboxFiles(teamName: string): Promise<string[]> {
108
+ try {
109
+ return (await readdir(join(TEAMS_DIR, teamName, "inboxes"))).filter(f => f.endsWith(".json"));
110
+ } catch { return []; }
111
+ }
112
+
113
+ /** Member record for an agent only known by its inbox filename. */
114
+ function synthesizeMember(name: string, teamName: string, status: string) {
115
+ return { name, agentId: `${name}@${teamName}`, agentType: "teammate", model: "unknown", status };
116
+ }
117
+
118
+ /** Whether a team directory holds anything usable.
119
+ * config.json is optional: Claude Code now creates the team implicitly, named
120
+ * after the session, and writes only inboxes/. Requiring a config would make
121
+ * every implicitly-created team invisible. */
122
+ export async function teamExists(teamName: string): Promise<boolean> {
123
+ if (await readTeamConfig(teamName)) return true;
124
+ return (await readInboxFiles(teamName)).length > 0;
125
+ }
126
+
95
127
  /** List all teams from ~/.claude/teams/ */
96
128
  export async function listTeams(): Promise<unknown[]> {
97
129
  try {
@@ -99,8 +131,20 @@ export async function listTeams(): Promise<unknown[]> {
99
131
  const teams = [];
100
132
  for (const entry of entries) {
101
133
  if (!entry.isDirectory()) continue;
102
- const config = await readTeamConfig(entry.name);
103
- if (config) teams.push(config);
134
+ const config = await readTeamConfig(entry.name) as any;
135
+ if (config) {
136
+ teams.push({ ...config, name: config.name ?? entry.name, implicit: false });
137
+ continue;
138
+ }
139
+ // Implicit team: membership is only discoverable from inbox filenames.
140
+ const inboxFiles = await readInboxFiles(entry.name);
141
+ if (inboxFiles.length === 0) continue;
142
+ teams.push({
143
+ name: entry.name,
144
+ team_name: entry.name,
145
+ implicit: true,
146
+ members: inboxFiles.map(f => synthesizeMember(f.replace(".json", ""), entry.name, "active")),
147
+ });
104
148
  }
105
149
  return teams;
106
150
  } catch { return []; }
@@ -109,14 +153,15 @@ export async function listTeams(): Promise<unknown[]> {
109
153
  /** Read team detail with merged inbox messages + inferred member status */
110
154
  export async function readTeamDetail(teamName: string): Promise<unknown | null> {
111
155
  const config = await readTeamConfig(teamName) as any;
112
- if (!config) return null;
113
-
114
156
  const inboxDir = join(TEAMS_DIR, teamName, "inboxes");
157
+ const inboxFiles = await readInboxFiles(teamName);
158
+ // Only a directory with neither a config nor any inbox is "not a team".
159
+ if (!config && inboxFiles.length === 0) return null;
160
+ const base = config ?? { name: teamName, team_name: teamName, implicit: true };
161
+
115
162
  const messages: unknown[] = [];
116
- let inboxFiles: string[] = [];
117
- try {
118
- inboxFiles = (await readdir(inboxDir)).filter(f => f.endsWith(".json"));
119
- for (const file of inboxFiles) {
163
+ for (const file of inboxFiles) {
164
+ try {
120
165
  const content = await Bun.file(join(inboxDir, file)).text();
121
166
  const agentName = file.replace(".json", "");
122
167
  const parsed = JSON.parse(content);
@@ -127,8 +172,8 @@ export async function readTeamDetail(teamName: string): Promise<unknown | null>
127
172
  parsedType: inferMessageType(m.text ?? ""),
128
173
  })));
129
174
  }
130
- }
131
- } catch { /* no inboxes dir */ }
175
+ } catch { /* mid-write or malformed inbox — skip this agent */ }
176
+ }
132
177
 
133
178
  // Sort by timestamp
134
179
  messages.sort((a: any, b: any) =>
@@ -136,7 +181,7 @@ export async function readTeamDetail(teamName: string): Promise<unknown | null>
136
181
  );
137
182
 
138
183
  // Infer member status from inboxes
139
- const members = (config.members ?? []).map((m: any) => ({
184
+ const members = (base.members ?? []).map((m: any) => ({
140
185
  ...m,
141
186
  status: inferMemberStatus(messages, m.name),
142
187
  }));
@@ -145,17 +190,11 @@ export async function readTeamDetail(teamName: string): Promise<unknown | null>
145
190
  for (const file of inboxFiles) {
146
191
  const name = file.replace(".json", "");
147
192
  if (!members.some((m: any) => m.name === name)) {
148
- members.push({
149
- name,
150
- agentId: `${name}@${teamName}`,
151
- agentType: "teammate",
152
- model: "unknown",
153
- status: inferMemberStatus(messages, name),
154
- });
193
+ members.push(synthesizeMember(name, teamName, inferMemberStatus(messages, name)));
155
194
  }
156
195
  }
157
196
 
158
- return { ...config, members, messages, memberCount: members.length };
197
+ return { ...base, name: base.name ?? teamName, members, messages, memberCount: members.length };
159
198
  }
160
199
 
161
200
  function inferMemberStatus(messages: unknown[], agentName: string): string {
@@ -1,5 +1,23 @@
1
+ import { statSync } from "node:fs";
1
2
  import { terminalService } from "../../services/terminal.service.ts";
2
3
  import { resolveProjectPath } from "../helpers/resolve-project.ts";
4
+ import { assertAllowed, resolvePath } from "../../services/fs-path-guard.service.ts";
5
+
6
+ /**
7
+ * Where a new shell starts. An explicit `cwd` (explorer "Open in Terminal") wins
8
+ * over the project root and goes through the same allowlist as the filesystem
9
+ * API, so a terminal cannot be spawned anywhere the explorer could not browse.
10
+ */
11
+ function resolveStartDir(projectName: string | undefined, cwd: string | undefined): string {
12
+ if (cwd) {
13
+ const resolved = resolvePath(cwd);
14
+ assertAllowed(resolved);
15
+ if (!statSync(resolved).isDirectory()) throw new Error("Not a directory");
16
+ return resolved;
17
+ }
18
+ if (projectName) return resolveProjectPath(projectName);
19
+ throw new Error("Session not found");
20
+ }
3
21
 
4
22
  /** Control message prefix for resize commands */
5
23
  const RESIZE_PREFIX = "\x01RESIZE:";
@@ -13,18 +31,18 @@ const PONG_MSG = "\x01PONG";
13
31
  * Handles terminal session attach, input, resize, and disconnect.
14
32
  */
15
33
  export const terminalWebSocket = {
16
- open(ws: { data: { type: string; id: string; projectName?: string }; send: (data: string) => void }) {
17
- const { id, projectName } = ws.data;
34
+ open(ws: { data: { type: string; id: string; projectName?: string; cwd?: string }; send: (data: string) => void }) {
35
+ const { id, projectName, cwd } = ws.data;
18
36
 
19
37
  let session = id !== "new" ? terminalService.get(id) : undefined;
20
38
 
21
- // If session doesn't exist and projectName is provided, create one
22
- if (!session && projectName) {
39
+ // If session doesn't exist and a start directory is known, create one
40
+ if (!session && (projectName || cwd)) {
23
41
  try {
24
- const projectPath = resolveProjectPath(projectName);
42
+ const startDir = resolveStartDir(projectName, cwd);
25
43
  // Create session with the requested ID — but TerminalService generates its own ID.
26
44
  // Instead, create and return the new session ID to client.
27
- const newId = terminalService.create(projectPath);
45
+ const newId = terminalService.create(startDir);
28
46
  session = terminalService.get(newId);
29
47
  if (session) {
30
48
  // Update ws.data to reflect actual session ID
@@ -0,0 +1,120 @@
1
+ import type { Stats } from "node:fs";
2
+ import { lstat, mkdir, rename, rm } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import {
5
+ assertAllowed,
6
+ assertNotPpmSubtreeDeep,
7
+ assertNotProtected,
8
+ resolvePath,
9
+ } from "../fs-path-guard.service.ts";
10
+ import { eexist } from "./fs-core-ops.ts";
11
+
12
+ export interface UploadResult {
13
+ path: string;
14
+ size: number;
15
+ }
16
+
17
+ /**
18
+ * Suffix for the in-progress write. The stream lands here first and is only renamed onto
19
+ * the real name once it ends cleanly, so a client abort or a server crash mid-transfer never
20
+ * leaves a truncated file where a complete one used to be (or overwrites one at all).
21
+ */
22
+ const TMP_SUFFIX = ".ppm-upload-tmp";
23
+
24
+ async function lstatOrNull(path: string): Promise<Stats | null> {
25
+ try {
26
+ return await lstat(path);
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Pumps a web `ReadableStream` into a file via Bun's incremental writer, chunk by chunk. Reads
34
+ * through `getReader()` rather than `for await`: the DOM lib's `ReadableStream` type does not
35
+ * (yet) declare `Symbol.asyncIterator`, even though Bun implements it at runtime.
36
+ */
37
+ async function pumpToFile(path: string, body: ReadableStream<Uint8Array>): Promise<number> {
38
+ const sink = Bun.file(path).writer();
39
+ const reader = body.getReader();
40
+ let size = 0;
41
+ for (;;) {
42
+ const { done, value } = await reader.read();
43
+ if (done) break;
44
+ sink.write(value);
45
+ size += value.byteLength;
46
+ }
47
+ await sink.end();
48
+ return size;
49
+ }
50
+
51
+ /**
52
+ * Stream an upload body straight to disk at `path`. `body` is the raw web `ReadableStream`
53
+ * off the request, pumped chunk-by-chunk into a Bun `FileSink` — there is no per-file size
54
+ * limit here, disk is the only bound, and nothing sits fully in memory.
55
+ *
56
+ * This does *not* use `Bun.write(path, new Response(body))`, the more obvious one-liner:
57
+ * verified against a real `Bun.serve()` request on this host (Bun 1.3.10, Windows), that call
58
+ * never resolves — the handler enters it and simply hangs until the client's own timeout,
59
+ * even for a body a few bytes long. A `FileSink` fed by the same stream's async iterator
60
+ * completes normally, so that is the write path here; only `Bun.write` itself is implicated
61
+ * (the round-trip through `Response` and back is the one thing this path skips).
62
+ *
63
+ * `FileSink` has no `createPath` option (unlike `Bun.write`), so missing parent directories
64
+ * are created explicitly. The immediate parent existing as a *file* is checked before that
65
+ * `mkdir`: libuv reports it as `ENOTDIR` on POSIX but `EEXIST` on Windows, and the client-facing
66
+ * answer for "there's a file in the way" should not depend on the host OS.
67
+ */
68
+ export async function uploadFile(
69
+ path: string,
70
+ body: ReadableStream<Uint8Array> | null,
71
+ overwrite: boolean,
72
+ ): Promise<UploadResult> {
73
+ if (!body) {
74
+ throw Object.assign(new Error("Missing request body"), { status: 400, code: "EINVAL" });
75
+ }
76
+ const target = resolvePath(path);
77
+ assertAllowed(target);
78
+ // Uploading into the PPM directory would let a client overwrite the config database, the
79
+ // auth token or the stored provider credentials.
80
+ await assertNotPpmSubtreeDeep(target);
81
+ await assertNotProtected(target);
82
+
83
+ const existing = await lstatOrNull(target);
84
+ if (existing) {
85
+ if (existing.isDirectory()) {
86
+ throw Object.assign(new Error(`${target} is a directory`), { status: 400, code: "EISDIR" });
87
+ }
88
+ if (!overwrite) throw eexist(target);
89
+ }
90
+
91
+ const parentStat = await lstatOrNull(dirname(target));
92
+ if (parentStat && !parentStat.isDirectory()) {
93
+ throw Object.assign(new Error(`${dirname(target)} is not a directory`), {
94
+ status: 400,
95
+ code: "ENOTDIR",
96
+ });
97
+ }
98
+
99
+ const tmpPath = `${target}${TMP_SUFFIX}`;
100
+ await mkdir(dirname(tmpPath), { recursive: true });
101
+
102
+ let size: number;
103
+ try {
104
+ size = await pumpToFile(tmpPath, body);
105
+ } catch (e) {
106
+ await rm(tmpPath, { force: true }).catch(() => {});
107
+ throw e;
108
+ }
109
+
110
+ try {
111
+ // `rename` already replaces an existing *file* destination on both POSIX and Windows —
112
+ // the collision was already refused above unless the caller opted into `overwrite`.
113
+ await rename(tmpPath, target);
114
+ } catch (e) {
115
+ await rm(tmpPath, { force: true }).catch(() => {});
116
+ throw e;
117
+ }
118
+
119
+ return { path: target, size };
120
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Discover ffmpeg/ffprobe on this machine and pick the fastest working H.264 encoder.
3
+ *
4
+ * ffmpeg is an *optional* dependency: without it PPM still plays browser-native
5
+ * media (mp4/webm) via Range requests, it just cannot transcode AVI/MKV. The
6
+ * result is cached for the process lifetime — the binary does not appear
7
+ * mid-session, and probing hardware encoders costs a real encode each.
8
+ *
9
+ * Hardware encoders are verified by encoding a few synthetic frames rather than by
10
+ * grepping `ffmpeg -encoders`: the list only says the build was compiled with
11
+ * NVENC/QSV support, not that a compatible GPU/driver is present.
12
+ */
13
+
14
+ export interface FfmpegCapabilities {
15
+ ffmpeg: string | null;
16
+ ffprobe: string | null;
17
+ /** ffmpeg encoder name (`h264_nvenc`, `libx264`…) or null when nothing encodes. */
18
+ encoder: string | null;
19
+ }
20
+
21
+ /** Candidate encoders in preference order; each is test-encoded before being trusted. */
22
+ function encoderCandidates(): string[] {
23
+ switch (process.platform) {
24
+ case "win32": return ["h264_nvenc", "h264_qsv", "h264_amf", "libx264"];
25
+ case "darwin": return ["h264_videotoolbox", "libx264"];
26
+ default: return ["h264_nvenc", "h264_qsv", "libx264"];
27
+ }
28
+ }
29
+
30
+ /** Per-encoder quality/speed flags tuned for "watch a file inside the IDE" latency. */
31
+ export function encoderArgs(encoder: string): string[] {
32
+ switch (encoder) {
33
+ case "h264_nvenc": return ["-c:v", "h264_nvenc", "-preset", "p3", "-rc", "vbr", "-cq", "26", "-b:v", "0"];
34
+ case "h264_qsv": return ["-c:v", "h264_qsv", "-preset", "veryfast", "-global_quality", "26"];
35
+ case "h264_amf": return ["-c:v", "h264_amf", "-quality", "speed", "-rc", "cqp", "-qp_i", "24", "-qp_p", "26"];
36
+ case "h264_videotoolbox": return ["-c:v", "h264_videotoolbox", "-b:v", "6M", "-realtime", "1"];
37
+ default: return ["-c:v", "libx264", "-preset", "veryfast", "-crf", "23", "-maxrate", "8M", "-bufsize", "16M"];
38
+ }
39
+ }
40
+
41
+ async function encoderWorks(ffmpeg: string, encoder: string): Promise<boolean> {
42
+ try {
43
+ const proc = Bun.spawn(
44
+ [
45
+ ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
46
+ // 256px: hardware encoders reject frames below ~145×49 (NVENC "Frame Dimension less than minimum").
47
+ "-f", "lavfi", "-i", "color=c=black:s=256x256:r=30:d=0.2",
48
+ "-frames:v", "3", "-pix_fmt", "yuv420p", ...encoderArgs(encoder),
49
+ "-f", "null", "-",
50
+ ],
51
+ { stdout: "ignore", stderr: "ignore", stdin: "ignore" },
52
+ );
53
+ // A hung driver must not stall the whole capability probe.
54
+ const timer = setTimeout(() => proc.kill(), 10_000);
55
+ const code = await proc.exited;
56
+ clearTimeout(timer);
57
+ return code === 0;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ let cached: Promise<FfmpegCapabilities> | null = null;
64
+
65
+ async function detect(): Promise<FfmpegCapabilities> {
66
+ const ffmpeg = Bun.which("ffmpeg");
67
+ const ffprobe = Bun.which("ffprobe");
68
+ if (!ffmpeg) return { ffmpeg: null, ffprobe, encoder: null };
69
+ for (const candidate of encoderCandidates()) {
70
+ if (await encoderWorks(ffmpeg, candidate)) return { ffmpeg, ffprobe, encoder: candidate };
71
+ }
72
+ return { ffmpeg, ffprobe, encoder: null };
73
+ }
74
+
75
+ /** Cached capability lookup; the first call pays for the encoder probes. */
76
+ export function getFfmpegCapabilities(): Promise<FfmpegCapabilities> {
77
+ if (!cached) {
78
+ cached = detect().catch((e) => {
79
+ cached = null; // let a later call retry after an unexpected failure
80
+ throw e;
81
+ });
82
+ }
83
+ return cached;
84
+ }
85
+
86
+ /** Test hook: drop the cache so a different PATH can be probed. */
87
+ export function resetFfmpegCapabilitiesCache(): void {
88
+ cached = null;
89
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Read container/codec facts about a media file with ffprobe.
3
+ *
4
+ * The transcoded stream the player receives is a live fragmented MP4 with no
5
+ * duration, so the UI needs the real duration from here to draw a seek bar.
6
+ */
7
+ import { getFfmpegCapabilities } from "./ffmpeg-capabilities.ts";
8
+
9
+ export interface MediaStreamInfo {
10
+ codec: string;
11
+ width?: number;
12
+ height?: number;
13
+ }
14
+
15
+ export interface MediaProbe {
16
+ /** Seconds; null when ffprobe could not determine it. */
17
+ duration: number | null;
18
+ container: string | null;
19
+ video: MediaStreamInfo | null;
20
+ audio: MediaStreamInfo | null;
21
+ }
22
+
23
+ interface FfprobeJson {
24
+ format?: { format_name?: string; duration?: string };
25
+ streams?: Array<{ codec_type?: string; codec_name?: string; width?: number; height?: number }>;
26
+ }
27
+
28
+ /** Returns null when ffprobe is unavailable or the file is not media it understands. */
29
+ export async function probeMedia(absPath: string): Promise<MediaProbe | null> {
30
+ const { ffprobe } = await getFfmpegCapabilities();
31
+ if (!ffprobe) return null;
32
+
33
+ const proc = Bun.spawn(
34
+ [
35
+ ffprobe, "-v", "error", "-of", "json",
36
+ "-show_entries", "format=format_name,duration:stream=codec_type,codec_name,width,height",
37
+ absPath,
38
+ ],
39
+ { stdout: "pipe", stderr: "ignore", stdin: "ignore" },
40
+ );
41
+ const timer = setTimeout(() => proc.kill(), 15_000);
42
+ const [text, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
43
+ clearTimeout(timer);
44
+ if (code !== 0) return null;
45
+
46
+ let json: FfprobeJson;
47
+ try {
48
+ json = JSON.parse(text);
49
+ } catch {
50
+ return null;
51
+ }
52
+ const pick = (type: string): MediaStreamInfo | null => {
53
+ const s = json.streams?.find((x) => x.codec_type === type && x.codec_name);
54
+ return s ? { codec: s.codec_name!, width: s.width, height: s.height } : null;
55
+ };
56
+ const duration = Number(json.format?.duration);
57
+ return {
58
+ duration: Number.isFinite(duration) && duration > 0 ? duration : null,
59
+ container: json.format?.format_name ?? null,
60
+ video: pick("video"),
61
+ audio: pick("audio"),
62
+ };
63
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Spawn ffmpeg to re-encode a file the browser cannot play (AVI/MJPEG, MKV/AC3, HEVC…)
3
+ * into a fragmented MP4 that streams to `<video>` while it is still being produced.
4
+ *
5
+ * Why fragmented MP4 rather than HLS: a single progressive pipe needs no segmenter,
6
+ * no playlist, no temp files, and starts within a second. The trade-off is that the
7
+ * stream has no duration and cannot be seeked natively — the client handles seeking
8
+ * by re-requesting with `start`, which becomes `-ss` *before* `-i` (fast keyframe
9
+ * seek; exact-frame seeking would decode from the file start every time).
10
+ *
11
+ * Every spawned process is tied to the response body: when the browser drops the
12
+ * connection (tab closed, seek, next file) the stream is cancelled and only that PID
13
+ * is killed — never a blanket kill by image name.
14
+ */
15
+ import { encoderArgs, getFfmpegCapabilities } from "./ffmpeg-capabilities.ts";
16
+
17
+ /** Parallel ffmpeg jobs allowed. A seek briefly overlaps old + new job, so allow a little slack. */
18
+ export const MAX_CONCURRENT_TRANSCODES = 3;
19
+ let active = 0;
20
+
21
+ /** How many transcodes are running right now (exposed for tests/diagnostics). */
22
+ export function activeTranscodeCount(): number {
23
+ return active;
24
+ }
25
+
26
+ export class TranscodeUnavailableError extends Error {
27
+ constructor(msg = "ffmpeg is not installed or has no working H.264 encoder") {
28
+ super(msg);
29
+ this.name = "TranscodeUnavailableError";
30
+ }
31
+ }
32
+
33
+ export class TranscodeBusyError extends Error {
34
+ constructor() {
35
+ super(`Too many concurrent transcodes (max ${MAX_CONCURRENT_TRANSCODES})`);
36
+ this.name = "TranscodeBusyError";
37
+ }
38
+ }
39
+
40
+ export interface TranscodeOptions {
41
+ /** Seconds to skip before encoding. */
42
+ start?: number;
43
+ /** Aborts the job when the HTTP request is aborted. */
44
+ signal?: AbortSignal;
45
+ }
46
+
47
+ /** Build the ffmpeg argv; pure so tests can assert on it without spawning. */
48
+ export function buildTranscodeArgs(ffmpeg: string, absPath: string, encoder: string, start = 0): string[] {
49
+ return [
50
+ ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
51
+ ...(start > 0 ? ["-ss", start.toFixed(3)] : []),
52
+ "-i", absPath,
53
+ // First video + first audio track; `?` makes the audio map optional (silent clips).
54
+ "-map", "0:v:0", "-map", "0:a:0?",
55
+ // Browsers only decode 4:2:0 H.264; cap at 1080p keeping even dimensions.
56
+ "-vf", "scale='min(1920,iw)':-2", "-pix_fmt", "yuv420p",
57
+ ...encoderArgs(encoder),
58
+ // 2s keyframe interval at 30fps → fragments start quickly after a seek.
59
+ "-g", "60",
60
+ "-c:a", "aac", "-b:a", "128k", "-ac", "2",
61
+ "-movflags", "frag_keyframe+empty_moov+default_base_moof",
62
+ "-f", "mp4", "pipe:1",
63
+ ];
64
+ }
65
+
66
+ export interface TranscodeJob {
67
+ stream: ReadableStream<Uint8Array>;
68
+ encoder: string;
69
+ }
70
+
71
+ /** Start a transcode; the returned stream is the fMP4 body to hand to `Response`. */
72
+ export async function startTranscode(absPath: string, opts: TranscodeOptions = {}): Promise<TranscodeJob> {
73
+ const caps = await getFfmpegCapabilities();
74
+ if (!caps.ffmpeg || !caps.encoder) throw new TranscodeUnavailableError();
75
+ if (active >= MAX_CONCURRENT_TRANSCODES) throw new TranscodeBusyError();
76
+
77
+ const proc = Bun.spawn(buildTranscodeArgs(caps.ffmpeg, absPath, caps.encoder, opts.start), {
78
+ stdout: "pipe",
79
+ stderr: "pipe",
80
+ stdin: "ignore",
81
+ });
82
+ active++;
83
+ let finished = false;
84
+ const finish = () => {
85
+ if (finished) return;
86
+ finished = true;
87
+ active--;
88
+ };
89
+
90
+ // Drain stderr so ffmpeg never blocks on a full pipe; keep the tail for the log.
91
+ const stderrTail = new Response(proc.stderr).text().catch(() => "");
92
+ proc.exited.then(async (code) => {
93
+ finish();
94
+ if (code !== 0 && code !== null && !proc.killed) {
95
+ const tail = (await stderrTail).trim().split("\n").slice(-3).join(" | ");
96
+ console.warn(`[transcode] ffmpeg exited ${code} for ${absPath}: ${tail}`);
97
+ }
98
+ });
99
+
100
+ const kill = () => {
101
+ if (!proc.killed) proc.kill();
102
+ finish();
103
+ };
104
+ opts.signal?.addEventListener("abort", kill, { once: true });
105
+
106
+ // Wrap stdout so a cancelled response body (client went away) kills this process.
107
+ //
108
+ // The wrapper is not optional: handing `proc.stdout` straight to `Response`, or calling
109
+ // `reader.cancel()` on it, segfaults Bun 1.3.x on Windows the moment the client
110
+ // disconnects (guarded by tests/integration/transcode-stream-client-disconnect.test.ts).
111
+ // Killing the process is enough — its stdout then ends and the pending read resolves `done`.
112
+ const reader = proc.stdout.getReader();
113
+ const stream = new ReadableStream<Uint8Array>({
114
+ async pull(controller) {
115
+ const { done, value } = await reader.read();
116
+ if (done) {
117
+ try { controller.close(); } catch { /* already cancelled */ }
118
+ finish();
119
+ return;
120
+ }
121
+ controller.enqueue(value);
122
+ },
123
+ cancel() {
124
+ kill();
125
+ },
126
+ });
127
+
128
+ return { stream, encoder: caps.encoder };
129
+ }
@@ -78,7 +78,7 @@ function indexAgentTranscripts(subagentsDir: string): Map<string, string> {
78
78
  }
79
79
 
80
80
  /** Parse one agent transcript into a flat children event list (stream order). */
81
- function parseAgentTranscript(filePath: string): ChatEvent[] {
81
+ export function parseAgentTranscript(filePath: string): ChatEvent[] {
82
82
  const children: ChatEvent[] = [];
83
83
  let text: string;
84
84
  try {