@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
@@ -42,6 +42,9 @@ export const Z_BASE = 30;
42
42
 
43
43
  const PREFERRED = { w: 880, h: 560 } as const;
44
44
  const SPAWN_MARGIN = 48;
45
+ /** Cap on a portrait window's height — a 9:16 window this tall already shows a full step
46
+ * list; taller than that just wastes space on an ultrawide monitor. */
47
+ const PORTRAIT_MAX_H = 780;
45
48
 
46
49
  const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
47
50
 
@@ -115,6 +118,21 @@ export function cascadeSpawnRect(existing: Rect[], bounds: Bounds): Rect {
115
118
  );
116
119
  }
117
120
 
121
+ /**
122
+ * Where an agent/teammate session window spawns: a portrait 9:16 rect pinned to the
123
+ * top-right, tall enough to read a step list without scrolling every other line, cascading
124
+ * like any other spawn once more than one is open.
125
+ */
126
+ export function portraitSpawnRect(existing: Rect[], bounds: Bounds): Rect {
127
+ const h = Math.min(bounds.h - SPAWN_MARGIN * 2, PORTRAIT_MAX_H);
128
+ const w = Math.max(MIN_SIZE.w, Math.round((h * 9) / 16));
129
+ const step = existing.length % MAX_WINDOWS;
130
+ return clampRect(
131
+ { x: bounds.w - SPAWN_MARGIN - w - step * CASCADE_STEP, y: SPAWN_MARGIN + step * CASCADE_STEP, w, h },
132
+ bounds,
133
+ );
134
+ }
135
+
118
136
  /** Nudge step for arrow keys on a focused titlebar (Shift = fine-grained). */
119
137
  export function nudgeRect(rect: Rect, dx: number, dy: number, bounds: Bounds): Rect {
120
138
  return clampRect({ ...rect, x: rect.x + dx, y: rect.y + dy }, bounds);
@@ -23,11 +23,12 @@ export interface PersistedWindow {
23
23
 
24
24
  /**
25
25
  * Kinds that may come back after a reload. Derived from WINDOW_KINDS so a new kind is
26
- * restorable by default; `team-member` is the one exclusion, because its body streams a
27
- * live subagent session that no longer exists after a reload — restoring it would open
28
- * an empty shell the user never asked for.
26
+ * restorable by default; `agent-session` is the one exclusion, because its body streams a
27
+ * live agent/teammate transcript that no longer exists after a reload — restoring it would
28
+ * open an empty shell the user never asked for. A persisted blob from before the kind was
29
+ * renamed (`team-member`) simply matches nothing here and is dropped the same way.
29
30
  */
30
- const RESTORABLE_KINDS: readonly WindowKind[] = WINDOW_KINDS.filter((k) => k !== "team-member");
31
+ const RESTORABLE_KINDS: readonly WindowKind[] = WINDOW_KINDS.filter((k) => k !== "agent-session");
31
32
  const STATES: WindowVisualState[] = ["normal", "maximized", "minimized"];
32
33
 
33
34
  function isRect(v: unknown): v is Rect {
@@ -7,7 +7,7 @@ import type { Rect } from "./window-geometry";
7
7
  * Single source of truth: the persistence layer filters this list instead of repeating it,
8
8
  * so a new kind can never be silently dropped on reload.
9
9
  */
10
- export const WINDOW_KINDS = ["explorer", "team-member", "system-monitor", "tab-host", "remote-desktop", "settings"] as const;
10
+ export const WINDOW_KINDS = ["explorer", "agent-session", "system-monitor", "tab-host", "remote-desktop", "settings"] as const;
11
11
 
12
12
  export type WindowKind = (typeof WINDOW_KINDS)[number];
13
13
 
@@ -10,7 +10,7 @@ export type OpenSettingsAction =
10
10
  | { kind: "focus"; id: string; category?: SettingsCategoryId };
11
11
 
12
12
  /**
13
- * There is exactly one set of settings, so unlike `"explorer"`/`"team-member"` (legitimately
13
+ * There is exactly one set of settings, so unlike `"explorer"`/`"agent-session"` (legitimately
14
14
  * multi-instance — different payload, different folder/teammate), a second `"settings"`
15
15
  * window is never a distinct instance, only a duplicate. When one is already open, focus it
16
16
  * instead of spawning another.
@@ -9,7 +9,7 @@ export type OpenSystemMonitorAction =
9
9
  | { kind: "focus"; id: string };
10
10
 
11
11
  /**
12
- * There is exactly one machine, so unlike `"explorer"`/`"team-member"` (legitimately
12
+ * There is exactly one machine, so unlike `"explorer"`/`"agent-session"` (legitimately
13
13
  * multi-instance — different payload, different folder/teammate), a second
14
14
  * `"system-monitor"` window is never a distinct instance, only a duplicate. When one
15
15
  * is already open, focus it instead of spawning another.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * "Who is running right now" for one session, from the transcript hub's liveness
3
+ * feed on `/ws/global` (the `agent-activity` push).
4
+ *
5
+ * Mirrors `use-agent-session-stream.ts`'s subscribe contract exactly: a
6
+ * subscription is never queued while the socket is down (`sendIfOpen` drops it
7
+ * instead — a queued subscribe would flush late, against a state the caller has
8
+ * already moved past), and `onGlobalReady` (fired on every connect and every
9
+ * reconnect) is what actually (re)subscribes. There is no cursor to resume from
10
+ * here — each push is a full snapshot of who is running, not a page of steps —
11
+ * so a resubscribe is just the same message again.
12
+ */
13
+ import { useEffect, useState } from "react";
14
+ import { onGlobalReady, sendIfOpen } from "@/lib/global-ws-channel";
15
+ import type {
16
+ AgentActivityMsg,
17
+ AgentTranscriptProviderId,
18
+ AgentTranscriptRunningEntry,
19
+ } from "../../shared/agent-transcript-protocol";
20
+
21
+ export interface UseAgentActivityOptions {
22
+ projectName: string;
23
+ providerId: AgentTranscriptProviderId;
24
+ /** Empty/absent — a draft chat with no session yet — subscribes to nothing. */
25
+ sessionId: string | null | undefined;
26
+ }
27
+
28
+ function newSubId(): string {
29
+ return `aa-${Math.random().toString(36).slice(2, 10)}`;
30
+ }
31
+
32
+ export function useAgentActivity(opts: UseAgentActivityOptions): AgentTranscriptRunningEntry[] {
33
+ const { projectName, providerId, sessionId } = opts;
34
+ const [running, setRunning] = useState<AgentTranscriptRunningEntry[]>([]);
35
+
36
+ useEffect(() => {
37
+ setRunning([]);
38
+ if (!sessionId || !projectName) return;
39
+ const subId = newSubId();
40
+
41
+ const subscribe = () => {
42
+ sendIfOpen(JSON.stringify({ type: "agent-activity:subscribe", subId, projectName, providerId, sessionId }));
43
+ };
44
+
45
+ const onActivity = (e: Event) => {
46
+ const data = (e as CustomEvent<AgentActivityMsg>).detail;
47
+ if (data.subId !== subId) return;
48
+ setRunning(data.running);
49
+ };
50
+
51
+ window.addEventListener("agent-activity", onActivity);
52
+ const stopResubscribing = onGlobalReady(subscribe);
53
+ subscribe();
54
+
55
+ return () => {
56
+ window.removeEventListener("agent-activity", onActivity);
57
+ stopResubscribing();
58
+ sendIfOpen(JSON.stringify({ type: "agent-activity:unsubscribe", subId }));
59
+ };
60
+ // projectName/providerId/sessionId are every real dependency.
61
+ // eslint-disable-next-line react-hooks/exhaustive-deps
62
+ }, [projectName, providerId, sessionId]);
63
+
64
+ return running;
65
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Follow one Agent card's or teammate's work session live, from the transcript hub on
3
+ * `/ws/global`.
4
+ *
5
+ * Generalises the old per-teammate HTTP poll (`use-member-transcript-tail.ts`) to the hub
6
+ * protocol: a subscription is never queued while the socket is down — `sendIfOpen` drops it
7
+ * instead, because a queued subscribe would replay later with a cursor that is already
8
+ * stale by the time the flush happens. `onGlobalReady` (fired on every connect and every
9
+ * reconnect) is what actually (re)subscribes, using the cursor the last response handed
10
+ * back, so a reconnect resumes with no gap and no duplicate instead of going quiet.
11
+ */
12
+ import { useEffect, useMemo, useState } from "react";
13
+ import { onGlobalReady, sendIfOpen } from "@/lib/global-ws-channel";
14
+ import { applyEnvelopeBatch, type StreamEntry } from "@/lib/agent-session-stream-merge";
15
+ import type {
16
+ AgentTranscriptCursor,
17
+ AgentTranscriptErrorCode,
18
+ AgentTranscriptErrorMsg,
19
+ AgentTranscriptEventsMsg,
20
+ AgentTranscriptProviderId,
21
+ AgentTranscriptSourceKind,
22
+ } from "../../shared/agent-transcript-protocol";
23
+ import type { ChatEvent } from "../../types/chat";
24
+
25
+ export interface UseAgentSessionStreamOptions {
26
+ projectName: string;
27
+ providerId: AgentTranscriptProviderId;
28
+ sessionId: string;
29
+ source: AgentTranscriptSourceKind;
30
+ /** Steps already held in chat memory — shown while the transcript stream is unavailable. */
31
+ fallbackEvents?: ChatEvent[];
32
+ }
33
+
34
+ export interface UseAgentSessionStream {
35
+ events: ChatEvent[];
36
+ /** True only once the server has answered with at least one readable file for this
37
+ * source. False before the first response, and after one that found no transcript (old
38
+ * session, never had one, or an error) — `events` falls back to `fallbackEvents` both
39
+ * times, so a window shows what is already known rather than a blank list while the
40
+ * first page is still in flight. */
41
+ available: boolean;
42
+ running: boolean;
43
+ loading: boolean;
44
+ error: AgentTranscriptErrorCode | null;
45
+ }
46
+
47
+ function newSubId(): string {
48
+ return `ats-${Math.random().toString(36).slice(2, 10)}`;
49
+ }
50
+
51
+ export function useAgentSessionStream(opts: UseAgentSessionStreamOptions): UseAgentSessionStream {
52
+ const { projectName, providerId, sessionId, source, fallbackEvents } = opts;
53
+ const [entries, setEntries] = useState<StreamEntry[]>([]);
54
+ // False until the first response says otherwise, so a window opened with fallback events
55
+ // shows them immediately instead of a blank list while the first page is in flight.
56
+ const [available, setAvailable] = useState(false);
57
+ const [running, setRunning] = useState(false);
58
+ const [loading, setLoading] = useState(true);
59
+ const [error, setError] = useState<AgentTranscriptErrorCode | null>(null);
60
+
61
+ // Any of these changing is a different subscription, not a resume of the same one —
62
+ // JSON-stringified so the effect only re-runs when the actual identity changes, not on
63
+ // every render a caller passes a fresh `source` object literal.
64
+ const sourceKey = JSON.stringify([projectName, providerId, sessionId, source]);
65
+
66
+ useEffect(() => {
67
+ const subId = newSubId();
68
+ let entriesBuf: StreamEntry[] = [];
69
+ let cursor: AgentTranscriptCursor | undefined;
70
+ setEntries([]);
71
+ setAvailable(false);
72
+ setRunning(false);
73
+ setLoading(true);
74
+ setError(null);
75
+
76
+ const subscribe = () => {
77
+ sendIfOpen(
78
+ JSON.stringify({
79
+ type: "agent-transcript:subscribe",
80
+ subId,
81
+ projectName,
82
+ providerId,
83
+ sessionId,
84
+ source,
85
+ ...(cursor ? { cursor } : {}),
86
+ }),
87
+ );
88
+ };
89
+
90
+ const onEvents = (e: Event) => {
91
+ const data = (e as CustomEvent<AgentTranscriptEventsMsg>).detail;
92
+ if (data.subId !== subId) return;
93
+ entriesBuf = applyEnvelopeBatch(entriesBuf, data);
94
+ cursor = data.cursor;
95
+ setEntries(entriesBuf);
96
+ setAvailable(data.available);
97
+ setRunning(data.running);
98
+ setLoading(false);
99
+ setError(null);
100
+ };
101
+
102
+ const onError = (e: Event) => {
103
+ const data = (e as CustomEvent<AgentTranscriptErrorMsg>).detail;
104
+ if (data.subId !== subId) return;
105
+ setAvailable(false);
106
+ setLoading(false);
107
+ setError(data.code);
108
+ };
109
+
110
+ window.addEventListener("agent-transcript:events", onEvents);
111
+ window.addEventListener("agent-transcript:error", onError);
112
+ const stopResubscribing = onGlobalReady(subscribe);
113
+ subscribe();
114
+
115
+ return () => {
116
+ window.removeEventListener("agent-transcript:events", onEvents);
117
+ window.removeEventListener("agent-transcript:error", onError);
118
+ stopResubscribing();
119
+ sendIfOpen(JSON.stringify({ type: "agent-transcript:unsubscribe", subId }));
120
+ };
121
+ // sourceKey captures every real dependency (projectName/providerId/sessionId/source).
122
+ // eslint-disable-next-line react-hooks/exhaustive-deps
123
+ }, [sourceKey]);
124
+
125
+ const events = useMemo(() => entries.map((e) => e.ev), [entries]);
126
+
127
+ return {
128
+ events: available ? events : (fallbackEvents ?? []),
129
+ available,
130
+ running,
131
+ loading,
132
+ error,
133
+ };
134
+ }
@@ -16,6 +16,13 @@ import type { ChatWsServerMessage, SessionPhase, BackgroundShell, VersionGroup }
16
16
  import { useBackgroundOutputStore } from "../stores/background-output-store";
17
17
  import { useSessionListStore } from "@/stores/session-list-store";
18
18
  import { projectRefForName } from "@/stores/session-list-sync-triggers";
19
+ import { applyChildToParent, slimHistoryEvents } from "@/lib/agent-step-summary";
20
+
21
+ /** Slim every Agent/Task card a provider stamped `transcriptAvailable` on, walking
22
+ * in from the REST history response before it ever reaches React state. */
23
+ function slimHistoryMessages(messages: ChatMessage[]): ChatMessage[] {
24
+ return messages.map((m) => (m.events ? { ...m, events: slimHistoryEvents(m.events) } : m));
25
+ }
19
26
 
20
27
  interface ApprovalRequest {
21
28
  requestId: string;
@@ -316,12 +323,8 @@ export function useChat(
316
323
  const event = events[i]!;
317
324
  if (event.type !== "tool_use") continue;
318
325
  if ((event.tool === "Agent" || event.tool === "Task") && (event as any).toolUseId === parentToolUseId) {
319
- const children = [...(event.children ?? [])];
320
- const id = (childEvent as any).toolUseId as string | undefined;
321
- const duplicate = id ? children.findIndex((c) => c.type === childEvent.type && (c as any).toolUseId === id) : -1;
322
- if (duplicate === -1) children.push(childEvent);
323
- else children[duplicate] = childEvent;
324
- return [[...events.slice(0, i), { ...event, children }, ...events.slice(i + 1)], true];
326
+ const updated = applyChildToParent(event, childEvent);
327
+ return [[...events.slice(0, i), updated, ...events.slice(i + 1)], true];
325
328
  }
326
329
  if (event.children?.length) {
327
330
  const [children, found] = append(event.children);
@@ -360,16 +363,7 @@ export function useChat(
360
363
  }
361
364
  if (idx === -1) return prev;
362
365
  const msg = prev[idx]!;
363
- const events = msg.events!.map((e) => {
364
- if (!isParent(e) || e.type !== "tool_use") return e;
365
- const children = [...(e.children ?? [])];
366
- // Replays can redeliver id-bearing children — upsert instead of duplicating
367
- const cid = (childEvent as any).toolUseId as string | undefined;
368
- const dup = cid ? children.findIndex((c) => c.type === childEvent.type && (c as any).toolUseId === cid) : -1;
369
- if (dup !== -1) children[dup] = childEvent;
370
- else children.push(childEvent);
371
- return { ...e, children };
372
- });
366
+ const events = msg.events!.map((e) => (isParent(e) ? applyChildToParent(e, childEvent) : e));
373
367
  return [...prev.slice(0, idx), { ...msg, events }, ...prev.slice(idx + 1)];
374
368
  });
375
369
  return true;
@@ -1237,7 +1231,7 @@ export function useChat(
1237
1231
  // cached bundle from before the upgrade would otherwise render an empty
1238
1232
  // history with no error.
1239
1233
  const payload = Array.isArray(data) ? { messages: data, versionMap: {} } : data;
1240
- let history: ChatMessage[] = Array.isArray(payload?.messages) ? payload.messages : [];
1234
+ let history: ChatMessage[] = slimHistoryMessages(Array.isArray(payload?.messages) ? payload.messages : []);
1241
1235
  if (payload?.versionMap) setVersionMap(payload.versionMap);
1242
1236
  // The server served this transcript from a different id than the one
1243
1237
  // asked for: the provider had renamed the session and this tab kept the
@@ -1529,7 +1523,7 @@ export function useChat(
1529
1523
  if (Array.isArray(payload?.messages) && payload.messages.length > 0) {
1530
1524
  historyReconciledRef.current++;
1531
1525
  if (syncRafRef.current) { clearTimeout(syncRafRef.current); syncRafRef.current = 0; }
1532
- setMessages(payload.messages);
1526
+ setMessages(slimHistoryMessages(payload.messages));
1533
1527
  streamingContentRef.current = "";
1534
1528
  streamingEventsRef.current = [];
1535
1529
  }
@@ -5,6 +5,14 @@ import { useNotificationStore } from "@/stores/notification-store";
5
5
  import { useStreamingStore } from "@/stores/streaming-store";
6
6
  import { syncRunningSessions } from "@/lib/sync-running-sessions";
7
7
  import { syncAllKnownProjects } from "@/stores/session-list-sync-triggers";
8
+ import { notifyGlobalReady, sendIfOpen, setGlobalWsClient } from "@/lib/global-ws-channel";
9
+
10
+ /** How often the client pings — well under the server's own idle timeout, so a live but
11
+ * silent socket never gets reaped as dead. */
12
+ const PING_INTERVAL_MS = 20_000;
13
+ /** No message (including a pong) for this long means the socket is dead even though it
14
+ * never fired close/error — force a fresh connect rather than waiting on the browser. */
15
+ const IDLE_TIMEOUT_MS = 45_000;
8
16
 
9
17
  /**
10
18
  * App-wide event bus client (`/ws/global`).
@@ -30,8 +38,18 @@ import { syncAllKnownProjects } from "@/stores/session-list-sync-triggers";
30
38
  * `design:comments_changed`). A design's `.design/` folder is not watched, so these
31
39
  * are the only signal that its snapshots or comments changed.
32
40
  *
41
+ * - `agent-transcript:events` / `agent-transcript:error` / `agent-activity` → re-dispatched
42
+ * as window events for `useAgentSessionStream` and the running-agents bar. Several of
43
+ * those hooks can be subscribed at once (one per open session window), each filtering the
44
+ * broadcast down to its own `subId` — same pattern as the other re-dispatches here.
45
+ *
33
46
  * Also tells the server which project to watch, so file watching follows the
34
47
  * active project instead of depending on a chat socket existing.
48
+ *
49
+ * Registers its `WsClient` with `global-ws-channel` and pings every 20s: the hub protocol's
50
+ * subscriptions (`useAgentSessionStream`) must never queue a send while this socket is
51
+ * down, so they need to know the instant it actually goes idle rather than only on the next
52
+ * browser-level close/error.
35
53
  */
36
54
  export function useGlobalEvents(enabled: boolean, projectName?: string): void {
37
55
  const clientRef = useRef<WsClient | null>(null);
@@ -44,8 +62,11 @@ export function useGlobalEvents(enabled: boolean, projectName?: string): void {
44
62
  if (!enabled) return;
45
63
 
46
64
  const token = getAuthToken();
47
- const client = new WsClient(`/ws/global${token ? `?token=${encodeURIComponent(token)}` : ""}`);
65
+ const client = new WsClient(`/ws/global${token ? `?token=${encodeURIComponent(token)}` : ""}`, {
66
+ idleTimeoutMs: IDLE_TIMEOUT_MS,
67
+ });
48
68
  clientRef.current = client;
69
+ setGlobalWsClient(client);
49
70
 
50
71
  const unsubscribe = client.onMessage((event) => {
51
72
  let data: { type?: string; [k: string]: unknown };
@@ -68,6 +89,7 @@ export function useGlobalEvents(enabled: boolean, projectName?: string): void {
68
89
  // device renamed, pinned or deleted a session) — re-sync every
69
90
  // project this browser already knows about.
70
91
  syncAllKnownProjects();
92
+ notifyGlobalReady();
71
93
  return;
72
94
  }
73
95
 
@@ -103,17 +125,28 @@ export function useGlobalEvents(enabled: boolean, projectName?: string): void {
103
125
  return;
104
126
  }
105
127
 
106
- if (type.startsWith("jira:") || type.startsWith("tunnel:") || type.startsWith("design:")) {
128
+ if (
129
+ type.startsWith("jira:")
130
+ || type.startsWith("tunnel:")
131
+ || type.startsWith("design:")
132
+ || type.startsWith("agent-transcript:")
133
+ || type === "agent-activity"
134
+ ) {
107
135
  window.dispatchEvent(new CustomEvent(type, { detail: data }));
108
136
  }
109
137
  });
110
138
 
111
139
  client.connect();
140
+ const pingTimer = setInterval(() => {
141
+ sendIfOpen(JSON.stringify({ type: "ping" }));
142
+ }, PING_INTERVAL_MS);
112
143
 
113
144
  return () => {
145
+ clearInterval(pingTimer);
114
146
  unsubscribe();
115
147
  client.disconnect();
116
148
  clientRef.current = null;
149
+ setGlobalWsClient(null);
117
150
  };
118
151
  }, [enabled]);
119
152
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Pure event-buffer reducer for the agent session stream hook.
3
+ *
4
+ * Split out from the hook itself so the ordering/de-dupe/replace/reset/cap rules are
5
+ * testable under `bun:test` with no WebSocket, no React and no store to mount. Two pushes
6
+ * for the same subscription are not guaranteed to arrive in `ts` order (a live tick can
7
+ * land while a catch-up backlog is still draining), and a Codex `replace` updates a step
8
+ * already shown in place rather than appending a new one.
9
+ */
10
+ import type { AgentTranscriptEventsMsg } from "../../shared/agent-transcript-protocol";
11
+ import type { ChatEvent } from "../../types/chat";
12
+
13
+ /** One accumulated step, kept alongside its de-dupe key and timestamp for re-ordering. */
14
+ export interface StreamEntry {
15
+ k: string;
16
+ ts: number;
17
+ ev: ChatEvent;
18
+ }
19
+
20
+ /** Mirrors the hub's own per-subscription cap so a long session stays renderable. */
21
+ export const MAX_STREAM_ENTRIES = 2000;
22
+
23
+ /**
24
+ * Apply one `agent-transcript:events` page to the entries accumulated so far.
25
+ *
26
+ * `reset` discards everything first — a truncated/rotated file or a Codex compaction means
27
+ * the server is telling us history itself changed, not just that more of it arrived.
28
+ * Otherwise: a `k` already held is either a `replace` (update in place) or a plain duplicate
29
+ * from an overlapping catch-up/live-tick window (skipped); a new `k` is inserted at the
30
+ * position its `ts` sorts into rather than appended blindly.
31
+ */
32
+ export function applyEnvelopeBatch(
33
+ current: StreamEntry[],
34
+ batch: AgentTranscriptEventsMsg,
35
+ ): StreamEntry[] {
36
+ const next = batch.reset ? [] : current.slice();
37
+ for (const { ev, ts, k, replace } of batch.events) {
38
+ const existingIndex = next.findIndex((e) => e.k === k);
39
+ if (existingIndex !== -1) {
40
+ if (replace) next[existingIndex] = { k, ts, ev };
41
+ continue;
42
+ }
43
+ let insertAt = next.length;
44
+ while (insertAt > 0 && next[insertAt - 1]!.ts > ts) insertAt--;
45
+ next.splice(insertAt, 0, { k, ts, ev });
46
+ }
47
+ return next.length > MAX_STREAM_ENTRIES ? next.slice(next.length - MAX_STREAM_ENTRIES) : next;
48
+ }