@hienlh/ppm 0.23.8 → 0.23.10

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 (448) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/CLAUDE.md +4 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/cli-reference.md +4 -4
  5. package/assets/skills/ppm/references/http-api.md +1 -1
  6. package/dist/web/assets/accounts-settings-section-BOBJbG7i.js +1 -0
  7. package/dist/web/assets/accounts-settings-section-BOBJbG7i.js.br +0 -0
  8. package/dist/web/assets/accounts-settings-section-BOBJbG7i.js.gz +0 -0
  9. package/dist/web/assets/agent-session-sheet-CjD4hBZV.js +2 -0
  10. package/dist/web/assets/agent-session-sheet-CjD4hBZV.js.br +0 -0
  11. package/dist/web/assets/agent-session-sheet-CjD4hBZV.js.gz +0 -0
  12. package/dist/web/assets/agent-session-sheet-store-BcQbnaV2.js +1 -0
  13. package/dist/web/assets/agent-session-window-content-Drm1HsSn.js +1 -0
  14. package/dist/web/assets/agent-session-window-content-Drm1HsSn.js.br +0 -0
  15. package/dist/web/assets/agent-session-window-content-Drm1HsSn.js.gz +0 -0
  16. package/dist/web/assets/{ai-resource-editor-DfVRMUHS.js → ai-resource-editor-M1Eax2Hc.js} +1 -1
  17. package/dist/web/assets/ai-resource-editor-M1Eax2Hc.js.br +0 -0
  18. package/dist/web/assets/ai-resource-editor-M1Eax2Hc.js.gz +0 -0
  19. package/dist/web/assets/ai-settings-section-B73t9SXm.js +1 -0
  20. package/dist/web/assets/{ai-settings-section-Ezhw4Fef.js → ai-settings-section-H173dm1k.js} +1 -1
  21. package/dist/web/assets/ai-settings-section-H173dm1k.js.br +0 -0
  22. package/dist/web/assets/ai-settings-section-H173dm1k.js.gz +0 -0
  23. package/dist/web/assets/{api-settings-tYKJ5Fqk.js → api-settings-BA_n8cPt.js} +1 -1
  24. package/dist/web/assets/{appearance-settings-section-CPOAp0fY.js → appearance-settings-section-Dqu20pt8.js} +1 -1
  25. package/dist/web/assets/appearance-settings-section-Dqu20pt8.js.br +0 -0
  26. package/dist/web/assets/appearance-settings-section-Dqu20pt8.js.gz +0 -0
  27. package/dist/web/assets/{apps-panel-Cz7_o-M4.js → apps-panel-mkrzY7K0.js} +1 -1
  28. package/dist/web/assets/apps-panel-mkrzY7K0.js.br +0 -0
  29. package/dist/web/assets/apps-panel-mkrzY7K0.js.gz +0 -0
  30. package/dist/web/assets/branch-review-tab-Dw3bNixU.js +1 -0
  31. package/dist/web/assets/branch-review-tab-Dw3bNixU.js.br +0 -0
  32. package/dist/web/assets/branch-review-tab-Dw3bNixU.js.gz +0 -0
  33. package/dist/web/assets/chat-tab-BAbcVFzD.js +15 -0
  34. package/dist/web/assets/chat-tab-BAbcVFzD.js.br +0 -0
  35. package/dist/web/assets/chat-tab-BAbcVFzD.js.gz +0 -0
  36. package/dist/web/assets/chat-tab-BaXGaBEC.js +1 -0
  37. package/dist/web/assets/chat-tab-BaXGaBEC.js.br +4 -0
  38. package/dist/web/assets/chat-tab-BaXGaBEC.js.gz +0 -0
  39. package/dist/web/assets/code-editor-CXLANXnI.js +7 -0
  40. package/dist/web/assets/code-editor-CXLANXnI.js.br +0 -0
  41. package/dist/web/assets/code-editor-CXLANXnI.js.gz +0 -0
  42. package/dist/web/assets/{codex-reset-credit-button-D2hal4bG.js → codex-reset-credit-button-CB8Dfhki.js} +1 -1
  43. package/dist/web/assets/codex-reset-credit-button-CB8Dfhki.js.br +0 -0
  44. package/dist/web/assets/codex-reset-credit-button-CB8Dfhki.js.gz +0 -0
  45. package/dist/web/assets/{conflict-editor-Cj_gUncw.js → conflict-editor-CKsxIw3v.js} +2 -2
  46. package/dist/web/assets/conflict-editor-CKsxIw3v.js.br +0 -0
  47. package/dist/web/assets/conflict-editor-CKsxIw3v.js.gz +0 -0
  48. package/dist/web/assets/{csv-preview-Be3ODNj0.js → csv-preview-U0v_r_-7.js} +1 -1
  49. package/dist/web/assets/csv-preview-U0v_r_-7.js.br +0 -0
  50. package/dist/web/assets/csv-preview-U0v_r_-7.js.gz +0 -0
  51. package/dist/web/assets/{database-viewer-B1b--4y8.js → database-viewer-xQyUcBoL.js} +1 -1
  52. package/dist/web/assets/database-viewer-xQyUcBoL.js.br +0 -0
  53. package/dist/web/assets/database-viewer-xQyUcBoL.js.gz +0 -0
  54. package/dist/web/assets/{design-tab-8wi0IHss.js → design-tab-aaAt2_QE.js} +4 -4
  55. package/dist/web/assets/design-tab-aaAt2_QE.js.br +0 -0
  56. package/dist/web/assets/design-tab-aaAt2_QE.js.gz +0 -0
  57. package/dist/web/assets/{designs-sidebar-panel-D5-1Q8zQ.js → designs-sidebar-panel-CTzO4Thu.js} +1 -1
  58. package/dist/web/assets/designs-sidebar-panel-CTzO4Thu.js.br +0 -0
  59. package/dist/web/assets/designs-sidebar-panel-CTzO4Thu.js.gz +0 -0
  60. package/dist/web/assets/{diff-viewer-Dcxk6UTG.js → diff-viewer-CSE2-OE7.js} +1 -1
  61. package/dist/web/assets/diff-viewer-CSE2-OE7.js.br +0 -0
  62. package/dist/web/assets/diff-viewer-CSE2-OE7.js.gz +0 -0
  63. package/dist/web/assets/{edit-diff-preview-DxDAtYhi.js → edit-diff-preview-gH6pRk4S.js} +1 -1
  64. package/dist/web/assets/edit-diff-preview-gH6pRk4S.js.br +0 -0
  65. package/dist/web/assets/{edit-diff-preview-DxDAtYhi.js.gz → edit-diff-preview-gH6pRk4S.js.gz} +0 -0
  66. package/dist/web/assets/{editor-language-service-BVJQnTSV.js → editor-language-service-CYl04pUy.js} +1 -1
  67. package/dist/web/assets/editor-language-service-CYl04pUy.js.br +0 -0
  68. package/dist/web/assets/editor-language-service-CYl04pUy.js.gz +0 -0
  69. package/dist/web/assets/explorer-body-BBOcvwmV.js +2 -0
  70. package/dist/web/assets/explorer-body-BBOcvwmV.js.br +0 -0
  71. package/dist/web/assets/explorer-body-BBOcvwmV.js.gz +0 -0
  72. package/dist/web/assets/{explorer-window-content-DH5AD3Ng.js → explorer-window-content-juCGcrkc.js} +1 -1
  73. package/dist/web/assets/explorer-window-content-juCGcrkc.js.br +0 -0
  74. package/dist/web/assets/explorer-window-content-juCGcrkc.js.gz +0 -0
  75. package/dist/web/assets/{extension-manager-section-BJr6ccWw.js → extension-manager-section-poMRmwvg.js} +1 -1
  76. package/dist/web/assets/extension-manager-section-poMRmwvg.js.br +0 -0
  77. package/dist/web/assets/extension-manager-section-poMRmwvg.js.gz +0 -0
  78. package/dist/web/assets/{extension-webview-CYNXHd8D.js → extension-webview-0QfzTI6s.js} +1 -1
  79. package/dist/web/assets/extension-webview-0QfzTI6s.js.br +0 -0
  80. package/dist/web/assets/extension-webview-0QfzTI6s.js.gz +0 -0
  81. package/dist/web/assets/{files-settings-section-JX7o4lex.js → files-settings-section-DJptxRgH.js} +1 -1
  82. package/dist/web/assets/files-settings-section-DJptxRgH.js.br +0 -0
  83. package/dist/web/assets/files-settings-section-DJptxRgH.js.gz +0 -0
  84. package/dist/web/assets/{general-settings-section-kF94Mz5q.js → general-settings-section-BFNbdDNv.js} +1 -1
  85. package/dist/web/assets/general-settings-section-BFNbdDNv.js.br +0 -0
  86. package/dist/web/assets/general-settings-section-BFNbdDNv.js.gz +0 -0
  87. package/dist/web/assets/{git-log-panel-Crlp9ofM.js → git-log-panel-SMMIbjSe.js} +1 -1
  88. package/dist/web/assets/git-log-panel-SMMIbjSe.js.br +0 -0
  89. package/dist/web/assets/git-log-panel-SMMIbjSe.js.gz +0 -0
  90. package/dist/web/assets/{glide-data-grid-O6MO5dKU.js → glide-data-grid-DZVmgu0T.js} +3 -3
  91. package/dist/web/assets/glide-data-grid-DZVmgu0T.js.br +0 -0
  92. package/dist/web/assets/glide-data-grid-DZVmgu0T.js.gz +0 -0
  93. package/dist/web/assets/group-chat-tab-d_aaYuK_.js +1 -0
  94. package/dist/web/assets/group-chat-tab-d_aaYuK_.js.br +0 -0
  95. package/dist/web/assets/group-chat-tab-d_aaYuK_.js.gz +0 -0
  96. package/dist/web/assets/{index-DQCw6d-2.js → index-CIscPw4g.js} +6 -6
  97. package/dist/web/assets/index-CIscPw4g.js.br +0 -0
  98. package/dist/web/assets/index-CIscPw4g.js.gz +0 -0
  99. package/dist/web/assets/{jira-watcher-section-DwM7gqHT.js → jira-watcher-section-CsbhnLLn.js} +1 -1
  100. package/dist/web/assets/keybindings-store-BPs0pM0r.js +1 -0
  101. package/dist/web/assets/{keyboard-shortcuts-section-CewWGwiF.js → keyboard-shortcuts-section-CXqc2OtM.js} +1 -1
  102. package/dist/web/assets/keyboard-shortcuts-section-CXqc2OtM.js.br +0 -0
  103. package/dist/web/assets/keyboard-shortcuts-section-CXqc2OtM.js.gz +0 -0
  104. package/dist/web/assets/{language-servers-section-nricdxke.js → language-servers-section-B4FsBVvZ.js} +2 -2
  105. package/dist/web/assets/language-servers-section-B4FsBVvZ.js.br +0 -0
  106. package/dist/web/assets/language-servers-section-B4FsBVvZ.js.gz +0 -0
  107. package/dist/web/assets/{lsp-client-DRMEWb-z.js → lsp-client-MQ4mjDfT.js} +1 -1
  108. package/dist/web/assets/lsp-client-MQ4mjDfT.js.br +0 -0
  109. package/dist/web/assets/lsp-client-MQ4mjDfT.js.gz +0 -0
  110. package/dist/web/assets/markdown-renderer-BvCZZlue.js +1 -0
  111. package/dist/web/assets/{markdown-renderer-Dab9Us6u.js → markdown-renderer-CjPb5pns.js} +3 -3
  112. package/dist/web/assets/markdown-renderer-CjPb5pns.js.br +0 -0
  113. package/dist/web/assets/markdown-renderer-CjPb5pns.js.gz +0 -0
  114. package/dist/web/assets/{mobile-explorer-sheet-DtgdpvJ9.js → mobile-explorer-sheet-0aGhVQtI.js} +1 -1
  115. package/dist/web/assets/mobile-explorer-sheet-0aGhVQtI.js.br +0 -0
  116. package/dist/web/assets/mobile-explorer-sheet-0aGhVQtI.js.gz +0 -0
  117. package/dist/web/assets/{new-design-dialog-Dd5KDB_p.js → new-design-dialog-2oT2oc_F.js} +1 -1
  118. package/dist/web/assets/new-design-dialog-2oT2oc_F.js.br +0 -0
  119. package/dist/web/assets/new-design-dialog-2oT2oc_F.js.gz +0 -0
  120. package/dist/web/assets/notification-store-Dd25PMPQ.js +1 -0
  121. package/dist/web/assets/{panel-store-DrSH3Nhp.js → panel-store-1X6yM3XZ.js} +1 -1
  122. package/dist/web/assets/panel-store-1X6yM3XZ.js.br +0 -0
  123. package/dist/web/assets/panel-store-1X6yM3XZ.js.gz +0 -0
  124. package/dist/web/assets/performance-panel-Ub96qrEH.js +3 -0
  125. package/dist/web/assets/performance-panel-Ub96qrEH.js.br +0 -0
  126. package/dist/web/assets/performance-panel-Ub96qrEH.js.gz +0 -0
  127. package/dist/web/assets/{ppmbot-settings-section-D67u6gPi.js → ppmbot-settings-section-CPMGP66g.js} +1 -1
  128. package/dist/web/assets/ppmbot-settings-section-CPMGP66g.js.br +0 -0
  129. package/dist/web/assets/ppmbot-settings-section-CPMGP66g.js.gz +0 -0
  130. package/dist/web/assets/{problems-panel-DKgIw4uZ.js → problems-panel-CWwzZIP3.js} +1 -1
  131. package/dist/web/assets/problems-panel-CWwzZIP3.js.br +0 -0
  132. package/dist/web/assets/problems-panel-CWwzZIP3.js.gz +0 -0
  133. package/dist/web/assets/process-table-BwPws3zS.js +1 -0
  134. package/dist/web/assets/process-table-BwPws3zS.js.br +0 -0
  135. package/dist/web/assets/process-table-BwPws3zS.js.gz +0 -0
  136. package/dist/web/assets/{proxy-settings-section-CFiYcTBA.js → proxy-settings-section-nS8VbxI_.js} +1 -1
  137. package/dist/web/assets/proxy-settings-section-nS8VbxI_.js.br +0 -0
  138. package/dist/web/assets/proxy-settings-section-nS8VbxI_.js.gz +0 -0
  139. package/dist/web/assets/{query-audit-section-BybgiPsc.js → query-audit-section-Dv5Pxn-w.js} +1 -1
  140. package/dist/web/assets/query-audit-section-Dv5Pxn-w.js.br +0 -0
  141. package/dist/web/assets/query-audit-section-Dv5Pxn-w.js.gz +0 -0
  142. package/dist/web/assets/{remote-desktop-clipboard-notice-Bs3fZSRw.js → remote-desktop-clipboard-notice-DEbdu0hC.js} +1 -1
  143. package/dist/web/assets/remote-desktop-clipboard-notice-DEbdu0hC.js.br +0 -0
  144. package/dist/web/assets/remote-desktop-clipboard-notice-DEbdu0hC.js.gz +0 -0
  145. package/dist/web/assets/remote-desktop-mobile-sheet-BJm-akVY.js +2 -0
  146. package/dist/web/assets/remote-desktop-mobile-sheet-BJm-akVY.js.br +0 -0
  147. package/dist/web/assets/remote-desktop-mobile-sheet-BJm-akVY.js.gz +0 -0
  148. package/dist/web/assets/{remote-desktop-mobile-view-DgGOfPVY.js → remote-desktop-mobile-view-DPdGFD6C.js} +1 -1
  149. package/dist/web/assets/remote-desktop-mobile-view-DPdGFD6C.js.br +0 -0
  150. package/dist/web/assets/remote-desktop-mobile-view-DPdGFD6C.js.gz +0 -0
  151. package/dist/web/assets/{remote-desktop-readiness-gate-DOWryrQx.js → remote-desktop-readiness-gate-DV7YgSpP.js} +1 -1
  152. package/dist/web/assets/remote-desktop-readiness-gate-DV7YgSpP.js.br +0 -0
  153. package/dist/web/assets/remote-desktop-readiness-gate-DV7YgSpP.js.gz +0 -0
  154. package/dist/web/assets/remote-desktop-window-content-BktcWMjD.js +1 -0
  155. package/dist/web/assets/remote-desktop-window-content-BktcWMjD.js.br +0 -0
  156. package/dist/web/assets/remote-desktop-window-content-BktcWMjD.js.gz +0 -0
  157. package/dist/web/assets/{run-in-terminal-Cdn9PaAl.js → run-in-terminal-B_PCeBqG.js} +1 -1
  158. package/dist/web/assets/{schedules-settings-section-DGhG-b5O.js → schedules-settings-section-B3HYKSVh.js} +1 -1
  159. package/dist/web/assets/schedules-settings-section-B3HYKSVh.js.br +0 -0
  160. package/dist/web/assets/schedules-settings-section-B3HYKSVh.js.gz +0 -0
  161. package/dist/web/assets/{send-to-chat-B09VjsB5.js → send-to-chat-Bjg6c8Ld.js} +1 -1
  162. package/dist/web/assets/send-to-chat-Bjg6c8Ld.js.br +0 -0
  163. package/dist/web/assets/send-to-chat-Bjg6c8Ld.js.gz +0 -0
  164. package/dist/web/assets/{services-panel-0klE3O4g.js → services-panel-D6k6AVS4.js} +1 -1
  165. package/dist/web/assets/services-panel-D6k6AVS4.js.br +0 -0
  166. package/dist/web/assets/services-panel-D6k6AVS4.js.gz +0 -0
  167. package/dist/web/assets/{settings-body-CfqpqFfq.js → settings-body-Cu0mNA5x.js} +2 -2
  168. package/dist/web/assets/settings-body-Cu0mNA5x.js.br +0 -0
  169. package/dist/web/assets/settings-body-Cu0mNA5x.js.gz +0 -0
  170. package/dist/web/assets/{settings-store-3jIBB8gx.js → settings-store-DIV25PM_.js} +2 -2
  171. package/dist/web/assets/settings-store-DIV25PM_.js.br +0 -0
  172. package/dist/web/assets/settings-store-DIV25PM_.js.gz +0 -0
  173. package/dist/web/assets/{settings-tab-CHweH5Nh.js → settings-tab-C-A6RNQ3.js} +1 -1
  174. package/dist/web/assets/{settings-window-content-C6Od3qV7.js → settings-window-content-48e5FJAq.js} +1 -1
  175. package/dist/web/assets/{sql-query-editor-ALQ5_X0j.js → sql-query-editor-CgGWYu9K.js} +1 -1
  176. package/dist/web/assets/sql-query-editor-CgGWYu9K.js.br +0 -0
  177. package/dist/web/assets/sql-query-editor-CgGWYu9K.js.gz +0 -0
  178. package/dist/web/assets/{sqlite-viewer-DKTty_jR.js → sqlite-viewer-Co1Oi3dv.js} +1 -1
  179. package/dist/web/assets/sqlite-viewer-Co1Oi3dv.js.br +0 -0
  180. package/dist/web/assets/sqlite-viewer-Co1Oi3dv.js.gz +0 -0
  181. package/dist/web/assets/{system-monitor-body-D6J6lq7-.js → system-monitor-body-DxPKVa-W.js} +2 -2
  182. package/dist/web/assets/system-monitor-body-DxPKVa-W.js.br +0 -0
  183. package/dist/web/assets/system-monitor-body-DxPKVa-W.js.gz +0 -0
  184. package/dist/web/assets/system-monitor-tab-4miOZuQQ.js +1 -0
  185. package/dist/web/assets/{system-monitor-window-content-D1t30f32.js → system-monitor-window-content-B-A7DdML.js} +1 -1
  186. package/dist/web/assets/{tab-host-window-content-DTcx2D0O.js → tab-host-window-content-CChcpg1v.js} +1 -1
  187. package/dist/web/assets/tab-host-window-content-CChcpg1v.js.br +0 -0
  188. package/dist/web/assets/tab-host-window-content-CChcpg1v.js.gz +0 -0
  189. package/dist/web/assets/{tab-store-CUPgQIQz.js → tab-store-D33icgdA.js} +1 -1
  190. package/dist/web/assets/{terminal-tab-DgTfQKBI.js → terminal-tab-BDqlyBLK.js} +2 -2
  191. package/dist/web/assets/terminal-tab-BDqlyBLK.js.br +0 -0
  192. package/dist/web/assets/terminal-tab-BDqlyBLK.js.gz +0 -0
  193. package/dist/web/assets/tool-cards-iJ8tyrLq.js +9 -0
  194. package/dist/web/assets/tool-cards-iJ8tyrLq.js.br +0 -0
  195. package/dist/web/assets/tool-cards-iJ8tyrLq.js.gz +0 -0
  196. package/dist/web/assets/{use-inline-blame-Dv111BKY.js → use-inline-blame-D9js570a.js} +4 -4
  197. package/dist/web/assets/use-inline-blame-D9js570a.js.br +0 -0
  198. package/dist/web/assets/use-inline-blame-D9js570a.js.gz +0 -0
  199. package/dist/web/assets/{use-lsp-CZTFJIXX.js → use-lsp-BYtylZId.js} +1 -1
  200. package/dist/web/assets/use-lsp-BYtylZId.js.br +0 -0
  201. package/dist/web/assets/use-lsp-BYtylZId.js.gz +0 -0
  202. package/dist/web/assets/{use-monaco-theme-DL2dIG5L.js → use-monaco-theme-DJ8y_7IZ.js} +1 -1
  203. package/dist/web/assets/{use-process-kill-D7D9QvJG.js → use-process-kill-BS84S-kD.js} +1 -1
  204. package/dist/web/assets/use-process-kill-BS84S-kD.js.br +0 -0
  205. package/dist/web/assets/use-process-kill-BS84S-kD.js.gz +0 -0
  206. package/dist/web/assets/{use-websocket-ziY-Gz_B.js → use-websocket-Mqj27umG.js} +1 -1
  207. package/dist/web/assets/{video-preview-DzwY3q9W.js → video-preview-BuzUAIma.js} +1 -1
  208. package/dist/web/assets/video-preview-BuzUAIma.js.br +0 -0
  209. package/dist/web/assets/video-preview-BuzUAIma.js.gz +0 -0
  210. package/dist/web/assets/{voice-settings-section-B7zMHsuz.js → voice-settings-section-DnwQjBbw.js} +1 -1
  211. package/dist/web/assets/voice-settings-section-DnwQjBbw.js.br +0 -0
  212. package/dist/web/assets/voice-settings-section-DnwQjBbw.js.gz +0 -0
  213. package/dist/web/assets/{window-pip-registry-dxrBFfkP.js → window-pip-registry-Bl--fK8-.js} +1 -1
  214. package/dist/web/assets/window-store-vo1N70nK.js +1 -0
  215. package/dist/web/assets/window-store-vo1N70nK.js.br +0 -0
  216. package/dist/web/assets/window-store-vo1N70nK.js.gz +0 -0
  217. package/dist/web/index.html +6 -6
  218. package/dist/web/index.html.br +0 -0
  219. package/dist/web/index.html.gz +0 -0
  220. package/dist/web/sw.js +1 -1
  221. package/dist/web/sw.js.br +0 -0
  222. package/dist/web/sw.js.gz +0 -0
  223. package/docs/architecture/ai-chat-and-providers.md +66 -0
  224. package/docs/architecture/workspace-and-ui.md +15 -3
  225. package/docs/design-guidelines.md +6 -1
  226. package/package.json +1 -1
  227. package/src/providers/codex-app-server/codex-history.ts +4 -4
  228. package/src/providers/codex-app-server/codex-provider.ts +1 -1
  229. package/src/providers/codex-app-server/codex-rollout-tail-parser.ts +203 -0
  230. package/src/providers/codex-app-server/codex-subagent-thread.ts +7 -1
  231. package/src/server/index.ts +7 -2
  232. package/src/server/routes/chat.ts +12 -4
  233. package/src/server/ws/global.ts +19 -3
  234. package/src/services/agent-transcript/agent-transcript-activity.ts +72 -0
  235. package/src/services/agent-transcript/agent-transcript-codex-activity.ts +161 -0
  236. package/src/services/agent-transcript/agent-transcript-codex-tail-cache.ts +85 -0
  237. package/src/services/agent-transcript/agent-transcript-envelope.ts +86 -0
  238. package/src/services/agent-transcript/agent-transcript-error-map.ts +33 -0
  239. package/src/services/agent-transcript/agent-transcript-file-tail-feed.ts +180 -0
  240. package/src/services/agent-transcript/agent-transcript-file-tail-state.ts +100 -0
  241. package/src/services/agent-transcript/agent-transcript-fs-io.ts +79 -0
  242. package/src/services/agent-transcript/agent-transcript-hub-clock.ts +37 -0
  243. package/src/services/agent-transcript/agent-transcript-hub-registry.ts +153 -0
  244. package/src/services/agent-transcript/agent-transcript-hub.ts +103 -0
  245. package/src/services/agent-transcript/agent-transcript-index-cache.ts +85 -0
  246. package/src/services/agent-transcript/agent-transcript-session-hub-activity.ts +103 -0
  247. package/src/services/agent-transcript/agent-transcript-session-hub-read.ts +194 -0
  248. package/src/services/agent-transcript/agent-transcript-session-hub-types.ts +81 -0
  249. package/src/services/agent-transcript/agent-transcript-session-hub.ts +196 -0
  250. package/src/services/agent-transcript/agent-transcript-sources.ts +199 -0
  251. package/src/services/agent-transcript/agent-transcript-ws-like.ts +19 -0
  252. package/src/services/agent-transcript/claude-projects-root.ts +21 -0
  253. package/src/services/agent-transcript/session-ownership.ts +152 -0
  254. package/src/services/nested-subagent-spy.ts +3 -3
  255. package/src/services/subagent-transcript-merger.ts +21 -7
  256. package/src/shared/agent-transcript-protocol.ts +158 -0
  257. package/src/types/chat.ts +27 -3
  258. package/src/web/app.tsx +5 -5
  259. package/src/web/components/chat/agent-card-summary.tsx +73 -0
  260. package/src/web/components/chat/agent-session-context.ts +34 -0
  261. package/src/web/components/chat/agent-session-fallback-store.ts +60 -0
  262. package/src/web/components/chat/agent-session-sheet-store.ts +23 -0
  263. package/src/web/components/chat/{team-member-sheet.tsx → agent-session-sheet.tsx} +12 -12
  264. package/src/web/components/chat/agent-session-window-content.tsx +133 -0
  265. package/src/web/components/chat/chat-history-bar.tsx +1 -0
  266. package/src/web/components/chat/chat-tab.tsx +21 -4
  267. package/src/web/components/chat/codex-usage-panel.tsx +26 -8
  268. package/src/web/components/chat/running-agents-bar.tsx +93 -0
  269. package/src/web/components/chat/team-activity-panel.tsx +19 -5
  270. package/src/web/components/chat/tool-cards.tsx +73 -4
  271. package/src/web/components/chat/use-open-agent-session.ts +83 -0
  272. package/src/web/components/floating-window/window-content-registry.ts +4 -5
  273. package/src/web/components/floating-window/window-geometry.ts +18 -0
  274. package/src/web/components/floating-window/window-persistence.ts +5 -4
  275. package/src/web/components/floating-window/window-store-types.ts +1 -1
  276. package/src/web/components/settings/resolve-open-settings-action.ts +1 -1
  277. package/src/web/components/system/resolve-open-system-monitor-action.ts +1 -1
  278. package/src/web/hooks/use-agent-activity.ts +65 -0
  279. package/src/web/hooks/use-agent-session-stream.ts +134 -0
  280. package/src/web/hooks/use-chat.ts +12 -18
  281. package/src/web/hooks/use-global-events.ts +35 -2
  282. package/src/web/lib/agent-session-stream-merge.ts +48 -0
  283. package/src/web/lib/agent-step-summary.ts +331 -0
  284. package/src/web/lib/global-ws-channel.ts +41 -0
  285. package/src/web/lib/running-agent-rows.ts +115 -0
  286. package/dist/web/assets/accounts-settings-section-BNcPvEq1.js +0 -1
  287. package/dist/web/assets/accounts-settings-section-BNcPvEq1.js.br +0 -0
  288. package/dist/web/assets/accounts-settings-section-BNcPvEq1.js.gz +0 -0
  289. package/dist/web/assets/ai-resource-editor-DfVRMUHS.js.br +0 -0
  290. package/dist/web/assets/ai-resource-editor-DfVRMUHS.js.gz +0 -0
  291. package/dist/web/assets/ai-settings-section-B_0-P8jx.js +0 -1
  292. package/dist/web/assets/ai-settings-section-Ezhw4Fef.js.br +0 -0
  293. package/dist/web/assets/ai-settings-section-Ezhw4Fef.js.gz +0 -0
  294. package/dist/web/assets/appearance-settings-section-CPOAp0fY.js.br +0 -0
  295. package/dist/web/assets/appearance-settings-section-CPOAp0fY.js.gz +0 -0
  296. package/dist/web/assets/apps-panel-Cz7_o-M4.js.br +0 -0
  297. package/dist/web/assets/apps-panel-Cz7_o-M4.js.gz +0 -0
  298. package/dist/web/assets/branch-review-tab-BPx3kHrM.js +0 -1
  299. package/dist/web/assets/branch-review-tab-BPx3kHrM.js.br +0 -0
  300. package/dist/web/assets/branch-review-tab-BPx3kHrM.js.gz +0 -0
  301. package/dist/web/assets/chat-tab-BadDM_j8.js +0 -1
  302. package/dist/web/assets/chat-tab-BadDM_j8.js.br +0 -0
  303. package/dist/web/assets/chat-tab-BadDM_j8.js.gz +0 -0
  304. package/dist/web/assets/chat-tab-IqAY4c7p.js +0 -15
  305. package/dist/web/assets/chat-tab-IqAY4c7p.js.br +0 -0
  306. package/dist/web/assets/chat-tab-IqAY4c7p.js.gz +0 -0
  307. package/dist/web/assets/code-editor-Dmitw9QS.js +0 -7
  308. package/dist/web/assets/code-editor-Dmitw9QS.js.br +0 -0
  309. package/dist/web/assets/code-editor-Dmitw9QS.js.gz +0 -0
  310. package/dist/web/assets/codex-reset-credit-button-D2hal4bG.js.br +0 -0
  311. package/dist/web/assets/codex-reset-credit-button-D2hal4bG.js.gz +0 -0
  312. package/dist/web/assets/conflict-editor-Cj_gUncw.js.br +0 -0
  313. package/dist/web/assets/conflict-editor-Cj_gUncw.js.gz +0 -0
  314. package/dist/web/assets/csv-preview-Be3ODNj0.js.br +0 -0
  315. package/dist/web/assets/csv-preview-Be3ODNj0.js.gz +0 -0
  316. package/dist/web/assets/database-viewer-B1b--4y8.js.br +0 -0
  317. package/dist/web/assets/database-viewer-B1b--4y8.js.gz +0 -0
  318. package/dist/web/assets/design-tab-8wi0IHss.js.br +0 -0
  319. package/dist/web/assets/design-tab-8wi0IHss.js.gz +0 -0
  320. package/dist/web/assets/designs-sidebar-panel-D5-1Q8zQ.js.br +0 -0
  321. package/dist/web/assets/designs-sidebar-panel-D5-1Q8zQ.js.gz +0 -0
  322. package/dist/web/assets/diff-viewer-Dcxk6UTG.js.br +0 -0
  323. package/dist/web/assets/diff-viewer-Dcxk6UTG.js.gz +0 -0
  324. package/dist/web/assets/edit-diff-preview-DxDAtYhi.js.br +0 -0
  325. package/dist/web/assets/editor-language-service-BVJQnTSV.js.br +0 -0
  326. package/dist/web/assets/editor-language-service-BVJQnTSV.js.gz +0 -0
  327. package/dist/web/assets/explorer-body-CTkrlrI3.js +0 -2
  328. package/dist/web/assets/explorer-body-CTkrlrI3.js.br +0 -0
  329. package/dist/web/assets/explorer-body-CTkrlrI3.js.gz +0 -0
  330. package/dist/web/assets/explorer-window-content-DH5AD3Ng.js.br +0 -0
  331. package/dist/web/assets/explorer-window-content-DH5AD3Ng.js.gz +0 -0
  332. package/dist/web/assets/extension-manager-section-BJr6ccWw.js.br +0 -0
  333. package/dist/web/assets/extension-manager-section-BJr6ccWw.js.gz +0 -0
  334. package/dist/web/assets/extension-webview-CYNXHd8D.js.br +0 -0
  335. package/dist/web/assets/extension-webview-CYNXHd8D.js.gz +0 -0
  336. package/dist/web/assets/files-settings-section-JX7o4lex.js.br +0 -0
  337. package/dist/web/assets/files-settings-section-JX7o4lex.js.gz +0 -0
  338. package/dist/web/assets/general-settings-section-kF94Mz5q.js.br +0 -0
  339. package/dist/web/assets/general-settings-section-kF94Mz5q.js.gz +0 -0
  340. package/dist/web/assets/git-log-panel-Crlp9ofM.js.br +0 -0
  341. package/dist/web/assets/git-log-panel-Crlp9ofM.js.gz +0 -0
  342. package/dist/web/assets/glide-data-grid-O6MO5dKU.js.br +0 -0
  343. package/dist/web/assets/glide-data-grid-O6MO5dKU.js.gz +0 -0
  344. package/dist/web/assets/group-chat-tab-DGLSGf37.js +0 -1
  345. package/dist/web/assets/group-chat-tab-DGLSGf37.js.br +0 -0
  346. package/dist/web/assets/group-chat-tab-DGLSGf37.js.gz +0 -0
  347. package/dist/web/assets/index-DQCw6d-2.js.br +0 -0
  348. package/dist/web/assets/index-DQCw6d-2.js.gz +0 -0
  349. package/dist/web/assets/keybindings-store-CJtEHFIQ.js +0 -1
  350. package/dist/web/assets/keyboard-shortcuts-section-CewWGwiF.js.br +0 -0
  351. package/dist/web/assets/keyboard-shortcuts-section-CewWGwiF.js.gz +0 -0
  352. package/dist/web/assets/language-servers-section-nricdxke.js.br +0 -0
  353. package/dist/web/assets/language-servers-section-nricdxke.js.gz +0 -0
  354. package/dist/web/assets/lsp-client-DRMEWb-z.js.br +0 -0
  355. package/dist/web/assets/lsp-client-DRMEWb-z.js.gz +0 -0
  356. package/dist/web/assets/markdown-renderer-D5-xliE9.js +0 -1
  357. package/dist/web/assets/markdown-renderer-Dab9Us6u.js.br +0 -0
  358. package/dist/web/assets/markdown-renderer-Dab9Us6u.js.gz +0 -0
  359. package/dist/web/assets/mobile-explorer-sheet-DtgdpvJ9.js.br +0 -0
  360. package/dist/web/assets/mobile-explorer-sheet-DtgdpvJ9.js.gz +0 -0
  361. package/dist/web/assets/new-design-dialog-Dd5KDB_p.js.br +0 -0
  362. package/dist/web/assets/new-design-dialog-Dd5KDB_p.js.gz +0 -0
  363. package/dist/web/assets/notification-store-D2vTyp_Q.js +0 -1
  364. package/dist/web/assets/panel-store-DrSH3Nhp.js.br +0 -0
  365. package/dist/web/assets/panel-store-DrSH3Nhp.js.gz +0 -0
  366. package/dist/web/assets/performance-panel-LMGefOQ2.js +0 -3
  367. package/dist/web/assets/performance-panel-LMGefOQ2.js.br +0 -0
  368. package/dist/web/assets/performance-panel-LMGefOQ2.js.gz +0 -0
  369. package/dist/web/assets/ppmbot-settings-section-D67u6gPi.js.br +0 -0
  370. package/dist/web/assets/ppmbot-settings-section-D67u6gPi.js.gz +0 -0
  371. package/dist/web/assets/problems-panel-DKgIw4uZ.js.br +0 -0
  372. package/dist/web/assets/problems-panel-DKgIw4uZ.js.gz +0 -0
  373. package/dist/web/assets/process-table-BIAgC_jG.js +0 -1
  374. package/dist/web/assets/process-table-BIAgC_jG.js.br +0 -0
  375. package/dist/web/assets/process-table-BIAgC_jG.js.gz +0 -0
  376. package/dist/web/assets/proxy-settings-section-CFiYcTBA.js.br +0 -0
  377. package/dist/web/assets/proxy-settings-section-CFiYcTBA.js.gz +0 -0
  378. package/dist/web/assets/query-audit-section-BybgiPsc.js.br +0 -0
  379. package/dist/web/assets/query-audit-section-BybgiPsc.js.gz +0 -0
  380. package/dist/web/assets/remote-desktop-clipboard-notice-Bs3fZSRw.js.br +0 -0
  381. package/dist/web/assets/remote-desktop-clipboard-notice-Bs3fZSRw.js.gz +0 -0
  382. package/dist/web/assets/remote-desktop-mobile-sheet-DgxTdWMh.js +0 -2
  383. package/dist/web/assets/remote-desktop-mobile-sheet-DgxTdWMh.js.br +0 -0
  384. package/dist/web/assets/remote-desktop-mobile-sheet-DgxTdWMh.js.gz +0 -0
  385. package/dist/web/assets/remote-desktop-mobile-view-DgGOfPVY.js.br +0 -0
  386. package/dist/web/assets/remote-desktop-mobile-view-DgGOfPVY.js.gz +0 -0
  387. package/dist/web/assets/remote-desktop-readiness-gate-DOWryrQx.js.br +0 -0
  388. package/dist/web/assets/remote-desktop-readiness-gate-DOWryrQx.js.gz +0 -0
  389. package/dist/web/assets/remote-desktop-window-content-7t0wQsHx.js +0 -1
  390. package/dist/web/assets/remote-desktop-window-content-7t0wQsHx.js.br +0 -0
  391. package/dist/web/assets/remote-desktop-window-content-7t0wQsHx.js.gz +0 -0
  392. package/dist/web/assets/schedules-settings-section-DGhG-b5O.js.br +0 -0
  393. package/dist/web/assets/schedules-settings-section-DGhG-b5O.js.gz +0 -0
  394. package/dist/web/assets/send-to-chat-B09VjsB5.js.br +0 -0
  395. package/dist/web/assets/send-to-chat-B09VjsB5.js.gz +0 -0
  396. package/dist/web/assets/services-panel-0klE3O4g.js.br +0 -0
  397. package/dist/web/assets/services-panel-0klE3O4g.js.gz +0 -0
  398. package/dist/web/assets/settings-body-CfqpqFfq.js.br +0 -0
  399. package/dist/web/assets/settings-body-CfqpqFfq.js.gz +0 -0
  400. package/dist/web/assets/settings-store-3jIBB8gx.js.br +0 -0
  401. package/dist/web/assets/settings-store-3jIBB8gx.js.gz +0 -0
  402. package/dist/web/assets/sql-query-editor-ALQ5_X0j.js.br +0 -0
  403. package/dist/web/assets/sql-query-editor-ALQ5_X0j.js.gz +0 -0
  404. package/dist/web/assets/sqlite-viewer-DKTty_jR.js.br +0 -0
  405. package/dist/web/assets/sqlite-viewer-DKTty_jR.js.gz +0 -0
  406. package/dist/web/assets/system-monitor-body-D6J6lq7-.js.br +0 -0
  407. package/dist/web/assets/system-monitor-body-D6J6lq7-.js.gz +0 -0
  408. package/dist/web/assets/system-monitor-tab-Di18zNNS.js +0 -1
  409. package/dist/web/assets/tab-host-window-content-DTcx2D0O.js.br +0 -0
  410. package/dist/web/assets/tab-host-window-content-DTcx2D0O.js.gz +0 -0
  411. package/dist/web/assets/team-member-sheet-CFSbPG6n.js +0 -2
  412. package/dist/web/assets/team-member-sheet-CFSbPG6n.js.br +0 -0
  413. package/dist/web/assets/team-member-sheet-CFSbPG6n.js.gz +0 -0
  414. package/dist/web/assets/team-member-window-content-VeI6DGeM.js +0 -1
  415. package/dist/web/assets/team-member-window-content-VeI6DGeM.js.br +0 -0
  416. package/dist/web/assets/team-member-window-content-VeI6DGeM.js.gz +0 -0
  417. package/dist/web/assets/terminal-tab-DgTfQKBI.js.br +0 -0
  418. package/dist/web/assets/terminal-tab-DgTfQKBI.js.gz +0 -0
  419. package/dist/web/assets/tool-cards-_fpYI1hB.js +0 -9
  420. package/dist/web/assets/tool-cards-_fpYI1hB.js.br +0 -0
  421. package/dist/web/assets/tool-cards-_fpYI1hB.js.gz +0 -0
  422. package/dist/web/assets/use-inline-blame-Dv111BKY.js.br +0 -0
  423. package/dist/web/assets/use-inline-blame-Dv111BKY.js.gz +0 -0
  424. package/dist/web/assets/use-lsp-CZTFJIXX.js.br +0 -0
  425. package/dist/web/assets/use-lsp-CZTFJIXX.js.gz +0 -0
  426. package/dist/web/assets/use-open-team-member-ElN3KmCI.js +0 -1
  427. package/dist/web/assets/use-process-kill-D7D9QvJG.js.br +0 -0
  428. package/dist/web/assets/use-process-kill-D7D9QvJG.js.gz +0 -0
  429. package/dist/web/assets/video-preview-DzwY3q9W.js.br +0 -0
  430. package/dist/web/assets/video-preview-DzwY3q9W.js.gz +0 -0
  431. package/dist/web/assets/voice-settings-section-B7zMHsuz.js.br +0 -0
  432. package/dist/web/assets/voice-settings-section-B7zMHsuz.js.gz +0 -0
  433. package/dist/web/assets/window-store-B4cbZ5rr.js +0 -1
  434. package/dist/web/assets/window-store-B4cbZ5rr.js.br +0 -0
  435. package/dist/web/assets/window-store-B4cbZ5rr.js.gz +0 -0
  436. package/src/web/components/chat/team-member-window-content.tsx +0 -66
  437. package/src/web/components/chat/team-working-bar.tsx +0 -62
  438. package/src/web/components/chat/use-open-team-member.ts +0 -41
  439. package/src/web/hooks/use-member-transcript-tail.ts +0 -95
  440. /package/dist/web/assets/{api-settings-BeINN5Dy.js → api-settings-BWgfXl7o.js} +0 -0
  441. /package/dist/web/assets/{api-settings-BeINN5Dy.js.br → api-settings-BWgfXl7o.js.br} +0 -0
  442. /package/dist/web/assets/{api-settings-BeINN5Dy.js.gz → api-settings-BWgfXl7o.js.gz} +0 -0
  443. /package/dist/web/assets/{libesm-CxeQSM3t.js → libesm-DAF5_F99.js} +0 -0
  444. /package/dist/web/assets/{libesm-CxeQSM3t.js.br → libesm-DAF5_F99.js.br} +0 -0
  445. /package/dist/web/assets/{libesm-CxeQSM3t.js.gz → libesm-DAF5_F99.js.gz} +0 -0
  446. /package/dist/web/assets/{onboarding-entry-DD3QSDiz.js → onboarding-entry-Bh-WE3vq.js} +0 -0
  447. /package/dist/web/assets/{onboarding-entry-DD3QSDiz.js.br → onboarding-entry-Bh-WE3vq.js.br} +0 -0
  448. /package/dist/web/assets/{onboarding-entry-DD3QSDiz.js.gz → onboarding-entry-Bh-WE3vq.js.gz} +0 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Message handlers for the `/ws/global` agent-transcript protocol (see
3
+ * `src/shared/agent-transcript-protocol.ts`). Per-client caps, `subId`
4
+ * bookkeeping and cross-hub client drop live in `agent-transcript-hub-registry.ts`;
5
+ * this module is only the four message shapes and their validation.
6
+ */
7
+ import { assertSessionInProject } from "./session-ownership.ts";
8
+ import { mapOwnershipErrorCode } from "./agent-transcript-error-map.ts";
9
+ import { sendWsMessage, type AgentTranscriptWsLike } from "./agent-transcript-ws-like.ts";
10
+ import { addTranscriptSubscription } from "./agent-transcript-session-hub.ts";
11
+ import { addActivitySubscription } from "./agent-transcript-session-hub-activity.ts";
12
+ import {
13
+ activitySubCountForClient, dropClientEverywhere, getOrCreateHub, maybeDropHub, projectPathFor,
14
+ registerActivitySub, registerTranscriptSub, totalActivitySubs, totalTranscriptSubs, transcriptSubCountForClient,
15
+ unregisterActivitySub, unregisterTranscriptSub,
16
+ } from "./agent-transcript-hub-registry.ts";
17
+ import {
18
+ MAX_TRANSCRIPT_SUBS_PER_CLIENT, MAX_TRANSCRIPT_SUBS_PER_SERVER,
19
+ type AgentActivitySubscribeMsg, type AgentActivityUnsubscribeMsg,
20
+ type AgentTranscriptErrorMsg, type AgentTranscriptSubscribeMsg, type AgentTranscriptUnsubscribeMsg,
21
+ } from "../../shared/agent-transcript-protocol.ts";
22
+
23
+ function sendError(ws: AgentTranscriptWsLike, subId: string, code: AgentTranscriptErrorMsg["code"]): void {
24
+ sendWsMessage(ws, { type: "agent-transcript:error", subId, code } satisfies AgentTranscriptErrorMsg);
25
+ }
26
+
27
+ export function handleAgentTranscriptSubscribe(ws: AgentTranscriptWsLike, msg: AgentTranscriptSubscribeMsg): void {
28
+ const { subId, projectName, providerId, sessionId, source, cursor } = msg;
29
+ if (typeof subId !== "string" || !subId) return;
30
+ if (
31
+ typeof projectName !== "string" || (providerId !== "claude" && providerId !== "codex") ||
32
+ typeof sessionId !== "string" || !source || typeof source !== "object" ||
33
+ (source.kind !== "card" && source.kind !== "member")
34
+ ) {
35
+ return sendError(ws, subId, "bad_request");
36
+ }
37
+
38
+ const projectPath = projectPathFor(projectName);
39
+ if (!projectPath) return sendError(ws, subId, "not_found");
40
+
41
+ // Replacing an existing subId costs nothing against the cap — drop it first.
42
+ unregisterTranscriptSub(ws, subId);
43
+
44
+ if (transcriptSubCountForClient(ws) >= MAX_TRANSCRIPT_SUBS_PER_CLIENT) return sendError(ws, subId, "limit");
45
+ if (totalTranscriptSubs() >= MAX_TRANSCRIPT_SUBS_PER_SERVER) return sendError(ws, subId, "limit");
46
+
47
+ const owned = assertSessionInProject({ providerId, sessionId, projectPath });
48
+ if (!owned.ok) return sendError(ws, subId, mapOwnershipErrorCode(owned.code));
49
+
50
+ const hub = getOrCreateHub(owned);
51
+ const result = addTranscriptSubscription(hub, ws, subId, source, cursor);
52
+ if (!result.ok) {
53
+ maybeDropHub(hub.key);
54
+ return sendError(ws, subId, result.code ?? "bad_request");
55
+ }
56
+
57
+ registerTranscriptSub(ws, subId, hub.key);
58
+ if (result.sendFailed) dropClientEverywhere(ws);
59
+ }
60
+
61
+ export function handleAgentTranscriptUnsubscribe(ws: AgentTranscriptWsLike, msg: AgentTranscriptUnsubscribeMsg): void {
62
+ if (typeof msg.subId === "string") unregisterTranscriptSub(ws, msg.subId);
63
+ }
64
+
65
+ export function handleAgentActivitySubscribe(ws: AgentTranscriptWsLike, msg: AgentActivitySubscribeMsg): void {
66
+ const { subId, projectName, providerId, sessionId } = msg;
67
+ if (typeof subId !== "string" || !subId) return;
68
+ if (typeof projectName !== "string" || (providerId !== "claude" && providerId !== "codex") || typeof sessionId !== "string") {
69
+ return sendError(ws, subId, "bad_request");
70
+ }
71
+ const projectPath = projectPathFor(projectName);
72
+ if (!projectPath) return sendError(ws, subId, "not_found");
73
+
74
+ // Replacing an existing subId costs nothing against the cap — drop it first.
75
+ unregisterActivitySub(ws, subId);
76
+
77
+ if (activitySubCountForClient(ws) >= MAX_TRANSCRIPT_SUBS_PER_CLIENT) return sendError(ws, subId, "limit");
78
+ if (totalActivitySubs() >= MAX_TRANSCRIPT_SUBS_PER_SERVER) return sendError(ws, subId, "limit");
79
+
80
+ const owned = assertSessionInProject({ providerId, sessionId, projectPath });
81
+ if (!owned.ok) return sendError(ws, subId, mapOwnershipErrorCode(owned.code));
82
+
83
+ const hub = getOrCreateHub(owned);
84
+ addActivitySubscription(hub, ws, subId);
85
+ registerActivitySub(ws, subId, hub.key);
86
+ }
87
+
88
+ export function handleAgentActivityUnsubscribe(ws: AgentTranscriptWsLike, msg: AgentActivityUnsubscribeMsg): void {
89
+ if (typeof msg.subId === "string") unregisterActivitySub(ws, msg.subId);
90
+ }
91
+
92
+ export function handleAgentTranscriptPing(ws: AgentTranscriptWsLike): void {
93
+ sendWsMessage(ws, { type: "pong" });
94
+ }
95
+
96
+ /** Socket closed: drop every transcript and activity subscription it held. */
97
+ export function handleAgentTranscriptClientClosed(ws: AgentTranscriptWsLike): void {
98
+ dropClientEverywhere(ws);
99
+ }
100
+
101
+ export {
102
+ _debugHubSnapshotForTest, _getSessionHubForTest, _resetAgentTranscriptHubForTest,
103
+ } from "./agent-transcript-hub-registry.ts";
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared cache over `groupSubagentsByCard`, so the hub and
3
+ * `nested-subagent-spy.ts` cost one directory scan per session per refresh
4
+ * window between them, not one each. `groupSubagentsByCard` itself reads
5
+ * every `*.meta.json` in the subagents dir — cheap for one caller, not for
6
+ * two independent pollers doing it every tick.
7
+ *
8
+ * Refreshed at most every `INDEX_REFRESH_MS`, except a directory mtime change
9
+ * (a new agent spawned, one finished) forces an immediate refresh regardless
10
+ * of how recently the last one ran — the 2s floor bounds cost, the mtime
11
+ * check bounds staleness.
12
+ */
13
+ import {
14
+ groupSubagentsByCard, indexTranscriptsByMember,
15
+ type SubagentTranscriptEntry,
16
+ } from "../team-member-activity/subagent-transcript-index.ts";
17
+ import { agentTranscriptClock } from "./agent-transcript-hub-clock.ts";
18
+ import { statDirMtimeSafe } from "./agent-transcript-fs-io.ts";
19
+ import { INDEX_REFRESH_MS } from "../../shared/agent-transcript-protocol.ts";
20
+
21
+ /** Bounds how many distinct subagents dirs stay cached at once — a server with
22
+ * many sessions ever subscribed-to should not keep every one of them forever. */
23
+ const MAX_CACHE_ENTRIES = 200;
24
+
25
+ interface CacheEntry {
26
+ groups: Map<string, SubagentTranscriptEntry[]>;
27
+ byMember: Map<string, SubagentTranscriptEntry>;
28
+ refreshedAt: number;
29
+ dirMtimeMs: number | null;
30
+ }
31
+
32
+ const cache = new Map<string, CacheEntry>();
33
+
34
+ function evictOldestIfOverCapacity(): void {
35
+ if (cache.size < MAX_CACHE_ENTRIES) return;
36
+ let oldestKey: string | undefined;
37
+ let oldestAt = Infinity;
38
+ for (const [key, entry] of cache) {
39
+ if (entry.refreshedAt < oldestAt) {
40
+ oldestAt = entry.refreshedAt;
41
+ oldestKey = key;
42
+ }
43
+ }
44
+ if (oldestKey !== undefined) cache.delete(oldestKey);
45
+ }
46
+
47
+ function refresh(subagentsDir: string): CacheEntry {
48
+ evictOldestIfOverCapacity();
49
+ const entry: CacheEntry = {
50
+ groups: groupSubagentsByCard(subagentsDir),
51
+ byMember: indexTranscriptsByMember(subagentsDir),
52
+ refreshedAt: agentTranscriptClock.now(),
53
+ dirMtimeMs: statDirMtimeSafe(subagentsDir),
54
+ };
55
+ cache.set(subagentsDir, entry);
56
+ return entry;
57
+ }
58
+
59
+ function cached(subagentsDir: string): CacheEntry {
60
+ const now = agentTranscriptClock.now();
61
+ const existing = cache.get(subagentsDir);
62
+ if (!existing) return refresh(subagentsDir);
63
+
64
+ const currentMtime = statDirMtimeSafe(subagentsDir);
65
+ const mtimeChanged = currentMtime !== null && currentMtime !== existing.dirMtimeMs;
66
+ if (mtimeChanged || now - existing.refreshedAt >= INDEX_REFRESH_MS) return refresh(subagentsDir);
67
+ return existing;
68
+ }
69
+
70
+ /** Cached `groupSubagentsByCard(subagentsDir)`, refreshed per the policy above. */
71
+ export function getCachedSubagentGroups(subagentsDir: string): Map<string, SubagentTranscriptEntry[]> {
72
+ return cached(subagentsDir).groups;
73
+ }
74
+
75
+ /** Cached `indexTranscriptsByMember(subagentsDir)`, same refresh policy as the
76
+ * card-group index — the activity feed's team-member pass used to re-read
77
+ * every `*.meta.json` itself, uncached, on top of the group scan above. */
78
+ export function getCachedTranscriptsByMember(subagentsDir: string): Map<string, SubagentTranscriptEntry> {
79
+ return cached(subagentsDir).byMember;
80
+ }
81
+
82
+ /** Test-only: forget every cached entry. */
83
+ export function _resetAgentTranscriptIndexCache(): void {
84
+ cache.clear();
85
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Activity-subscription half of a session hub: "who is running right now",
3
+ * pushed every 3s while at least one client is watching. Independent of the
4
+ * transcript ticker — a running bar costs nothing extra when no window is
5
+ * open, and a window being open costs nothing extra for the bar.
6
+ */
7
+ import { computeRunningAgents } from "./agent-transcript-activity.ts";
8
+ import { agentTranscriptClock, agentTranscriptTimers } from "./agent-transcript-hub-clock.ts";
9
+ import { sendWsMessage, type AgentTranscriptWsLike } from "./agent-transcript-ws-like.ts";
10
+ import type { SessionHub } from "./agent-transcript-session-hub-types.ts";
11
+ import { ACTIVITY_TICK_MS, type AgentActivityMsg } from "../../shared/agent-transcript-protocol.ts";
12
+
13
+ function ensureActivityTicker(hub: SessionHub): void {
14
+ if (hub.activityHandle !== null) return;
15
+ hub.activityHandle = agentTranscriptTimers.setInterval(() => {
16
+ void tickActivity(hub);
17
+ }, ACTIVITY_TICK_MS);
18
+ }
19
+
20
+ function stopActivityTickerIfEmpty(hub: SessionHub): void {
21
+ if (hub.activitySubs.size > 0 || hub.activityHandle === null) return;
22
+ agentTranscriptTimers.clearInterval(hub.activityHandle);
23
+ hub.activityHandle = null;
24
+ }
25
+
26
+ export function addActivitySubscription(hub: SessionHub, ws: AgentTranscriptWsLike, subId: string): void {
27
+ let subIds = hub.activitySubs.get(ws);
28
+ if (!subIds) {
29
+ subIds = new Set();
30
+ hub.activitySubs.set(ws, subIds);
31
+ }
32
+ subIds.add(subId);
33
+ ensureActivityTicker(hub);
34
+ // Guarded the same way `tickActivity` is below: this runs un-awaited, so a
35
+ // throw anywhere in `computeRunningAgents` (a descendant read racing a
36
+ // delete, a locked rollout) would otherwise become an unhandled rejection
37
+ // rather than something this function can react to.
38
+ void (async () => {
39
+ try {
40
+ const now = agentTranscriptClock.now();
41
+ const running = await computeRunningAgents(hub.owned, now);
42
+ if (!hub.tokenStillValid(ws) || !pushActivity(ws, [subId], running)) hub.onSendFailure(ws);
43
+ } catch {
44
+ console.warn(`[agent-activity] hub=${hub.key} subId=${subId} initial compute failed`);
45
+ }
46
+ })();
47
+ }
48
+
49
+ export function removeActivitySubscription(hub: SessionHub, ws: AgentTranscriptWsLike, subId: string): boolean {
50
+ const subIds = hub.activitySubs.get(ws);
51
+ if (!subIds?.delete(subId)) return false;
52
+ if (subIds.size === 0) hub.activitySubs.delete(ws);
53
+ stopActivityTickerIfEmpty(hub);
54
+ return true;
55
+ }
56
+
57
+ export function dropAllActivityForWs(hub: SessionHub, ws: AgentTranscriptWsLike): number {
58
+ const subIds = hub.activitySubs.get(ws);
59
+ if (!subIds) return 0;
60
+ hub.activitySubs.delete(ws);
61
+ stopActivityTickerIfEmpty(hub);
62
+ return subIds.size;
63
+ }
64
+
65
+ export function activitySubIdsForWs(hub: SessionHub, ws: AgentTranscriptWsLike): string[] {
66
+ return [...(hub.activitySubs.get(ws)?.values() ?? [])];
67
+ }
68
+
69
+ /** Push an already-computed running list to one client's given subscriptions. */
70
+ function pushActivity(ws: AgentTranscriptWsLike, subIds: string[], running: AgentActivityMsg["running"]): boolean {
71
+ for (const subId of subIds) {
72
+ const msg: AgentActivityMsg = { type: "agent-activity", subId, running };
73
+ if (!sendWsMessage(ws, msg)) return false;
74
+ }
75
+ return true;
76
+ }
77
+
78
+ /**
79
+ * One 3s wake: compute the running list once for the whole hub and fan it
80
+ * out to every subscriber — a per-session scan (Codex especially) is not
81
+ * cheap enough to repeat once per client watching the same session.
82
+ *
83
+ * Wrapped in its own try/catch because this is invoked as `void tickActivity(hub)`
84
+ * (fire-and-forget, from an un-awaited `setInterval` callback): a throw
85
+ * anywhere in `computeRunningAgents` — a descendant rollout that disappears
86
+ * or locks up between the stat and the read that bounds it — would otherwise
87
+ * surface as an unhandled promise rejection, counted the same way an
88
+ * uncaught exception is, on a 3s timer instead of the 250ms one the cursor fix covered.
89
+ */
90
+ export async function tickActivity(hub: SessionHub): Promise<void> {
91
+ if (hub.activitySubs.size === 0) return;
92
+ try {
93
+ const now = agentTranscriptClock.now();
94
+ const running = await computeRunningAgents(hub.owned, now);
95
+ const failed: AgentTranscriptWsLike[] = [];
96
+ for (const [ws, subIds] of hub.activitySubs) {
97
+ if (!hub.tokenStillValid(ws) || !pushActivity(ws, [...subIds], running)) failed.push(ws);
98
+ }
99
+ for (const ws of failed) hub.onSendFailure(ws);
100
+ } catch {
101
+ console.warn(`[agent-activity] hub=${hub.key} tick failed`);
102
+ }
103
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * The read-and-push half of a transcript subscription: one tick's worth of
3
+ * "what's new across every file this subscription watches, including any
4
+ * file the set itself grew by" turned into paged WS pushes with an exact
5
+ * per-page cursor. Split out from `agent-transcript-session-hub.ts` (the
6
+ * subscribe/unsubscribe/tick-scheduling half) to keep both files within the
7
+ * project's file-size guideline.
8
+ */
9
+ import { resolveCodexDescendantFile, resolveSources } from "./agent-transcript-sources.ts";
10
+ import { createFileTailState } from "./agent-transcript-file-tail-state.ts";
11
+ import { processFileTail, type ProcessFileResult } from "./agent-transcript-file-tail-feed.ts";
12
+ import { paginateTagged, type TaggedEnvelope } from "./agent-transcript-envelope.ts";
13
+ import type { SessionHub, TranscriptSubscription } from "./agent-transcript-session-hub-types.ts";
14
+ import { sendWsMessage, type AgentTranscriptWsLike } from "./agent-transcript-ws-like.ts";
15
+ import {
16
+ IDLE_AFTER_MS, type AgentTranscriptCursor, type AgentTranscriptEventsMsg,
17
+ } from "../../shared/agent-transcript-protocol.ts";
18
+
19
+ type FileEntry = { key: string; result: ProcessFileResult };
20
+
21
+ export function buildCursor(sub: TranscriptSubscription): AgentTranscriptCursor {
22
+ const cursor: AgentTranscriptCursor = {};
23
+ for (const [key, f] of sub.files) cursor[key] = f.offset;
24
+ return cursor;
25
+ }
26
+
27
+ export function isIdle(sub: TranscriptSubscription, now: number): boolean {
28
+ if (sub.files.size === 0) return false;
29
+ let latestGrowth = 0;
30
+ for (const f of sub.files.values()) latestGrowth = Math.max(latestGrowth, f.lastGrowthAt);
31
+ return now - latestGrowth >= IDLE_AFTER_MS;
32
+ }
33
+
34
+ /** Re-derive every file in a subscription from byte 0, for the rare case where any one of them reset. */
35
+ function fullResync(sub: TranscriptSubscription, now: number): FileEntry[] {
36
+ const entries: FileEntry[] = [];
37
+ for (const [key, f] of sub.files) {
38
+ const fresh = createFileTailState(f.ref, 0, now);
39
+ const result = processFileTail(fresh, now);
40
+ sub.files.set(key, fresh);
41
+ entries.push({ key, result });
42
+ }
43
+ return entries;
44
+ }
45
+
46
+ /**
47
+ * Bring a Claude card subscription's file set up to date with the card's
48
+ * current group — a grandchild whose own transcript appears after subscribe
49
+ * must join the stream rather than wait for the window to be reopened. Runs
50
+ * every tick but stays cheap: `resolveSources` caches its own result for ~2s.
51
+ */
52
+ function refreshClaudeCardFiles(hub: SessionHub, sub: TranscriptSubscription, now: number): void {
53
+ if (hub.owned.providerId !== "claude" || sub.source.kind !== "card") return;
54
+ const resolved = resolveSources(hub.owned, sub.source);
55
+ if (!Array.isArray(resolved)) return;
56
+ for (const ref of resolved) {
57
+ if (sub.files.has(ref.key)) continue;
58
+ sub.files.set(ref.key, createFileTailState(ref, 0, now));
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Fold newly-discovered Codex descendant threads — surfaced as `links` from
64
+ * this tick's own reads — into the subscription, each re-validated through
65
+ * the exact same fail-closed descendant chain a directly-requested card goes
66
+ * through (`resolveCodexDescendantFile`), never a weaker check invented for
67
+ * the live-discovery path.
68
+ */
69
+ function addDiscoveredCodexFiles(hub: SessionHub, sub: TranscriptSubscription, entries: FileEntry[], now: number): FileEntry[] {
70
+ if (hub.owned.providerId !== "codex" || sub.source.kind !== "card") return [];
71
+ const seen = new Set<string>();
72
+ const added: FileEntry[] = [];
73
+ for (const { result } of entries) {
74
+ for (const threadId of result.links) {
75
+ if (seen.has(threadId) || sub.files.has(threadId)) continue;
76
+ seen.add(threadId);
77
+ const ref = resolveCodexDescendantFile(hub.owned, threadId);
78
+ if (!ref) continue;
79
+ const fresh = createFileTailState(ref, 0, now);
80
+ sub.files.set(threadId, fresh);
81
+ added.push({ key: threadId, result: processFileTail(fresh, now) });
82
+ }
83
+ }
84
+ return added;
85
+ }
86
+
87
+ function tagEntries(entries: FileEntry[]): TaggedEnvelope[] {
88
+ const out: TaggedEnvelope[] = [];
89
+ for (const { key, result } of entries) {
90
+ for (let i = 0; i < result.envelopes.length; i++) {
91
+ out.push({ envelope: result.envelopes[i]!, fileKey: key, consumedThrough: result.consumedThrough[i]! });
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+
97
+ /**
98
+ * One read across every file in the subscription, plus whatever the file set
99
+ * itself grew by this tick (new Claude group entries, newly-linked Codex
100
+ * descendants). If any file reports a reset, every file is re-derived from 0
101
+ * — a client wiping its view on `reset: true` must not lose content from a
102
+ * file that did not itself change.
103
+ */
104
+ function readSubscription(hub: SessionHub, sub: TranscriptSubscription, now: number): { items: TaggedEnvelope[]; reset: boolean; hasMoreBacklog: boolean } {
105
+ refreshClaudeCardFiles(hub, sub, now);
106
+
107
+ let entries: FileEntry[] = [...sub.files.entries()].map(([key, f]) => ({ key, result: processFileTail(f, now) }));
108
+ const reset = entries.some(({ result }) => result.reset);
109
+ if (reset) entries = fullResync(sub, now);
110
+
111
+ entries = [...entries, ...addDiscoveredCodexFiles(hub, sub, entries, now)];
112
+
113
+ const hasMoreBacklog = entries.some(({ result }) => result.hasMoreBacklog);
114
+ return { items: tagEntries(entries), reset, hasMoreBacklog };
115
+ }
116
+
117
+ interface PushOutcome {
118
+ sent: boolean;
119
+ /** Whether a per-tick read budget left bytes unread — the caller must keep
120
+ * polling at the live cadence, even for an otherwise-idle subscription. */
121
+ hasMoreBacklog: boolean;
122
+ }
123
+
124
+ /**
125
+ * Drain and send a subscription's current backlog, paging large ones. Every
126
+ * page's `cursor` reflects exactly the file offsets consumed by that page's
127
+ * own content — not the subscription's final offset after the whole tick's
128
+ * read — so a client that only received page k of a multi-page push before
129
+ * dropping resumes at page k+1 on reconnect, never past it.
130
+ *
131
+ * `alwaysSend` covers the subscribe-time call: a client that subscribed
132
+ * already caught up (cursor === current size) still gets one confirming
133
+ * message — silence would be indistinguishable from a subscribe that never
134
+ * reached the hub at all. A live tick with nothing new AND nothing left to
135
+ * catch up on sends nothing, or every idle subscription would cost a message
136
+ * every 250ms/2s forever.
137
+ */
138
+ function pushUpdate(hub: SessionHub, ws: AgentTranscriptWsLike, sub: TranscriptSubscription, now: number, alwaysSend: boolean): PushOutcome {
139
+ const preTickCursor = buildCursor(sub);
140
+ const { items, reset, hasMoreBacklog } = readSubscription(hub, sub, now);
141
+ const pages = paginateTagged(items);
142
+ if (pages.length === 0) {
143
+ if (!reset && !alwaysSend && !hasMoreBacklog) return { sent: true, hasMoreBacklog: false };
144
+ pages.push([]);
145
+ }
146
+ const running = !isIdle(sub, now);
147
+ // On reset every file is being replaced from scratch, so the cursor for
148
+ // this push starts empty rather than carrying over pre-tick offsets that
149
+ // no longer describe what a client holding them has actually been sent.
150
+ const runningCursor: AgentTranscriptCursor = reset ? {} : { ...preTickCursor };
151
+ for (let i = 0; i < pages.length; i++) {
152
+ for (const item of pages[i]!) runningCursor[item.fileKey] = item.consumedThrough;
153
+ const msg: AgentTranscriptEventsMsg = {
154
+ type: "agent-transcript:events",
155
+ subId: sub.subId,
156
+ events: pages[i]!.map((item) => item.envelope),
157
+ cursor: { ...runningCursor },
158
+ available: true,
159
+ running,
160
+ // Not just "another page is already queued behind this one" — also
161
+ // true on the LAST page when the read budget left bytes unread, so the
162
+ // client keeps showing "still catching up" instead of "done".
163
+ ...(i < pages.length - 1 || hasMoreBacklog ? { more: true as const } : {}),
164
+ ...(reset && i === 0 ? { reset: true as const } : {}),
165
+ };
166
+ if (!sendWsMessage(ws, msg)) return { sent: false, hasMoreBacklog };
167
+ }
168
+ return { sent: true, hasMoreBacklog };
169
+ }
170
+
171
+ export type PushResult =
172
+ | { outcome: "ok"; hasMoreBacklog: boolean }
173
+ | { outcome: "send-failed"; hasMoreBacklog: boolean }
174
+ | { outcome: "error"; hasMoreBacklog: false };
175
+
176
+ /**
177
+ * `pushUpdate`, but a throw anywhere in the read/parse/send path is caught
178
+ * and reported as `outcome: "error"` instead of reaching the caller — a
179
+ * single malformed record or a raced file error must never climb out of a
180
+ * WS message handler or a `setInterval` tick into an uncaught exception that
181
+ * takes the whole server down. Distinct from `"send-failed"` (the socket
182
+ * itself is gone) because the caller must react very differently: a
183
+ * send failure means the WHOLE client is unreachable and every hub should
184
+ * drop it; a thrown error means only THIS subscription's own read/parse
185
+ * broke, and every other subscription on the same socket must keep working.
186
+ */
187
+ export function safePushUpdate(hub: SessionHub, ws: AgentTranscriptWsLike, sub: TranscriptSubscription, now: number, alwaysSend: boolean): PushResult {
188
+ try {
189
+ const { sent, hasMoreBacklog } = pushUpdate(hub, ws, sub, now, alwaysSend);
190
+ return sent ? { outcome: "ok", hasMoreBacklog } : { outcome: "send-failed", hasMoreBacklog };
191
+ } catch {
192
+ return { outcome: "error", hasMoreBacklog: false };
193
+ }
194
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Shared state for one `(providerId, sessionId)` hub: the subscriptions
3
+ * currently reading it, and the two lazily-started tickers (live transcript
4
+ * push, activity feed) that exist only while something is subscribed.
5
+ */
6
+ import type { OwnedSession } from "./session-ownership.ts";
7
+ import type { FileTailState } from "./agent-transcript-file-tail-state.ts";
8
+ import type { AgentTranscriptWsLike } from "./agent-transcript-ws-like.ts";
9
+ import type { AgentTranscriptSourceKind } from "../../shared/agent-transcript-protocol.ts";
10
+
11
+ export interface TranscriptSubscription {
12
+ subId: string;
13
+ source: AgentTranscriptSourceKind;
14
+ files: Map<string, FileTailState>;
15
+ nextTickAt: number;
16
+ }
17
+
18
+ export interface SessionHub {
19
+ key: string;
20
+ owned: OwnedSession;
21
+ /** By socket, then by that socket's own `subId` — a client may hold several subscriptions. */
22
+ subscriptions: Map<AgentTranscriptWsLike, Map<string, TranscriptSubscription>>;
23
+ activitySubs: Map<AgentTranscriptWsLike, Set<string>>;
24
+ tickHandle: unknown | null;
25
+ activityHandle: unknown | null;
26
+ /**
27
+ * Owned by the top-level registry (`agent-transcript-hub.ts`), which is the
28
+ * only layer that knows a socket's subscriptions can span several session
29
+ * hubs at once. A failed push or a stale auth token must drop the client
30
+ * everywhere, not just in the hub that happened to detect it.
31
+ */
32
+ onSendFailure: (ws: AgentTranscriptWsLike) => void;
33
+ /**
34
+ * Owned by the top-level registry, same reason as `onSendFailure` — drop
35
+ * ONE subscription's bookkeeping everywhere it is tracked (this hub's own
36
+ * map AND the registry's per-client `subId` index), not just here. Used
37
+ * when a single subscription's own tick throws: the rest of that socket's
38
+ * subscriptions, on this hub or any other, must keep working.
39
+ */
40
+ onSubscriptionError: (ws: AgentTranscriptWsLike, subId: string) => void;
41
+ /** True while `ws`'s token snapshot still matches the live config. */
42
+ tokenStillValid: (ws: AgentTranscriptWsLike) => boolean;
43
+ }
44
+
45
+ export function createSessionHub(
46
+ key: string,
47
+ owned: OwnedSession,
48
+ onSendFailure: (ws: AgentTranscriptWsLike) => void,
49
+ onSubscriptionError: (ws: AgentTranscriptWsLike, subId: string) => void,
50
+ tokenStillValid: (ws: AgentTranscriptWsLike) => boolean,
51
+ ): SessionHub {
52
+ return {
53
+ key,
54
+ owned,
55
+ subscriptions: new Map(),
56
+ activitySubs: new Map(),
57
+ tickHandle: null,
58
+ activityHandle: null,
59
+ onSendFailure,
60
+ onSubscriptionError,
61
+ tokenStillValid,
62
+ };
63
+ }
64
+
65
+ /** Total transcript subscriptions across every client on this hub. */
66
+ export function transcriptSubCount(hub: SessionHub): number {
67
+ let n = 0;
68
+ for (const subs of hub.subscriptions.values()) n += subs.size;
69
+ return n;
70
+ }
71
+
72
+ /** Total activity subscriptions across every client on this hub. */
73
+ export function activitySubCount(hub: SessionHub): number {
74
+ let n = 0;
75
+ for (const subs of hub.activitySubs.values()) n += subs.size;
76
+ return n;
77
+ }
78
+
79
+ export function hubIsEmpty(hub: SessionHub): boolean {
80
+ return hub.subscriptions.size === 0 && hub.activitySubs.size === 0;
81
+ }