@hienlh/ppm 0.19.0 → 0.19.2
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 -1
- 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/{adaptive-context-menu-JfuUSqbe.js → adaptive-context-menu-BbuRVTLn.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-MfdJKVM5.js → ai-resource-editor-BCCOUzox.js} +1 -1
- package/dist/web/assets/{ai-settings-section-CJE7jFsj.js → ai-settings-section-BEM1z7kp.js} +1 -1
- package/dist/web/assets/{angular-html-VzxSzTCy.js → angular-html-BlcrLjUx.js} +1 -1
- package/dist/web/assets/{angular-ts-CmJ4bmzM.js → angular-ts-BUjtceAJ.js} +1 -1
- package/dist/web/assets/{api-client-qRWu6_s5.js → api-client-BzWM7XPR.js} +1 -1
- package/dist/web/assets/api-client-D1CePgQ4.js +1 -0
- package/dist/web/assets/api-settings-CNtKkHqW.js +1 -0
- package/dist/web/assets/{api-settings-CPg4yGvS.js → api-settings-Cubuqjsu.js} +1 -1
- package/dist/web/assets/{api-themes-DsAp8m7m.js → api-themes-YsZgf6Xj.js} +1 -1
- package/dist/web/assets/{apl-do17bnVx.js → apl-B3g0v1pk.js} +1 -1
- package/dist/web/assets/{astro-ClY0e0OA.js → astro-FRnyn7cH.js} +1 -1
- package/dist/web/assets/{audio-preview-Fw935Mok.js → audio-preview-DWTRm4HC.js} +1 -1
- package/dist/web/assets/{blade-BXSdXDGq.js → blade-NLdOqUKw.js} +1 -1
- package/dist/web/assets/{bot-CWZAuloY.js → bot-D6sx11y0.js} +1 -1
- package/dist/web/assets/{bsl-MxsMKDUZ.js → bsl-CenHZfaU.js} +1 -1
- package/dist/web/assets/{button-CrwgGd1y.js → button-Cq8yixjf.js} +1 -1
- package/dist/web/assets/{cairo-4CIrzX_p.js → cairo-B_WR0aiQ.js} +1 -1
- package/dist/web/assets/chat-tab-CmXGsJGc.js +15 -0
- package/dist/web/assets/{chevron-down-D7CbB5f6.js → chevron-down-Dp0rpHb8.js} +1 -1
- package/dist/web/assets/chevron-right-De-Wz5Lz.js +1 -0
- package/dist/web/assets/circle-BCy0AUCo.js +1 -0
- package/dist/web/assets/{circle-question-mark-BBzFZrNh.js → circle-question-mark-CzR7YRc-.js} +1 -1
- package/dist/web/assets/clock-C1CQUQPZ.js +1 -0
- package/dist/web/assets/{cobol-phPOE89d.js → cobol-DOynhWYj.js} +1 -1
- package/dist/web/assets/code-DJgYrFzm.js +1 -0
- package/dist/web/assets/code-editor-DtHXzNZ1.js +7 -0
- package/dist/web/assets/{coffee-Cjuukfz3.js → coffee-Bn6dbw4P.js} +1 -1
- package/dist/web/assets/{conflict-editor-Ni0PPsKu.js → conflict-editor-Cgb-z12D.js} +1 -1
- package/dist/web/assets/{context-menu-BEAa1qhd.js → context-menu-DfWZAxQy.js} +1 -1
- package/dist/web/assets/{copy-CbPdNp7E.js → copy-BewHj66E.js} +1 -1
- package/dist/web/assets/{cpp-CT7tGBC3.js → cpp-CoZR9t8r.js} +1 -1
- package/dist/web/assets/{createLucideIcon-ConhwJnb.js → createLucideIcon-D8pyde1A.js} +1 -1
- package/dist/web/assets/{crystal-D8U1Qnqh.js → crystal-BSheONSg.js} +1 -1
- package/dist/web/assets/{csv-preview-Coc2oed1.js → csv-preview-BZMHfFH3.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-B8WQkZDB.js → data-grid-overlay-editor-gwPoVSCU.js} +1 -1
- package/dist/web/assets/{data-grid-types-KpjaKAci.js → data-grid-types-Dq_H1IPa.js} +1 -1
- package/dist/web/assets/{database-BqyeGogo.js → database-Y3ESDK4-.js} +1 -1
- package/dist/web/assets/database-viewer-GVaQVMdb.js +1 -0
- package/dist/web/assets/{dialog-DrL3WEXd.js → dialog-BSfuiTkJ.js} +1 -1
- package/dist/web/assets/{diff-viewer-CT0lmDc9.js → diff-viewer-BG5_fH1E.js} +1 -1
- package/dist/web/assets/{dist-C2_dKAa_.js → dist-aDSxTR_v.js} +1 -1
- package/dist/web/assets/{docx-preview-76KDw1eE.js → docx-preview-DqVDOMmv.js} +1 -1
- package/dist/web/assets/{download-DAF7SXcf.js → download-DG3_9xXy.js} +1 -1
- package/dist/web/assets/{edge-Db4ze6bw.js → edge-DTtoslQu.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-DyC_-4My.js → edit-diff-preview-CXSbSUBX.js} +1 -1
- package/dist/web/assets/{elixir-RZPZ0472.js → elixir-CIBCZ3Zi.js} +1 -1
- package/dist/web/assets/{elm-DNZcQt_7.js → elm-BLmb7K1H.js} +1 -1
- package/dist/web/assets/{erb-DRONsZXs.js → erb-BBY8ApRd.js} +1 -1
- package/dist/web/assets/{erlang-DeG0-QwJ.js → erlang-D5Zb5_P0.js} +1 -1
- package/dist/web/assets/{esm-CZy-sSoK.js → esm-6F8v1jLL.js} +1 -1
- package/dist/web/assets/explorer-body-BPFnjxYj.js +2 -0
- package/dist/web/assets/explorer-window-content-Da1T9uM4.js +1 -0
- package/dist/web/assets/{extension-store-ClQpnxQr.js → extension-store-Bhu--fPX.js} +1 -1
- package/dist/web/assets/{extension-webview-BW5GuBnH.js → extension-webview-BSh6KGd7.js} +1 -1
- package/dist/web/assets/{external-link-ojDHcQxQ.js → external-link-B6Hl0U4X.js} +1 -1
- package/dist/web/assets/{eye-off-Rq1n2-oe.js → eye-off-CUdzgmX8.js} +1 -1
- package/dist/web/assets/{file-exclamation-point-Bpg-zDPP.js → file-exclamation-point-JzhTU2TX.js} +1 -1
- package/dist/web/assets/{file-store-28e44KP3.js → file-store-6HW2Lfl4.js} +1 -1
- package/dist/web/assets/{folder-symlink-LD-mr54V.js → folder-symlink-Dx1hOk8X.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CK5VRJNd.js → fortran-fixed-form-CBdwkEUJ.js} +1 -1
- package/dist/web/assets/{fsharp-TVVgzHWp.js → fsharp-AT4XdVYH.js} +1 -1
- package/dist/web/assets/{gdresource-DiUcyewI.js → gdresource-D852fWBR.js} +1 -1
- package/dist/web/assets/{git-commit-CfOruFrg.js → git-commit-DWfWAyxy.js} +1 -1
- package/dist/web/assets/{git-log-panel-D1-YXZ0O.js → git-log-panel-DNC9fV0M.js} +1 -1
- package/dist/web/assets/{git-rebase-Bxyr24oc.js → git-rebase-C6opk8gr.js} +1 -1
- package/dist/web/assets/glide-data-grid-CICFjJon.js +141 -0
- package/dist/web/assets/{glimmer-js-CT5aJxz_.js → glimmer-js-DLi5mWYO.js} +1 -1
- package/dist/web/assets/{glimmer-ts-CRjP9Yey.js → glimmer-ts-D-scFX30.js} +1 -1
- package/dist/web/assets/{glsl-BX2--oI_.js → glsl-dbQzF4iA.js} +1 -1
- package/dist/web/assets/{graphql-BIQsvBxQ.js → graphql-D1m5Wen2.js} +1 -1
- package/dist/web/assets/group-chat-tab-DbUkcOaJ.js +1 -0
- package/dist/web/assets/{hack-CTWk0k1m.js → hack-CqYmpO7u.js} +1 -1
- package/dist/web/assets/{haml-DGh27hXc.js → haml-BXzKWEag.js} +1 -1
- package/dist/web/assets/{hand-DKSxDa5c.js → hand-x0ejq3fS.js} +1 -1
- package/dist/web/assets/{handlebars-BJL1jNrG.js → handlebars-DXnrtHtq.js} +1 -1
- package/dist/web/assets/{html-DALyDNqU.js → html-DhgQJ-xL.js} +1 -1
- package/dist/web/assets/{html-derivative-B4XcU57e.js → html-derivative-xZRft-YE.js} +1 -1
- package/dist/web/assets/{http-DfMo7nNu.js → http-B53mSj14.js} +1 -1
- package/dist/web/assets/{hurl-CGc_n2Qf.js → hurl-CsomVy04.js} +1 -1
- package/dist/web/assets/{hxml-40zZ1t4x.js → hxml-LffjWwKA.js} +1 -1
- package/dist/web/assets/{image-preview-Cp3XB8UO.js → image-preview-CNA6XMNS.js} +1 -1
- package/dist/web/assets/index-DTqbMiVc.js +52 -0
- package/dist/web/assets/{input-DTmI80l2.js → input-D0ol5jW0.js} +1 -1
- package/dist/web/assets/{jinja-D5zuIn9u.js → jinja-CCHURl7V.js} +1 -1
- package/dist/web/assets/{jison-Dxut9QwA.js → jison-7qBd8dnR.js} +1 -1
- package/dist/web/assets/{julia-Cu85tTPo.js → julia-BY9pnTcy.js} +1 -1
- package/dist/web/assets/{just-CctM4uLa.js → just-D7wRSNRr.js} +1 -1
- package/dist/web/assets/{keybindings-store-Bi0q9x4Q.js → keybindings-store-BGkB72gM.js} +1 -1
- package/dist/web/assets/keybindings-store-Bq6VWl5X.js +1 -0
- package/dist/web/assets/{keyboard-AdwmYDSu.js → keyboard-DBBaoQSu.js} +1 -1
- package/dist/web/assets/{latex-kpwS_CC7.js → latex-RgsCae55.js} +1 -1
- package/dist/web/assets/{lib-Ca7ai8xa.js → lib-v9x_qXgI.js} +1 -1
- package/dist/web/assets/{liquid-B8Rr0xue.js → liquid-CnDFzvOZ.js} +1 -1
- package/dist/web/assets/loader-circle-DouKgIXy.js +1 -0
- package/dist/web/assets/{lock-BEtt_eTO.js → lock-CNxUGpTk.js} +1 -1
- package/dist/web/assets/{lua-C_3gixwM.js → lua-0lj5W8sm.js} +1 -1
- package/dist/web/assets/markdown-renderer-DEAVpo0C.js +1 -0
- package/dist/web/assets/markdown-renderer-dbLIvU2M.js +3 -0
- package/dist/web/assets/{marko-B28rvNL-.js → marko-CQgqAhbV.js} +1 -1
- package/dist/web/assets/{mdc-DQ6-STFO.js → mdc-BkIcTiIy.js} +1 -1
- package/dist/web/assets/{media-url-BjvbfTTl.js → media-url-DDciwvLU.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-DXn5g2LB.js → mobile-bottom-sheet-pjcHKorA.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CD_kl-jA.js +1 -0
- package/dist/web/assets/{monitor-CXI_XpWJ.js → monitor-CxPV_fQI.js} +1 -1
- package/dist/web/assets/{monitor-smartphone-C1wUPOSg.js → monitor-smartphone-DLZ6Q05s.js} +1 -1
- package/dist/web/assets/{nextflow-B1xq2O8L.js → nextflow-DvIW68DS.js} +1 -1
- package/dist/web/assets/{nginx-xsLxRGxO.js → nginx-O-hft0gG.js} +1 -1
- package/dist/web/assets/{nim-wqcek3WO.js → nim-BxrR4HwQ.js} +1 -1
- package/dist/web/assets/notification-store-Dgbsw9p9.js +1 -0
- package/dist/web/assets/{number-overlay-editor-1c4sgZJt.js → number-overlay-editor-BoLRYuZT.js} +1 -1
- package/dist/web/assets/{overview-panel-Cr2xsHc6.js → overview-panel-l3g1gT5e.js} +1 -1
- package/dist/web/assets/{panel-store-Dv28vz3L.js → panel-store-UOV8wrfR.js} +1 -1
- package/dist/web/assets/{pdf-preview-DKeIIJTv.js → pdf-preview-B2CJ_EKv.js} +1 -1
- package/dist/web/assets/{pencil-CHKLOgLs.js → pencil-BlIIg6RH.js} +1 -1
- package/dist/web/assets/{perl-BH54gN9q.js → perl-Da-uNqmL.js} +1 -1
- package/dist/web/assets/{php-Br5ZrEZm.js → php-JMu45mEx.js} +1 -1
- package/dist/web/assets/play-DpEEBJTT.js +1 -0
- package/dist/web/assets/{plus-BxqD2FIZ.js → plus-BR0qewJI.js} +1 -1
- package/dist/web/assets/portal-container-context-D-495m5C.js +1 -0
- package/dist/web/assets/{postgres-viewer-BJPr_FYL.js → postgres-viewer-DstZwUai.js} +3 -3
- package/dist/web/assets/{process-table-BqTcAJ-h.js → process-table-kmQ2-8qJ.js} +1 -1
- package/dist/web/assets/{project-store-KHjFTCxf.js → project-store-DTUCS65m.js} +1 -1
- package/dist/web/assets/{pug-8VCubZ3H.js → pug-CLzyQhia.js} +1 -1
- package/dist/web/assets/{qml-D8AF1RZ6.js → qml-BHXfGB1l.js} +1 -1
- package/dist/web/assets/{razor-6v042IJW.js → razor-BqetJLN3.js} +1 -1
- package/dist/web/assets/{react-DHs6efqI.js → react-B549sK_T.js} +1 -1
- package/dist/web/assets/{refresh-cw-BALzw7L3.js → refresh-cw-BQU5g2CY.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-KKTRNjOL.js +2 -0
- package/dist/web/assets/{remote-desktop-mobile-view-DaoB49k4.js → remote-desktop-mobile-view-C1ST4-Fm.js} +1 -1
- package/dist/web/assets/{remote-desktop-readiness-gate--LJgsuoj.js → remote-desktop-readiness-gate-BH3BE5wA.js} +1 -1
- package/dist/web/assets/{remote-desktop-window-content-Cihx4PNZ.js → remote-desktop-window-content-BaZJQt_N.js} +1 -1
- package/dist/web/assets/{rotate-ccw-CuvNBM80.js → rotate-ccw-CpjRo90J.js} +1 -1
- package/dist/web/assets/{rst-mTGFo4AQ.js → rst-iQdJZ0BP.js} +1 -1
- package/dist/web/assets/{ruby-BBaOP636.js → ruby-CNwzZGdH.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Zl5iIx-B.js → run-in-terminal-CBXmVGWa.js} +1 -1
- package/dist/web/assets/{sas-D5QEkIH2.js → sas-BpBIT2eR.js} +1 -1
- package/dist/web/assets/{save-Dxq-Zepc.js → save-DYLiVzU8.js} +1 -1
- package/dist/web/assets/{scroll-area-B5yhPpZo.js → scroll-area-lc0ZHORr.js} +1 -1
- package/dist/web/assets/{scss-C5Mu6y2u.js → scss-D1MotuEp.js} +1 -1
- package/dist/web/assets/{send-Cits8q0l.js → send-C_sLFt56.js} +1 -1
- package/dist/web/assets/{send-to-chat-Cv7ikFUC.js → send-to-chat-DJAdlvJ8.js} +1 -1
- package/dist/web/assets/{settings-store-Dp4i8zeA.js → settings-store-zbPkG7o-.js} +2 -2
- package/dist/web/assets/settings-tab-Cn-AqUP_.js +1 -0
- package/dist/web/assets/{settings-tab-BtMvvKAF.js → settings-tab-xyRpt2gq.js} +1 -1
- package/dist/web/assets/{shaderlab-mLnpMpN3.js → shaderlab-BNqmcPQi.js} +1 -1
- package/dist/web/assets/{shallow-BBJ_qoEF.js → shallow-CmPMAPte.js} +1 -1
- package/dist/web/assets/{shellsession-qdcDfUuk.js → shellsession-BBLOVOO-.js} +1 -1
- package/dist/web/assets/{shield-check-CH7F1P7m.js → shield-check-CUI36aSc.js} +1 -1
- package/dist/web/assets/{shield-off-C9HiF0-6.js → shield-off-GdkkVhb3.js} +1 -1
- package/dist/web/assets/{soy-CGGf1G43.js → soy-BpTGo9uy.js} +1 -1
- package/dist/web/assets/{sparkles-BnuLH6Ck.js → sparkles-Dh-_J2Rd.js} +1 -1
- package/dist/web/assets/{sparql-tQHB50VD.js → sparql-D7A3yKob.js} +1 -1
- package/dist/web/assets/{sql-query-editor-DZqnYIB7.js → sql-query-editor-B86NiZHI.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DXutos0e.js → sqlite-viewer-DB775F6C.js} +1 -1
- package/dist/web/assets/{stata-DVQNLMcR.js → stata-B7N56ptS.js} +1 -1
- package/dist/web/assets/{surrealql-lu576tBi.js → surrealql-CTmnD-lv.js} +1 -1
- package/dist/web/assets/{svelte-CSCrOBjT.js → svelte-BppwY9TR.js} +1 -1
- package/dist/web/assets/{switch-BPol3BFC.js → switch-BmjKoIpR.js} +1 -1
- package/dist/web/assets/{system-monitor-body-Ct8_GX_f.js → system-monitor-body-BDxHQvy9.js} +2 -2
- package/dist/web/assets/system-monitor-tab-BoTHLx6B.js +1 -0
- package/dist/web/assets/system-monitor-window-content-B9Y98UW6.js +1 -0
- package/dist/web/assets/{tab-host-window-content-C5kYP0-z.js → tab-host-window-content-C1j8zr68.js} +1 -1
- package/dist/web/assets/{tab-store-DS3YlUwR.js → tab-store-DzYSsw5a.js} +1 -1
- package/dist/web/assets/{table-BXDyImqk.js → table-D5Q4ap4W.js} +1 -1
- package/dist/web/assets/team-member-sheet-Cxz6h5Ne.js +2 -0
- package/dist/web/assets/{team-member-window-content-BJ_XFw8-.js → team-member-window-content-CUSuRHCB.js} +1 -1
- package/dist/web/assets/{templ-CPt8AS4Q.js → templ-CvTMKKra.js} +1 -1
- package/dist/web/assets/{terminal-tab-8bKQ3CWR.js → terminal-tab-B9I_HlES.js} +3 -3
- package/dist/web/assets/{tex-BrTByT7i.js → tex--c4POEUE.js} +1 -1
- package/dist/web/assets/{text-wrap-CzMjB6GD.js → text-wrap-DBvQD3kc.js} +1 -1
- package/dist/web/assets/{tool-cards-pIu_cmEL.js → tool-cards-D3KSzd2K.js} +4 -4
- package/dist/web/assets/{trash-2-BjShZ7if.js → trash-2-By4VMbC4.js} +1 -1
- package/dist/web/assets/{triangle-alert-qQ6Plydp.js → triangle-alert-CUTURFbG.js} +1 -1
- package/dist/web/assets/{ts-tags-DSqEZ7A8.js → ts-tags--kx7D-Hw.js} +1 -1
- package/dist/web/assets/{twig-DW9qc1bf.js → twig-UG9uMHGd.js} +1 -1
- package/dist/web/assets/{upload-7Zs6oaK5.js → upload-BNlX8D07.js} +1 -1
- package/dist/web/assets/{use-blob-url-DKbV7PM_.js → use-blob-url-Bdxopwwc.js} +1 -1
- package/dist/web/assets/{use-coarse-long-press-DGyC48NF.js → use-coarse-long-press-BmI_GVW5.js} +1 -1
- package/dist/web/assets/{use-is-mobile-ClE-UcPF.js → use-is-mobile-B9UZNwSm.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DaxAk3gB.js → use-monaco-theme-CVCye2kq.js} +1 -1
- package/dist/web/assets/use-open-team-member-D9EOB8-i.js +1 -0
- package/dist/web/assets/{use-remote-desktop-display-choice-BUA_1eaH.js → use-remote-desktop-display-choice-neUqyE3p.js} +1 -1
- package/dist/web/assets/{use-visual-viewport-CCX21c9w.js → use-visual-viewport-Sfb6CZ80.js} +1 -1
- package/dist/web/assets/{use-websocket-CWpG7PWN.js → use-websocket-xOOwBdwP.js} +1 -1
- package/dist/web/assets/vendor-markdown-Ls5AcU91.js +291 -0
- package/dist/web/assets/{vendor-ui-CXJtBULr.js → vendor-ui-CaeeFO0B.js} +1 -1
- package/dist/web/assets/video-preview-DvpgBDhM.js +1 -0
- package/dist/web/assets/{vue-html-CG5BKtq1.js → vue-html-8KEW6-I9.js} +1 -1
- package/dist/web/assets/{vue-CJNDL1g-.js → vue-jjTomlbt.js} +1 -1
- package/dist/web/assets/{vue-vine-Dd5HZ7T5.js → vue-vine-C5tohCkU.js} +1 -1
- package/dist/web/assets/{window-pip-registry-9H6Y-HqR.js → window-pip-registry-FdZdGF7h.js} +1 -1
- package/dist/web/assets/{window-store-CNzkeUyO.js → window-store-D0x_fxr0.js} +1 -1
- package/dist/web/assets/x-DiOUR_mp.js +1 -0
- package/dist/web/assets/{xml-DTOm5niz.js → xml-ByFFosqL.js} +1 -1
- package/dist/web/assets/{xsl-BD_kBC04.js → xsl-CZUwiLkj.js} +1 -1
- package/dist/web/index.html +59 -59
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/scripts/check-ppm-dir-usage.sh +0 -0
- package/scripts/install.sh +0 -0
- package/scripts/release.sh +0 -0
- package/src/providers/claude-agent-sdk.ts +51 -43
- package/src/providers/codex-app-server/codex-provider.ts +7 -1
- package/src/server/routes/chat.ts +28 -3
- package/src/server/ws/chat.ts +9 -4
- package/src/server/ws/terminal.ts +9 -3
- package/src/services/media-transcode/ffmpeg-capabilities.ts +48 -7
- package/src/services/named-tunnel/named-tunnel-retry.ts +26 -0
- package/src/services/named-tunnel/network-ready.ts +63 -0
- package/src/services/supervisor.ts +98 -0
- package/src/services/transcript-images.ts +14 -7
- package/src/services/upgrade.service.ts +7 -2
- package/src/shared/image-limits.ts +37 -0
- package/src/types/api.ts +3 -1
- package/src/types/chat.ts +6 -0
- package/src/types/extension-messages.ts +2 -0
- package/src/web/components/chat/attachment-chips.tsx +10 -0
- package/src/web/components/chat/chat-tab.tsx +37 -13
- package/src/web/components/chat/message-input.tsx +102 -11
- package/src/web/components/chat/session-debug-dialog.tsx +2 -1
- package/src/web/components/chat/user-message-parse.ts +33 -11
- package/src/web/components/database/database-viewer.tsx +34 -11
- package/src/web/components/database/extract-query-table.ts +24 -0
- package/src/web/components/database/glide-data-grid.tsx +11 -4
- package/src/web/components/database/glide-grid-types.ts +4 -0
- package/src/web/components/database/split-sql-statements.ts +116 -0
- package/src/web/components/database/sql-completion-provider.ts +1 -26
- package/src/web/components/database/sql-query-editor.tsx +2 -1
- package/src/web/components/database/use-database.ts +16 -1
- package/src/web/components/database/use-glide-cell-content.ts +11 -6
- package/src/web/components/editor/code-editor.tsx +30 -56
- package/src/web/hooks/use-chat.ts +6 -3
- package/src/web/lib/attachment-marker-split.ts +44 -0
- package/src/web/lib/image-animation.ts +51 -0
- package/src/web/lib/image-resize-limits.ts +47 -0
- package/src/web/lib/image-resize.ts +133 -0
- package/dist/web/assets/api-client-DLWwuUkJ.js +0 -1
- package/dist/web/assets/api-settings-BFuaZrNC.js +0 -1
- package/dist/web/assets/chat-tab-bSOVoC5N.js +0 -15
- package/dist/web/assets/chevron-right-D7Pn5agw.js +0 -1
- package/dist/web/assets/circle-4g2coECH.js +0 -1
- package/dist/web/assets/clock-DbiNiwCG.js +0 -1
- package/dist/web/assets/code-DdKXqtf_.js +0 -1
- package/dist/web/assets/code-editor-COTa37oe.js +0 -10
- package/dist/web/assets/database-viewer-Dta7lSSk.js +0 -1
- package/dist/web/assets/explorer-body-BGjR2Gbt.js +0 -2
- package/dist/web/assets/explorer-window-content-DAhHh5EQ.js +0 -1
- package/dist/web/assets/glide-data-grid-BSsqxe2v.js +0 -138
- package/dist/web/assets/group-chat-tab-CMtzoaFc.js +0 -1
- package/dist/web/assets/index-BkHlMfPD.js +0 -52
- package/dist/web/assets/keybindings-store-_KfovSz7.js +0 -1
- package/dist/web/assets/loader-circle-DD65e9-7.js +0 -1
- package/dist/web/assets/markdown-renderer-DroTmKbJ.js +0 -1
- package/dist/web/assets/markdown-renderer-ntDi3Tx-.js +0 -3
- package/dist/web/assets/mobile-explorer-sheet-BP6iU4ma.js +0 -1
- package/dist/web/assets/notification-store-II5Kw9rD.js +0 -1
- package/dist/web/assets/play-CPbVzGgq.js +0 -1
- package/dist/web/assets/portal-container-context-BmG4--I-.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-CYZUjYTO.js +0 -2
- package/dist/web/assets/settings-tab-UPPycGRF.js +0 -1
- package/dist/web/assets/system-monitor-tab-DC_EerdI.js +0 -1
- package/dist/web/assets/system-monitor-window-content-D25DMNpw.js +0 -1
- package/dist/web/assets/team-member-sheet-DvmioeQL.js +0 -2
- package/dist/web/assets/use-open-team-member-CopUuDHG.js +0 -1
- package/dist/web/assets/vendor-markdown-CKTlmXbz.js +0 -291
- package/dist/web/assets/video-preview-C2_fdunE.js +0 -1
- package/dist/web/assets/x-Dzu9ecti.js +0 -1
- /package/dist/web/assets/{abap-Cjj2olMr.js → abap-C5DFF0s3.js} +0 -0
- /package/dist/web/assets/{actionscript-3-DCkW7kWC.js → actionscript-3-B0qyRNbc.js} +0 -0
- /package/dist/web/assets/{ada-AEWvZXuX.js → ada-bAJzplug.js} +0 -0
- /package/dist/web/assets/{andromeeda-D7cSpME6.js → andromeeda-DUCpISy6.js} +0 -0
- /package/dist/web/assets/{apache-DjvX0RSe.js → apache-BhAr84Z_.js} +0 -0
- /package/dist/web/assets/{apex-70xIZ0pH.js → apex-CRv8YnNq.js} +0 -0
- /package/dist/web/assets/{applescript-C3qKqxmA.js → applescript-CD-Cw8bt.js} +0 -0
- /package/dist/web/assets/{ara-iGt2zixk.js → ara-BUzeQYoG.js} +0 -0
- /package/dist/web/assets/{asciidoc-Bs2t-06W.js → asciidoc-CT2hTg-v.js} +0 -0
- /package/dist/web/assets/{asm-B-T0eqhE.js → asm-CAu35JeY.js} +0 -0
- /package/dist/web/assets/{aurora-x-D3h_9M3E.js → aurora-x-BeFJoIA1.js} +0 -0
- /package/dist/web/assets/{awk-CZwfwpPi.js → awk-Vau-DrGN.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DulkglEA.js → ayu-dark-CLe-OTsQ.js} +0 -0
- /package/dist/web/assets/{ayu-light-DyNQiRpn.js → ayu-light-CJbQfu4M.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-CcEt4xkI.js → ayu-mirage-GWaKwdrx.js} +0 -0
- /package/dist/web/assets/{ballerina-OhQPjyHS.js → ballerina-DWu0USI5.js} +0 -0
- /package/dist/web/assets/{bat-DqKcJTUn.js → bat-_07Huw7E.js} +0 -0
- /package/dist/web/assets/{beancount-BdXlMi8T.js → beancount-EcvogrcF.js} +0 -0
- /package/dist/web/assets/{berry-DvgY6BkY.js → berry-BBB87SY7.js} +0 -0
- /package/dist/web/assets/{bibtex-BSRJq0yM.js → bibtex-DbueS87A.js} +0 -0
- /package/dist/web/assets/{bicep-DpAdumKt.js → bicep-tcmXnUo4.js} +0 -0
- /package/dist/web/assets/{bird2-rlbtp8di.js → bird2-B2qwilIR.js} +0 -0
- /package/dist/web/assets/{c-AjOLBXIs.js → c-Z58oivSt.js} +0 -0
- /package/dist/web/assets/{c3-DqIXz4Ia.js → c3-U40cGT9p.js} +0 -0
- /package/dist/web/assets/{cadence-DAJtUpOE.js → cadence-BFcD-LyM.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DUPoeZfb.js → catppuccin-frappe-RkC9AX6J.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CK3U68Ql.js → catppuccin-latte-Bsi4l-nO.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-BzteLnIX.js → catppuccin-macchiato-B8JIdTW-.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-ozLd0aBR.js → catppuccin-mocha-oH8wwrDj.js} +0 -0
- /package/dist/web/assets/{clarity-6KqEoBfz.js → clarity-C3WNeRCR.js} +0 -0
- /package/dist/web/assets/{clojure-B_uBL5C7.js → clojure-CFoAz7u9.js} +0 -0
- /package/dist/web/assets/{cmake-B3nxBNmj.js → cmake-Bcsqp9yR.js} +0 -0
- /package/dist/web/assets/{codeowners-DkS-51at.js → codeowners-DC2Z7heP.js} +0 -0
- /package/dist/web/assets/{codeql-rpr1z_mR.js → codeql-BfG68Al5.js} +0 -0
- /package/dist/web/assets/{common-lisp-BfNbv_W6.js → common-lisp-Z5P-lKQe.js} +0 -0
- /package/dist/web/assets/{coq-EAW-1nw0.js → coq-BJslqrnv.js} +0 -0
- /package/dist/web/assets/{csharp-BE3Gn5bq.js → csharp-DhhZTOTs.js} +0 -0
- /package/dist/web/assets/{css-Cr27CfPY.js → css-DiFIpMfE.js} +0 -0
- /package/dist/web/assets/{csv-B5VtaPtH.js → csv-DViLbHxI.js} +0 -0
- /package/dist/web/assets/{cue-jm6lYNrx.js → cue-CoG3mg2I.js} +0 -0
- /package/dist/web/assets/{cypher-DXkvK-pn.js → cypher-jTxC8NAB.js} +0 -0
- /package/dist/web/assets/{d-CgG1_yzd.js → d-6hKIUAbU.js} +0 -0
- /package/dist/web/assets/{dark-plus-CALzcIVB.js → dark-plus-D_-np2Sr.js} +0 -0
- /package/dist/web/assets/{dart-BEVoLlWg.js → dart-CVpxLpVn.js} +0 -0
- /package/dist/web/assets/{dax-H0BW3Q_P.js → dax-BPqJ8qIY.js} +0 -0
- /package/dist/web/assets/{desktop-BtYq1l_0.js → desktop-u_y5_Hr5.js} +0 -0
- /package/dist/web/assets/{diff-CnBYdfTg.js → diff-Duj1XMrd.js} +0 -0
- /package/dist/web/assets/{dist-CJJKpgR7.js → dist-CEZ4plgH.js} +0 -0
- /package/dist/web/assets/{docker-COBitpld.js → docker-8UGSCc7N.js} +0 -0
- /package/dist/web/assets/{dotenv-CG0x1m7c.js → dotenv-DVnj8kyR.js} +0 -0
- /package/dist/web/assets/{dracula-CLCi47X-.js → dracula-D0cEmSg7.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BMv5cCAV.js → dracula-soft-DfGS5Yfr.js} +0 -0
- /package/dist/web/assets/{dream-maker-1Bd4OSww.js → dream-maker-qG8kMGTF.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-B-2zKLn-.js → emacs-lisp-iVqyMLyT.js} +0 -0
- /package/dist/web/assets/{everforest-dark-BGVynCbV.js → everforest-dark-Cruygd5U.js} +0 -0
- /package/dist/web/assets/{everforest-light--rn3R7Cm.js → everforest-light-BiJa8RHL.js} +0 -0
- /package/dist/web/assets/{fennel-SWg9Us_u.js → fennel-H6XSEIoz.js} +0 -0
- /package/dist/web/assets/{fish-B04oqgKk.js → fish-ho_7CxX6.js} +0 -0
- /package/dist/web/assets/{fluent-BefzAGMN.js → fluent-1l5ssJTF.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-alw7jMxb.js → fortran-free-form-Bo1x233A.js} +0 -0
- /package/dist/web/assets/{gdscript-BAyhvjCx.js → gdscript-DffFMp_k.js} +0 -0
- /package/dist/web/assets/{gdshader-Crl09X-B.js → gdshader-CvnpAiAF.js} +0 -0
- /package/dist/web/assets/{genie-BSIGRHu7.js → genie-2eOvAEzW.js} +0 -0
- /package/dist/web/assets/{gherkin-DJhSnSWH.js → gherkin-DL-5OUlV.js} +0 -0
- /package/dist/web/assets/{github-dark-r8C6qwug.js → github-dark-DrTZprgG.js} +0 -0
- /package/dist/web/assets/{github-dark-default-FVtXjcm8.js → github-dark-default-_mDH8IMP.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CzrUPgSb.js → github-dark-dimmed-C48hJuLn.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-1s9rF5EB.js → github-dark-high-contrast-BQ-zGGNe.js} +0 -0
- /package/dist/web/assets/{github-light-BvIWCkl7.js → github-light-BYc90Yx8.js} +0 -0
- /package/dist/web/assets/{github-light-default-CVdhzrxV.js → github-light-default-Daecj4J5.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-CBAf6hjF.js → github-light-high-contrast-DoafCbsU.js} +0 -0
- /package/dist/web/assets/{gleam-DvL1q7aa.js → gleam-hUWWjmnp.js} +0 -0
- /package/dist/web/assets/{gn-BG5NvwAf.js → gn-7PJ3R70o.js} +0 -0
- /package/dist/web/assets/{gnuplot-BARnM-zG.js → gnuplot-V-Pxck7b.js} +0 -0
- /package/dist/web/assets/{go-DXvjjq4q.js → go-ARF6DprD.js} +0 -0
- /package/dist/web/assets/{groovy-CRylSKhr.js → groovy-CKGBIPOz.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-BVPjnqm7.js → gruvbox-dark-hard-Cm9wqdI3.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-U_PDgq0a.js → gruvbox-dark-medium-Cmsd6dG8.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-CkzNtpbr.js → gruvbox-dark-soft-BBGlGGCz.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-DfMmR2G8.js → gruvbox-light-hard-BCR7KmqG.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-BGCak-dd.js → gruvbox-light-medium-CP32bga8.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DnpM3fkN.js → gruvbox-light-soft-qjxk8ps6.js} +0 -0
- /package/dist/web/assets/{haskell-DXgGIDD_.js → haskell-C5DzwGRa.js} +0 -0
- /package/dist/web/assets/{haxe-B7qlTpRz.js → haxe-C8f_jCVv.js} +0 -0
- /package/dist/web/assets/{hcl-ADWfVe_Q.js → hcl-DetJkf6E.js} +0 -0
- /package/dist/web/assets/{hjson-BaM9gLbq.js → hjson-DeZhwIdo.js} +0 -0
- /package/dist/web/assets/{hlsl-iqaRA4MG.js → hlsl-SMIDLRyX.js} +0 -0
- /package/dist/web/assets/{horizon-BPpTnhB9.js → horizon-BMAVDxMx.js} +0 -0
- /package/dist/web/assets/{horizon-bright-yBLg3QbE.js → horizon-bright-3BaV_VGD.js} +0 -0
- /package/dist/web/assets/{houston-eFOp0ySL.js → houston-BWBbEPvb.js} +0 -0
- /package/dist/web/assets/{hy-BALeMn3i.js → hy-DcsmE-Nm.js} +0 -0
- /package/dist/web/assets/{imba-CdGUm_GD.js → imba-CnkgnYMf.js} +0 -0
- /package/dist/web/assets/{ini-DG7imsqe.js → ini-BnvTdau7.js} +0 -0
- /package/dist/web/assets/{java-D5GTT474.js → java-BLFACXat.js} +0 -0
- /package/dist/web/assets/{javascript-V7WwJvRm.js → javascript-BKcFWENk.js} +0 -0
- /package/dist/web/assets/{json-B9FhwAgR.js → json-3AJtJADf.js} +0 -0
- /package/dist/web/assets/{json5--Abk7MUy.js → json5-DLyDVlwe.js} +0 -0
- /package/dist/web/assets/{jsonc-C8jb-Z6x.js → jsonc-B5DgF1jW.js} +0 -0
- /package/dist/web/assets/{jsonl-CaJNxfkm.js → jsonl-CV_O71a2.js} +0 -0
- /package/dist/web/assets/{jsonnet-CPrKMSCU.js → jsonnet-DZfPLgYz.js} +0 -0
- /package/dist/web/assets/{jssm-DEu-9_uo.js → jssm-Cr1zYOsY.js} +0 -0
- /package/dist/web/assets/{jsx-MfAsCWDW.js → jsx-VN-JS2_q.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-DnbcAduF.js → kanagawa-dragon-D9CcT3xg.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-yxsY3ew8.js → kanagawa-lotus-DLXsB8fr.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-C2RyvZaM.js → kanagawa-wave-weDs3Wjz.js} +0 -0
- /package/dist/web/assets/{kdl-CvssmvcU.js → kdl-0XF56jnT.js} +0 -0
- /package/dist/web/assets/{kotlin-jufO49mU.js → kotlin-BPkH9vzk.js} +0 -0
- /package/dist/web/assets/{kusto-BAFnylR7.js → kusto-DMVpADb9.js} +0 -0
- /package/dist/web/assets/{laserwave-BgxFC4Ge.js → laserwave-Cl6T2cGP.js} +0 -0
- /package/dist/web/assets/{lean-Bgt24V1D.js → lean-BvwzQGl1.js} +0 -0
- /package/dist/web/assets/{less-Bk_UaqsM.js → less-DI_03xkC.js} +0 -0
- /package/dist/web/assets/{light-plus-nMxFfInN.js → light-plus-hIiIsQN_.js} +0 -0
- /package/dist/web/assets/{llvm-3_PASDOO.js → llvm-B5hjjSWc.js} +0 -0
- /package/dist/web/assets/{log-CKm0CxB1.js → log-D28raQiE.js} +0 -0
- /package/dist/web/assets/{logo-DFP1_5U6.js → logo-6Cdd4ASb.js} +0 -0
- /package/dist/web/assets/{luau-BcZnoyh3.js → luau-CK9nKpOX.js} +0 -0
- /package/dist/web/assets/{make-CpNSwgwT.js → make-CVnxKCB6.js} +0 -0
- /package/dist/web/assets/{markdown-DhU3Djpv.js → markdown-B9mMQzD5.js} +0 -0
- /package/dist/web/assets/{material-theme-DD5L3OqF.js → material-theme-BZSs2o0j.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-CG2_tD3R.js → material-theme-darker-BeAQBzQ_.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-CzNkUc9Y.js → material-theme-lighter-BAe0IXcs.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-BtLiAaF_.js → material-theme-ocean-CiPIf0Np.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-Brvjz035.js → material-theme-palenight-DJBb4_XN.js} +0 -0
- /package/dist/web/assets/{matlab-CCY0KtMD.js → matlab-CY0dbp_s.js} +0 -0
- /package/dist/web/assets/{mdx-CH6YY0aS.js → mdx-DU9zbF7h.js} +0 -0
- /package/dist/web/assets/{mermaid-Bqh3gFI0.js → mermaid-UmmjGwdh.js} +0 -0
- /package/dist/web/assets/{min-dark-BUwfTzI6.js → min-dark-C59GkzOm.js} +0 -0
- /package/dist/web/assets/{min-light-oLanXCOk.js → min-light-B9QglrdQ.js} +0 -0
- /package/dist/web/assets/{mipsasm-CTh3hPWK.js → mipsasm-Dc6Jsr_D.js} +0 -0
- /package/dist/web/assets/{mojo-DXdszZKa.js → mojo-D5ij-nGW.js} +0 -0
- /package/dist/web/assets/{monokai-TNE_tRxc.js → monokai-QAtE_yCP.js} +0 -0
- /package/dist/web/assets/{moonbit-jrk-Gx1X.js → moonbit-BMtIdiTL.js} +0 -0
- /package/dist/web/assets/{move-DItn61bH.js → move-BB_jMHYZ.js} +0 -0
- /package/dist/web/assets/{narrat-3kB4dkh0.js → narrat-BvvoKGVn.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CSKtwETr.js → nextflow-groovy-CqxtAsnF.js} +0 -0
- /package/dist/web/assets/{night-owl-Dm91nSIq.js → night-owl-CLoE9kjt.js} +0 -0
- /package/dist/web/assets/{night-owl-light-riwrcPRg.js → night-owl-light-BwIFT5Xd.js} +0 -0
- /package/dist/web/assets/{nix-sLgl9zGF.js → nix-BKlnVT9r.js} +0 -0
- /package/dist/web/assets/{nord-TWkJPI6M.js → nord-B7R1KRAK.js} +0 -0
- /package/dist/web/assets/{nushell-CDZi4-Gf.js → nushell-CzUme-NJ.js} +0 -0
- /package/dist/web/assets/{objective-c-DW9PboNp.js → objective-c-D-UKKiOc.js} +0 -0
- /package/dist/web/assets/{objective-cpp-2XCCX6q1.js → objective-cpp-BucWn4Fx.js} +0 -0
- /package/dist/web/assets/{ocaml-CxPdnjrh.js → ocaml-EJCNXMGo.js} +0 -0
- /package/dist/web/assets/{odin-BJjlKpkM.js → odin-CEjnRU_3.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-K1KOmBZ7.js → one-dark-pro-Bv469NX5.js} +0 -0
- /package/dist/web/assets/{one-light-DQB-vUyZ.js → one-light-BlzO5RWI.js} +0 -0
- /package/dist/web/assets/{openscad-DCoLwn4j.js → openscad-Czl6alLw.js} +0 -0
- /package/dist/web/assets/{pascal-z2ktbbWp.js → pascal-BI9fLvG4.js} +0 -0
- /package/dist/web/assets/{pkl-Dyq4-v6z.js → pkl-ChQLsyjU.js} +0 -0
- /package/dist/web/assets/{plastic-C4Rq5PnD.js → plastic-DGL3mtdL.js} +0 -0
- /package/dist/web/assets/{plsql-BCIjO63y.js → plsql-BJDjqcWp.js} +0 -0
- /package/dist/web/assets/{po-_kXpd_0n.js → po-DK5O3OpF.js} +0 -0
- /package/dist/web/assets/{poimandres-C9XggjB3.js → poimandres-CnuqZT4T.js} +0 -0
- /package/dist/web/assets/{polar-DV_1L04z.js → polar-DCsUxSsQ.js} +0 -0
- /package/dist/web/assets/{postcss-BuqS91mQ.js → postcss-8UjdFmis.js} +0 -0
- /package/dist/web/assets/{powerquery-DK13B2Dc.js → powerquery-CAp0CGQj.js} +0 -0
- /package/dist/web/assets/{powershell-tkuhhtuP.js → powershell-CbBsKZJP.js} +0 -0
- /package/dist/web/assets/{prisma-WNXfBtpr.js → prisma-BnNpXCJN.js} +0 -0
- /package/dist/web/assets/{prolog-BzEdNKfO.js → prolog-CC-l9E8Y.js} +0 -0
- /package/dist/web/assets/{proto-B5meVsrA.js → proto-CV_1k2rL.js} +0 -0
- /package/dist/web/assets/{puppet-C7h_p4r2.js → puppet-2D8HU2bv.js} +0 -0
- /package/dist/web/assets/{purescript-qH1KxAs6.js → purescript-BEJAORgU.js} +0 -0
- /package/dist/web/assets/{python-DeTIYd5V.js → python-BBdbb7nn.js} +0 -0
- /package/dist/web/assets/{qmldir-4Rm5AvD3.js → qmldir-D5ZYI4Ie.js} +0 -0
- /package/dist/web/assets/{qss-YRbeNtr0.js → qss-BOJVGyXE.js} +0 -0
- /package/dist/web/assets/{r-DHwHK0Jh.js → r-B5dlEtkj.js} +0 -0
- /package/dist/web/assets/{racket-gKn4SFLQ.js → racket-BUAEygJ6.js} +0 -0
- /package/dist/web/assets/{raku-DWTmAj3A.js → raku-BG0Ss0ID.js} +0 -0
- /package/dist/web/assets/{red-BFuHRZFS.js → red-C5yyQbtG.js} +0 -0
- /package/dist/web/assets/{reg-DYoOF5tH.js → reg-DHoBWtLf.js} +0 -0
- /package/dist/web/assets/{regexp-6rXQHD2-.js → regexp-DDtJrlKv.js} +0 -0
- /package/dist/web/assets/{rel-Z_tTruCU.js → rel-CR6WJDRD.js} +0 -0
- /package/dist/web/assets/{riscv-DfTfKuvU.js → riscv-2V9tC-nx.js} +0 -0
- /package/dist/web/assets/{ron-CsUtW8gQ.js → ron-QLTmqGUw.js} +0 -0
- /package/dist/web/assets/{rose-pine-Dng_vCo6.js → rose-pine-be0__X3A.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-Ds88Nezf.js → rose-pine-dawn-n_WQJwsW.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-DWpzrqNz.js → rose-pine-moon-t2aLtzgB.js} +0 -0
- /package/dist/web/assets/{rosmsg-h2sO84oD.js → rosmsg-Ce1JD8vt.js} +0 -0
- /package/dist/web/assets/{rust-Dx86yfCi.js → rust-5fHH-ZoW.js} +0 -0
- /package/dist/web/assets/{sass-BwwVhdT9.js → sass-DPmGFiOJ.js} +0 -0
- /package/dist/web/assets/{scala-WJqNEN-w.js → scala-BRMVFbZF.js} +0 -0
- /package/dist/web/assets/{scheme-uwj-t1Rp.js → scheme-BMSJWevF.js} +0 -0
- /package/dist/web/assets/{sdbl-BYy5hgEz.js → sdbl-DTZ-pEHI.js} +0 -0
- /package/dist/web/assets/{shellscript-DWTW_w2K.js → shellscript-6-9yg5Cz.js} +0 -0
- /package/dist/web/assets/{slack-dark-BvhGq5vQ.js → slack-dark-DKJgiR7e.js} +0 -0
- /package/dist/web/assets/{slack-ochin-C9g0ZRFr.js → slack-ochin-WjehVgyK.js} +0 -0
- /package/dist/web/assets/{smalltalk-ZEiZ6i7U.js → smalltalk-BChf4SN1.js} +0 -0
- /package/dist/web/assets/{snazzy-light-29OA24q5.js → snazzy-light-C1WAwOx6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-Bnq9IOn_.js → solarized-dark-BxREZrGx.js} +0 -0
- /package/dist/web/assets/{solarized-light-CYdI7T5g.js → solarized-light-DG29B1pR.js} +0 -0
- /package/dist/web/assets/{solidity-DcuIDB-_.js → solidity-bDbtGwdR.js} +0 -0
- /package/dist/web/assets/{splunk-D5ONE91g.js → splunk-Ceqx5njI.js} +0 -0
- /package/dist/web/assets/{sql-DIwJJDU-.js → sql-OwMEKbz8.js} +0 -0
- /package/dist/web/assets/{ssh-config-D8717tLI.js → ssh-config-WAR3lu2k.js} +0 -0
- /package/dist/web/assets/{stylus-Cm1lO4Y6.js → stylus-Cjmoz3d8.js} +0 -0
- /package/dist/web/assets/{swift-X8oWpSeU.js → swift-S_ZSBG1q.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DF-t1UKY.js → synthwave-84-DiUeWMSh.js} +0 -0
- /package/dist/web/assets/{system-verilog-JYyHFpPf.js → system-verilog-RRjqPjUV.js} +0 -0
- /package/dist/web/assets/{systemd-Da-5Ma_c.js → systemd-BD3SkXoU.js} +0 -0
- /package/dist/web/assets/{talonscript-CXraVPho.js → talonscript-DWefISOm.js} +0 -0
- /package/dist/web/assets/{tasl-BCmWezH9.js → tasl-DCjSS-09.js} +0 -0
- /package/dist/web/assets/{tcl-BTufzfbf.js → tcl-DTYqT_66.js} +0 -0
- /package/dist/web/assets/{terraform-CtUA9c8W.js → terraform-CkbP5QnR.js} +0 -0
- /package/dist/web/assets/{tokyo-night-BCThYXmz.js → tokyo-night-qVSqO9xy.js} +0 -0
- /package/dist/web/assets/{toml-D5YlDr6Y.js → toml-D9F5Osqs.js} +0 -0
- /package/dist/web/assets/{tsv-CBB-P_Sl.js → tsv-C5tpHoxm.js} +0 -0
- /package/dist/web/assets/{tsx-D5B4juDp.js → tsx-CMWkGtGm.js} +0 -0
- /package/dist/web/assets/{turtle-D8PCeJ-v.js → turtle-Cn4z3xOi.js} +0 -0
- /package/dist/web/assets/{typescript-CMPnTawp.js → typescript-DSyAEWsn.js} +0 -0
- /package/dist/web/assets/{typespec-CHxldxIW.js → typespec-B93Qap7y.js} +0 -0
- /package/dist/web/assets/{typst-Q8CqX5OR.js → typst-DtlOmFlX.js} +0 -0
- /package/dist/web/assets/{v-Bf2hKAMu.js → v-Dfzf-szS.js} +0 -0
- /package/dist/web/assets/{vala-Cbete-UE.js → vala-CyiQZQ9f.js} +0 -0
- /package/dist/web/assets/{vb-CA-xWAcX.js → vb-C7DGSldd.js} +0 -0
- /package/dist/web/assets/{verilog-B3wb8CfT.js → verilog-kF-hClY2.js} +0 -0
- /package/dist/web/assets/{vesper-D6r3VJq_.js → vesper-BmKcADuz.js} +0 -0
- /package/dist/web/assets/{vhdl-C42S_797.js → vhdl-CDnb16KJ.js} +0 -0
- /package/dist/web/assets/{viml-BePrIAs8.js → viml-B8cs6Ady.js} +0 -0
- /package/dist/web/assets/{vitesse-black-DRqn61Ck.js → vitesse-black-BR6o9Gto.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-39_8UE-w.js → vitesse-dark-Dye14-KB.js} +0 -0
- /package/dist/web/assets/{vitesse-light-B1jwrxDm.js → vitesse-light-BLpswIxf.js} +0 -0
- /package/dist/web/assets/{vyper-tDJv3imF.js → vyper-DU51Z97W.js} +0 -0
- /package/dist/web/assets/{wasm-DR7eskZH.js → wasm-CVqlJAR2.js} +0 -0
- /package/dist/web/assets/{wenyan-CezVfqN3.js → wenyan-DhCa3hCn.js} +0 -0
- /package/dist/web/assets/{wgsl-CwOcIiIG.js → wgsl-Bj1Ec4vv.js} +0 -0
- /package/dist/web/assets/{wikitext-C5coomYc.js → wikitext-C90DHNa0.js} +0 -0
- /package/dist/web/assets/{wit-C9yyX6-U.js → wit-DUgbOD7Z.js} +0 -0
- /package/dist/web/assets/{wolfram-BWi0rMAa.js → wolfram-BgwUqD1I.js} +0 -0
- /package/dist/web/assets/{yaml-CGXhKUtT.js → yaml-DEQulj-8.js} +0 -0
- /package/dist/web/assets/{zenscript-Do7WLoJh.js → zenscript-C7Jp7TgT.js} +0 -0
- /package/dist/web/assets/{zig-Cr80Qrru.js → zig-9x4qXYLg.js} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { terminalService } from "../../services/terminal.service.ts";
|
|
3
4
|
import { resolveProjectPath } from "../helpers/resolve-project.ts";
|
|
4
5
|
import { assertAllowed, resolvePath } from "../../services/fs-path-guard.service.ts";
|
|
@@ -7,6 +8,9 @@ import { assertAllowed, resolvePath } from "../../services/fs-path-guard.service
|
|
|
7
8
|
* Where a new shell starts. An explicit `cwd` (explorer "Open in Terminal") wins
|
|
8
9
|
* over the project root and goes through the same allowlist as the filesystem
|
|
9
10
|
* API, so a terminal cannot be spawned anywhere the explorer could not browse.
|
|
11
|
+
* Neither given (a project-less "run this command" — the remote-desktop
|
|
12
|
+
* checklist's `brew install ffmpeg` button) → the user's home, the same place the
|
|
13
|
+
* OS explorer starts browsing from.
|
|
10
14
|
*/
|
|
11
15
|
function resolveStartDir(projectName: string | undefined, cwd: string | undefined): string {
|
|
12
16
|
if (cwd) {
|
|
@@ -16,7 +20,7 @@ function resolveStartDir(projectName: string | undefined, cwd: string | undefine
|
|
|
16
20
|
return resolved;
|
|
17
21
|
}
|
|
18
22
|
if (projectName) return resolveProjectPath(projectName);
|
|
19
|
-
|
|
23
|
+
return homedir();
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/** Control message prefix for resize commands */
|
|
@@ -36,8 +40,10 @@ export const terminalWebSocket = {
|
|
|
36
40
|
|
|
37
41
|
let session = id !== "new" ? terminalService.get(id) : undefined;
|
|
38
42
|
|
|
39
|
-
//
|
|
40
|
-
|
|
43
|
+
// A fresh socket ("new") always gets a shell — project root, explicit cwd, or home — and
|
|
44
|
+
// so does a stale id that still knows its project/cwd (tab reopened after a restart).
|
|
45
|
+
// Only a stale id with no context left is "Session not found".
|
|
46
|
+
if (!session && (id === "new" || projectName || cwd)) {
|
|
41
47
|
try {
|
|
42
48
|
const startDir = resolveStartDir(projectName, cwd);
|
|
43
49
|
// Create session with the requested ID — but TerminalService generates its own ID.
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
* Discover ffmpeg/ffprobe on this machine and pick the fastest working H.264 encoder.
|
|
3
3
|
*
|
|
4
4
|
* ffmpeg is an *optional* dependency: without it PPM still plays browser-native
|
|
5
|
-
* media (mp4/webm) via Range requests, it just cannot transcode AVI/MKV.
|
|
6
|
-
* result is cached for the process lifetime —
|
|
7
|
-
*
|
|
5
|
+
* media (mp4/webm) via Range requests, it just cannot transcode AVI/MKV. A
|
|
6
|
+
* positive result is cached for the process lifetime — probing hardware encoders
|
|
7
|
+
* costs a real encode each. A *negative* result is not: the remote-desktop
|
|
8
|
+
* checklist tells the user to install ffmpeg and promises to notice, so "not
|
|
9
|
+
* found" is re-checked on every call (one `which` + a few `stat`s).
|
|
10
|
+
*
|
|
11
|
+
* `PATH` alone is not enough to find it. A daemon started by launchd inherits
|
|
12
|
+
* `/usr/bin:/bin:/usr/sbin:/sbin` — Homebrew's `/opt/homebrew/bin` is not on it —
|
|
13
|
+
* so the well-known install directories are probed after `Bun.which`.
|
|
8
14
|
*
|
|
9
15
|
* Hardware encoders are verified by encoding a few synthetic frames rather than by
|
|
10
16
|
* grepping `ffmpeg -encoders`: the list only says the build was compiled with
|
|
@@ -60,11 +66,42 @@ async function encoderWorks(ffmpeg: string, encoder: string): Promise<boolean> {
|
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
/** Where package managers put ffmpeg when it is not on the daemon's PATH. */
|
|
70
|
+
function wellKnownBinDirs(): string[] {
|
|
71
|
+
switch (process.platform) {
|
|
72
|
+
case "darwin": return ["/opt/homebrew/bin", "/usr/local/bin", "/opt/local/bin"];
|
|
73
|
+
case "win32": {
|
|
74
|
+
const local = process.env.LOCALAPPDATA;
|
|
75
|
+
return [
|
|
76
|
+
...(local ? [`${local}\\Microsoft\\WinGet\\Links`] : []),
|
|
77
|
+
"C:\\ProgramData\\chocolatey\\bin", "C:\\ffmpeg\\bin",
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
default: return ["/usr/local/bin", "/usr/bin", "/snap/bin", `${process.env.HOME ?? ""}/.local/bin`];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** `Bun.which` on `searchPath` (the process PATH by default) first, then the well-known
|
|
85
|
+
* directories. Exported for tests. */
|
|
86
|
+
export function findFfmpegBinary(
|
|
87
|
+
name: "ffmpeg" | "ffprobe",
|
|
88
|
+
dirs: string[] = wellKnownBinDirs(),
|
|
89
|
+
searchPath: string | undefined = process.env.PATH,
|
|
90
|
+
): string | null {
|
|
91
|
+
const onPath = Bun.which(name, searchPath ? { PATH: searchPath } : undefined);
|
|
92
|
+
if (onPath) return onPath;
|
|
93
|
+
for (const dir of dirs) {
|
|
94
|
+
const found = Bun.which(name, { PATH: dir });
|
|
95
|
+
if (found) return found;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
let cached: Promise<FfmpegCapabilities> | null = null;
|
|
64
101
|
|
|
65
102
|
async function detect(): Promise<FfmpegCapabilities> {
|
|
66
|
-
const ffmpeg =
|
|
67
|
-
const ffprobe =
|
|
103
|
+
const ffmpeg = findFfmpegBinary("ffmpeg");
|
|
104
|
+
const ffprobe = findFfmpegBinary("ffprobe");
|
|
68
105
|
if (!ffmpeg) return { ffmpeg: null, ffprobe, encoder: null };
|
|
69
106
|
for (const candidate of encoderCandidates()) {
|
|
70
107
|
if (await encoderWorks(ffmpeg, candidate)) return { ffmpeg, ffprobe, encoder: candidate };
|
|
@@ -72,10 +109,14 @@ async function detect(): Promise<FfmpegCapabilities> {
|
|
|
72
109
|
return { ffmpeg, ffprobe, encoder: null };
|
|
73
110
|
}
|
|
74
111
|
|
|
75
|
-
/** Cached capability lookup; the first call pays for the encoder probes.
|
|
112
|
+
/** Cached capability lookup; the first call that finds ffmpeg pays for the encoder probes.
|
|
113
|
+
* "Not installed" is never cached — the user may be running `brew install ffmpeg` right now. */
|
|
76
114
|
export function getFfmpegCapabilities(): Promise<FfmpegCapabilities> {
|
|
77
115
|
if (!cached) {
|
|
78
|
-
cached = detect().
|
|
116
|
+
cached = detect().then((caps) => {
|
|
117
|
+
if (!caps.ffmpeg) cached = null;
|
|
118
|
+
return caps;
|
|
119
|
+
}).catch((e) => {
|
|
79
120
|
cached = null; // let a later call retry after an unexpected failure
|
|
80
121
|
throw e;
|
|
81
122
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backoff schedule for re-attempting a named tunnel after it lost a spawn and
|
|
3
|
+
* the supervisor fell back to a quick URL.
|
|
4
|
+
*
|
|
5
|
+
* The fallback exists so a broken named setup never leaves the machine dark,
|
|
6
|
+
* but it used to be one-way: once quick was live, named was only retried on the
|
|
7
|
+
* next supervisor start or an explicit `retunnel`. That is wrong for a
|
|
8
|
+
* *transient* failure — observed live, a laptop waking from sleep spawns the
|
|
9
|
+
* connector before Wi-Fi is back, the 30s readiness wait expires, and the
|
|
10
|
+
* permanent hostname then serves 530 for hours even though the network
|
|
11
|
+
* recovered seconds later.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 1min, 5min, 15min — then stop and leave the warning standing. */
|
|
15
|
+
export const NAMED_RETRY_DELAYS_MS = [60_000, 300_000, 900_000] as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Delay before retry number `attempt` (0-based), or null once the schedule is
|
|
19
|
+
* exhausted. Bounded on purpose: a genuinely broken setup (deleted tunnel,
|
|
20
|
+
* revoked token) must settle on quick with a visible warning rather than
|
|
21
|
+
* restart the connector forever.
|
|
22
|
+
*/
|
|
23
|
+
export function nextNamedRetryDelayMs(attempt: number): number | null {
|
|
24
|
+
if (!Number.isInteger(attempt) || attempt < 0) return null;
|
|
25
|
+
return NAMED_RETRY_DELAYS_MS[attempt] ?? null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Is the network back yet?" gate for the named-tunnel spawn path.
|
|
3
|
+
*
|
|
4
|
+
* A connector started before the machine's Wi-Fi has reassociated cannot
|
|
5
|
+
* register, and the readiness wait then expires on a failure that would have
|
|
6
|
+
* cleared itself seconds later. Observed live after a laptop resumed from
|
|
7
|
+
* sleep: the supervisor regenerated the tunnel immediately, named timed out,
|
|
8
|
+
* and the permanent hostname served 530 until someone intervened.
|
|
9
|
+
*
|
|
10
|
+
* Only the named path waits: a quick tunnel that fails is retried with a fresh
|
|
11
|
+
* URL anyway, while a named failure costs the user their fixed address.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Cheap, unauthenticated, and the exact host the connector needs anyway. */
|
|
15
|
+
const PROBE_URL = "https://api.cloudflare.com/client/v4/";
|
|
16
|
+
|
|
17
|
+
export interface NetworkReadyOptions {
|
|
18
|
+
/** Give up after this long and let the caller try regardless. */
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
/** Wait between probes. */
|
|
21
|
+
intervalMs?: number;
|
|
22
|
+
/** Per-probe timeout. */
|
|
23
|
+
probeTimeoutMs?: number;
|
|
24
|
+
/** Injectable for tests; defaults to a HEAD against Cloudflare's API. */
|
|
25
|
+
probe?: () => Promise<boolean>;
|
|
26
|
+
sleep?: (ms: number) => Promise<void>;
|
|
27
|
+
now?: () => number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
31
|
+
|
|
32
|
+
async function defaultProbe(probeTimeoutMs: number): Promise<boolean> {
|
|
33
|
+
try {
|
|
34
|
+
// Any HTTP answer proves DNS + TCP + TLS to Cloudflare work; the status
|
|
35
|
+
// code is irrelevant (401/404 are just as good a signal as 200).
|
|
36
|
+
await fetch(PROBE_URL, { method: "HEAD", signal: AbortSignal.timeout(probeTimeoutMs) });
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolves `true` as soon as Cloudflare is reachable, `false` if the budget
|
|
45
|
+
* runs out. Never throws: a failed wait is a hint, not a veto — the caller
|
|
46
|
+
* still attempts the spawn, because a probe can fail for reasons (proxy, DNS
|
|
47
|
+
* filtering) that would not stop the connector itself.
|
|
48
|
+
*/
|
|
49
|
+
export async function waitForCloudflareReachable(opts: NetworkReadyOptions = {}): Promise<boolean> {
|
|
50
|
+
const timeoutMs = opts.timeoutMs ?? 60_000;
|
|
51
|
+
const intervalMs = opts.intervalMs ?? 2_000;
|
|
52
|
+
const probeTimeoutMs = opts.probeTimeoutMs ?? 3_000;
|
|
53
|
+
const probe = opts.probe ?? (() => defaultProbe(probeTimeoutMs));
|
|
54
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
55
|
+
const now = opts.now ?? Date.now;
|
|
56
|
+
|
|
57
|
+
const deadline = now() + timeoutMs;
|
|
58
|
+
for (;;) {
|
|
59
|
+
if (await probe()) return true;
|
|
60
|
+
if (now() >= deadline) return false;
|
|
61
|
+
await sleep(intervalMs);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
import type { ResolvedTunnelConfig, TunnelMode } from "./named-tunnel/named-tunnel-config.ts";
|
|
23
23
|
import { readTunnelConfigFresh, chooseTunnelSpawn } from "./named-tunnel/named-tunnel-runtime.ts";
|
|
24
24
|
import { waitForLogLine } from "./named-tunnel/named-tunnel-readiness.ts";
|
|
25
|
+
import { nextNamedRetryDelayMs } from "./named-tunnel/named-tunnel-retry.ts";
|
|
26
|
+
import { waitForCloudflareReachable } from "./named-tunnel/network-ready.ts";
|
|
25
27
|
import { decideNamedProbeAction, publicHostnameIsOurs } from "./named-tunnel/named-tunnel-probe-state.ts";
|
|
26
28
|
import { isCloudflaredPid } from "./tunnel-registry.service.ts";
|
|
27
29
|
import { getQuickTunnelArgs } from "./cloudflared.service.ts";
|
|
@@ -102,6 +104,18 @@ let namedTunnelMode: ResolvedTunnelConfig | null = null;
|
|
|
102
104
|
// but live=quick, and every status write / throttle / restart decision must
|
|
103
105
|
// key off what's actually running.
|
|
104
106
|
let lastSpawnMode: TunnelMode = "quick";
|
|
107
|
+
// How long to wait for the network before attempting a named tunnel.
|
|
108
|
+
const NETWORK_WAIT_MS = 60_000;
|
|
109
|
+
// Falling back to quick used to be permanent until the next supervisor start.
|
|
110
|
+
// These drive a bounded set of retries so a transient failure (network not up
|
|
111
|
+
// yet after a resume) heals itself instead of leaving the hostname dark.
|
|
112
|
+
let namedRetryAttempt = 0;
|
|
113
|
+
let namedRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
114
|
+
// A retry that finds the machine still offline re-arms the same rung instead
|
|
115
|
+
// of burning it. Bounded so a laptop left off the network doesn't log a
|
|
116
|
+
// deferral every minute for the rest of its uptime.
|
|
117
|
+
let namedRetryDeferrals = 0;
|
|
118
|
+
const MAX_NAMED_RETRY_DEFERRALS = 10;
|
|
105
119
|
// Set once the named probe has already tried a restart-and-hope; a second
|
|
106
120
|
// consecutive failure then warns and stops instead of looping kills forever.
|
|
107
121
|
let namedProbeRestartAttempted = false;
|
|
@@ -293,6 +307,72 @@ async function ensureBindablePort(preferred: number, host: string): Promise<numb
|
|
|
293
307
|
* The trycloudflare URL will change — unavoidable, the old quick-tunnel session
|
|
294
308
|
* is dead. Fire-and-forget: spawnTunnel's own loop owns liveness afterwards.
|
|
295
309
|
*/
|
|
310
|
+
/** Cancel a pending named retry — a newer decision supersedes it. */
|
|
311
|
+
function cancelNamedRetry(): void {
|
|
312
|
+
if (namedRetryTimer) { clearTimeout(namedRetryTimer); namedRetryTimer = null; }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* After a downgrade to quick, try named again on a bounded backoff.
|
|
317
|
+
*
|
|
318
|
+
* Re-checks everything at fire time rather than trusting the state captured
|
|
319
|
+
* when the timer was set: the user may have switched to quick deliberately, a
|
|
320
|
+
* `retunnel` may already have restored named, or the supervisor may be going
|
|
321
|
+
* down. Any of those makes the retry pointless or actively wrong.
|
|
322
|
+
*/
|
|
323
|
+
function scheduleNamedRetry(port: number): void {
|
|
324
|
+
cancelNamedRetry();
|
|
325
|
+
const delay = nextNamedRetryDelayMs(namedRetryAttempt);
|
|
326
|
+
if (delay === null) {
|
|
327
|
+
log("WARN", "Named tunnel still failing after the last retry — staying on the quick URL");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const attemptNumber = namedRetryAttempt + 1;
|
|
331
|
+
log("INFO", `Named tunnel downgraded to quick — retrying in ${Math.round(delay / 1000)}s (#${attemptNumber})`);
|
|
332
|
+
namedRetryTimer = setTimeout(() => {
|
|
333
|
+
namedRetryTimer = null;
|
|
334
|
+
if (shuttingDown || getState() !== "running") return;
|
|
335
|
+
if (namedTunnelMode?.mode !== "named") return; // user switched to quick meanwhile
|
|
336
|
+
if (lastSpawnMode === "named") return; // already recovered
|
|
337
|
+
// Restarting tears down the quick tunnel that is currently serving the
|
|
338
|
+
// machine, so only do it once the network can actually carry a connector —
|
|
339
|
+
// otherwise a still-offline laptop loses its working temporary URL too and
|
|
340
|
+
// gets a new one for nothing. Offline: re-arm the same rung, don't burn it.
|
|
341
|
+
void waitForCloudflareReachable({ timeoutMs: 5_000, intervalMs: 1_000 }).then((reachable) => {
|
|
342
|
+
if (shuttingDown || getState() !== "running") return;
|
|
343
|
+
if (namedTunnelMode?.mode !== "named" || lastSpawnMode === "named") return;
|
|
344
|
+
if (!reachable) {
|
|
345
|
+
if (++namedRetryDeferrals > MAX_NAMED_RETRY_DEFERRALS) {
|
|
346
|
+
log("WARN", "Named retry abandoned — network still down after repeated attempts");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
log("INFO", `Named retry #${attemptNumber} deferred — network still down`);
|
|
350
|
+
scheduleNamedRetry(port);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
namedRetryDeferrals = 0;
|
|
354
|
+
namedRetryAttempt = attemptNumber;
|
|
355
|
+
log("INFO", `Retrying the named tunnel (#${attemptNumber})`);
|
|
356
|
+
restartTunnel(port);
|
|
357
|
+
});
|
|
358
|
+
}, delay);
|
|
359
|
+
namedRetryTimer.unref?.();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Called once per successful spawn: arm the retry ladder when we had to fall
|
|
364
|
+
* back to quick, clear it the moment named is actually live again.
|
|
365
|
+
*/
|
|
366
|
+
function noteSpawnOutcomeForNamedRetry(mode: TunnelMode, downgraded: boolean, port: number): void {
|
|
367
|
+
if (mode === "named") {
|
|
368
|
+
cancelNamedRetry();
|
|
369
|
+
namedRetryAttempt = 0;
|
|
370
|
+
namedRetryDeferrals = 0;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (downgraded) scheduleNamedRetry(port);
|
|
374
|
+
}
|
|
375
|
+
|
|
296
376
|
function restartTunnel(port: number) {
|
|
297
377
|
lastTunnelRegenAt = Date.now();
|
|
298
378
|
if (tunnelChild) { try { tunnelChild.kill(); } catch {} tunnelChild = null; }
|
|
@@ -744,6 +824,15 @@ export async function spawnTunnel(port: number, generation: number = ++tunnelGen
|
|
|
744
824
|
}
|
|
745
825
|
|
|
746
826
|
const attempts = buildTunnelAttempts(namedTunnelMode, port);
|
|
827
|
+
// Only the named path pays for this: its failure costs the user their fixed
|
|
828
|
+
// address, while a quick tunnel just retries with a new URL. Spawning a
|
|
829
|
+
// connector before the machine's network is back is the difference between
|
|
830
|
+
// "reconnects in 5s" and "hostname dark until someone notices".
|
|
831
|
+
if (attempts[0]?.mode === "named") {
|
|
832
|
+
const reachable = await waitForCloudflareReachable({ timeoutMs: NETWORK_WAIT_MS });
|
|
833
|
+
if (!reachable) log("WARN", `Cloudflare unreachable after ${NETWORK_WAIT_MS / 1000}s — attempting the tunnel anyway`);
|
|
834
|
+
if (generation !== tunnelGeneration) return; // superseded while waiting
|
|
835
|
+
}
|
|
747
836
|
const logPath = cloudflaredLogPath();
|
|
748
837
|
// Truncate stale content from a prior generation; best-effort — Windows can
|
|
749
838
|
// silently fail this while a previous detached cloudflared still holds the
|
|
@@ -821,6 +910,7 @@ export async function spawnTunnel(port: number, generation: number = ++tunnelGen
|
|
|
821
910
|
adoptedTunnelPid = winPid;
|
|
822
911
|
tunnelChild = null;
|
|
823
912
|
lastSpawnMode = successfulMode;
|
|
913
|
+
noteSpawnOutcomeForNamedRetry(successfulMode, downgraded, port);
|
|
824
914
|
// `namedProbeRestartAttempted` is deliberately NOT touched here — see the
|
|
825
915
|
// probe's `decideNamedProbeAction` state machine. A successful spawn only
|
|
826
916
|
// proves cloudflared reconnected to Cloudflare's edge, not that the
|
|
@@ -908,6 +998,7 @@ export async function spawnTunnel(port: number, generation: number = ++tunnelGen
|
|
|
908
998
|
}
|
|
909
999
|
|
|
910
1000
|
lastSpawnMode = successfulMode;
|
|
1001
|
+
noteSpawnOutcomeForNamedRetry(successfulMode, downgraded, port);
|
|
911
1002
|
// `namedProbeRestartAttempted` is deliberately NOT touched here — see the
|
|
912
1003
|
// probe's `decideNamedProbeAction` state machine and the win32 branch above
|
|
913
1004
|
// for why a bare spawn success must never re-arm the one-restart budget.
|
|
@@ -1708,6 +1799,7 @@ export async function softStop() {
|
|
|
1708
1799
|
export function shutdown() {
|
|
1709
1800
|
if (shuttingDown) return;
|
|
1710
1801
|
shuttingDown = true;
|
|
1802
|
+
cancelNamedRetry();
|
|
1711
1803
|
log("INFO", "Supervisor shutting down");
|
|
1712
1804
|
|
|
1713
1805
|
// Unblock if paused
|
|
@@ -1910,6 +2002,10 @@ export async function runSupervisor(opts: {
|
|
|
1910
2002
|
// over from the outage trips "restart once" after a handful of probes.
|
|
1911
2003
|
namedProbeRestartAttempted = false;
|
|
1912
2004
|
tunnelFailCount = 0;
|
|
2005
|
+
// A deliberate retunnel supersedes any pending automatic retry, and
|
|
2006
|
+
// restarts the ladder so a later transient failure gets a full budget.
|
|
2007
|
+
cancelNamedRetry();
|
|
2008
|
+
namedRetryAttempt = 0;
|
|
1913
2009
|
restartTunnel(_opts.port);
|
|
1914
2010
|
// Deliberate fall-through (no return): a bare `ppm restart` sends a
|
|
1915
2011
|
// bare SIGUSR2 with no command file of its own, and a `retunnel` that
|
|
@@ -2059,6 +2155,8 @@ export async function runSupervisor(opts: {
|
|
|
2059
2155
|
// rather than inheriting a stale exhausted one (flag AND counter).
|
|
2060
2156
|
namedProbeRestartAttempted = false;
|
|
2061
2157
|
tunnelFailCount = 0;
|
|
2158
|
+
cancelNamedRetry();
|
|
2159
|
+
namedRetryAttempt = 0;
|
|
2062
2160
|
restartTunnel(_opts.port);
|
|
2063
2161
|
});
|
|
2064
2162
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readImageDimensions } from "./image-dimensions.ts";
|
|
2
2
|
import { base64ByteLength, imagePlaceholderText } from "../shared/tool-result-content.ts";
|
|
3
|
+
import { MAX_IMAGE_DIMENSION } from "../shared/image-limits.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Auditing and removal of image payloads inside a Claude Code session transcript.
|
|
@@ -11,9 +12,14 @@ import { base64ByteLength, imagePlaceholderText } from "../shared/tool-result-co
|
|
|
11
12
|
*
|
|
12
13
|
* Images live in two places, and they are not equally safe to remove. A tool result can be
|
|
13
14
|
* produced again by re-reading the file it came from, so those are removed by default. An
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* attachment is left alone unless the caller opts in.
|
|
16
|
+
*
|
|
17
|
+
* That default is now more cautious than it needs to be for images this app sent itself: the
|
|
18
|
+
* composer uploads a copy before embedding the payload, and leaves the path in the message, so
|
|
19
|
+
* such an attachment is as re-readable as a tool result. It stays opt-in because a transcript
|
|
20
|
+
* also collects attachments from elsewhere — another client, an older build, a session shared
|
|
21
|
+
* with the CLI — and for those the payload really is the last copy. The caller must be able to
|
|
22
|
+
* reach it either way: one oversized attachment fails every later turn of the session.
|
|
17
23
|
*
|
|
18
24
|
* A tool result's payload sits at `message.content[].content[]`; an attachment sits directly
|
|
19
25
|
* at `message.content[]`. A record also carries an image-shaped echo at the top-level
|
|
@@ -28,7 +34,7 @@ import { base64ByteLength, imagePlaceholderText } from "../shared/tool-result-co
|
|
|
28
34
|
* Comparisons here are `>=` rather than `>` for that reason: treating 2000px as acceptable
|
|
29
35
|
* hides the one image the API is rejecting and makes a cleanup look like a no-op.
|
|
30
36
|
*/
|
|
31
|
-
export const MANY_IMAGE_DIMENSION_LIMIT =
|
|
37
|
+
export const MANY_IMAGE_DIMENSION_LIMIT = MAX_IMAGE_DIMENSION;
|
|
32
38
|
|
|
33
39
|
/** Base64 characters decoded to inspect a header — a JPEG frame can sit behind EXIF. */
|
|
34
40
|
const HEADER_CHARS = 4096;
|
|
@@ -38,9 +44,10 @@ export type StripMode = "oversized" | "all";
|
|
|
38
44
|
/** Which of the two image locations a scan or strip should cover. */
|
|
39
45
|
export interface ImageScopeOpts {
|
|
40
46
|
/**
|
|
41
|
-
* Also cover images the user attached to their own messages. Off by default
|
|
42
|
-
*
|
|
43
|
-
*
|
|
47
|
+
* Also cover images the user attached to their own messages. Off by default, because a
|
|
48
|
+
* transcript may hold attachments this app never uploaded — from another client or an older
|
|
49
|
+
* build — for which the embedded payload is the last copy. Attachments the composer sent
|
|
50
|
+
* keep an uploaded file and its path, so removing those is recoverable.
|
|
44
51
|
*/
|
|
45
52
|
includeAttachments?: boolean;
|
|
46
53
|
}
|
|
@@ -117,10 +117,15 @@ let upgradeInProgress = false;
|
|
|
117
117
|
|
|
118
118
|
/** Build the global-install command. Absolute runtime path (not a bare
|
|
119
119
|
* "bun"/"npm") because autostart (launchd/systemd) may not have the runtime's
|
|
120
|
-
* bin dir on $PATH, where a bare name fails with "Executable not found".
|
|
120
|
+
* bin dir on $PATH, where a bare name fails with "Executable not found".
|
|
121
|
+
*
|
|
122
|
+
* bun gets `--no-cache`: it resolves versions from a cached package manifest
|
|
123
|
+
* and, right after a release, that manifest predates the version the update
|
|
124
|
+
* check just found — `bun install -g pkg@new` then fails with "No version
|
|
125
|
+
* matching … (but package exists)" until the cache happens to expire. */
|
|
121
126
|
export function buildUpgradeCommand(method: "bun" | "npm", pkg: string): string[] {
|
|
122
127
|
return method === "bun"
|
|
123
|
-
? [process.execPath, "install", "-g", pkg]
|
|
128
|
+
? [process.execPath, "install", "-g", "--no-cache", pkg]
|
|
124
129
|
: [resolveNpmBin(), "install", "-g", pkg];
|
|
125
130
|
}
|
|
126
131
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-image dimension ceiling the API applies, and the arithmetic for staying under it.
|
|
3
|
+
*
|
|
4
|
+
* Shared because both ends of the pipeline need the same number: the browser downscales an
|
|
5
|
+
* attachment before it is ever uploaded, and the transcript tooling reports on images that
|
|
6
|
+
* slipped in from elsewhere (another client, an older build) still carrying the full size.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Longest side, in pixels, at which the API starts refusing an image once a request carries
|
|
11
|
+
* several of them. The value is a ceiling, so an image measuring exactly this is already over
|
|
12
|
+
* it — comparisons are `>=`, and a downscale targets strictly below.
|
|
13
|
+
*/
|
|
14
|
+
export const MAX_IMAGE_DIMENSION = 2000;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Dimensions that bring an image under the cap, or null when it is already small enough.
|
|
18
|
+
*
|
|
19
|
+
* Scales the longest side to one pixel below the cap and takes the other side with it, so the
|
|
20
|
+
* aspect ratio survives and the result cannot land back on the boundary through rounding.
|
|
21
|
+
*/
|
|
22
|
+
export function fitWithin(
|
|
23
|
+
width: number,
|
|
24
|
+
height: number,
|
|
25
|
+
max: number = MAX_IMAGE_DIMENSION,
|
|
26
|
+
): { width: number; height: number } | null {
|
|
27
|
+
if (!(width > 0) || !(height > 0)) return null;
|
|
28
|
+
const longest = Math.max(width, height);
|
|
29
|
+
if (longest < max) return null;
|
|
30
|
+
|
|
31
|
+
const target = max - 1;
|
|
32
|
+
const scale = target / longest;
|
|
33
|
+
return {
|
|
34
|
+
width: width >= height ? target : Math.max(1, Math.round(width * scale)),
|
|
35
|
+
height: height > width ? target : Math.max(1, Math.round(height * scale)),
|
|
36
|
+
};
|
|
37
|
+
}
|
package/src/types/api.ts
CHANGED
|
@@ -23,7 +23,9 @@ export type TerminalWsMessage =
|
|
|
23
23
|
|
|
24
24
|
/** WebSocket message types (chat) */
|
|
25
25
|
export type ChatWsClientMessage =
|
|
26
|
-
| { type: "message"; content: string; permissionMode?: string; priority?: 'now' | 'next' | 'later'; images?: Array<{ data: string; mediaType: string }>;
|
|
26
|
+
| { type: "message"; content: string; permissionMode?: string; priority?: 'now' | 'next' | 'later'; images?: Array<{ data: string; mediaType: string }>;
|
|
27
|
+
/** Uploaded paths for the same images, for providers that take a file not a payload. */
|
|
28
|
+
imagePaths?: string[]; model?: string; effort?: string; thinking?: boolean }
|
|
27
29
|
| { type: "cancel" }
|
|
28
30
|
| { type: "set_model"; model: string }
|
|
29
31
|
| { type: "set_effort"; effort: string }
|
package/src/types/chat.ts
CHANGED
|
@@ -2,6 +2,12 @@ export interface SendMessageOpts {
|
|
|
2
2
|
permissionMode?: import("./config").PermissionMode | string;
|
|
3
3
|
priority?: 'now' | 'next' | 'later';
|
|
4
4
|
images?: Array<{ data: string; mediaType: string }>;
|
|
5
|
+
/**
|
|
6
|
+
* Uploaded paths for the same attachments, for providers that take a file rather than a
|
|
7
|
+
* payload. Codex's turn input accepts `localImage` by path and has no base64 form, so an
|
|
8
|
+
* image reaches it this way or not at all.
|
|
9
|
+
*/
|
|
10
|
+
imagePaths?: string[];
|
|
5
11
|
/** Per-session model override; falls back to provider config model when absent */
|
|
6
12
|
model?: string;
|
|
7
13
|
/** Override the provider's 1M-context setting for this call (false = never add the
|
|
@@ -22,6 +22,8 @@ export interface TreeItemMsg {
|
|
|
22
22
|
description?: string;
|
|
23
23
|
tooltip?: string;
|
|
24
24
|
icon?: string;
|
|
25
|
+
/** Label wraps instead of truncating — for rows whose text is the content. */
|
|
26
|
+
wrap?: boolean;
|
|
25
27
|
collapsibleState: "none" | "collapsed" | "expanded";
|
|
26
28
|
command?: string;
|
|
27
29
|
children?: TreeItemMsg[];
|
|
@@ -52,6 +52,16 @@ export function AttachmentChips({ attachments, onRemove }: AttachmentChipsProps)
|
|
|
52
52
|
<Loader2 className="size-3 shrink-0 animate-spin text-text-subtle" />
|
|
53
53
|
) : att.status === "error" ? (
|
|
54
54
|
<span className="text-error shrink-0" title="Upload failed">!</span>
|
|
55
|
+
) : att.resized ? (
|
|
56
|
+
// Says what happened to the image. A screenshot arriving at the model smaller
|
|
57
|
+
// than the one that was pasted is worth stating rather than leaving to be
|
|
58
|
+
// discovered, and the measurement was already being recorded for nothing.
|
|
59
|
+
<span
|
|
60
|
+
className="shrink-0 text-[10px] text-text-subtle"
|
|
61
|
+
title={`Shrunk from ${att.resized.from.width}x${att.resized.from.height} to ${att.resized.to.width}x${att.resized.to.height}`}
|
|
62
|
+
>
|
|
63
|
+
{att.resized.to.width}×{att.resized.to.height}
|
|
64
|
+
</span>
|
|
55
65
|
) : null}
|
|
56
66
|
|
|
57
67
|
{/* Remove button */}
|
|
@@ -2,6 +2,8 @@ import { useState, useCallback, useRef, useEffect } from "react";
|
|
|
2
2
|
import { Loader2, Upload, X } from "lucide-react";
|
|
3
3
|
import { toast } from "sonner";
|
|
4
4
|
import { api, projectUrl } from "@/lib/api-client";
|
|
5
|
+
import { selectInlineImages } from "@/lib/image-resize-limits";
|
|
6
|
+
import { splitAttachmentMarkers } from "@/lib/attachment-marker-split";
|
|
5
7
|
import { useChat } from "@/hooks/use-chat";
|
|
6
8
|
import { useUsage } from "@/hooks/use-usage";
|
|
7
9
|
import { useTabStore } from "@/stores/tab-store";
|
|
@@ -56,7 +58,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
56
58
|
);
|
|
57
59
|
|
|
58
60
|
// Pending message to send after WS connects (replaces unreliable setTimeout)
|
|
59
|
-
const pendingSendRef = useRef<{ content: string; permissionMode?: string } | null>(null);
|
|
61
|
+
const pendingSendRef = useRef<{ content: string; permissionMode?: string; images?: Array<{ data: string; mediaType: string }>; imagePaths?: string[] } | null>(null);
|
|
60
62
|
|
|
61
63
|
// Drag-and-drop state
|
|
62
64
|
const [isDragging, setIsDragging] = useState(false);
|
|
@@ -142,9 +144,9 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
142
144
|
// Flush pending message once WS connects (replaces unreliable setTimeout)
|
|
143
145
|
useEffect(() => {
|
|
144
146
|
if (isConnected && pendingSendRef.current) {
|
|
145
|
-
const { content, permissionMode: pm } = pendingSendRef.current;
|
|
147
|
+
const { content, permissionMode: pm, images: pendingImages, imagePaths: pendingPaths } = pendingSendRef.current;
|
|
146
148
|
pendingSendRef.current = null;
|
|
147
|
-
sendMessage(content, { permissionMode: pm });
|
|
149
|
+
sendMessage(content, { permissionMode: pm, ...(pendingImages?.length && { images: pendingImages }), ...(pendingPaths?.length && { imagePaths: pendingPaths }) });
|
|
148
150
|
}
|
|
149
151
|
}, [isConnected, sendMessage]);
|
|
150
152
|
|
|
@@ -350,7 +352,11 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
350
352
|
|
|
351
353
|
/** Build message content with file references and inline text snippets prepended */
|
|
352
354
|
const buildMessageWithAttachments = useCallback(
|
|
353
|
-
(
|
|
355
|
+
(
|
|
356
|
+
content: string,
|
|
357
|
+
attachments: ChatAttachment[],
|
|
358
|
+
inlineImages: Array<{ data: string; mediaType: string }> = [],
|
|
359
|
+
): string => {
|
|
354
360
|
if (attachments.length === 0) return content;
|
|
355
361
|
|
|
356
362
|
const parts: string[] = [];
|
|
@@ -360,12 +366,27 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
360
366
|
if (att.textContent) parts.push(att.textContent);
|
|
361
367
|
}
|
|
362
368
|
|
|
363
|
-
// Server-uploaded file references
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
369
|
+
// Server-uploaded file references. An image keeps its path even when the payload rides
|
|
370
|
+
// along with the message, because the transcript is what the chat re-renders from and
|
|
371
|
+
// the path is the only thing in it a thumbnail can be drawn from.
|
|
372
|
+
//
|
|
373
|
+
// An image sent inline says so in the marker. A bare path in a user message reads as an
|
|
374
|
+
// invitation to open it, and a model that takes it pays for the same picture twice —
|
|
375
|
+
// once inline, once as a tool result, both replayed on every later turn.
|
|
376
|
+
//
|
|
377
|
+
// Which marker an image gets follows the payloads that are actually going out, not
|
|
378
|
+
// `imageData` alone: the per-message caps can leave one behind, and announcing that its
|
|
379
|
+
// contents are included would hand the model neither the picture nor a reason to open
|
|
380
|
+
// the file. `inlineImages` defaults to none, so a caller that sends no payloads at all
|
|
381
|
+
// (the edit-fork path takes only text) gets the plain markers throughout.
|
|
382
|
+
const { inlineImagePaths, pathOnlyPaths } = splitAttachmentMarkers(attachments, inlineImages);
|
|
383
|
+
for (const path of inlineImagePaths) {
|
|
384
|
+
parts.push(`[Attached image (contents included in this message): ${path}]`);
|
|
385
|
+
}
|
|
386
|
+
if (pathOnlyPaths.length > 0) {
|
|
387
|
+
const fileRefs = pathOnlyPaths.join("\n");
|
|
367
388
|
parts.push(
|
|
368
|
-
|
|
389
|
+
pathOnlyPaths.length === 1
|
|
369
390
|
? `[Attached file: ${fileRefs}]`
|
|
370
391
|
: `[Attached files:\n${fileRefs}\n]`,
|
|
371
392
|
);
|
|
@@ -379,8 +400,11 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
379
400
|
|
|
380
401
|
const handleSend = useCallback(
|
|
381
402
|
async (content: string, attachments: ChatAttachment[] = [], priority?: MessagePriority) => {
|
|
382
|
-
const
|
|
383
|
-
|
|
403
|
+
const images = selectInlineImages(attachments);
|
|
404
|
+
const fullContent = buildMessageWithAttachments(content, attachments, images);
|
|
405
|
+
// Providers that take a file rather than a payload (codex) read these instead.
|
|
406
|
+
const imagePaths = attachments.filter((a) => a.isImage && a.serverPath).map((a) => a.serverPath!);
|
|
407
|
+
if (!fullContent.trim() && images.length === 0) return;
|
|
384
408
|
|
|
385
409
|
if (!sessionId) {
|
|
386
410
|
try {
|
|
@@ -394,14 +418,14 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
394
418
|
setSessionId(session.id);
|
|
395
419
|
setProviderId(session.providerId);
|
|
396
420
|
// Queue message — will be sent by effect when WS reports isConnected
|
|
397
|
-
pendingSendRef.current = { content: fullContent, permissionMode };
|
|
421
|
+
pendingSendRef.current = { content: fullContent, permissionMode, images, imagePaths };
|
|
398
422
|
return;
|
|
399
423
|
} catch (e) {
|
|
400
424
|
console.error("Failed to create session:", e);
|
|
401
425
|
return;
|
|
402
426
|
}
|
|
403
427
|
}
|
|
404
|
-
sendMessage(fullContent, { permissionMode, priority });
|
|
428
|
+
sendMessage(fullContent, { permissionMode, priority, ...(images.length > 0 && { images }), ...(imagePaths.length > 0 && { imagePaths }) });
|
|
405
429
|
},
|
|
406
430
|
[sessionId, providerId, projectName, sendMessage, buildMessageWithAttachments, permissionMode, metadata],
|
|
407
431
|
);
|