@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,133 @@
1
+ /**
2
+ * Floating-window body replaying one agent's or teammate's work session, live.
3
+ *
4
+ * Generalised from the old team-member-only window: any card (an Agent/Task tool call) or
5
+ * named teammate streams through the same hub subscription (`useAgentSessionStream`), so a
6
+ * running background agent, a resumed SendMessage teammate and an old finished session all
7
+ * render through the same steps view. Nested Agent rows inside the stream stay expanded
8
+ * inline — `variant="window"` on `SubagentChildren` is what stops a nested Agent card from
9
+ * trying to open a second window for work this window is already the live view of.
10
+ */
11
+ import { useMemo, useState } from "react";
12
+ import { ChevronDown, ChevronRight, Loader2 } from "@/lib/icons";
13
+ import { cn } from "@/lib/utils";
14
+ import { useAgentSessionStream } from "@/hooks/use-agent-session-stream";
15
+ import { usePrefersCoarsePointer } from "@/components/os-explorer/use-coarse-long-press";
16
+ import { formatStepCount } from "@/lib/agent-step-summary";
17
+ import { fallbackKey, useAgentSessionFallbackStore } from "./agent-session-fallback-store";
18
+ import type { WindowContentProps } from "@/components/floating-window/window-content-registry";
19
+ import type { AgentTranscriptProviderId, AgentTranscriptSourceKind } from "../../../shared/agent-transcript-protocol";
20
+ import type { ChatEvent } from "../../../types/chat";
21
+ import { SubagentChildren } from "./tool-cards";
22
+
23
+ /** Payload a window or sheet opens an agent/teammate session with — the identity the hub
24
+ * subscribes on, plus display-only extras the opener already knows. */
25
+ export interface AgentSessionWindowPayload {
26
+ projectName: string;
27
+ providerId: AgentTranscriptProviderId;
28
+ sessionId: string;
29
+ source: AgentTranscriptSourceKind;
30
+ /** Window/sheet title — the opener names it (member handle, card description, …). */
31
+ title?: string;
32
+ /** The Agent/Task tool call's own prompt, shown as a collapsible header for a card source. */
33
+ prompt?: string;
34
+ }
35
+
36
+ function stepCount(events: ChatEvent[]): number {
37
+ return events.filter((e) => e.type === "tool_use").length;
38
+ }
39
+
40
+ type Status = "loading" | "running" | "failed" | "done";
41
+
42
+ function StatusBadge({ status }: { status: Status }) {
43
+ if (status === "loading") return null;
44
+ if (status === "running") {
45
+ return (
46
+ <span className="flex items-center gap-1 text-[10px] text-primary shrink-0">
47
+ <Loader2 className="size-2.5 animate-spin" /> running
48
+ </span>
49
+ );
50
+ }
51
+ if (status === "failed") return <span className="text-[10px] text-error shrink-0">failed</span>;
52
+ return <span className="text-[10px] text-success shrink-0">done</span>;
53
+ }
54
+
55
+ export default function AgentSessionWindowContent({ payload }: WindowContentProps) {
56
+ const p = (payload ?? {}) as unknown as AgentSessionWindowPayload;
57
+ const key = p.sessionId ? fallbackKey(p.sessionId, p.source) : "";
58
+ const fallbackEvents = useAgentSessionFallbackStore((s) => (key ? s.entries[key] : undefined));
59
+ const { events, available, running, loading, error } = useAgentSessionStream({
60
+ projectName: p.projectName,
61
+ providerId: p.providerId,
62
+ sessionId: p.sessionId,
63
+ source: p.source,
64
+ fallbackEvents,
65
+ });
66
+ const [promptOpen, setPromptOpen] = useState(false);
67
+ // Touch needs the 44px minimum even at desktop window width; a mouse does not.
68
+ const coarse = usePrefersCoarsePointer();
69
+
70
+ const steps = useMemo(() => stepCount(events), [events]);
71
+ // Best-effort only: the transcript carries no explicit terminal marker of its own, so a
72
+ // stream that stopped ("running" went false) on an error result is read as failed.
73
+ const tail = events[events.length - 1];
74
+ const tailFailed = !running && !!tail && tail.type === "tool_result" && !!tail.isError;
75
+ const status: Status = loading ? "loading" : running ? "running" : tailFailed ? "failed" : "done";
76
+
77
+ return (
78
+ <div className="flex flex-col h-full min-h-0 bg-surface @container">
79
+ <div className="flex items-center gap-2 px-3 py-1.5 border-b border-border/30 shrink-0">
80
+ <span className="truncate text-xs font-medium">{p.title ?? "Agent session"}</span>
81
+ {events.length > 0 && <span className="text-[10px] text-text-subtle shrink-0">{formatStepCount(steps)}</span>}
82
+ <StatusBadge status={status} />
83
+ {!available && !loading && (
84
+ <span
85
+ className="ml-auto text-[10px] text-text-subtle shrink-0"
86
+ title="No live transcript for this session — showing steps already held in memory"
87
+ >
88
+ offline
89
+ </span>
90
+ )}
91
+ </div>
92
+
93
+ {!!p.prompt && (
94
+ <div className="border-b border-border/30 shrink-0">
95
+ <button
96
+ type="button"
97
+ onClick={() => setPromptOpen((v) => !v)}
98
+ className={cn(
99
+ "flex w-full items-center gap-1.5 px-3 py-1.5 text-[11px] text-text-subtle hover:text-foreground",
100
+ coarse ? "min-h-[44px]" : "min-h-[28px]",
101
+ )}
102
+ >
103
+ {promptOpen ? <ChevronDown className="size-3" /> : <ChevronRight className="size-3" />}
104
+ Prompt
105
+ </button>
106
+ {promptOpen && (
107
+ <div className="px-3 pb-2 text-[11px] text-text-secondary whitespace-pre-wrap select-text">
108
+ {p.prompt}
109
+ </div>
110
+ )}
111
+ </div>
112
+ )}
113
+
114
+ {loading && events.length === 0 ? (
115
+ <div className="flex-1 flex items-center justify-center gap-2 text-xs text-text-subtle">
116
+ <Loader2 className="size-3 animate-spin" />
117
+ Loading session…
118
+ </div>
119
+ ) : events.length === 0 ? (
120
+ <div className="flex-1 flex items-center justify-center text-xs text-text-subtle px-4 text-center">
121
+ {error ? "Could not load this session" : "No recorded steps"}
122
+ </div>
123
+ ) : (
124
+ <SubagentChildren
125
+ events={events}
126
+ projectName={p.projectName}
127
+ variant="window"
128
+ className="flex-1 min-h-0 overflow-y-auto px-3 py-2 space-y-1"
129
+ />
130
+ )}
131
+ </div>
132
+ );
133
+ }
@@ -669,6 +669,7 @@ export function ChatHistoryBar({
669
669
  messages={teamMessages ?? []}
670
670
  sessionId={sessionId}
671
671
  projectName={projectName}
672
+ providerId={providerId}
672
673
  />
673
674
  </div>
674
675
  )}
@@ -1,4 +1,4 @@
1
- import { useState, useCallback, useRef, useEffect } from "react";
1
+ import { useState, useCallback, useRef, useEffect, useMemo } from "react";
2
2
  import { Loader2, Upload, X } from "@/lib/icons";
3
3
  import { toast } from "sonner";
4
4
  import { api, projectUrl } from "@/lib/api-client";
@@ -17,7 +17,8 @@ import { useChatAccountClaim } from "@/hooks/use-chat-account-claim";
17
17
  import { startPrepare, getPrepare, isPrepared, forgetPrepare } from "@/lib/new-chat-prepare-client";
18
18
  import { MessageList } from "./message-list";
19
19
  import { BackgroundCommandBar } from "./background-command-bar";
20
- import { TeamWorkingBar } from "./team-working-bar";
20
+ import { RunningAgentsBar } from "./running-agents-bar";
21
+ import { AgentSessionProvider, normalizeProviderId } from "./agent-session-context";
21
22
  import { McpSignInBar } from "@/components/mcp-auth/mcp-sign-in-bar";
22
23
  import { useTeamActivityFeed } from "@/hooks/use-team-activity-feed";
23
24
  import { MessageInput, type ChatAttachment, type MessagePriority } from "./message-input";
@@ -446,6 +447,13 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
446
447
  const primaryTeam = teamActivity?.teamNames?.[0] ?? "";
447
448
  const { members: teamMembers } = useTeamActivityFeed(primaryTeam, !!primaryTeam);
448
449
 
450
+ // Identity every Agent/Task card's one-line summary reads to open its session window —
451
+ // memoized so a card's context read doesn't churn on every unrelated re-render.
452
+ const agentSessionIdentity = useMemo(
453
+ () => ({ projectName, providerId: normalizeProviderId(providerId), sessionId: sessionId ?? "" }),
454
+ [projectName, providerId, sessionId],
455
+ );
456
+
449
457
  // Auto-clear notification badge when this tab is active and document is visible.
450
458
  // Checks ALL panels (not just focused) so split-panel scenarios also clear.
451
459
  useEffect(() => {
@@ -1085,6 +1093,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1085
1093
  }, []);
1086
1094
 
1087
1095
  return (
1096
+ <AgentSessionProvider value={agentSessionIdentity}>
1088
1097
  <div
1089
1098
  data-onboarding="chat"
1090
1099
  className="flex flex-col h-full relative"
@@ -1163,8 +1172,15 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1163
1172
  bashPartialOutput={bashPartialOutput}
1164
1173
  />}
1165
1174
 
1166
- {/* Teammates still working — pinned here so it is the last thing under the conversation */}
1167
- <TeamWorkingBar teamName={primaryTeam} members={teamMembers} projectName={projectName} />
1175
+ {/* Agents still working — pinned here so it is the last thing under the conversation */}
1176
+ <RunningAgentsBar
1177
+ projectName={projectName}
1178
+ providerId={agentSessionIdentity.providerId}
1179
+ sessionId={sessionId}
1180
+ messages={messages}
1181
+ teamName={primaryTeam}
1182
+ teamMembers={teamMembers}
1183
+ />
1168
1184
 
1169
1185
  {/* MCP servers this session cannot use until someone signs in */}
1170
1186
  <McpSignInBar key={sessionId ?? "draft"} needsAuth={mcpNeedsAuth} projectName={projectName || undefined} />
@@ -1287,5 +1303,6 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1287
1303
 
1288
1304
  {/* Bug report popup is now global — see BugReportPopup in app.tsx */}
1289
1305
  </div>
1306
+ </AgentSessionProvider>
1290
1307
  );
1291
1308
  }
@@ -12,7 +12,7 @@
12
12
  * all — so the quota rows follow what the account actually reports.
13
13
  */
14
14
 
15
- import { useState, useEffect, useCallback } from "react";
15
+ import { useState, useEffect, useCallback, useRef } from "react";
16
16
  import { Loader2 } from "@/lib/icons";
17
17
  import { api } from "@/lib/api-client";
18
18
  import { AccountCard } from "@/components/settings/accounts/account-card";
@@ -68,29 +68,47 @@ export function CodexUsagePanel({ onClose, usage, onReload, onSelectAccount, sel
68
68
  const [selectingId, setSelectingId] = useState<string | null>(null);
69
69
  const [togglingId, setTogglingId] = useState<string | null>(null);
70
70
  const [panelError, setPanelError] = useState<string | null>(null);
71
+ const requestRef = useRef(0);
72
+ const refreshingRef = useRef(false);
71
73
  /** The signed-out account whose "Sign in again" chip was pressed. */
72
74
  const [signInAgain, setSignInAgain] = useState<CodexAccount | null>(null);
73
75
 
74
76
  const load = useCallback(async () => {
77
+ if (refreshingRef.current) return;
78
+ const request = ++requestRef.current;
75
79
  setLoading(true);
76
80
  try {
77
81
  const d = await api.get<{ accounts: CodexAccount[] }>("/api/codex-accounts");
82
+ if (request !== requestRef.current) return;
78
83
  setAccounts(d.accounts);
79
84
  const u = await api.get<Record<string, Usage>>("/api/codex-accounts/usage");
80
- setUsages(u);
81
- } catch { /* ignore */ } finally { setLoading(false); }
85
+ if (request === requestRef.current) setUsages(u);
86
+ } catch { /* ignore */ } finally {
87
+ if (request === requestRef.current) setLoading(false);
88
+ }
82
89
  }, []);
83
- useEffect(() => { load(); }, [load]);
90
+ useEffect(() => {
91
+ void load();
92
+ // The server refreshes quota every five minutes. Keep open cards in sync
93
+ // by re-reading its cache on the same cadence as the chat header.
94
+ const timer = setInterval(() => { void load(); }, 120_000);
95
+ return () => { clearInterval(timer); ++requestRef.current; };
96
+ }, [load]);
84
97
 
85
98
  const reload = useCallback(async () => {
99
+ const request = ++requestRef.current;
100
+ refreshingRef.current = true;
86
101
  setLoading(true);
87
102
  try {
88
- // Unlike the account-list endpoint, the chat refresh carries refresh=1
89
- // and invalidates Codex's five-minute provider-usage cache first.
103
+ // The chat refresh carries refresh=1 and fetches every Codex account
104
+ // live before this cached account-list read.
90
105
  await onReload?.();
91
106
  const u = await api.get<Record<string, Usage>>("/api/codex-accounts/usage");
92
- setUsages(u);
93
- } catch { /* retain the last successful reading */ } finally { setLoading(false); }
107
+ if (request === requestRef.current) setUsages(u);
108
+ } catch { /* retain the last successful reading */ } finally {
109
+ refreshingRef.current = false;
110
+ if (request === requestRef.current) setLoading(false);
111
+ }
94
112
  }, [onReload]);
95
113
 
96
114
  /**
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Every agent that is really still working, pinned under the conversation.
3
+ *
4
+ * Replaces `TeamWorkingBar`: that bar only ever knew about named teammates
5
+ * polled over REST. This one also lists a backgrounded agent and one resumed
6
+ * by SendMessage (which writes no new card at all) — anything the transcript
7
+ * hub's `agent-activity` feed reports as recently written to disk, merged with
8
+ * the team panel's richer per-member poll for agent type / elapsed time.
9
+ *
10
+ * Liveness is entirely disk-derived on the server, never from chat state in
11
+ * memory, so a replayed old session shows nothing here even though its cards
12
+ * are still in the transcript.
13
+ */
14
+ import { useMemo } from "react";
15
+ import { Bot, Users } from "@/lib/icons";
16
+ import { useAgentActivity } from "@/hooks/use-agent-activity";
17
+ import type { TeamMemberActivity } from "@/hooks/use-team-activity-feed";
18
+ import { buildRunningRows, findCardLabel } from "@/lib/running-agent-rows";
19
+ import { formatDuration, shortAgentType } from "./team-member-activity-format";
20
+ import { useOpenAgentSession } from "./use-open-agent-session";
21
+ import { usePrefersCoarsePointer } from "@/components/os-explorer/use-coarse-long-press";
22
+ import { cn } from "@/lib/utils";
23
+ import type { ChatMessage } from "../../../types/chat";
24
+ import type { AgentTranscriptProviderId } from "../../../shared/agent-transcript-protocol";
25
+
26
+ interface RunningAgentsBarProps {
27
+ projectName: string;
28
+ providerId: AgentTranscriptProviderId;
29
+ sessionId: string | null;
30
+ /** For labelling a card row from what the chat already knows about it. */
31
+ messages: ChatMessage[];
32
+ /** The session's implicit team name, for a member row's `source`. */
33
+ teamName: string;
34
+ teamMembers: TeamMemberActivity[];
35
+ }
36
+
37
+ export function RunningAgentsBar({ projectName, providerId, sessionId, messages, teamName, teamMembers }: RunningAgentsBarProps) {
38
+ const running = useAgentActivity({ projectName, providerId, sessionId });
39
+ const openAgentSession = useOpenAgentSession();
40
+ const rows = useMemo(() => buildRunningRows(running, teamMembers), [running, teamMembers]);
41
+ // Touch needs the 44px minimum even at desktop width; a mouse keeps the tighter 36px row.
42
+ const coarse = usePrefersCoarsePointer();
43
+
44
+ if (!sessionId || rows.length === 0) return null;
45
+
46
+ return (
47
+ <div className="shrink-0 border-t border-border bg-surface-elevated/60 px-2 py-1.5 space-y-1">
48
+ {rows.map((row) => {
49
+ const label = row.cardId ? findCardLabel(messages, row.cardId) : null;
50
+ const name = row.memberName ?? label?.handle ?? null;
51
+ const description = label?.description || (row.cardId ? "Agent" : row.memberName!);
52
+ const elapsed = row.startedAt ? formatDuration(row.startedAt) : undefined;
53
+ const agentType = shortAgentType(row.agentType);
54
+
55
+ return (
56
+ <button
57
+ key={row.key}
58
+ type="button"
59
+ onClick={() => openAgentSession({
60
+ projectName,
61
+ providerId,
62
+ sessionId,
63
+ source: row.cardId ? { kind: "card", cardId: row.cardId } : { kind: "member", teamName, memberName: row.memberName! },
64
+ title: name ? `Session — ${name}` : "Agent session",
65
+ prompt: label?.description,
66
+ })}
67
+ className={cn(
68
+ "flex w-full items-center gap-2 rounded px-1 py-1.5 text-left text-xs hover:bg-surface transition-colors",
69
+ coarse ? "min-h-[44px]" : "min-h-[36px]",
70
+ )}
71
+ title={name ? `Open ${name}'s session` : "Open agent session"}
72
+ >
73
+ <span className="relative flex size-2 shrink-0">
74
+ <span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-60" />
75
+ <span className="relative inline-flex size-2 rounded-full bg-emerald-500" />
76
+ </span>
77
+ {row.memberName
78
+ ? <Users className="size-3.5 shrink-0 text-accent-2" />
79
+ : <Bot className="size-3.5 shrink-0 text-primary" />}
80
+ <span className="font-medium text-text-primary shrink-0">{name ?? description}</span>
81
+ {!!agentType && <span className="text-text-3 shrink-0 hidden sm:inline">{agentType}</span>}
82
+ {!!row.lastStep && (
83
+ <span className="flex-1 truncate text-text-subtle" title={row.lastStep}>
84
+ {row.lastStep}
85
+ </span>
86
+ )}
87
+ {!!elapsed && <span className="ml-auto shrink-0 text-text-3 font-mono">{elapsed}</span>}
88
+ </button>
89
+ );
90
+ })}
91
+ </div>
92
+ );
93
+ }
@@ -11,7 +11,8 @@ import { RefreshCw } from "@/lib/icons";
11
11
  import { cn } from "@/lib/utils";
12
12
  import type { TeamMessageItem } from "@/hooks/use-chat";
13
13
  import { useTeamActivityFeed } from "@/hooks/use-team-activity-feed";
14
- import { useOpenTeamMember } from "./use-open-team-member";
14
+ import { useOpenAgentSession } from "./use-open-agent-session";
15
+ import { normalizeProviderId } from "./agent-session-context";
15
16
  import { usePrefersCoarsePointer } from "@/components/os-explorer/use-coarse-long-press";
16
17
  import { TeamMemberList } from "./team-member-list";
17
18
  import { TeamMessageList } from "./team-message-list";
@@ -25,6 +26,9 @@ interface TeamActivityPanelProps {
25
26
  sessionId?: string | null;
26
27
  /** Passed to a member window so its steps resolve project-relative paths. */
27
28
  projectName?: string;
29
+ /** Which provider's hub to subscribe the session window on — forwarded by
30
+ * `chat-history-bar.tsx` from the chat's own `providerId`; defaults to Claude when absent. */
31
+ providerId?: string;
28
32
  }
29
33
 
30
34
  /** Implicit teams are named after the session, which is an unreadable uuid. */
@@ -32,10 +36,10 @@ function teamLabel(name: string, sessionId?: string | null): string {
32
36
  return name === sessionId ? "Team (current session)" : name;
33
37
  }
34
38
 
35
- export function TeamActivityPanel({ teamNames, messages, sessionId, projectName }: TeamActivityPanelProps) {
39
+ export function TeamActivityPanel({ teamNames, messages, sessionId, projectName, providerId }: TeamActivityPanelProps) {
36
40
  const [selectedTeam, setSelectedTeam] = useState(teamNames[0] ?? "");
37
41
  const [tab, setTab] = useState<TeamTab>("members");
38
- const openMember = useOpenTeamMember();
42
+ const openAgentSession = useOpenAgentSession();
39
43
  // Touch needs the 44px minimum even at desktop width; a mouse does not.
40
44
  const coarse = usePrefersCoarsePointer();
41
45
 
@@ -51,9 +55,19 @@ export function TeamActivityPanel({ teamNames, messages, sessionId, projectName
51
55
 
52
56
  const openMemberSession = useCallback(
53
57
  (memberName: string) => {
54
- openMember({ teamName: selectedTeam, memberName, projectName });
58
+ // A team name is not a session id — the hub's per-session subscription would name a
59
+ // session that never exists and fail ownership. Without the real id there is nothing
60
+ // to open (no worse than the old handle lookup, which had the same gap).
61
+ if (!sessionId) return;
62
+ openAgentSession({
63
+ projectName: projectName ?? "",
64
+ providerId: normalizeProviderId(providerId),
65
+ sessionId,
66
+ source: { kind: "member", teamName: selectedTeam, memberName },
67
+ title: `Session — ${memberName}`,
68
+ });
55
69
  },
56
- [openMember, selectedTeam, projectName],
70
+ [openAgentSession, selectedTeam, projectName, providerId, sessionId],
57
71
  );
58
72
 
59
73
  /**
@@ -101,6 +101,10 @@ import { isImageExtension } from "../../../shared/image-extensions";
101
101
  import { resultHasImagePlaceholder } from "../../../shared/tool-result-content";
102
102
  import { isAsyncAgentLaunchAck } from "../../../shared/background-agent-status";
103
103
  import { ToolImagePreview } from "./tool-image-preview";
104
+ import { AgentCardSummary, type AgentCardStatus } from "./agent-card-summary";
105
+ import { useOpenAgentSession } from "./use-open-agent-session";
106
+ import { useAgentSessionContext, normalizeProviderId } from "./agent-session-context";
107
+ import { agentStepInfo, formatStepCount, mergeFallbackEvents } from "@/lib/agent-step-summary";
104
108
 
105
109
  /** Extract tool name and input from a ChatEvent */
106
110
  function extractToolInfo(tool: ChatEvent): { toolName: string; input: Record<string, unknown> } {
@@ -145,12 +149,18 @@ export function ToolCard({
145
149
  completed,
146
150
  projectName,
147
151
  bashPartialOutput,
152
+ variant = "chat",
148
153
  }: {
149
154
  tool: ChatEvent;
150
155
  result?: ChatEvent;
151
156
  completed?: boolean;
152
157
  projectName?: string;
153
158
  bashPartialOutput?: React.RefObject<Map<string, BashPartialEntry>>;
159
+ /** "window" inside an agent-session window: a nested Agent/Task card must keep expanding
160
+ * inline there (the window already *is* the live view of that work) rather than opening
161
+ * a second window — the chat's own one-line summary branch below only fires for the
162
+ * default `"chat"` variant. */
163
+ variant?: "chat" | "window";
154
164
  }) {
155
165
  const [expanded, setExpanded] = useState(() => {
156
166
  // Edit/MultiEdit cards open by default so the inline diff is visible without
@@ -162,6 +172,11 @@ export function ToolCard({
162
172
  // so show it without requiring a click.
163
173
  return !!imageReadPath(tool);
164
174
  });
175
+ // Called unconditionally (Rules of Hooks) even for tool types that never reach the
176
+ // subagent-summary branch below — cheap, and `variant`/tool type are stable for the
177
+ // life of one ToolCard instance, same as the `tool.type === "error"` early return above.
178
+ const openAgentSession = useOpenAgentSession();
179
+ const agentSessionIdentity = useAgentSessionContext();
165
180
 
166
181
  if (tool.type === "error") {
167
182
  return (
@@ -209,6 +224,45 @@ export function ToolCard({
209
224
 
210
225
  // Read partial output for streaming Bash/PowerShell tools
211
226
  const toolUseId = tool.type === "tool_use" ? (tool as any).toolUseId as string | undefined : undefined;
227
+
228
+ // In chat, every Agent/Task card — named teammates included — is one line that opens the
229
+ // live session window/sheet rather than expanding inline; `variant="window"` (the window's
230
+ // own nested-card rendering) keeps today's inline expansion below instead.
231
+ if (isSubagent && variant === "chat" && tool.type === "tool_use") {
232
+ const handle = addressableAgentName(toolName, input);
233
+ const description = handle ? "" : truncate(String((input as any).description ?? (input as any).prompt ?? ""), 60);
234
+ const { stepCount, lastStep } = agentStepInfo(tool);
235
+ const status: AgentCardStatus = (isError || bgFailed) ? "error" : isDone ? "done" : "running";
236
+ const cardId = toolUseId ?? "";
237
+ // No provider gave this card a real session identity (the group-chat transcript viewer
238
+ // has none) — a synthetic id the hub can never resolve still lets the card open, just
239
+ // straight into memory-only mode via the fallback events below.
240
+ const identity = agentSessionIdentity ?? {
241
+ projectName: projectName ?? "",
242
+ providerId: normalizeProviderId(undefined),
243
+ sessionId: `local:${cardId || "card"}`,
244
+ };
245
+ const fallbackEvents = mergeFallbackEvents(tool.children ?? [], tool.recentChildren ?? []);
246
+ return (
247
+ <AgentCardSummary
248
+ handle={handle}
249
+ description={description}
250
+ stepCount={stepCount}
251
+ lastStep={lastStep}
252
+ status={status}
253
+ bgRunning={bgRunning}
254
+ onOpen={() => openAgentSession({
255
+ projectName: identity.projectName,
256
+ providerId: identity.providerId,
257
+ sessionId: identity.sessionId,
258
+ source: { kind: "card", cardId },
259
+ title: handle ? `Session — ${handle}` : (description || "Agent session"),
260
+ prompt: String((input as any).prompt ?? (input as any).description ?? ""),
261
+ }, fallbackEvents)}
262
+ />
263
+ );
264
+ }
265
+
212
266
  const partial = (toolName === "Bash" || toolName === "PowerShell") && !hasResult && toolUseId
213
267
  ? bashPartialOutput?.current?.get(toolUseId)
214
268
  : undefined;
@@ -256,7 +310,7 @@ export function ToolCard({
256
310
  <span className="text-[10px] text-warning">{partial!.lineCount} line{partial!.lineCount !== 1 ? "s" : ""} streaming…</span>
257
311
  )}
258
312
  {hasChildren && !isStreamingBash && (
259
- <span className="text-[10px] text-text-3 font-mono">{children!.length} steps</span>
313
+ <span className="text-[10px] text-text-3 font-mono">{formatStepCount(children!.length)}</span>
260
314
  )}
261
315
  {bgRunning && (
262
316
  <span className="text-[10px] text-primary">running…</span>
@@ -283,7 +337,7 @@ export function ToolCard({
283
337
  {partial && <StreamingBashOutput content={partial.content} lineCount={partial.lineCount} />}
284
338
  {/* Subagent children: render nested tool events */}
285
339
  {hasChildren && (
286
- <SubagentChildren events={children!} projectName={projectName} />
340
+ <SubagentChildren events={children!} projectName={projectName} variant={variant} />
287
341
  )}
288
342
  {imagePath && (
289
343
  <ToolImagePreview filePath={imagePath} projectName={projectName ?? ""} />
@@ -753,7 +807,13 @@ function CollapsibleOutput({ output }: { output: string }) {
753
807
  * session with the same step rendering the inline Agent card uses.
754
808
  * `className` overrides the container so a full-height surface can drop the
755
809
  * card's fixed max-height. */
756
- export function SubagentChildren({ events, projectName, className }: { events: ChatEvent[]; projectName?: string; className?: string }) {
810
+ export function SubagentChildren({ events, projectName, className, variant = "chat" }: {
811
+ events: ChatEvent[];
812
+ projectName?: string;
813
+ className?: string;
814
+ /** Forwarded to every child `ToolCard` — see its own doc comment. */
815
+ variant?: "chat" | "window";
816
+ }) {
757
817
  // Group children similar to InterleavedEvents: pair tool_use + tool_result, merge text
758
818
  type ChildGroup =
759
819
  | { kind: "text"; content: string }
@@ -806,7 +866,16 @@ export function SubagentChildren({ events, projectName, className }: { events: C
806
866
  </div>
807
867
  );
808
868
  }
809
- return <ToolCard key={`sc-${i}`} tool={g.tool} result={g.result} completed={!!(g.result)} projectName={projectName} />;
869
+ return (
870
+ <ToolCard
871
+ key={`sc-${i}`}
872
+ tool={g.tool}
873
+ result={g.result}
874
+ completed={!!(g.result)}
875
+ projectName={projectName}
876
+ variant={variant}
877
+ />
878
+ );
810
879
  })}
811
880
  </div>
812
881
  );
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Opening an agent's or teammate's work session, on whichever presentation the device has.
3
+ *
4
+ * `WindowLayer` renders nothing below the `md` breakpoint, so `openWindow("agent-session")`
5
+ * is a silent no-op on a phone — the tap looked broken. Desktop keeps the floating window,
6
+ * spawning portrait top-right; mobile gets the full-screen sheet. Both host the same content.
7
+ *
8
+ * Every session opens by its own `(sessionId, source)` identity: a second open for the one
9
+ * already showing focuses that window instead of spawning a duplicate. At the window cap the
10
+ * oldest *agent-session* window is replaced — not some unrelated window the user is still
11
+ * using — and if there is no agent-session window to replace, the open is refused instead of
12
+ * silently evicting something else (focus-and-do-nothing would be worse).
13
+ *
14
+ * The returned callback reads the window store via `getState()` at call time rather than
15
+ * subscribing to it with the `useWindowStore(selector)` hook form: every `ToolCard` in every
16
+ * mounted chat tab calls this hook unconditionally (Bash/Read cards too, not just Agent/Task),
17
+ * and a window-store subscription there re-rendered all of them on every drag/resize frame of
18
+ * any floating window, anywhere — dragging a window made every chat tab's tool cards repaint.
19
+ * The callback only ever needs the *current* windows at the moment a card is tapped, never a
20
+ * live view of them.
21
+ */
22
+ import { useCallback } from "react";
23
+ import { toast } from "sonner";
24
+ import { useIsMobile } from "@/hooks/use-is-mobile";
25
+ import { useWindowStore } from "@/components/floating-window/window-store";
26
+ import { MAX_WINDOWS, portraitSpawnRect } from "@/components/floating-window/window-geometry";
27
+ import type { ChatEvent } from "../../../types/chat";
28
+ import type { AgentSessionWindowPayload } from "./agent-session-window-content";
29
+ import { useAgentSessionSheetStore } from "./agent-session-sheet-store";
30
+ import { fallbackKey, setFallbackEvents } from "./agent-session-fallback-store";
31
+
32
+ function samePayloadSource(
33
+ a: Record<string, unknown> | undefined,
34
+ b: AgentSessionWindowPayload,
35
+ ): boolean {
36
+ const p = a as Partial<AgentSessionWindowPayload> | undefined;
37
+ if (typeof p?.sessionId !== "string" || !p.source) return false;
38
+ return fallbackKey(p.sessionId, p.source) === fallbackKey(b.sessionId, b.source);
39
+ }
40
+
41
+ /** Callback that opens a session the right way for this viewport and the current windows. */
42
+ export function useOpenAgentSession(): (payload: AgentSessionWindowPayload, fallbackEvents?: ChatEvent[]) => void {
43
+ const isMobile = useIsMobile();
44
+ const openSheet = useAgentSessionSheetStore((s) => s.open);
45
+
46
+ return useCallback(
47
+ (payload: AgentSessionWindowPayload, fallbackEvents?: ChatEvent[]) => {
48
+ if (!payload.sessionId) return; // nothing the hub can stream without a session id
49
+
50
+ if (fallbackEvents) {
51
+ setFallbackEvents(fallbackKey(payload.sessionId, payload.source), fallbackEvents);
52
+ }
53
+
54
+ if (isMobile) {
55
+ openSheet(payload);
56
+ return;
57
+ }
58
+
59
+ const store = useWindowStore.getState();
60
+ const asRecord = payload as unknown as Record<string, unknown>;
61
+ const all = Object.values(store.windows);
62
+ const existing = all.find((w) => w.kind === "agent-session" && samePayloadSource(w.payload, payload));
63
+ if (existing) {
64
+ store.focus(existing.id);
65
+ return;
66
+ }
67
+
68
+ if (all.length >= MAX_WINDOWS) {
69
+ const agentWindows = all.filter((w) => w.kind === "agent-session").sort((a, b) => a.rank - b.rank);
70
+ if (agentWindows.length === 0) {
71
+ toast.error("Too many windows open", { description: "Close one to open this session." });
72
+ return;
73
+ }
74
+ store.close(agentWindows[0]!.id);
75
+ }
76
+
77
+ const afterClose = Object.values(useWindowStore.getState().windows);
78
+ const rect = portraitSpawnRect(afterClose.map((w) => w.rect), useWindowStore.getState().bounds);
79
+ useWindowStore.getState().open("agent-session", asRecord, rect);
80
+ },
81
+ [isMobile, openSheet],
82
+ );
83
+ }
@@ -19,7 +19,7 @@ export interface WindowContentProps {
19
19
 
20
20
  export const WINDOW_CONTENT: Record<WindowKind, LazyExoticComponent<ComponentType<WindowContentProps>>> = {
21
21
  explorer: lazy(() => import("@/components/os-explorer/explorer-window-content")),
22
- "team-member": lazy(() => import("@/components/chat/team-member-window-content")),
22
+ "agent-session": lazy(() => import("@/components/chat/agent-session-window-content")),
23
23
  "system-monitor": lazy(() => import("@/components/system/system-monitor-window-content")),
24
24
  "tab-host": lazy(() => import("./tab-host-window-content")),
25
25
  "remote-desktop": lazy(() => import("@/components/remote-desktop/remote-desktop-window-content")),
@@ -30,10 +30,9 @@ export const WINDOW_CONTENT: Record<WindowKind, LazyExoticComponent<ComponentTyp
30
30
  export function windowTitle(kind: WindowKind, payload?: Record<string, unknown>): string {
31
31
  const explicit = payload?.title;
32
32
  if (typeof explicit === "string" && explicit.trim()) return explicit;
33
- if (kind === "team-member") {
34
- const member = payload?.memberName;
35
- return typeof member === "string" && member ? `Session — ${member}` : "Team member";
36
- }
33
+ // Every agent-session window is opened with an explicit title (member name or card
34
+ // description) by its opener, so this only fires for a malformed/legacy payload.
35
+ if (kind === "agent-session") return "Agent session";
37
36
  if (kind === "system-monitor") return "System Monitor";
38
37
  if (kind === "remote-desktop") return "Remote Desktop";
39
38
  if (kind === "settings") return "Settings";