@hienlh/ppm 0.23.16 → 0.23.18

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 +13 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/cli-reference.md +4 -4
  4. package/assets/skills/ppm/references/http-api.md +1 -1
  5. package/dist/web/assets/{accounts-settings-section-DhzuFkDO.js → accounts-settings-section-BES4EFGQ.js} +1 -1
  6. package/dist/web/assets/accounts-settings-section-BES4EFGQ.js.br +0 -0
  7. package/dist/web/assets/accounts-settings-section-BES4EFGQ.js.gz +0 -0
  8. package/dist/web/assets/{agent-session-sheet-CZAmOwSU.js → agent-session-sheet-BlZmcb40.js} +2 -2
  9. package/dist/web/assets/agent-session-sheet-BlZmcb40.js.br +0 -0
  10. package/dist/web/assets/agent-session-sheet-BlZmcb40.js.gz +0 -0
  11. package/dist/web/assets/{agent-session-window-content-CwokWtJq.js → agent-session-window-content-zCzFZ0XT.js} +1 -1
  12. package/dist/web/assets/agent-session-window-content-zCzFZ0XT.js.br +0 -0
  13. package/dist/web/assets/agent-session-window-content-zCzFZ0XT.js.gz +0 -0
  14. package/dist/web/assets/{ai-resource-editor-CT1JCaRQ.js → ai-resource-editor-Dh8sQhL2.js} +1 -1
  15. package/dist/web/assets/ai-resource-editor-Dh8sQhL2.js.br +0 -0
  16. package/dist/web/assets/ai-resource-editor-Dh8sQhL2.js.gz +0 -0
  17. package/dist/web/assets/{ai-settings-section-Ct9-lNqA.js → ai-settings-section-C6NKrktb.js} +1 -1
  18. package/dist/web/assets/ai-settings-section-C6NKrktb.js.br +0 -0
  19. package/dist/web/assets/ai-settings-section-C6NKrktb.js.gz +0 -0
  20. package/dist/web/assets/{ai-settings-section-CBlS1FZK.js → ai-settings-section-Ck_0ArV1.js} +1 -1
  21. package/dist/web/assets/{android-tab-CUPQN2M4.js → android-tab-BLxZKtMP.js} +1 -1
  22. package/dist/web/assets/android-tab-BLxZKtMP.js.br +0 -0
  23. package/dist/web/assets/android-tab-BLxZKtMP.js.gz +0 -0
  24. package/dist/web/assets/{appearance-settings-section-DpYcNqXH.js → appearance-settings-section-DFJdBUZ8.js} +1 -1
  25. package/dist/web/assets/appearance-settings-section-DFJdBUZ8.js.br +0 -0
  26. package/dist/web/assets/appearance-settings-section-DFJdBUZ8.js.gz +0 -0
  27. package/dist/web/assets/{apps-panel-BqF_CkKy.js → apps-panel-C2dyMFVy.js} +1 -1
  28. package/dist/web/assets/apps-panel-C2dyMFVy.js.br +0 -0
  29. package/dist/web/assets/apps-panel-C2dyMFVy.js.gz +0 -0
  30. package/dist/web/assets/{branch-review-tab-BEZqFSlw.js → branch-review-tab-BIpjOgOQ.js} +1 -1
  31. package/dist/web/assets/branch-review-tab-BIpjOgOQ.js.br +0 -0
  32. package/dist/web/assets/branch-review-tab-BIpjOgOQ.js.gz +0 -0
  33. package/dist/web/assets/{chat-tab-N1B-Yv0P.js → chat-tab-CADhIdsz.js} +1 -1
  34. package/dist/web/assets/chat-tab-CADhIdsz.js.br +2 -0
  35. package/dist/web/assets/chat-tab-CADhIdsz.js.gz +0 -0
  36. package/dist/web/assets/chat-tab-WbP7BIUp.js +19 -0
  37. package/dist/web/assets/chat-tab-WbP7BIUp.js.br +0 -0
  38. package/dist/web/assets/chat-tab-WbP7BIUp.js.gz +0 -0
  39. package/dist/web/assets/{code-editor-d1CXl2WL.js → code-editor-BEjBAz0q.js} +4 -4
  40. package/dist/web/assets/code-editor-BEjBAz0q.js.br +0 -0
  41. package/dist/web/assets/code-editor-BEjBAz0q.js.gz +0 -0
  42. package/dist/web/assets/{codex-reset-credit-button-CwSSBK6P.js → codex-reset-credit-button-Qths4oQl.js} +1 -1
  43. package/dist/web/assets/codex-reset-credit-button-Qths4oQl.js.br +0 -0
  44. package/dist/web/assets/codex-reset-credit-button-Qths4oQl.js.gz +0 -0
  45. package/dist/web/assets/{conflict-editor-BucKz-uE.js → conflict-editor-DwFgcGGr.js} +1 -1
  46. package/dist/web/assets/conflict-editor-DwFgcGGr.js.br +0 -0
  47. package/dist/web/assets/conflict-editor-DwFgcGGr.js.gz +0 -0
  48. package/dist/web/assets/{connection-form-tab-DIc25Bko.js → connection-form-tab-DJ3Lxpzd.js} +1 -1
  49. package/dist/web/assets/connection-form-tab-DJ3Lxpzd.js.br +0 -0
  50. package/dist/web/assets/connection-form-tab-DJ3Lxpzd.js.gz +0 -0
  51. package/dist/web/assets/{csv-preview-CqDaGV5N.js → csv-preview-BMF0HuuN.js} +1 -1
  52. package/dist/web/assets/csv-preview-BMF0HuuN.js.br +0 -0
  53. package/dist/web/assets/csv-preview-BMF0HuuN.js.gz +0 -0
  54. package/dist/web/assets/{db-tab-parts-lp8ROGQB.js → db-tab-parts-BbNfuvwC.js} +1 -1
  55. package/dist/web/assets/db-tab-parts-BbNfuvwC.js.br +0 -0
  56. package/dist/web/assets/db-tab-parts-BbNfuvwC.js.gz +0 -0
  57. package/dist/web/assets/{design-settings-section-DxWEBH1L.js → design-settings-section-BVcBTBdz.js} +1 -1
  58. package/dist/web/assets/design-settings-section-BVcBTBdz.js.br +0 -0
  59. package/dist/web/assets/design-settings-section-BVcBTBdz.js.gz +0 -0
  60. package/dist/web/assets/{design-tab-Cl0CMAUx.js → design-tab-msL92Mgr.js} +1 -1
  61. package/dist/web/assets/design-tab-msL92Mgr.js.br +0 -0
  62. package/dist/web/assets/design-tab-msL92Mgr.js.gz +0 -0
  63. package/dist/web/assets/{designs-sidebar-panel-aCr-EGfj.js → designs-sidebar-panel-hLIuDsDd.js} +1 -1
  64. package/dist/web/assets/designs-sidebar-panel-hLIuDsDd.js.br +0 -0
  65. package/dist/web/assets/designs-sidebar-panel-hLIuDsDd.js.gz +0 -0
  66. package/dist/web/assets/{diff-viewer-XyJgWKwe.js → diff-viewer-BLYGfIik.js} +1 -1
  67. package/dist/web/assets/diff-viewer-BLYGfIik.js.br +0 -0
  68. package/dist/web/assets/diff-viewer-BLYGfIik.js.gz +0 -0
  69. package/dist/web/assets/{docx-preview-KJaj2YGN.js → docx-preview-D0ORQkvI.js} +1 -1
  70. package/dist/web/assets/docx-preview-D0ORQkvI.js.br +0 -0
  71. package/dist/web/assets/docx-preview-D0ORQkvI.js.gz +0 -0
  72. package/dist/web/assets/{editor-dialog-9tW6c9mu.js → editor-dialog-BdkAgK-M.js} +1 -1
  73. package/dist/web/assets/editor-dialog-BdkAgK-M.js.br +0 -0
  74. package/dist/web/assets/editor-dialog-BdkAgK-M.js.gz +0 -0
  75. package/dist/web/assets/{editor-language-service-CkdO9ih8.js → editor-language-service-B2TYY2sw.js} +1 -1
  76. package/dist/web/assets/editor-language-service-B2TYY2sw.js.br +0 -0
  77. package/dist/web/assets/editor-language-service-B2TYY2sw.js.gz +0 -0
  78. package/dist/web/assets/{explorer-body-B8AkNO5o.js → explorer-body-B-67PDYd.js} +1 -1
  79. package/dist/web/assets/explorer-body-B-67PDYd.js.br +0 -0
  80. package/dist/web/assets/explorer-body-B-67PDYd.js.gz +0 -0
  81. package/dist/web/assets/{explorer-window-content-D174CXKz.js → explorer-window-content-ndGjHH-i.js} +1 -1
  82. package/dist/web/assets/explorer-window-content-ndGjHH-i.js.br +0 -0
  83. package/dist/web/assets/explorer-window-content-ndGjHH-i.js.gz +0 -0
  84. package/dist/web/assets/{extension-manager-section-CQsqXsPF.js → extension-manager-section-C3Z99hLd.js} +1 -1
  85. package/dist/web/assets/extension-manager-section-C3Z99hLd.js.br +0 -0
  86. package/dist/web/assets/extension-manager-section-C3Z99hLd.js.gz +0 -0
  87. package/dist/web/assets/{extension-webview-AfaxC7PL.js → extension-webview-oBN-quI3.js} +1 -1
  88. package/dist/web/assets/extension-webview-oBN-quI3.js.br +0 -0
  89. package/dist/web/assets/extension-webview-oBN-quI3.js.gz +0 -0
  90. package/dist/web/assets/{files-settings-section-CC0aJryq.js → files-settings-section-pzPZEYe2.js} +1 -1
  91. package/dist/web/assets/files-settings-section-pzPZEYe2.js.br +0 -0
  92. package/dist/web/assets/files-settings-section-pzPZEYe2.js.gz +0 -0
  93. package/dist/web/assets/{general-settings-section-DQTr20G_.js → general-settings-section-BxNNbh5b.js} +1 -1
  94. package/dist/web/assets/general-settings-section-BxNNbh5b.js.br +0 -0
  95. package/dist/web/assets/general-settings-section-BxNNbh5b.js.gz +0 -0
  96. package/dist/web/assets/{git-log-panel-CisEJPJz.js → git-log-panel-CEAF4ST-.js} +1 -1
  97. package/dist/web/assets/git-log-panel-CEAF4ST-.js.br +0 -0
  98. package/dist/web/assets/git-log-panel-CEAF4ST-.js.gz +0 -0
  99. package/dist/web/assets/{git-review-tab-DpWEy03w.js → git-review-tab-Cp5DYE5b.js} +1 -1
  100. package/dist/web/assets/git-review-tab-Cp5DYE5b.js.br +0 -0
  101. package/dist/web/assets/git-review-tab-Cp5DYE5b.js.gz +0 -0
  102. package/dist/web/assets/{glide-data-grid-hOML-uXP.js → glide-data-grid-BrwKKQhc.js} +1 -1
  103. package/dist/web/assets/glide-data-grid-BrwKKQhc.js.br +0 -0
  104. package/dist/web/assets/glide-data-grid-BrwKKQhc.js.gz +0 -0
  105. package/dist/web/assets/{grid-save-dialog-Bx61akzH.js → grid-save-dialog-inaY39lZ.js} +1 -1
  106. package/dist/web/assets/grid-save-dialog-inaY39lZ.js.br +0 -0
  107. package/dist/web/assets/grid-save-dialog-inaY39lZ.js.gz +0 -0
  108. package/dist/web/assets/{group-chat-tab-eQdJHa8d.js → group-chat-tab-DpvHkXCK.js} +1 -1
  109. package/dist/web/assets/group-chat-tab-DpvHkXCK.js.br +0 -0
  110. package/dist/web/assets/group-chat-tab-DpvHkXCK.js.gz +0 -0
  111. package/dist/web/assets/{impexp-tab-BjIDxYTj.js → impexp-tab-CDgODpaL.js} +1 -1
  112. package/dist/web/assets/impexp-tab-CDgODpaL.js.br +0 -0
  113. package/dist/web/assets/impexp-tab-CDgODpaL.js.gz +0 -0
  114. package/dist/web/assets/{index-COwBji1t.js → index-EznfgwH9.js} +5 -5
  115. package/dist/web/assets/index-EznfgwH9.js.br +0 -0
  116. package/dist/web/assets/index-EznfgwH9.js.gz +0 -0
  117. package/dist/web/assets/{jira-watcher-section-dzpmNZA2.js → jira-watcher-section-CvxfscZl.js} +1 -1
  118. package/dist/web/assets/{keybindings-store-DRjk8512.js → keybindings-store-BqXn-D3b.js} +1 -1
  119. package/dist/web/assets/{keyboard-shortcuts-section-BhyalK8A.js → keyboard-shortcuts-section-HaqDDilx.js} +1 -1
  120. package/dist/web/assets/keyboard-shortcuts-section-HaqDDilx.js.br +0 -0
  121. package/dist/web/assets/keyboard-shortcuts-section-HaqDDilx.js.gz +0 -0
  122. package/dist/web/assets/{language-servers-section-BXLaicUY.js → language-servers-section-B2mGPtGX.js} +2 -2
  123. package/dist/web/assets/language-servers-section-B2mGPtGX.js.br +0 -0
  124. package/dist/web/assets/language-servers-section-B2mGPtGX.js.gz +0 -0
  125. package/dist/web/assets/{logs-app-BnjU6CRy.js → logs-app-Cv2fsZxf.js} +1 -1
  126. package/dist/web/assets/logs-app-Cv2fsZxf.js.br +0 -0
  127. package/dist/web/assets/logs-app-Cv2fsZxf.js.gz +0 -0
  128. package/dist/web/assets/{logs-tab-7uyD7gKW.js → logs-tab-D9orkAwZ.js} +1 -1
  129. package/dist/web/assets/{logs-window-content-B78a6oM2.js → logs-window-content-DygG8-n0.js} +1 -1
  130. package/dist/web/assets/{lsp-client-DOHnxEUE.js → lsp-client-DpKk7qms.js} +1 -1
  131. package/dist/web/assets/lsp-client-DpKk7qms.js.br +0 -0
  132. package/dist/web/assets/lsp-client-DpKk7qms.js.gz +0 -0
  133. package/dist/web/assets/{markdown-renderer-B_hL6lRL.js → markdown-renderer-YDuDxTPt.js} +1 -1
  134. package/dist/web/assets/{markdown-renderer-Dz0ZcxNo.js → markdown-renderer-zuzkV4A5.js} +1 -1
  135. package/dist/web/assets/markdown-renderer-zuzkV4A5.js.br +0 -0
  136. package/dist/web/assets/markdown-renderer-zuzkV4A5.js.gz +0 -0
  137. package/dist/web/assets/{mobile-explorer-sheet-C_3dq99D.js → mobile-explorer-sheet-DrQaPEZo.js} +1 -1
  138. package/dist/web/assets/mobile-explorer-sheet-DrQaPEZo.js.br +0 -0
  139. package/dist/web/assets/mobile-explorer-sheet-DrQaPEZo.js.gz +0 -0
  140. package/dist/web/assets/{new-design-dialog-CaubNbMT.js → new-design-dialog-C00L2Ei1.js} +1 -1
  141. package/dist/web/assets/new-design-dialog-C00L2Ei1.js.br +0 -0
  142. package/dist/web/assets/new-design-dialog-C00L2Ei1.js.gz +0 -0
  143. package/dist/web/assets/{notification-store-BTk3PZAj.js → notification-store-BHQjJkNC.js} +1 -1
  144. package/dist/web/assets/{notifications-settings-section-CJJ1l5cL.js → notifications-settings-section-DHx7YbrU.js} +1 -1
  145. package/dist/web/assets/notifications-settings-section-DHx7YbrU.js.br +0 -0
  146. package/dist/web/assets/notifications-settings-section-DHx7YbrU.js.gz +0 -0
  147. package/dist/web/assets/{ppmbot-settings-section-BFniAm3D.js → ppmbot-settings-section-8OxZnTeE.js} +1 -1
  148. package/dist/web/assets/ppmbot-settings-section-8OxZnTeE.js.br +0 -0
  149. package/dist/web/assets/ppmbot-settings-section-8OxZnTeE.js.gz +0 -0
  150. package/dist/web/assets/{problems-panel-CIxaCYJw.js → problems-panel-uCMYh7nY.js} +1 -1
  151. package/dist/web/assets/problems-panel-uCMYh7nY.js.br +0 -0
  152. package/dist/web/assets/problems-panel-uCMYh7nY.js.gz +0 -0
  153. package/dist/web/assets/{process-table-l1fVcESX.js → process-table-D6fDra-t.js} +1 -1
  154. package/dist/web/assets/process-table-D6fDra-t.js.br +0 -0
  155. package/dist/web/assets/process-table-D6fDra-t.js.gz +0 -0
  156. package/dist/web/assets/{proxy-settings-section-Di5nBuy6.js → proxy-settings-section-B8vjc9n-.js} +1 -1
  157. package/dist/web/assets/proxy-settings-section-B8vjc9n-.js.br +0 -0
  158. package/dist/web/assets/proxy-settings-section-B8vjc9n-.js.gz +0 -0
  159. package/dist/web/assets/{query-tab-hyKnRZr3.js → query-tab-BaIWJrsN.js} +1 -1
  160. package/dist/web/assets/query-tab-BaIWJrsN.js.br +0 -0
  161. package/dist/web/assets/{query-tab-hyKnRZr3.js.gz → query-tab-BaIWJrsN.js.gz} +0 -0
  162. package/dist/web/assets/{remote-access-settings-section-HpZAP0Al.js → remote-access-settings-section-DIrFElKv.js} +1 -1
  163. package/dist/web/assets/remote-access-settings-section-DIrFElKv.js.br +0 -0
  164. package/dist/web/assets/remote-access-settings-section-DIrFElKv.js.gz +0 -0
  165. package/dist/web/assets/{remote-desktop-clipboard-notice-DY7tWgjf.js → remote-desktop-clipboard-notice-Cg2Z-66K.js} +1 -1
  166. package/dist/web/assets/remote-desktop-clipboard-notice-Cg2Z-66K.js.br +0 -0
  167. package/dist/web/assets/remote-desktop-clipboard-notice-Cg2Z-66K.js.gz +0 -0
  168. package/dist/web/assets/{remote-desktop-mobile-sheet-CX9mDRle.js → remote-desktop-mobile-sheet-CPkPaOeS.js} +2 -2
  169. package/dist/web/assets/remote-desktop-mobile-sheet-CPkPaOeS.js.br +0 -0
  170. package/dist/web/assets/remote-desktop-mobile-sheet-CPkPaOeS.js.gz +0 -0
  171. package/dist/web/assets/{remote-desktop-mobile-view-DNg0EpbV.js → remote-desktop-mobile-view-CFyOCkms.js} +1 -1
  172. package/dist/web/assets/remote-desktop-mobile-view-CFyOCkms.js.br +0 -0
  173. package/dist/web/assets/remote-desktop-mobile-view-CFyOCkms.js.gz +0 -0
  174. package/dist/web/assets/{remote-desktop-readiness-gate-BTTLlVDc.js → remote-desktop-readiness-gate-CBAZK0-B.js} +1 -1
  175. package/dist/web/assets/remote-desktop-readiness-gate-CBAZK0-B.js.br +0 -0
  176. package/dist/web/assets/remote-desktop-readiness-gate-CBAZK0-B.js.gz +0 -0
  177. package/dist/web/assets/{remote-desktop-window-content-CqxnhSu0.js → remote-desktop-window-content-CNk7SS-W.js} +1 -1
  178. package/dist/web/assets/remote-desktop-window-content-CNk7SS-W.js.br +0 -0
  179. package/dist/web/assets/remote-desktop-window-content-CNk7SS-W.js.gz +0 -0
  180. package/dist/web/assets/{rename-object-dialog-CnhMsNbT.js → rename-object-dialog-MhvD2boE.js} +1 -1
  181. package/dist/web/assets/rename-object-dialog-MhvD2boE.js.br +0 -0
  182. package/dist/web/assets/rename-object-dialog-MhvD2boE.js.gz +0 -0
  183. package/dist/web/assets/{review-code-CunYhmcC.js → review-code-B9zXrfyG.js} +1 -1
  184. package/dist/web/assets/review-code-B9zXrfyG.js.br +0 -0
  185. package/dist/web/assets/review-code-B9zXrfyG.js.gz +0 -0
  186. package/dist/web/assets/{save-as-dialog-BtGSwTKZ.js → save-as-dialog-5yBgewV3.js} +1 -1
  187. package/dist/web/assets/save-as-dialog-5yBgewV3.js.br +0 -0
  188. package/dist/web/assets/save-as-dialog-5yBgewV3.js.gz +0 -0
  189. package/dist/web/assets/{schedules-settings-section-CvlcMyEz.js → schedules-settings-section-BD3-tlU-.js} +1 -1
  190. package/dist/web/assets/schedules-settings-section-BD3-tlU-.js.br +0 -0
  191. package/dist/web/assets/schedules-settings-section-BD3-tlU-.js.gz +0 -0
  192. package/dist/web/assets/{session-review-tab-C3HuIPD2.js → session-review-tab-DYJFhsGk.js} +1 -1
  193. package/dist/web/assets/session-review-tab-DYJFhsGk.js.br +0 -0
  194. package/dist/web/assets/session-review-tab-DYJFhsGk.js.gz +0 -0
  195. package/dist/web/assets/{settings-body-DCHEAh2_.js → settings-body-CWBVeKKg.js} +1 -1
  196. package/dist/web/assets/settings-body-CWBVeKKg.js.br +4 -0
  197. package/dist/web/assets/settings-body-CWBVeKKg.js.gz +0 -0
  198. package/dist/web/assets/{settings-rows-D8Y9o1ZD.js → settings-rows-B7fyQuu_.js} +1 -1
  199. package/dist/web/assets/settings-rows-B7fyQuu_.js.br +0 -0
  200. package/dist/web/assets/settings-rows-B7fyQuu_.js.gz +0 -0
  201. package/dist/web/assets/settings-section-content-BPQN6KdN.js +1 -0
  202. package/dist/web/assets/{settings-section-content-Dluhkh-u.js → settings-section-content-CoJcm6JM.js} +2 -2
  203. package/dist/web/assets/settings-section-content-CoJcm6JM.js.br +0 -0
  204. package/dist/web/assets/settings-section-content-CoJcm6JM.js.gz +0 -0
  205. package/dist/web/assets/{settings-tab-Cj1GkYi3.js → settings-tab-DWzU1RkL.js} +1 -1
  206. package/dist/web/assets/{settings-window-content-1fweMynB.js → settings-window-content-CAThkpa8.js} +1 -1
  207. package/dist/web/assets/{slash-command-picker-BdKd_AiS.js → slash-command-picker-dDl9riKP.js} +1 -1
  208. package/dist/web/assets/slash-command-picker-dDl9riKP.js.br +0 -0
  209. package/dist/web/assets/slash-command-picker-dDl9riKP.js.gz +0 -0
  210. package/dist/web/assets/{sql-object-tab-DkdF_7Jz.js → sql-object-tab-DND9osIv.js} +1 -1
  211. package/dist/web/assets/sql-object-tab-DND9osIv.js.br +0 -0
  212. package/dist/web/assets/sql-object-tab-DND9osIv.js.gz +0 -0
  213. package/dist/web/assets/{sqlite-viewer-BoQMjJHj.js → sqlite-viewer-P7UpXmyZ.js} +1 -1
  214. package/dist/web/assets/sqlite-viewer-P7UpXmyZ.js.br +0 -0
  215. package/dist/web/assets/sqlite-viewer-P7UpXmyZ.js.gz +0 -0
  216. package/dist/web/assets/{structure-save-dialog-DGHxHJ4T.js → structure-save-dialog-DX1H1Qy2.js} +1 -1
  217. package/dist/web/assets/structure-save-dialog-DX1H1Qy2.js.br +0 -0
  218. package/dist/web/assets/structure-save-dialog-DX1H1Qy2.js.gz +0 -0
  219. package/dist/web/assets/{structure-tab-DuuX1e9c.js → structure-tab-1QhNP2ob.js} +1 -1
  220. package/dist/web/assets/structure-tab-1QhNP2ob.js.br +0 -0
  221. package/dist/web/assets/structure-tab-1QhNP2ob.js.gz +0 -0
  222. package/dist/web/assets/{system-monitor-body-CZVoh652.js → system-monitor-body-CPAAjIdT.js} +2 -2
  223. package/dist/web/assets/system-monitor-body-CPAAjIdT.js.br +0 -0
  224. package/dist/web/assets/system-monitor-body-CPAAjIdT.js.gz +0 -0
  225. package/dist/web/assets/{system-monitor-tab-eGta1jpw.js → system-monitor-tab-D_1PBGwv.js} +1 -1
  226. package/dist/web/assets/{system-monitor-window-content-BUG72ffC.js → system-monitor-window-content-dVxWFqHP.js} +1 -1
  227. package/dist/web/assets/{tab-host-window-content-BTYuY43x.js → tab-host-window-content-BBsg5_fz.js} +1 -1
  228. package/dist/web/assets/tab-host-window-content-BBsg5_fz.js.br +0 -0
  229. package/dist/web/assets/tab-host-window-content-BBsg5_fz.js.gz +0 -0
  230. package/dist/web/assets/{table-tab-BsfgtqFE.js → table-tab-BO-q-hSc.js} +1 -1
  231. package/dist/web/assets/table-tab-BO-q-hSc.js.br +0 -0
  232. package/dist/web/assets/table-tab-BO-q-hSc.js.gz +0 -0
  233. package/dist/web/assets/{telegram-bot-token-field-CA8DEKRL.js → telegram-bot-token-field-BVYWBpX_.js} +1 -1
  234. package/dist/web/assets/telegram-bot-token-field-BVYWBpX_.js.br +0 -0
  235. package/dist/web/assets/telegram-bot-token-field-BVYWBpX_.js.gz +0 -0
  236. package/dist/web/assets/{terminal-tab-CoT5KIfe.js → terminal-tab-DjPXnuO8.js} +1 -1
  237. package/dist/web/assets/terminal-tab-DjPXnuO8.js.br +0 -0
  238. package/dist/web/assets/terminal-tab-DjPXnuO8.js.gz +0 -0
  239. package/dist/web/assets/{tool-cards-oZ6cptRr.js → tool-cards-DXV9SEE8.js} +3 -3
  240. package/dist/web/assets/tool-cards-DXV9SEE8.js.br +0 -0
  241. package/dist/web/assets/tool-cards-DXV9SEE8.js.gz +0 -0
  242. package/dist/web/assets/{tools-settings-section-CtcD-iTk.js → tools-settings-section-Cc5YKOB6.js} +1 -1
  243. package/dist/web/assets/tools-settings-section-Cc5YKOB6.js.br +0 -0
  244. package/dist/web/assets/tools-settings-section-Cc5YKOB6.js.gz +0 -0
  245. package/dist/web/assets/{use-inline-blame-BUuJj1Ru.js → use-inline-blame-f6WMZkEv.js} +1 -1
  246. package/dist/web/assets/use-inline-blame-f6WMZkEv.js.br +0 -0
  247. package/dist/web/assets/use-inline-blame-f6WMZkEv.js.gz +0 -0
  248. package/dist/web/assets/{use-lsp-Bo4z4gLx.js → use-lsp-B_fmU9ja.js} +1 -1
  249. package/dist/web/assets/use-lsp-B_fmU9ja.js.br +0 -0
  250. package/dist/web/assets/use-lsp-B_fmU9ja.js.gz +0 -0
  251. package/dist/web/assets/{use-monaco-theme-1ANtPWqA.js → use-monaco-theme-nu5Fogu3.js} +1 -1
  252. package/dist/web/assets/{use-process-kill-CvlXPal4.js → use-process-kill-B8Mf4_Yy.js} +1 -1
  253. package/dist/web/assets/use-process-kill-B8Mf4_Yy.js.br +0 -0
  254. package/dist/web/assets/use-process-kill-B8Mf4_Yy.js.gz +0 -0
  255. package/dist/web/assets/{use-sql-schema-info-DQzUqb7q.js → use-sql-schema-info-Dp6TCir5.js} +1 -1
  256. package/dist/web/assets/use-sql-schema-info-Dp6TCir5.js.br +0 -0
  257. package/dist/web/assets/use-sql-schema-info-Dp6TCir5.js.gz +0 -0
  258. package/dist/web/assets/{use-websocket-QCAD3F5b.js → use-websocket-RuENPZ_x.js} +1 -1
  259. package/dist/web/assets/web-preview-tab-ClOiV3BO.js +1 -0
  260. package/dist/web/assets/web-preview-tab-ClOiV3BO.js.br +0 -0
  261. package/dist/web/assets/web-preview-tab-ClOiV3BO.js.gz +0 -0
  262. package/dist/web/index.html +1 -1
  263. package/dist/web/index.html.br +0 -0
  264. package/dist/web/index.html.gz +0 -0
  265. package/dist/web/sw.js +1 -1
  266. package/dist/web/sw.js.br +0 -0
  267. package/dist/web/sw.js.gz +0 -0
  268. package/docs/architecture/ai-chat-and-providers.md +22 -0
  269. package/docs/architecture/workspace-and-ui.md +11 -0
  270. package/package.json +1 -1
  271. package/src/providers/codex-app-server/codex-event-mapper.ts +3 -0
  272. package/src/providers/codex-app-server/codex-provider.ts +3 -21
  273. package/src/providers/codex-app-server/codex-rollout-items.ts +3 -0
  274. package/src/providers/codex-app-server/codex-thread-params.ts +1 -0
  275. package/src/providers/codex-app-server/codex-user-input.ts +41 -0
  276. package/src/services/port-forward/forward-hop.ts +20 -5
  277. package/src/services/port-forward/preview-navigation.ts +45 -0
  278. package/src/shared/chat-questions.ts +19 -0
  279. package/src/web/components/chat/async-question-card.tsx +24 -0
  280. package/src/web/components/chat/chat-tab.tsx +3 -2
  281. package/src/web/components/chat/message-events.tsx +6 -0
  282. package/src/web/components/chat/question-card.tsx +8 -10
  283. package/src/web/components/chat/tool-cards.tsx +4 -4
  284. package/src/web/components/web-preview/web-preview-tab.tsx +38 -5
  285. package/dist/web/assets/accounts-settings-section-DhzuFkDO.js.br +0 -0
  286. package/dist/web/assets/accounts-settings-section-DhzuFkDO.js.gz +0 -0
  287. package/dist/web/assets/agent-session-sheet-CZAmOwSU.js.br +0 -0
  288. package/dist/web/assets/agent-session-sheet-CZAmOwSU.js.gz +0 -0
  289. package/dist/web/assets/agent-session-window-content-CwokWtJq.js.br +0 -0
  290. package/dist/web/assets/agent-session-window-content-CwokWtJq.js.gz +0 -0
  291. package/dist/web/assets/ai-resource-editor-CT1JCaRQ.js.br +0 -0
  292. package/dist/web/assets/ai-resource-editor-CT1JCaRQ.js.gz +0 -0
  293. package/dist/web/assets/ai-settings-section-Ct9-lNqA.js.br +0 -0
  294. package/dist/web/assets/ai-settings-section-Ct9-lNqA.js.gz +0 -0
  295. package/dist/web/assets/android-tab-CUPQN2M4.js.br +0 -0
  296. package/dist/web/assets/android-tab-CUPQN2M4.js.gz +0 -0
  297. package/dist/web/assets/appearance-settings-section-DpYcNqXH.js.br +0 -0
  298. package/dist/web/assets/appearance-settings-section-DpYcNqXH.js.gz +0 -0
  299. package/dist/web/assets/apps-panel-BqF_CkKy.js.br +0 -0
  300. package/dist/web/assets/apps-panel-BqF_CkKy.js.gz +0 -0
  301. package/dist/web/assets/branch-review-tab-BEZqFSlw.js.br +0 -0
  302. package/dist/web/assets/branch-review-tab-BEZqFSlw.js.gz +0 -0
  303. package/dist/web/assets/chat-tab-N1B-Yv0P.js.br +0 -0
  304. package/dist/web/assets/chat-tab-N1B-Yv0P.js.gz +0 -0
  305. package/dist/web/assets/chat-tab-kk-mAF05.js +0 -16
  306. package/dist/web/assets/chat-tab-kk-mAF05.js.br +0 -0
  307. package/dist/web/assets/chat-tab-kk-mAF05.js.gz +0 -0
  308. package/dist/web/assets/code-editor-d1CXl2WL.js.br +0 -0
  309. package/dist/web/assets/code-editor-d1CXl2WL.js.gz +0 -0
  310. package/dist/web/assets/codex-reset-credit-button-CwSSBK6P.js.br +0 -0
  311. package/dist/web/assets/codex-reset-credit-button-CwSSBK6P.js.gz +0 -0
  312. package/dist/web/assets/conflict-editor-BucKz-uE.js.br +0 -0
  313. package/dist/web/assets/conflict-editor-BucKz-uE.js.gz +0 -0
  314. package/dist/web/assets/connection-form-tab-DIc25Bko.js.br +0 -0
  315. package/dist/web/assets/connection-form-tab-DIc25Bko.js.gz +0 -0
  316. package/dist/web/assets/csv-preview-CqDaGV5N.js.br +0 -0
  317. package/dist/web/assets/csv-preview-CqDaGV5N.js.gz +0 -0
  318. package/dist/web/assets/db-tab-parts-lp8ROGQB.js.br +0 -0
  319. package/dist/web/assets/db-tab-parts-lp8ROGQB.js.gz +0 -0
  320. package/dist/web/assets/design-settings-section-DxWEBH1L.js.br +0 -0
  321. package/dist/web/assets/design-settings-section-DxWEBH1L.js.gz +0 -0
  322. package/dist/web/assets/design-tab-Cl0CMAUx.js.br +0 -0
  323. package/dist/web/assets/design-tab-Cl0CMAUx.js.gz +0 -0
  324. package/dist/web/assets/designs-sidebar-panel-aCr-EGfj.js.br +0 -0
  325. package/dist/web/assets/designs-sidebar-panel-aCr-EGfj.js.gz +0 -0
  326. package/dist/web/assets/diff-viewer-XyJgWKwe.js.br +0 -0
  327. package/dist/web/assets/diff-viewer-XyJgWKwe.js.gz +0 -0
  328. package/dist/web/assets/docx-preview-KJaj2YGN.js.br +0 -0
  329. package/dist/web/assets/docx-preview-KJaj2YGN.js.gz +0 -0
  330. package/dist/web/assets/editor-dialog-9tW6c9mu.js.br +0 -0
  331. package/dist/web/assets/editor-dialog-9tW6c9mu.js.gz +0 -0
  332. package/dist/web/assets/editor-language-service-CkdO9ih8.js.br +0 -0
  333. package/dist/web/assets/editor-language-service-CkdO9ih8.js.gz +0 -0
  334. package/dist/web/assets/explorer-body-B8AkNO5o.js.br +0 -0
  335. package/dist/web/assets/explorer-body-B8AkNO5o.js.gz +0 -0
  336. package/dist/web/assets/explorer-window-content-D174CXKz.js.br +0 -0
  337. package/dist/web/assets/explorer-window-content-D174CXKz.js.gz +0 -0
  338. package/dist/web/assets/extension-manager-section-CQsqXsPF.js.br +0 -0
  339. package/dist/web/assets/extension-manager-section-CQsqXsPF.js.gz +0 -0
  340. package/dist/web/assets/extension-webview-AfaxC7PL.js.br +0 -0
  341. package/dist/web/assets/extension-webview-AfaxC7PL.js.gz +0 -0
  342. package/dist/web/assets/files-settings-section-CC0aJryq.js.br +0 -0
  343. package/dist/web/assets/files-settings-section-CC0aJryq.js.gz +0 -0
  344. package/dist/web/assets/general-settings-section-DQTr20G_.js.br +0 -0
  345. package/dist/web/assets/general-settings-section-DQTr20G_.js.gz +0 -0
  346. package/dist/web/assets/git-log-panel-CisEJPJz.js.br +0 -0
  347. package/dist/web/assets/git-log-panel-CisEJPJz.js.gz +0 -0
  348. package/dist/web/assets/git-review-tab-DpWEy03w.js.br +0 -0
  349. package/dist/web/assets/git-review-tab-DpWEy03w.js.gz +0 -0
  350. package/dist/web/assets/glide-data-grid-hOML-uXP.js.br +0 -0
  351. package/dist/web/assets/glide-data-grid-hOML-uXP.js.gz +0 -0
  352. package/dist/web/assets/grid-save-dialog-Bx61akzH.js.br +0 -0
  353. package/dist/web/assets/grid-save-dialog-Bx61akzH.js.gz +0 -0
  354. package/dist/web/assets/group-chat-tab-eQdJHa8d.js.br +0 -0
  355. package/dist/web/assets/group-chat-tab-eQdJHa8d.js.gz +0 -0
  356. package/dist/web/assets/impexp-tab-BjIDxYTj.js.br +0 -0
  357. package/dist/web/assets/impexp-tab-BjIDxYTj.js.gz +0 -0
  358. package/dist/web/assets/index-COwBji1t.js.br +0 -0
  359. package/dist/web/assets/index-COwBji1t.js.gz +0 -0
  360. package/dist/web/assets/keyboard-shortcuts-section-BhyalK8A.js.br +0 -0
  361. package/dist/web/assets/keyboard-shortcuts-section-BhyalK8A.js.gz +0 -0
  362. package/dist/web/assets/language-servers-section-BXLaicUY.js.br +0 -0
  363. package/dist/web/assets/language-servers-section-BXLaicUY.js.gz +0 -0
  364. package/dist/web/assets/logs-app-BnjU6CRy.js.br +0 -0
  365. package/dist/web/assets/logs-app-BnjU6CRy.js.gz +0 -0
  366. package/dist/web/assets/lsp-client-DOHnxEUE.js.br +0 -0
  367. package/dist/web/assets/lsp-client-DOHnxEUE.js.gz +0 -0
  368. package/dist/web/assets/markdown-renderer-Dz0ZcxNo.js.br +0 -0
  369. package/dist/web/assets/markdown-renderer-Dz0ZcxNo.js.gz +0 -0
  370. package/dist/web/assets/mobile-explorer-sheet-C_3dq99D.js.br +0 -0
  371. package/dist/web/assets/mobile-explorer-sheet-C_3dq99D.js.gz +0 -0
  372. package/dist/web/assets/new-design-dialog-CaubNbMT.js.br +0 -0
  373. package/dist/web/assets/new-design-dialog-CaubNbMT.js.gz +0 -0
  374. package/dist/web/assets/notifications-settings-section-CJJ1l5cL.js.br +0 -0
  375. package/dist/web/assets/notifications-settings-section-CJJ1l5cL.js.gz +0 -0
  376. package/dist/web/assets/ppmbot-settings-section-BFniAm3D.js.br +0 -0
  377. package/dist/web/assets/ppmbot-settings-section-BFniAm3D.js.gz +0 -0
  378. package/dist/web/assets/problems-panel-CIxaCYJw.js.br +0 -0
  379. package/dist/web/assets/problems-panel-CIxaCYJw.js.gz +0 -0
  380. package/dist/web/assets/process-table-l1fVcESX.js.br +0 -0
  381. package/dist/web/assets/process-table-l1fVcESX.js.gz +0 -0
  382. package/dist/web/assets/proxy-settings-section-Di5nBuy6.js.br +0 -0
  383. package/dist/web/assets/proxy-settings-section-Di5nBuy6.js.gz +0 -0
  384. package/dist/web/assets/query-tab-hyKnRZr3.js.br +0 -0
  385. package/dist/web/assets/remote-access-settings-section-HpZAP0Al.js.br +0 -0
  386. package/dist/web/assets/remote-access-settings-section-HpZAP0Al.js.gz +0 -0
  387. package/dist/web/assets/remote-desktop-clipboard-notice-DY7tWgjf.js.br +0 -0
  388. package/dist/web/assets/remote-desktop-clipboard-notice-DY7tWgjf.js.gz +0 -0
  389. package/dist/web/assets/remote-desktop-mobile-sheet-CX9mDRle.js.br +0 -0
  390. package/dist/web/assets/remote-desktop-mobile-sheet-CX9mDRle.js.gz +0 -0
  391. package/dist/web/assets/remote-desktop-mobile-view-DNg0EpbV.js.br +0 -0
  392. package/dist/web/assets/remote-desktop-mobile-view-DNg0EpbV.js.gz +0 -0
  393. package/dist/web/assets/remote-desktop-readiness-gate-BTTLlVDc.js.br +0 -0
  394. package/dist/web/assets/remote-desktop-readiness-gate-BTTLlVDc.js.gz +0 -0
  395. package/dist/web/assets/remote-desktop-window-content-CqxnhSu0.js.br +0 -0
  396. package/dist/web/assets/remote-desktop-window-content-CqxnhSu0.js.gz +0 -0
  397. package/dist/web/assets/rename-object-dialog-CnhMsNbT.js.br +0 -0
  398. package/dist/web/assets/rename-object-dialog-CnhMsNbT.js.gz +0 -0
  399. package/dist/web/assets/review-code-CunYhmcC.js.br +0 -0
  400. package/dist/web/assets/review-code-CunYhmcC.js.gz +0 -0
  401. package/dist/web/assets/save-as-dialog-BtGSwTKZ.js.br +0 -0
  402. package/dist/web/assets/save-as-dialog-BtGSwTKZ.js.gz +0 -0
  403. package/dist/web/assets/schedules-settings-section-CvlcMyEz.js.br +0 -0
  404. package/dist/web/assets/schedules-settings-section-CvlcMyEz.js.gz +0 -0
  405. package/dist/web/assets/session-review-tab-C3HuIPD2.js.br +0 -0
  406. package/dist/web/assets/session-review-tab-C3HuIPD2.js.gz +0 -0
  407. package/dist/web/assets/settings-body-DCHEAh2_.js.br +0 -0
  408. package/dist/web/assets/settings-body-DCHEAh2_.js.gz +0 -0
  409. package/dist/web/assets/settings-rows-D8Y9o1ZD.js.br +0 -0
  410. package/dist/web/assets/settings-rows-D8Y9o1ZD.js.gz +0 -0
  411. package/dist/web/assets/settings-section-content-BXPg2H0N.js +0 -1
  412. package/dist/web/assets/settings-section-content-Dluhkh-u.js.br +0 -0
  413. package/dist/web/assets/settings-section-content-Dluhkh-u.js.gz +0 -0
  414. package/dist/web/assets/slash-command-picker-BdKd_AiS.js.br +0 -0
  415. package/dist/web/assets/slash-command-picker-BdKd_AiS.js.gz +0 -0
  416. package/dist/web/assets/sql-object-tab-DkdF_7Jz.js.br +0 -0
  417. package/dist/web/assets/sql-object-tab-DkdF_7Jz.js.gz +0 -0
  418. package/dist/web/assets/sqlite-viewer-BoQMjJHj.js.br +0 -0
  419. package/dist/web/assets/sqlite-viewer-BoQMjJHj.js.gz +0 -0
  420. package/dist/web/assets/structure-save-dialog-DGHxHJ4T.js.br +0 -0
  421. package/dist/web/assets/structure-save-dialog-DGHxHJ4T.js.gz +0 -0
  422. package/dist/web/assets/structure-tab-DuuX1e9c.js.br +0 -0
  423. package/dist/web/assets/structure-tab-DuuX1e9c.js.gz +0 -0
  424. package/dist/web/assets/system-monitor-body-CZVoh652.js.br +0 -0
  425. package/dist/web/assets/system-monitor-body-CZVoh652.js.gz +0 -0
  426. package/dist/web/assets/tab-host-window-content-BTYuY43x.js.br +0 -0
  427. package/dist/web/assets/tab-host-window-content-BTYuY43x.js.gz +0 -0
  428. package/dist/web/assets/table-tab-BsfgtqFE.js.br +0 -0
  429. package/dist/web/assets/table-tab-BsfgtqFE.js.gz +0 -0
  430. package/dist/web/assets/telegram-bot-token-field-CA8DEKRL.js.br +0 -0
  431. package/dist/web/assets/telegram-bot-token-field-CA8DEKRL.js.gz +0 -0
  432. package/dist/web/assets/terminal-tab-CoT5KIfe.js.br +0 -0
  433. package/dist/web/assets/terminal-tab-CoT5KIfe.js.gz +0 -0
  434. package/dist/web/assets/tool-cards-oZ6cptRr.js.br +0 -0
  435. package/dist/web/assets/tool-cards-oZ6cptRr.js.gz +0 -0
  436. package/dist/web/assets/tools-settings-section-CtcD-iTk.js.br +0 -0
  437. package/dist/web/assets/tools-settings-section-CtcD-iTk.js.gz +0 -0
  438. package/dist/web/assets/use-inline-blame-BUuJj1Ru.js.br +0 -0
  439. package/dist/web/assets/use-inline-blame-BUuJj1Ru.js.gz +0 -0
  440. package/dist/web/assets/use-lsp-Bo4z4gLx.js.br +0 -0
  441. package/dist/web/assets/use-lsp-Bo4z4gLx.js.gz +0 -0
  442. package/dist/web/assets/use-process-kill-CvlXPal4.js.br +0 -0
  443. package/dist/web/assets/use-process-kill-CvlXPal4.js.gz +0 -0
  444. package/dist/web/assets/use-sql-schema-info-DQzUqb7q.js.br +0 -0
  445. package/dist/web/assets/use-sql-schema-info-DQzUqb7q.js.gz +0 -0
  446. package/dist/web/assets/web-preview-tab-Cp0LuL0T.js +0 -1
  447. package/dist/web/assets/web-preview-tab-Cp0LuL0T.js.br +0 -0
  448. package/dist/web/assets/web-preview-tab-Cp0LuL0T.js.gz +0 -0
@@ -51,6 +51,7 @@ import { getOrFetchUsage, registerUsageSource } from "../../services/provider-us
51
51
  import { codexUsageSource } from "./codex-usage-source.ts";
52
52
  import { AMBIENT_ACCOUNT_KEY } from "../../services/provider-usage/usage-source.ts";
53
53
  import { redactTruncate } from "./codex-redact.ts";
54
+ import { buildUserInputResponse, normalizeCodexQuestions } from "./codex-user-input.ts";
54
55
  import { localizeRollout } from "./codex-rollout-transfer.ts";
55
56
  import { getRolloutMessagesAsync } from "./codex-history-async.ts";
56
57
  import {
@@ -62,7 +63,6 @@ import type {
62
63
  ModelListResponse,
63
64
  ServerRequest,
64
65
  JsonRpcNotification,
65
- ToolRequestUserInputResponse,
66
66
  Thread,
67
67
  UserInput,
68
68
  CodexSkill,
@@ -328,24 +328,6 @@ function approvalToolLabel(method: string, params: unknown): string {
328
328
  return String(p.tool ?? "Tool");
329
329
  }
330
330
 
331
- function buildUserInputResponse(questions: unknown, data: unknown): ToolRequestUserInputResponse {
332
- const answers: ToolRequestUserInputResponse["answers"] = {};
333
- if (data && typeof data === "object" && !Array.isArray(data)) {
334
- for (const [k, v] of Object.entries(data as Record<string, unknown>)) {
335
- answers[k] = { answers: Array.isArray(v) ? v.map(String) : [String(v)] };
336
- }
337
- return { answers };
338
- }
339
- const arr = Array.isArray(data) ? data : data != null ? [data] : [];
340
- const qs = Array.isArray(questions) ? questions : [];
341
- qs.forEach((q, i) => {
342
- const qid = (q && typeof q === "object" && ((q as any).id ?? (q as any).questionId)) || String(i);
343
- const a = arr[i];
344
- answers[qid] = { answers: a != null ? [String(a)] : [] };
345
- });
346
- return { answers };
347
- }
348
-
349
331
  /**
350
332
  * Codex provider — implements AIProvider directly (mirrors claude-agent-sdk),
351
333
  * driving `codex app-server` over JSON-RPC. Per-session live subprocess map;
@@ -1160,7 +1142,7 @@ export class CodexAppServerProvider implements AIProvider {
1160
1142
  const ppmReqId = crypto.randomUUID();
1161
1143
  const questions = (req.params as { questions?: unknown })?.questions;
1162
1144
  live.pendingApprovals.set(ppmReqId, { codexId: req.id, method, questions });
1163
- live.channel.push({ type: "approval_request", requestId: ppmReqId, tool: "AskUserQuestion", input: redactTruncate(req.params) });
1145
+ live.channel.push({ type: "approval_request", requestId: ppmReqId, tool: "AskUserQuestion", input: { questions: normalizeCodexQuestions(questions) } });
1164
1146
  return;
1165
1147
  }
1166
1148
  // permissions/* response is a granted-profile, not a decision → decline.
@@ -1174,7 +1156,7 @@ export class CodexAppServerProvider implements AIProvider {
1174
1156
  if (!pending) continue;
1175
1157
  live.pendingApprovals.delete(requestId);
1176
1158
  if (pending.method === "item/tool/requestUserInput") {
1177
- this.respondOnce(live, pending.codexId, buildUserInputResponse(pending.questions, data));
1159
+ this.respondOnce(live, pending.codexId, buildUserInputResponse(pending.questions, approved ? data : undefined));
1178
1160
  } else if (isApprovalMethod(pending.method)) {
1179
1161
  this.respondOnce(live, pending.codexId, decisionFor(pending.method as ApprovalMethod, approved));
1180
1162
  }
@@ -1,5 +1,6 @@
1
1
  import type { ChatEvent } from "../provider.interface.ts";
2
2
  import { redactTruncate } from "./codex-redact.ts";
3
+ import { codexAsyncQuestionEvent } from "./codex-user-input.ts";
3
4
  import { parseSubagentActivity, type SubagentActivity } from "./codex-subagent-thread.ts";
4
5
  import { changeToToolUse, rolloutFileChanges } from "./codex-patch.ts";
5
6
 
@@ -161,6 +162,8 @@ export function mapRolloutItem(item: unknown): RolloutItemMapping {
161
162
  return text ? { kind: "user", text } : { kind: "ignore" };
162
163
  }
163
164
  if (type === "AgentMessage") {
165
+ const question = codexAsyncQuestionEvent(it);
166
+ if (question) return { kind: "events", events: [question] };
164
167
  const text = textFrom(it.content);
165
168
  return text ? { kind: "assistant", text } : { kind: "ignore" };
166
169
  }
@@ -116,6 +116,7 @@ export function dbToolsMcpEnv(access: DbToolsMcpAccess | undefined): Record<stri
116
116
  export function buildThreadParams(input: ThreadParamsInput): CodexThreadParams {
117
117
  const instructions = input.developerInstructions?.trim();
118
118
  const config = {
119
+ "features.default_mode_request_user_input": true,
119
120
  ...(input.configOverrides?.config ?? {}),
120
121
  ...designMcpConfig(input.designMcp),
121
122
  ...tabToolsMcpConfig(input.tabToolsMcp),
@@ -0,0 +1,41 @@
1
+ import type { ChatEvent } from "../provider.interface.ts";
2
+ import { ASYNC_QUESTION_TOOL, type ChatQuestion } from "../../shared/chat-questions.ts";
3
+ import type { ToolRequestUserInputResponse } from "./codex-protocol.ts";
4
+
5
+ export function normalizeCodexQuestions(value: unknown): ChatQuestion[] {
6
+ if (!Array.isArray(value)) return [];
7
+ return value.flatMap((value, index) => {
8
+ if (!value || typeof value !== "object") return [];
9
+ const question = typeof value.question === "string" ? value.question : value.title;
10
+ if (typeof question !== "string" || !question.trim()) return [];
11
+ const options = Array.isArray(value.options) ? value.options.flatMap((option: unknown) => {
12
+ if (typeof option === "string") return [{ label: option }];
13
+ if (!option || typeof option !== "object" || !("label" in option) || typeof option.label !== "string") return [];
14
+ return [{ label: option.label, ...("description" in option && typeof option.description === "string" ? { description: option.description } : {}) }];
15
+ }) : [];
16
+ return [{ id: typeof value.id === "string" ? value.id : String(index), question,
17
+ ...(typeof value.header === "string" ? { header: value.header } : {}), options }];
18
+ });
19
+ }
20
+
21
+ export function codexAsyncQuestionEvent(item: Record<string, unknown>): ChatEvent | null {
22
+ if (item.type !== "agentMessage" && item.type !== "AgentMessage") return null;
23
+ const questions = normalizeCodexQuestions(item.questions);
24
+ if (typeof item.id !== "string" || !questions.length) return null;
25
+ return { type: "tool_use", tool: ASYNC_QUESTION_TOOL, toolUseId: item.id, input: { questions } };
26
+ }
27
+
28
+ export function buildUserInputResponse(questions: unknown, data: unknown): ToolRequestUserInputResponse {
29
+ const answers: ToolRequestUserInputResponse["answers"] = {};
30
+ if (!data || typeof data !== "object") return { answers };
31
+ normalizeCodexQuestions(questions).forEach((question, index) => {
32
+ const id = question.id!;
33
+ const value = Array.isArray(data) ? data[index]
34
+ : Object.hasOwn(data, id) ? (data as Record<string, unknown>)[id]
35
+ : Object.hasOwn(data, question.question) ? (data as Record<string, unknown>)[question.question] : undefined;
36
+ if (value != null) Object.defineProperty(answers, id, {
37
+ value: { answers: Array.isArray(value) ? value.map(String) : [String(value)] }, enumerable: true,
38
+ });
39
+ });
40
+ return { answers };
41
+ }
@@ -25,6 +25,7 @@
25
25
  */
26
26
  import type { ServerWebSocket } from "bun";
27
27
  import { framingOrigins, letOriginsFrame } from "./frame-ancestors.ts";
28
+ import { PREVIEW_NAVIGATION_PATH, previewNavigationScript, withPreviewNavigation } from "./preview-navigation.ts";
28
29
 
29
30
  export type Loopback = "127.0.0.1" | "::1";
30
31
  /** IPv6 first: the order a browser on the host tries `localhost` in. */
@@ -170,6 +171,7 @@ export function startForwardHop(targetPort: number): ForwardHop {
170
171
  let upstreamHost: Loopback | null = null;
171
172
  /** The Host the forward's public URL sends, lower case and without a default port. */
172
173
  let publicHost: string | null = null;
174
+ let publicOrigin = "";
173
175
  const sockets = new Set<ServerWebSocket<HopSocket>>();
174
176
 
175
177
  /** The loopback to dial; re-resolved when the last one refused, as a restarted dev server may bind the other family. */
@@ -180,6 +182,13 @@ export function startForwardHop(targetPort: number): ForwardHop {
180
182
 
181
183
  async function proxyHttp(req: Request): Promise<Response> {
182
184
  const url = new URL(req.url);
185
+ const framers = framingOrigins();
186
+ if (url.pathname === PREVIEW_NAVIGATION_PATH && req.method === "GET") {
187
+ return new Response(previewNavigationScript(framers), { headers: { "content-type": "text/javascript", "cache-control": "no-store" } });
188
+ }
189
+ const documentRequest = framers.length > 0 && req.method === "GET"
190
+ && (req.headers.get("sec-fetch-dest") === "iframe" || req.headers.get("sec-fetch-dest") === "document"
191
+ || !!req.headers.get("accept")?.includes("text/html"));
183
192
  const headers = upstreamRequestHeaders(req.headers, targetPort, req.headers.get("host"));
184
193
  const body = req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer();
185
194
  for (const refresh of [false, true]) {
@@ -188,14 +197,19 @@ export function startForwardHop(targetPort: number): ForwardHop {
188
197
  try {
189
198
  const res = await fetch(`http://${hostPart(host)}:${targetPort}${url.pathname}${url.search}`, {
190
199
  method: req.method, headers, body, redirect: "manual", signal: req.signal,
191
- // Pass the bytes through as they are: a body Bun had decoded would still carry the
192
- // upstream's Content-Encoding, and the browser would decode it a second time.
193
- decompress: false,
200
+ // Decode navigations for HTML instrumentation; other responses stay byte-for-byte.
201
+ decompress: documentRequest,
194
202
  });
195
203
  const nullBody = NULL_BODY_STATUS.has(res.status) || req.method === "HEAD";
196
- return new Response(nullBody ? null : res.body, {
197
- status: res.status, statusText: res.statusText, headers: downstreamResponseHeaders(res.headers, targetPort, framingOrigins()),
204
+ const responseHeaders = downstreamResponseHeaders(res.headers, targetPort, framers);
205
+ if (documentRequest && res.headers.has("content-encoding")) {
206
+ responseHeaders.delete("content-encoding");
207
+ responseHeaders.delete("content-length");
208
+ }
209
+ const response = new Response(nullBody ? null : res.body, {
210
+ status: res.status, statusText: res.statusText, headers: responseHeaders,
198
211
  });
212
+ return documentRequest && !nullBody ? withPreviewNavigation(response, publicOrigin) : response;
199
213
  } catch (error) {
200
214
  // Only a refused connection is retried: anything later may have reached the dev
201
215
  // server already, and sending a POST twice is worse than a 502.
@@ -284,6 +298,7 @@ export function startForwardHop(targetPort: number): ForwardHop {
284
298
  targetPort,
285
299
  setPublicUrl(url) {
286
300
  publicHost = new URL(url).host;
301
+ publicOrigin = new URL(url).origin;
287
302
  },
288
303
  stop() {
289
304
  if (stopped) return;
@@ -0,0 +1,45 @@
1
+ /** Served from the forward's own origin; no inline-script CSP exception is needed. */
2
+ export const PREVIEW_NAVIGATION_PATH = "/__ppm_preview_navigation__.js";
3
+
4
+ export function previewNavigationScript(origins: readonly string[]): string {
5
+ return `(() => {
6
+ if (parent === window) return;
7
+ const origins = ${JSON.stringify(origins)};
8
+ const report = () => {
9
+ for (const origin of origins) parent.postMessage({ ppm: "web-preview-navigation", url: location.href }, origin);
10
+ };
11
+ for (const name of ["pushState", "replaceState"]) {
12
+ const original = history[name];
13
+ history[name] = function(...args) { const result = original.apply(this, args); report(); return result; };
14
+ }
15
+ addEventListener("popstate", report);
16
+ addEventListener("hashchange", report);
17
+ addEventListener("pageshow", report);
18
+ report();
19
+ })();`;
20
+ }
21
+
22
+ /** Stream the document through the HTML parser, preserving the application's scripts and CSP. */
23
+ export function withPreviewNavigation(response: Response, origin: string): Response {
24
+ if (!response.headers.get("content-type")?.toLowerCase().startsWith("text/html")
25
+ || response.status !== 200 || response.headers.has("content-disposition")) return response;
26
+ for (const header of ["content-length", "etag", "last-modified", "content-md5"]) response.headers.delete(header);
27
+ response.headers.set("cache-control", "no-store");
28
+ let inserted = false;
29
+ const script = `<script src="${origin}${PREVIEW_NAVIGATION_PATH}"></script>`;
30
+ const insert = (element: { before(content: string, options: { html: boolean }): void }) => {
31
+ if (inserted) return;
32
+ inserted = true;
33
+ // Absolute URL also works when the app sets a <base> pointing elsewhere.
34
+ element.before(script, { html: true });
35
+ };
36
+ return new HTMLRewriter().on("head", {
37
+ element(element) {
38
+ if (inserted) return;
39
+ inserted = true;
40
+ element.prepend(script, { html: true });
41
+ },
42
+ }).on("body, script", { element: insert }).onDocument({
43
+ end(end) { if (!inserted) end.append(script, { html: true }); },
44
+ }).transform(response);
45
+ }
@@ -0,0 +1,19 @@
1
+ export interface ChatQuestion {
2
+ id?: string;
3
+ question: string;
4
+ header?: string;
5
+ options: { label: string; description?: string }[];
6
+ multiSelect?: boolean;
7
+ }
8
+
9
+ export const ASYNC_QUESTION_TOOL = "RequestUserInput";
10
+
11
+ export function questionReplyPrefix(itemId: string): string {
12
+ return `Answers to Codex question ${encodeURIComponent(itemId)}:\n`;
13
+ }
14
+
15
+ export function formatQuestionReply(itemId: string, questions: ChatQuestion[], answers?: Record<string, string>): string {
16
+ return questionReplyPrefix(itemId) + (answers
17
+ ? questions.map((question) => `${question.question}\n${answers[question.id ?? question.question] ?? ""}`).join("\n\n")
18
+ : "Skipped by the user.");
19
+ }
@@ -0,0 +1,24 @@
1
+ import { createContext, useContext } from "react";
2
+ import type { ChatMessage } from "../../../types/chat";
3
+ import { formatQuestionReply, questionReplyPrefix, type ChatQuestion } from "../../../shared/chat-questions";
4
+ import { QuestionCard } from "./question-card";
5
+
6
+ export const AsyncQuestionContext = createContext<{
7
+ messages: ChatMessage[];
8
+ onSend?: (content: string) => void;
9
+ }>({ messages: [] });
10
+
11
+ export function AsyncQuestionCard({ itemId, questions }: { itemId: string; questions: ChatQuestion[] }) {
12
+ const { messages, onSend } = useContext(AsyncQuestionContext);
13
+ const prefix = questionReplyPrefix(itemId);
14
+ const reply = messages.find((message) => message.role === "user" && message.content.startsWith(prefix));
15
+ if (reply || !onSend) return (
16
+ <div className="rounded-lg border border-border bg-surface p-3 text-sm whitespace-pre-wrap">
17
+ {reply ? reply.content.slice(prefix.length) : questions.map((question) => question.question).join("\n")}
18
+ <div className="mt-2 text-xs text-text-secondary">{reply ? "Response sent" : "Connect to answer this question"}</div>
19
+ </div>
20
+ );
21
+ return <QuestionCard questions={questions} autoFocus={false}
22
+ onSubmit={(answers) => onSend(formatQuestionReply(itemId, questions, answers))}
23
+ onSkip={() => onSend(formatQuestionReply(itemId, questions))} />;
24
+ }
@@ -1,4 +1,5 @@
1
1
  import { useState, useCallback, useRef, useEffect, useMemo } from "react";
2
+ import { AsyncQuestionContext } from "./async-question-card";
2
3
  import { Loader2, Upload, X } from "@/lib/icons";
3
4
  import { toast } from "sonner";
4
5
  import { api, projectUrl } from "@/lib/api-client";
@@ -1315,7 +1316,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1315
1316
  <span>Starting conversation...</span>
1316
1317
  </div>
1317
1318
  </div>
1318
- ) : <SessionChangesContext.Provider value={sessionChangesValue}><MessageList
1319
+ ) : <AsyncQuestionContext.Provider value={{ messages: renderedMessages, onSend: isConnected ? (content) => sendMessage(content, { permissionMode }) : undefined }}><SessionChangesContext.Provider value={sessionChangesValue}><MessageList
1319
1320
  messages={renderedMessages}
1320
1321
  onExpandCompact={expandCompact}
1321
1322
  isCompactExpanded={isCompactExpanded}
@@ -1346,7 +1347,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1346
1347
  onDismissMessage={dismissMessage}
1347
1348
  onClearErrors={clearErrors}
1348
1349
  bashPartialOutput={bashPartialOutput}
1349
- /></SessionChangesContext.Provider>}
1350
+ /></SessionChangesContext.Provider></AsyncQuestionContext.Provider>}
1350
1351
 
1351
1352
  {/* Agents still working — pinned here so it is the last thing under the conversation */}
1352
1353
  <RunningAgentsBar
@@ -12,6 +12,8 @@ import type { SessionPhase } from "../../../types/api";
12
12
  import type { BashPartialEntry } from "../../hooks/use-chat";
13
13
  import { ToolCard } from "./tool-cards";
14
14
  import { MarkdownContent } from "./message-markdown";
15
+ import { AsyncQuestionCard } from "./async-question-card";
16
+ import { ASYNC_QUESTION_TOOL, type ChatQuestion } from "../../../shared/chat-questions";
15
17
 
16
18
  /**
17
19
  * Renders events in order — consecutive text events merged into one bubble,
@@ -163,6 +165,10 @@ export function InterleavedEvents({ events, isStreaming, projectName, bashPartia
163
165
  </div>
164
166
  );
165
167
  }
168
+ if (group.tool.type === "tool_use" && group.tool.tool === ASYNC_QUESTION_TOOL && !group.tool.parentToolUseId) {
169
+ const input = group.tool.input as { questions: ChatQuestion[] };
170
+ return <AsyncQuestionCard key={group.tool.toolUseId} itemId={group.tool.toolUseId!} questions={input.questions} />;
171
+ }
166
172
  return <ToolCard key={`tool-${i}`} tool={group.tool} result={group.result} completed={group.completed} projectName={projectName} bashPartialOutput={bashPartialOutput} />;
167
173
  })}
168
174
  </>
@@ -1,4 +1,5 @@
1
1
  import { useState, useCallback, useMemo, useEffect, useRef } from "react";
2
+ import type { ChatQuestion } from "../../../shared/chat-questions";
2
3
 
3
4
  /* ── Types ── */
4
5
  export interface QuestionOption {
@@ -6,17 +7,13 @@ export interface QuestionOption {
6
7
  description?: string;
7
8
  }
8
9
 
9
- export interface Question {
10
- question: string;
11
- header?: string;
12
- options: QuestionOption[];
13
- multiSelect?: boolean;
14
- }
10
+ export type Question = ChatQuestion;
15
11
 
16
12
  interface QuestionCardProps {
17
13
  questions: Question[];
18
14
  onSubmit: (answers: Record<string, string>) => void;
19
15
  onSkip: () => void;
16
+ autoFocus?: boolean;
20
17
  }
21
18
 
22
19
  /* ── Hook: form state ── */
@@ -81,6 +78,7 @@ function useQuestionKeyboard(config: {
81
78
  onSubmit: () => void;
82
79
  customInputRef: React.RefObject<HTMLInputElement | null>;
83
80
  enabled: boolean;
81
+ autoFocus: boolean;
84
82
  }) {
85
83
  const [focusedOption, setFocusedOption] = useState(0);
86
84
  const containerRef = useRef<HTMLDivElement>(null);
@@ -139,7 +137,7 @@ function useQuestionKeyboard(config: {
139
137
  // Same reason: asking the main document whether focus is already inside the card answers
140
138
  // "no" for a card living in another document, and this effect re-runs on every render —
141
139
  // which would yank focus out of the custom input after each keystroke.
142
- if (!el.contains(el.ownerDocument.activeElement)) el.focus();
140
+ if (config.autoFocus && !el.contains(el.ownerDocument.activeElement)) el.focus();
143
141
  }
144
142
  return () => { el?.removeEventListener("keydown", handleKeyDown); };
145
143
  }, [config, focusedOption]);
@@ -148,7 +146,7 @@ function useQuestionKeyboard(config: {
148
146
  }
149
147
 
150
148
  /* ── Component ── */
151
- export function QuestionCard({ questions, onSubmit, onSkip }: QuestionCardProps) {
149
+ export function QuestionCard({ questions, onSubmit, onSkip, autoFocus = true }: QuestionCardProps) {
152
150
  const customInputRef = useRef<HTMLInputElement>(null);
153
151
  const form = useQuestionForm(questions);
154
152
  const currentQ = questions[form.activeTab];
@@ -158,7 +156,7 @@ export function QuestionCard({ questions, onSubmit, onSkip }: QuestionCardProps)
158
156
  const handleSubmit = useCallback(() => {
159
157
  if (!form.allAnswered) return;
160
158
  const result: Record<string, string> = {};
161
- questions.forEach((q, i) => { result[q.question] = form.getFinalAnswer(i); });
159
+ questions.forEach((q, i) => { result[q.id ?? q.question] = form.getFinalAnswer(i); });
162
160
  onSubmit(result);
163
161
  }, [form.allAnswered, form.getFinalAnswer, questions, onSubmit]);
164
162
 
@@ -182,7 +180,7 @@ export function QuestionCard({ questions, onSubmit, onSkip }: QuestionCardProps)
182
180
  allAnswered: form.allAnswered, hasAnswer: form.hasAnswer,
183
181
  onSelectOption: handleSelectOption,
184
182
  goToNextTab: form.goToNextTab, goToPrevTab: form.goToPrevTab,
185
- onSubmit: handleSubmit, customInputRef, enabled: true,
183
+ onSubmit: handleSubmit, customInputRef, enabled: true, autoFocus,
186
184
  });
187
185
 
188
186
  const selectWithFocus = useCallback(
@@ -626,7 +626,7 @@ function ToolDetails({
626
626
  </div>
627
627
  );
628
628
  case "AskUserQuestion": {
629
- const qs = (input.questions as Array<{ question: string; header?: string; options: Array<{ label: string; description?: string }>; multiSelect?: boolean }>) ?? [];
629
+ const qs = (input.questions as Array<{ id?: string; question: string; header?: string; options: Array<{ label: string; description?: string }>; multiSelect?: boolean }>) ?? [];
630
630
  const answers = (input.answers as Record<string, string>) ?? {};
631
631
  return (
632
632
  <div className="space-y-2">
@@ -635,7 +635,7 @@ function ToolDetails({
635
635
  <p className="text-text-primary font-medium">{q.header ? `${q.header}: ` : ""}{q.question}</p>
636
636
  <div className="flex flex-wrap gap-1">
637
637
  {q.options.map((opt, oi) => {
638
- const answer = answers[q.question] ?? "";
638
+ const answer = answers[q.id ?? q.question] ?? answers[q.question] ?? "";
639
639
  const isSelected = answer.split(", ").includes(opt.label);
640
640
  return (
641
641
  <span key={oi} className={`inline-block rounded px-1.5 py-0.5 text-xs border ${
@@ -646,8 +646,8 @@ function ToolDetails({
646
646
  );
647
647
  })}
648
648
  </div>
649
- {answers[q.question] && (
650
- <p className="text-foreground text-xs">Answer: {answers[q.question]}</p>
649
+ {(answers[q.id ?? q.question] ?? answers[q.question]) && (
650
+ <p className="text-foreground text-xs">Answer: {answers[q.id ?? q.question] ?? answers[q.question]}</p>
651
651
  )}
652
652
  </div>
653
653
  ))}
@@ -1,9 +1,10 @@
1
- import { useEffect, useState } from "react";
1
+ import { useEffect, useRef, useState } from "react";
2
2
  import { toast } from "sonner";
3
3
  import { Check, Copy, ExternalLink, Globe, Lock, RotateCw } from "@/lib/icons";
4
4
  import { tunnelsApi } from "@/lib/api-tunnels";
5
5
  import { copyToClipboard } from "@/lib/clipboard";
6
6
  import { safePreviewUrl } from "@/lib/web-preview-url";
7
+ import { usePanelStore } from "@/stores/panel-store";
7
8
 
8
9
  const actionClass =
9
10
  "flex items-center justify-center shrink-0 rounded min-h-11 min-w-11 md:min-h-7 md:min-w-7 text-muted-foreground hover:text-foreground hover:bg-muted";
@@ -28,14 +29,45 @@ function allowFramingHere(): Promise<void> {
28
29
  * A dev server on the host, forwarded and shown in a tab. The toolbar sits at the bottom
29
30
  * below `md`, in the thumb zone, as a phone browser's address bar does.
30
31
  */
31
- export function WebPreviewTab({ metadata }: { metadata?: Record<string, unknown> }) {
32
- const url = safePreviewUrl(metadata?.url, window.location.origin);
32
+ export function WebPreviewTab({ metadata, tabId }: { metadata?: Record<string, unknown>; tabId?: string }) {
33
+ const requestedUrl = safePreviewUrl(metadata?.url, window.location.origin);
34
+ const savedUrl = safePreviewUrl(metadata?.currentUrl, window.location.origin);
35
+ const initialUrl = savedUrl && requestedUrl && new URL(savedUrl).origin === new URL(requestedUrl).origin ? savedUrl : requestedUrl;
36
+ const [url, setUrl] = useState(initialUrl);
37
+ const [src, setSrc] = useState(initialUrl);
38
+ const iframe = useRef<HTMLIFrameElement>(null);
39
+ const command = `${requestedUrl}:${String(metadata?.aiOpenAt ?? "")}`;
40
+ const lastCommand = useRef(command);
33
41
  // `local` is the AI's open_url on the host's own browser: no forward, so only this machine reaches it.
34
42
  const isPrivate = metadata?.via === "tailscale" || metadata?.via === "local";
35
43
  const [reloadKey, setReloadKey] = useState(0);
36
44
  const [copied, setCopied] = useState(false);
37
45
  const [framingAsked, setFramingAsked] = useState(false);
38
46
 
47
+ useEffect(() => {
48
+ if (lastCommand.current === command) return;
49
+ lastCommand.current = command;
50
+ setUrl(requestedUrl);
51
+ setSrc(requestedUrl);
52
+ }, [command, requestedUrl]);
53
+
54
+ useEffect(() => {
55
+ const receive = (event: MessageEvent) => {
56
+ if (!requestedUrl || !iframe.current?.contentWindow || event.source !== iframe.current.contentWindow
57
+ || event.origin !== new URL(requestedUrl).origin || event.data?.ppm !== "web-preview-navigation") return;
58
+ const next = safePreviewUrl(event.data.url, window.location.origin);
59
+ if (!next || new URL(next).origin !== event.origin) return;
60
+ setUrl(next);
61
+ if (tabId) {
62
+ const store = usePanelStore.getState();
63
+ const tab = Object.values(store.panels).flatMap((panel) => panel.tabs).find((item) => item.id === tabId);
64
+ if (tab && tab.metadata?.currentUrl !== next) store.updateTab(tabId, { metadata: { ...tab.metadata, currentUrl: next } });
65
+ }
66
+ };
67
+ window.addEventListener("message", receive);
68
+ return () => window.removeEventListener("message", receive);
69
+ }, [requestedUrl, tabId]);
70
+
39
71
  useEffect(() => {
40
72
  let live = true;
41
73
  void allowFramingHere().then(() => { if (live) setFramingAsked(true); });
@@ -64,7 +96,7 @@ export function WebPreviewTab({ metadata }: { metadata?: Record<string, unknown>
64
96
  {isPrivate ? <Lock className="size-3.5" /> : <Globe className="size-3.5" />}
65
97
  </span>
66
98
  <span className="flex-1 min-w-0 truncate text-xs text-muted-foreground select-all">{url}</span>
67
- <button type="button" onClick={() => void allowFramingHere().then(() => setReloadKey((k) => k + 1))} title="Reload" aria-label="Reload" className={actionClass}>
99
+ <button type="button" onClick={() => void allowFramingHere().then(() => { setSrc(url); setReloadKey((k) => k + 1); })} title="Reload" aria-label="Reload" className={actionClass}>
68
100
  <RotateCw className="size-4" />
69
101
  </button>
70
102
  <button type="button" onClick={copy} title="Copy URL" aria-label="Copy URL" className={actionClass}>
@@ -78,10 +110,11 @@ export function WebPreviewTab({ metadata }: { metadata?: Record<string, unknown>
78
110
  own cookies and storage; without allow-top-navigation it cannot navigate PPM away. */}
79
111
  {framingAsked ? (
80
112
  <iframe
113
+ ref={iframe}
81
114
  // The AI's open_url sets aiOpenAt on every call: a page it opens again reloads.
82
115
  key={`${reloadKey}:${String(metadata?.aiOpenAt ?? "")}`}
83
116
  title={`Preview of ${url}`}
84
- src={url}
117
+ src={src ?? undefined}
85
118
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads"
86
119
  allow="clipboard-read; clipboard-write; fullscreen"
87
120
  referrerPolicy="no-referrer"