@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.
- package/CHANGELOG.md +13 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{accounts-settings-section-DhzuFkDO.js → accounts-settings-section-BES4EFGQ.js} +1 -1
- package/dist/web/assets/accounts-settings-section-BES4EFGQ.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-BES4EFGQ.js.gz +0 -0
- package/dist/web/assets/{agent-session-sheet-CZAmOwSU.js → agent-session-sheet-BlZmcb40.js} +2 -2
- package/dist/web/assets/agent-session-sheet-BlZmcb40.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-BlZmcb40.js.gz +0 -0
- package/dist/web/assets/{agent-session-window-content-CwokWtJq.js → agent-session-window-content-zCzFZ0XT.js} +1 -1
- package/dist/web/assets/agent-session-window-content-zCzFZ0XT.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-zCzFZ0XT.js.gz +0 -0
- package/dist/web/assets/{ai-resource-editor-CT1JCaRQ.js → ai-resource-editor-Dh8sQhL2.js} +1 -1
- package/dist/web/assets/ai-resource-editor-Dh8sQhL2.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-Dh8sQhL2.js.gz +0 -0
- package/dist/web/assets/{ai-settings-section-Ct9-lNqA.js → ai-settings-section-C6NKrktb.js} +1 -1
- package/dist/web/assets/ai-settings-section-C6NKrktb.js.br +0 -0
- package/dist/web/assets/ai-settings-section-C6NKrktb.js.gz +0 -0
- package/dist/web/assets/{ai-settings-section-CBlS1FZK.js → ai-settings-section-Ck_0ArV1.js} +1 -1
- package/dist/web/assets/{android-tab-CUPQN2M4.js → android-tab-BLxZKtMP.js} +1 -1
- package/dist/web/assets/android-tab-BLxZKtMP.js.br +0 -0
- package/dist/web/assets/android-tab-BLxZKtMP.js.gz +0 -0
- package/dist/web/assets/{appearance-settings-section-DpYcNqXH.js → appearance-settings-section-DFJdBUZ8.js} +1 -1
- package/dist/web/assets/appearance-settings-section-DFJdBUZ8.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-DFJdBUZ8.js.gz +0 -0
- package/dist/web/assets/{apps-panel-BqF_CkKy.js → apps-panel-C2dyMFVy.js} +1 -1
- package/dist/web/assets/apps-panel-C2dyMFVy.js.br +0 -0
- package/dist/web/assets/apps-panel-C2dyMFVy.js.gz +0 -0
- package/dist/web/assets/{branch-review-tab-BEZqFSlw.js → branch-review-tab-BIpjOgOQ.js} +1 -1
- package/dist/web/assets/branch-review-tab-BIpjOgOQ.js.br +0 -0
- package/dist/web/assets/branch-review-tab-BIpjOgOQ.js.gz +0 -0
- package/dist/web/assets/{chat-tab-N1B-Yv0P.js → chat-tab-CADhIdsz.js} +1 -1
- package/dist/web/assets/chat-tab-CADhIdsz.js.br +2 -0
- package/dist/web/assets/chat-tab-CADhIdsz.js.gz +0 -0
- package/dist/web/assets/chat-tab-WbP7BIUp.js +19 -0
- package/dist/web/assets/chat-tab-WbP7BIUp.js.br +0 -0
- package/dist/web/assets/chat-tab-WbP7BIUp.js.gz +0 -0
- package/dist/web/assets/{code-editor-d1CXl2WL.js → code-editor-BEjBAz0q.js} +4 -4
- package/dist/web/assets/code-editor-BEjBAz0q.js.br +0 -0
- package/dist/web/assets/code-editor-BEjBAz0q.js.gz +0 -0
- package/dist/web/assets/{codex-reset-credit-button-CwSSBK6P.js → codex-reset-credit-button-Qths4oQl.js} +1 -1
- package/dist/web/assets/codex-reset-credit-button-Qths4oQl.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-Qths4oQl.js.gz +0 -0
- package/dist/web/assets/{conflict-editor-BucKz-uE.js → conflict-editor-DwFgcGGr.js} +1 -1
- package/dist/web/assets/conflict-editor-DwFgcGGr.js.br +0 -0
- package/dist/web/assets/conflict-editor-DwFgcGGr.js.gz +0 -0
- package/dist/web/assets/{connection-form-tab-DIc25Bko.js → connection-form-tab-DJ3Lxpzd.js} +1 -1
- package/dist/web/assets/connection-form-tab-DJ3Lxpzd.js.br +0 -0
- package/dist/web/assets/connection-form-tab-DJ3Lxpzd.js.gz +0 -0
- package/dist/web/assets/{csv-preview-CqDaGV5N.js → csv-preview-BMF0HuuN.js} +1 -1
- package/dist/web/assets/csv-preview-BMF0HuuN.js.br +0 -0
- package/dist/web/assets/csv-preview-BMF0HuuN.js.gz +0 -0
- package/dist/web/assets/{db-tab-parts-lp8ROGQB.js → db-tab-parts-BbNfuvwC.js} +1 -1
- package/dist/web/assets/db-tab-parts-BbNfuvwC.js.br +0 -0
- package/dist/web/assets/db-tab-parts-BbNfuvwC.js.gz +0 -0
- package/dist/web/assets/{design-settings-section-DxWEBH1L.js → design-settings-section-BVcBTBdz.js} +1 -1
- package/dist/web/assets/design-settings-section-BVcBTBdz.js.br +0 -0
- package/dist/web/assets/design-settings-section-BVcBTBdz.js.gz +0 -0
- package/dist/web/assets/{design-tab-Cl0CMAUx.js → design-tab-msL92Mgr.js} +1 -1
- package/dist/web/assets/design-tab-msL92Mgr.js.br +0 -0
- package/dist/web/assets/design-tab-msL92Mgr.js.gz +0 -0
- package/dist/web/assets/{designs-sidebar-panel-aCr-EGfj.js → designs-sidebar-panel-hLIuDsDd.js} +1 -1
- package/dist/web/assets/designs-sidebar-panel-hLIuDsDd.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-hLIuDsDd.js.gz +0 -0
- package/dist/web/assets/{diff-viewer-XyJgWKwe.js → diff-viewer-BLYGfIik.js} +1 -1
- package/dist/web/assets/diff-viewer-BLYGfIik.js.br +0 -0
- package/dist/web/assets/diff-viewer-BLYGfIik.js.gz +0 -0
- package/dist/web/assets/{docx-preview-KJaj2YGN.js → docx-preview-D0ORQkvI.js} +1 -1
- package/dist/web/assets/docx-preview-D0ORQkvI.js.br +0 -0
- package/dist/web/assets/docx-preview-D0ORQkvI.js.gz +0 -0
- package/dist/web/assets/{editor-dialog-9tW6c9mu.js → editor-dialog-BdkAgK-M.js} +1 -1
- package/dist/web/assets/editor-dialog-BdkAgK-M.js.br +0 -0
- package/dist/web/assets/editor-dialog-BdkAgK-M.js.gz +0 -0
- package/dist/web/assets/{editor-language-service-CkdO9ih8.js → editor-language-service-B2TYY2sw.js} +1 -1
- package/dist/web/assets/editor-language-service-B2TYY2sw.js.br +0 -0
- package/dist/web/assets/editor-language-service-B2TYY2sw.js.gz +0 -0
- package/dist/web/assets/{explorer-body-B8AkNO5o.js → explorer-body-B-67PDYd.js} +1 -1
- package/dist/web/assets/explorer-body-B-67PDYd.js.br +0 -0
- package/dist/web/assets/explorer-body-B-67PDYd.js.gz +0 -0
- package/dist/web/assets/{explorer-window-content-D174CXKz.js → explorer-window-content-ndGjHH-i.js} +1 -1
- package/dist/web/assets/explorer-window-content-ndGjHH-i.js.br +0 -0
- package/dist/web/assets/explorer-window-content-ndGjHH-i.js.gz +0 -0
- package/dist/web/assets/{extension-manager-section-CQsqXsPF.js → extension-manager-section-C3Z99hLd.js} +1 -1
- package/dist/web/assets/extension-manager-section-C3Z99hLd.js.br +0 -0
- package/dist/web/assets/extension-manager-section-C3Z99hLd.js.gz +0 -0
- package/dist/web/assets/{extension-webview-AfaxC7PL.js → extension-webview-oBN-quI3.js} +1 -1
- package/dist/web/assets/extension-webview-oBN-quI3.js.br +0 -0
- package/dist/web/assets/extension-webview-oBN-quI3.js.gz +0 -0
- package/dist/web/assets/{files-settings-section-CC0aJryq.js → files-settings-section-pzPZEYe2.js} +1 -1
- package/dist/web/assets/files-settings-section-pzPZEYe2.js.br +0 -0
- package/dist/web/assets/files-settings-section-pzPZEYe2.js.gz +0 -0
- package/dist/web/assets/{general-settings-section-DQTr20G_.js → general-settings-section-BxNNbh5b.js} +1 -1
- package/dist/web/assets/general-settings-section-BxNNbh5b.js.br +0 -0
- package/dist/web/assets/general-settings-section-BxNNbh5b.js.gz +0 -0
- package/dist/web/assets/{git-log-panel-CisEJPJz.js → git-log-panel-CEAF4ST-.js} +1 -1
- package/dist/web/assets/git-log-panel-CEAF4ST-.js.br +0 -0
- package/dist/web/assets/git-log-panel-CEAF4ST-.js.gz +0 -0
- package/dist/web/assets/{git-review-tab-DpWEy03w.js → git-review-tab-Cp5DYE5b.js} +1 -1
- package/dist/web/assets/git-review-tab-Cp5DYE5b.js.br +0 -0
- package/dist/web/assets/git-review-tab-Cp5DYE5b.js.gz +0 -0
- package/dist/web/assets/{glide-data-grid-hOML-uXP.js → glide-data-grid-BrwKKQhc.js} +1 -1
- package/dist/web/assets/glide-data-grid-BrwKKQhc.js.br +0 -0
- package/dist/web/assets/glide-data-grid-BrwKKQhc.js.gz +0 -0
- package/dist/web/assets/{grid-save-dialog-Bx61akzH.js → grid-save-dialog-inaY39lZ.js} +1 -1
- package/dist/web/assets/grid-save-dialog-inaY39lZ.js.br +0 -0
- package/dist/web/assets/grid-save-dialog-inaY39lZ.js.gz +0 -0
- package/dist/web/assets/{group-chat-tab-eQdJHa8d.js → group-chat-tab-DpvHkXCK.js} +1 -1
- package/dist/web/assets/group-chat-tab-DpvHkXCK.js.br +0 -0
- package/dist/web/assets/group-chat-tab-DpvHkXCK.js.gz +0 -0
- package/dist/web/assets/{impexp-tab-BjIDxYTj.js → impexp-tab-CDgODpaL.js} +1 -1
- package/dist/web/assets/impexp-tab-CDgODpaL.js.br +0 -0
- package/dist/web/assets/impexp-tab-CDgODpaL.js.gz +0 -0
- package/dist/web/assets/{index-COwBji1t.js → index-EznfgwH9.js} +5 -5
- package/dist/web/assets/index-EznfgwH9.js.br +0 -0
- package/dist/web/assets/index-EznfgwH9.js.gz +0 -0
- package/dist/web/assets/{jira-watcher-section-dzpmNZA2.js → jira-watcher-section-CvxfscZl.js} +1 -1
- package/dist/web/assets/{keybindings-store-DRjk8512.js → keybindings-store-BqXn-D3b.js} +1 -1
- package/dist/web/assets/{keyboard-shortcuts-section-BhyalK8A.js → keyboard-shortcuts-section-HaqDDilx.js} +1 -1
- package/dist/web/assets/keyboard-shortcuts-section-HaqDDilx.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-HaqDDilx.js.gz +0 -0
- package/dist/web/assets/{language-servers-section-BXLaicUY.js → language-servers-section-B2mGPtGX.js} +2 -2
- package/dist/web/assets/language-servers-section-B2mGPtGX.js.br +0 -0
- package/dist/web/assets/language-servers-section-B2mGPtGX.js.gz +0 -0
- package/dist/web/assets/{logs-app-BnjU6CRy.js → logs-app-Cv2fsZxf.js} +1 -1
- package/dist/web/assets/logs-app-Cv2fsZxf.js.br +0 -0
- package/dist/web/assets/logs-app-Cv2fsZxf.js.gz +0 -0
- package/dist/web/assets/{logs-tab-7uyD7gKW.js → logs-tab-D9orkAwZ.js} +1 -1
- package/dist/web/assets/{logs-window-content-B78a6oM2.js → logs-window-content-DygG8-n0.js} +1 -1
- package/dist/web/assets/{lsp-client-DOHnxEUE.js → lsp-client-DpKk7qms.js} +1 -1
- package/dist/web/assets/lsp-client-DpKk7qms.js.br +0 -0
- package/dist/web/assets/lsp-client-DpKk7qms.js.gz +0 -0
- package/dist/web/assets/{markdown-renderer-B_hL6lRL.js → markdown-renderer-YDuDxTPt.js} +1 -1
- package/dist/web/assets/{markdown-renderer-Dz0ZcxNo.js → markdown-renderer-zuzkV4A5.js} +1 -1
- package/dist/web/assets/markdown-renderer-zuzkV4A5.js.br +0 -0
- package/dist/web/assets/markdown-renderer-zuzkV4A5.js.gz +0 -0
- package/dist/web/assets/{mobile-explorer-sheet-C_3dq99D.js → mobile-explorer-sheet-DrQaPEZo.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-DrQaPEZo.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-DrQaPEZo.js.gz +0 -0
- package/dist/web/assets/{new-design-dialog-CaubNbMT.js → new-design-dialog-C00L2Ei1.js} +1 -1
- package/dist/web/assets/new-design-dialog-C00L2Ei1.js.br +0 -0
- package/dist/web/assets/new-design-dialog-C00L2Ei1.js.gz +0 -0
- package/dist/web/assets/{notification-store-BTk3PZAj.js → notification-store-BHQjJkNC.js} +1 -1
- package/dist/web/assets/{notifications-settings-section-CJJ1l5cL.js → notifications-settings-section-DHx7YbrU.js} +1 -1
- package/dist/web/assets/notifications-settings-section-DHx7YbrU.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-DHx7YbrU.js.gz +0 -0
- package/dist/web/assets/{ppmbot-settings-section-BFniAm3D.js → ppmbot-settings-section-8OxZnTeE.js} +1 -1
- package/dist/web/assets/ppmbot-settings-section-8OxZnTeE.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-8OxZnTeE.js.gz +0 -0
- package/dist/web/assets/{problems-panel-CIxaCYJw.js → problems-panel-uCMYh7nY.js} +1 -1
- package/dist/web/assets/problems-panel-uCMYh7nY.js.br +0 -0
- package/dist/web/assets/problems-panel-uCMYh7nY.js.gz +0 -0
- package/dist/web/assets/{process-table-l1fVcESX.js → process-table-D6fDra-t.js} +1 -1
- package/dist/web/assets/process-table-D6fDra-t.js.br +0 -0
- package/dist/web/assets/process-table-D6fDra-t.js.gz +0 -0
- package/dist/web/assets/{proxy-settings-section-Di5nBuy6.js → proxy-settings-section-B8vjc9n-.js} +1 -1
- package/dist/web/assets/proxy-settings-section-B8vjc9n-.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-B8vjc9n-.js.gz +0 -0
- package/dist/web/assets/{query-tab-hyKnRZr3.js → query-tab-BaIWJrsN.js} +1 -1
- package/dist/web/assets/query-tab-BaIWJrsN.js.br +0 -0
- package/dist/web/assets/{query-tab-hyKnRZr3.js.gz → query-tab-BaIWJrsN.js.gz} +0 -0
- package/dist/web/assets/{remote-access-settings-section-HpZAP0Al.js → remote-access-settings-section-DIrFElKv.js} +1 -1
- package/dist/web/assets/remote-access-settings-section-DIrFElKv.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-DIrFElKv.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-clipboard-notice-DY7tWgjf.js → remote-desktop-clipboard-notice-Cg2Z-66K.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-Cg2Z-66K.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-Cg2Z-66K.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-mobile-sheet-CX9mDRle.js → remote-desktop-mobile-sheet-CPkPaOeS.js} +2 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-CPkPaOeS.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-CPkPaOeS.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-mobile-view-DNg0EpbV.js → remote-desktop-mobile-view-CFyOCkms.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-view-CFyOCkms.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-CFyOCkms.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-BTTLlVDc.js → remote-desktop-readiness-gate-CBAZK0-B.js} +1 -1
- package/dist/web/assets/remote-desktop-readiness-gate-CBAZK0-B.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CBAZK0-B.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-window-content-CqxnhSu0.js → remote-desktop-window-content-CNk7SS-W.js} +1 -1
- package/dist/web/assets/remote-desktop-window-content-CNk7SS-W.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-CNk7SS-W.js.gz +0 -0
- package/dist/web/assets/{rename-object-dialog-CnhMsNbT.js → rename-object-dialog-MhvD2boE.js} +1 -1
- package/dist/web/assets/rename-object-dialog-MhvD2boE.js.br +0 -0
- package/dist/web/assets/rename-object-dialog-MhvD2boE.js.gz +0 -0
- package/dist/web/assets/{review-code-CunYhmcC.js → review-code-B9zXrfyG.js} +1 -1
- package/dist/web/assets/review-code-B9zXrfyG.js.br +0 -0
- package/dist/web/assets/review-code-B9zXrfyG.js.gz +0 -0
- package/dist/web/assets/{save-as-dialog-BtGSwTKZ.js → save-as-dialog-5yBgewV3.js} +1 -1
- package/dist/web/assets/save-as-dialog-5yBgewV3.js.br +0 -0
- package/dist/web/assets/save-as-dialog-5yBgewV3.js.gz +0 -0
- package/dist/web/assets/{schedules-settings-section-CvlcMyEz.js → schedules-settings-section-BD3-tlU-.js} +1 -1
- package/dist/web/assets/schedules-settings-section-BD3-tlU-.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-BD3-tlU-.js.gz +0 -0
- package/dist/web/assets/{session-review-tab-C3HuIPD2.js → session-review-tab-DYJFhsGk.js} +1 -1
- package/dist/web/assets/session-review-tab-DYJFhsGk.js.br +0 -0
- package/dist/web/assets/session-review-tab-DYJFhsGk.js.gz +0 -0
- package/dist/web/assets/{settings-body-DCHEAh2_.js → settings-body-CWBVeKKg.js} +1 -1
- package/dist/web/assets/settings-body-CWBVeKKg.js.br +4 -0
- package/dist/web/assets/settings-body-CWBVeKKg.js.gz +0 -0
- package/dist/web/assets/{settings-rows-D8Y9o1ZD.js → settings-rows-B7fyQuu_.js} +1 -1
- package/dist/web/assets/settings-rows-B7fyQuu_.js.br +0 -0
- package/dist/web/assets/settings-rows-B7fyQuu_.js.gz +0 -0
- package/dist/web/assets/settings-section-content-BPQN6KdN.js +1 -0
- package/dist/web/assets/{settings-section-content-Dluhkh-u.js → settings-section-content-CoJcm6JM.js} +2 -2
- package/dist/web/assets/settings-section-content-CoJcm6JM.js.br +0 -0
- package/dist/web/assets/settings-section-content-CoJcm6JM.js.gz +0 -0
- package/dist/web/assets/{settings-tab-Cj1GkYi3.js → settings-tab-DWzU1RkL.js} +1 -1
- package/dist/web/assets/{settings-window-content-1fweMynB.js → settings-window-content-CAThkpa8.js} +1 -1
- package/dist/web/assets/{slash-command-picker-BdKd_AiS.js → slash-command-picker-dDl9riKP.js} +1 -1
- package/dist/web/assets/slash-command-picker-dDl9riKP.js.br +0 -0
- package/dist/web/assets/slash-command-picker-dDl9riKP.js.gz +0 -0
- package/dist/web/assets/{sql-object-tab-DkdF_7Jz.js → sql-object-tab-DND9osIv.js} +1 -1
- package/dist/web/assets/sql-object-tab-DND9osIv.js.br +0 -0
- package/dist/web/assets/sql-object-tab-DND9osIv.js.gz +0 -0
- package/dist/web/assets/{sqlite-viewer-BoQMjJHj.js → sqlite-viewer-P7UpXmyZ.js} +1 -1
- package/dist/web/assets/sqlite-viewer-P7UpXmyZ.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-P7UpXmyZ.js.gz +0 -0
- package/dist/web/assets/{structure-save-dialog-DGHxHJ4T.js → structure-save-dialog-DX1H1Qy2.js} +1 -1
- package/dist/web/assets/structure-save-dialog-DX1H1Qy2.js.br +0 -0
- package/dist/web/assets/structure-save-dialog-DX1H1Qy2.js.gz +0 -0
- package/dist/web/assets/{structure-tab-DuuX1e9c.js → structure-tab-1QhNP2ob.js} +1 -1
- package/dist/web/assets/structure-tab-1QhNP2ob.js.br +0 -0
- package/dist/web/assets/structure-tab-1QhNP2ob.js.gz +0 -0
- package/dist/web/assets/{system-monitor-body-CZVoh652.js → system-monitor-body-CPAAjIdT.js} +2 -2
- package/dist/web/assets/system-monitor-body-CPAAjIdT.js.br +0 -0
- package/dist/web/assets/system-monitor-body-CPAAjIdT.js.gz +0 -0
- package/dist/web/assets/{system-monitor-tab-eGta1jpw.js → system-monitor-tab-D_1PBGwv.js} +1 -1
- package/dist/web/assets/{system-monitor-window-content-BUG72ffC.js → system-monitor-window-content-dVxWFqHP.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BTYuY43x.js → tab-host-window-content-BBsg5_fz.js} +1 -1
- package/dist/web/assets/tab-host-window-content-BBsg5_fz.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-BBsg5_fz.js.gz +0 -0
- package/dist/web/assets/{table-tab-BsfgtqFE.js → table-tab-BO-q-hSc.js} +1 -1
- package/dist/web/assets/table-tab-BO-q-hSc.js.br +0 -0
- package/dist/web/assets/table-tab-BO-q-hSc.js.gz +0 -0
- package/dist/web/assets/{telegram-bot-token-field-CA8DEKRL.js → telegram-bot-token-field-BVYWBpX_.js} +1 -1
- package/dist/web/assets/telegram-bot-token-field-BVYWBpX_.js.br +0 -0
- package/dist/web/assets/telegram-bot-token-field-BVYWBpX_.js.gz +0 -0
- package/dist/web/assets/{terminal-tab-CoT5KIfe.js → terminal-tab-DjPXnuO8.js} +1 -1
- package/dist/web/assets/terminal-tab-DjPXnuO8.js.br +0 -0
- package/dist/web/assets/terminal-tab-DjPXnuO8.js.gz +0 -0
- package/dist/web/assets/{tool-cards-oZ6cptRr.js → tool-cards-DXV9SEE8.js} +3 -3
- package/dist/web/assets/tool-cards-DXV9SEE8.js.br +0 -0
- package/dist/web/assets/tool-cards-DXV9SEE8.js.gz +0 -0
- package/dist/web/assets/{tools-settings-section-CtcD-iTk.js → tools-settings-section-Cc5YKOB6.js} +1 -1
- package/dist/web/assets/tools-settings-section-Cc5YKOB6.js.br +0 -0
- package/dist/web/assets/tools-settings-section-Cc5YKOB6.js.gz +0 -0
- package/dist/web/assets/{use-inline-blame-BUuJj1Ru.js → use-inline-blame-f6WMZkEv.js} +1 -1
- package/dist/web/assets/use-inline-blame-f6WMZkEv.js.br +0 -0
- package/dist/web/assets/use-inline-blame-f6WMZkEv.js.gz +0 -0
- package/dist/web/assets/{use-lsp-Bo4z4gLx.js → use-lsp-B_fmU9ja.js} +1 -1
- package/dist/web/assets/use-lsp-B_fmU9ja.js.br +0 -0
- package/dist/web/assets/use-lsp-B_fmU9ja.js.gz +0 -0
- package/dist/web/assets/{use-monaco-theme-1ANtPWqA.js → use-monaco-theme-nu5Fogu3.js} +1 -1
- package/dist/web/assets/{use-process-kill-CvlXPal4.js → use-process-kill-B8Mf4_Yy.js} +1 -1
- package/dist/web/assets/use-process-kill-B8Mf4_Yy.js.br +0 -0
- package/dist/web/assets/use-process-kill-B8Mf4_Yy.js.gz +0 -0
- package/dist/web/assets/{use-sql-schema-info-DQzUqb7q.js → use-sql-schema-info-Dp6TCir5.js} +1 -1
- package/dist/web/assets/use-sql-schema-info-Dp6TCir5.js.br +0 -0
- package/dist/web/assets/use-sql-schema-info-Dp6TCir5.js.gz +0 -0
- package/dist/web/assets/{use-websocket-QCAD3F5b.js → use-websocket-RuENPZ_x.js} +1 -1
- package/dist/web/assets/web-preview-tab-ClOiV3BO.js +1 -0
- package/dist/web/assets/web-preview-tab-ClOiV3BO.js.br +0 -0
- package/dist/web/assets/web-preview-tab-ClOiV3BO.js.gz +0 -0
- package/dist/web/index.html +1 -1
- package/dist/web/index.html.br +0 -0
- package/dist/web/index.html.gz +0 -0
- package/dist/web/sw.js +1 -1
- package/dist/web/sw.js.br +0 -0
- package/dist/web/sw.js.gz +0 -0
- package/docs/architecture/ai-chat-and-providers.md +22 -0
- package/docs/architecture/workspace-and-ui.md +11 -0
- package/package.json +1 -1
- package/src/providers/codex-app-server/codex-event-mapper.ts +3 -0
- package/src/providers/codex-app-server/codex-provider.ts +3 -21
- package/src/providers/codex-app-server/codex-rollout-items.ts +3 -0
- package/src/providers/codex-app-server/codex-thread-params.ts +1 -0
- package/src/providers/codex-app-server/codex-user-input.ts +41 -0
- package/src/services/port-forward/forward-hop.ts +20 -5
- package/src/services/port-forward/preview-navigation.ts +45 -0
- package/src/shared/chat-questions.ts +19 -0
- package/src/web/components/chat/async-question-card.tsx +24 -0
- package/src/web/components/chat/chat-tab.tsx +3 -2
- package/src/web/components/chat/message-events.tsx +6 -0
- package/src/web/components/chat/question-card.tsx +8 -10
- package/src/web/components/chat/tool-cards.tsx +4 -4
- package/src/web/components/web-preview/web-preview-tab.tsx +38 -5
- package/dist/web/assets/accounts-settings-section-DhzuFkDO.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-DhzuFkDO.js.gz +0 -0
- package/dist/web/assets/agent-session-sheet-CZAmOwSU.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-CZAmOwSU.js.gz +0 -0
- package/dist/web/assets/agent-session-window-content-CwokWtJq.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-CwokWtJq.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-CT1JCaRQ.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-CT1JCaRQ.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-Ct9-lNqA.js.br +0 -0
- package/dist/web/assets/ai-settings-section-Ct9-lNqA.js.gz +0 -0
- package/dist/web/assets/android-tab-CUPQN2M4.js.br +0 -0
- package/dist/web/assets/android-tab-CUPQN2M4.js.gz +0 -0
- package/dist/web/assets/appearance-settings-section-DpYcNqXH.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-DpYcNqXH.js.gz +0 -0
- package/dist/web/assets/apps-panel-BqF_CkKy.js.br +0 -0
- package/dist/web/assets/apps-panel-BqF_CkKy.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-BEZqFSlw.js.br +0 -0
- package/dist/web/assets/branch-review-tab-BEZqFSlw.js.gz +0 -0
- package/dist/web/assets/chat-tab-N1B-Yv0P.js.br +0 -0
- package/dist/web/assets/chat-tab-N1B-Yv0P.js.gz +0 -0
- package/dist/web/assets/chat-tab-kk-mAF05.js +0 -16
- package/dist/web/assets/chat-tab-kk-mAF05.js.br +0 -0
- package/dist/web/assets/chat-tab-kk-mAF05.js.gz +0 -0
- package/dist/web/assets/code-editor-d1CXl2WL.js.br +0 -0
- package/dist/web/assets/code-editor-d1CXl2WL.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-CwSSBK6P.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-CwSSBK6P.js.gz +0 -0
- package/dist/web/assets/conflict-editor-BucKz-uE.js.br +0 -0
- package/dist/web/assets/conflict-editor-BucKz-uE.js.gz +0 -0
- package/dist/web/assets/connection-form-tab-DIc25Bko.js.br +0 -0
- package/dist/web/assets/connection-form-tab-DIc25Bko.js.gz +0 -0
- package/dist/web/assets/csv-preview-CqDaGV5N.js.br +0 -0
- package/dist/web/assets/csv-preview-CqDaGV5N.js.gz +0 -0
- package/dist/web/assets/db-tab-parts-lp8ROGQB.js.br +0 -0
- package/dist/web/assets/db-tab-parts-lp8ROGQB.js.gz +0 -0
- package/dist/web/assets/design-settings-section-DxWEBH1L.js.br +0 -0
- package/dist/web/assets/design-settings-section-DxWEBH1L.js.gz +0 -0
- package/dist/web/assets/design-tab-Cl0CMAUx.js.br +0 -0
- package/dist/web/assets/design-tab-Cl0CMAUx.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-aCr-EGfj.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-aCr-EGfj.js.gz +0 -0
- package/dist/web/assets/diff-viewer-XyJgWKwe.js.br +0 -0
- package/dist/web/assets/diff-viewer-XyJgWKwe.js.gz +0 -0
- package/dist/web/assets/docx-preview-KJaj2YGN.js.br +0 -0
- package/dist/web/assets/docx-preview-KJaj2YGN.js.gz +0 -0
- package/dist/web/assets/editor-dialog-9tW6c9mu.js.br +0 -0
- package/dist/web/assets/editor-dialog-9tW6c9mu.js.gz +0 -0
- package/dist/web/assets/editor-language-service-CkdO9ih8.js.br +0 -0
- package/dist/web/assets/editor-language-service-CkdO9ih8.js.gz +0 -0
- package/dist/web/assets/explorer-body-B8AkNO5o.js.br +0 -0
- package/dist/web/assets/explorer-body-B8AkNO5o.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-D174CXKz.js.br +0 -0
- package/dist/web/assets/explorer-window-content-D174CXKz.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-CQsqXsPF.js.br +0 -0
- package/dist/web/assets/extension-manager-section-CQsqXsPF.js.gz +0 -0
- package/dist/web/assets/extension-webview-AfaxC7PL.js.br +0 -0
- package/dist/web/assets/extension-webview-AfaxC7PL.js.gz +0 -0
- package/dist/web/assets/files-settings-section-CC0aJryq.js.br +0 -0
- package/dist/web/assets/files-settings-section-CC0aJryq.js.gz +0 -0
- package/dist/web/assets/general-settings-section-DQTr20G_.js.br +0 -0
- package/dist/web/assets/general-settings-section-DQTr20G_.js.gz +0 -0
- package/dist/web/assets/git-log-panel-CisEJPJz.js.br +0 -0
- package/dist/web/assets/git-log-panel-CisEJPJz.js.gz +0 -0
- package/dist/web/assets/git-review-tab-DpWEy03w.js.br +0 -0
- package/dist/web/assets/git-review-tab-DpWEy03w.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-hOML-uXP.js.br +0 -0
- package/dist/web/assets/glide-data-grid-hOML-uXP.js.gz +0 -0
- package/dist/web/assets/grid-save-dialog-Bx61akzH.js.br +0 -0
- package/dist/web/assets/grid-save-dialog-Bx61akzH.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-eQdJHa8d.js.br +0 -0
- package/dist/web/assets/group-chat-tab-eQdJHa8d.js.gz +0 -0
- package/dist/web/assets/impexp-tab-BjIDxYTj.js.br +0 -0
- package/dist/web/assets/impexp-tab-BjIDxYTj.js.gz +0 -0
- package/dist/web/assets/index-COwBji1t.js.br +0 -0
- package/dist/web/assets/index-COwBji1t.js.gz +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-BhyalK8A.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-BhyalK8A.js.gz +0 -0
- package/dist/web/assets/language-servers-section-BXLaicUY.js.br +0 -0
- package/dist/web/assets/language-servers-section-BXLaicUY.js.gz +0 -0
- package/dist/web/assets/logs-app-BnjU6CRy.js.br +0 -0
- package/dist/web/assets/logs-app-BnjU6CRy.js.gz +0 -0
- package/dist/web/assets/lsp-client-DOHnxEUE.js.br +0 -0
- package/dist/web/assets/lsp-client-DOHnxEUE.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-Dz0ZcxNo.js.br +0 -0
- package/dist/web/assets/markdown-renderer-Dz0ZcxNo.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-C_3dq99D.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-C_3dq99D.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-CaubNbMT.js.br +0 -0
- package/dist/web/assets/new-design-dialog-CaubNbMT.js.gz +0 -0
- package/dist/web/assets/notifications-settings-section-CJJ1l5cL.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-CJJ1l5cL.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-BFniAm3D.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-BFniAm3D.js.gz +0 -0
- package/dist/web/assets/problems-panel-CIxaCYJw.js.br +0 -0
- package/dist/web/assets/problems-panel-CIxaCYJw.js.gz +0 -0
- package/dist/web/assets/process-table-l1fVcESX.js.br +0 -0
- package/dist/web/assets/process-table-l1fVcESX.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-Di5nBuy6.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-Di5nBuy6.js.gz +0 -0
- package/dist/web/assets/query-tab-hyKnRZr3.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-HpZAP0Al.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-HpZAP0Al.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-DY7tWgjf.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-DY7tWgjf.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-CX9mDRle.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-CX9mDRle.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DNg0EpbV.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DNg0EpbV.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BTTLlVDc.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BTTLlVDc.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-CqxnhSu0.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-CqxnhSu0.js.gz +0 -0
- package/dist/web/assets/rename-object-dialog-CnhMsNbT.js.br +0 -0
- package/dist/web/assets/rename-object-dialog-CnhMsNbT.js.gz +0 -0
- package/dist/web/assets/review-code-CunYhmcC.js.br +0 -0
- package/dist/web/assets/review-code-CunYhmcC.js.gz +0 -0
- package/dist/web/assets/save-as-dialog-BtGSwTKZ.js.br +0 -0
- package/dist/web/assets/save-as-dialog-BtGSwTKZ.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-CvlcMyEz.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-CvlcMyEz.js.gz +0 -0
- package/dist/web/assets/session-review-tab-C3HuIPD2.js.br +0 -0
- package/dist/web/assets/session-review-tab-C3HuIPD2.js.gz +0 -0
- package/dist/web/assets/settings-body-DCHEAh2_.js.br +0 -0
- package/dist/web/assets/settings-body-DCHEAh2_.js.gz +0 -0
- package/dist/web/assets/settings-rows-D8Y9o1ZD.js.br +0 -0
- package/dist/web/assets/settings-rows-D8Y9o1ZD.js.gz +0 -0
- package/dist/web/assets/settings-section-content-BXPg2H0N.js +0 -1
- package/dist/web/assets/settings-section-content-Dluhkh-u.js.br +0 -0
- package/dist/web/assets/settings-section-content-Dluhkh-u.js.gz +0 -0
- package/dist/web/assets/slash-command-picker-BdKd_AiS.js.br +0 -0
- package/dist/web/assets/slash-command-picker-BdKd_AiS.js.gz +0 -0
- package/dist/web/assets/sql-object-tab-DkdF_7Jz.js.br +0 -0
- package/dist/web/assets/sql-object-tab-DkdF_7Jz.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-BoQMjJHj.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-BoQMjJHj.js.gz +0 -0
- package/dist/web/assets/structure-save-dialog-DGHxHJ4T.js.br +0 -0
- package/dist/web/assets/structure-save-dialog-DGHxHJ4T.js.gz +0 -0
- package/dist/web/assets/structure-tab-DuuX1e9c.js.br +0 -0
- package/dist/web/assets/structure-tab-DuuX1e9c.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-CZVoh652.js.br +0 -0
- package/dist/web/assets/system-monitor-body-CZVoh652.js.gz +0 -0
- package/dist/web/assets/tab-host-window-content-BTYuY43x.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-BTYuY43x.js.gz +0 -0
- package/dist/web/assets/table-tab-BsfgtqFE.js.br +0 -0
- package/dist/web/assets/table-tab-BsfgtqFE.js.gz +0 -0
- package/dist/web/assets/telegram-bot-token-field-CA8DEKRL.js.br +0 -0
- package/dist/web/assets/telegram-bot-token-field-CA8DEKRL.js.gz +0 -0
- package/dist/web/assets/terminal-tab-CoT5KIfe.js.br +0 -0
- package/dist/web/assets/terminal-tab-CoT5KIfe.js.gz +0 -0
- package/dist/web/assets/tool-cards-oZ6cptRr.js.br +0 -0
- package/dist/web/assets/tool-cards-oZ6cptRr.js.gz +0 -0
- package/dist/web/assets/tools-settings-section-CtcD-iTk.js.br +0 -0
- package/dist/web/assets/tools-settings-section-CtcD-iTk.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-BUuJj1Ru.js.br +0 -0
- package/dist/web/assets/use-inline-blame-BUuJj1Ru.js.gz +0 -0
- package/dist/web/assets/use-lsp-Bo4z4gLx.js.br +0 -0
- package/dist/web/assets/use-lsp-Bo4z4gLx.js.gz +0 -0
- package/dist/web/assets/use-process-kill-CvlXPal4.js.br +0 -0
- package/dist/web/assets/use-process-kill-CvlXPal4.js.gz +0 -0
- package/dist/web/assets/use-sql-schema-info-DQzUqb7q.js.br +0 -0
- package/dist/web/assets/use-sql-schema-info-DQzUqb7q.js.gz +0 -0
- package/dist/web/assets/web-preview-tab-Cp0LuL0T.js +0 -1
- package/dist/web/assets/web-preview-tab-Cp0LuL0T.js.br +0 -0
- 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:
|
|
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
|
-
//
|
|
192
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
|
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
|
|
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={
|
|
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"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|