cx-viewer 1.0.2 → 1.0.4
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/README.md +75 -28
- package/build.js +15 -0
- package/cli.js +108 -38
- package/concepts/ar/BodyDiffJSON.md +1 -1
- package/concepts/ar/BodyFields.md +30 -33
- package/concepts/ar/GlobalSettings.md +165 -167
- package/concepts/ar/MainAgent.md +3 -4
- package/concepts/ar/ProxySwitch.md +3 -3
- package/concepts/ar/QRCode.md +1 -1
- package/concepts/ar/ResponseFields.md +29 -41
- package/concepts/ar/SubAgent-Search.md +12 -12
- package/concepts/ar/Teammate.md +16 -16
- package/concepts/ar/ToolsFirst.md +42 -48
- package/concepts/ar/TranslateContextPollution.md +9 -9
- package/concepts/ar/UltraPlan.md +38 -38
- package/concepts/da/BodyDiffJSON.md +2 -2
- package/concepts/da/BodyFields.md +30 -33
- package/concepts/da/GlobalSettings.md +165 -167
- package/concepts/da/MainAgent.md +3 -4
- package/concepts/da/ProxySwitch.md +3 -3
- package/concepts/da/QRCode.md +1 -1
- package/concepts/da/ResponseFields.md +29 -41
- package/concepts/da/SubAgent-Search.md +11 -11
- package/concepts/da/Teammate.md +16 -16
- package/concepts/da/ToolsFirst.md +42 -48
- package/concepts/da/TranslateContextPollution.md +9 -9
- package/concepts/da/UltraPlan.md +38 -38
- package/concepts/de/BodyDiffJSON.md +2 -2
- package/concepts/de/BodyFields.md +30 -33
- package/concepts/de/GlobalSettings.md +165 -167
- package/concepts/de/MainAgent.md +3 -4
- package/concepts/de/ProxySwitch.md +3 -3
- package/concepts/de/QRCode.md +1 -1
- package/concepts/de/ResponseFields.md +29 -41
- package/concepts/de/SubAgent-Search.md +11 -11
- package/concepts/de/Teammate.md +15 -15
- package/concepts/de/ToolsFirst.md +41 -47
- package/concepts/de/TranslateContextPollution.md +9 -9
- package/concepts/de/UltraPlan.md +38 -38
- package/concepts/en/BodyDiffJSON.md +5 -5
- package/concepts/en/BodyFields.md +17 -20
- package/concepts/en/GlobalSettings.md +49 -51
- package/concepts/en/MainAgent.md +8 -8
- package/concepts/en/ProxySwitch.md +3 -3
- package/concepts/en/QRCode.md +6 -6
- package/concepts/en/ResponseFields.md +7 -19
- package/concepts/en/SubAgent-Search.md +2 -2
- package/concepts/en/Teammate.md +4 -4
- package/concepts/en/Tool-apply_patch.md +11 -0
- package/concepts/en/Tool-create_goal.md +8 -0
- package/concepts/en/Tool-exec.md +88 -0
- package/concepts/en/Tool-followup_task.md +10 -0
- package/concepts/en/Tool-get_goal.md +5 -0
- package/concepts/en/Tool-image_generation.md +5 -0
- package/concepts/en/Tool-interrupt_agent.md +9 -0
- package/concepts/en/Tool-list_agents.md +9 -0
- package/concepts/en/Tool-list_mcp_resource_templates.md +8 -0
- package/concepts/en/Tool-list_mcp_resources.md +8 -0
- package/concepts/en/Tool-read_mcp_resource.md +10 -0
- package/concepts/en/Tool-request_user_input.md +9 -0
- package/concepts/en/Tool-send_message.md +10 -0
- package/concepts/en/Tool-shell_command.md +12 -0
- package/concepts/en/Tool-spawn_agent.md +11 -0
- package/concepts/en/Tool-tool_search.md +12 -0
- package/concepts/en/Tool-update_goal.md +9 -0
- package/concepts/en/Tool-update_plan.md +9 -0
- package/concepts/en/Tool-view_image.md +10 -0
- package/concepts/en/Tool-wait.md +12 -0
- package/concepts/en/Tool-wait_agent.md +9 -0
- package/concepts/en/Tool-web_search.md +11 -0
- package/concepts/en/Tools.md +29 -58
- package/concepts/en/ToolsFirst.md +24 -30
- package/concepts/en/TranslateContextPollution.md +10 -38
- package/concepts/en/UltraPlan.md +7 -7
- package/concepts/es/BodyDiffJSON.md +1 -1
- package/concepts/es/BodyFields.md +35 -38
- package/concepts/es/GlobalSettings.md +165 -167
- package/concepts/es/MainAgent.md +3 -4
- package/concepts/es/ProxySwitch.md +3 -3
- package/concepts/es/QRCode.md +1 -1
- package/concepts/es/ResponseFields.md +28 -40
- package/concepts/es/SubAgent-Search.md +12 -12
- package/concepts/es/Teammate.md +17 -17
- package/concepts/es/ToolsFirst.md +41 -47
- package/concepts/es/TranslateContextPollution.md +9 -9
- package/concepts/es/UltraPlan.md +38 -38
- package/concepts/fr/BodyDiffJSON.md +2 -2
- package/concepts/fr/BodyFields.md +35 -38
- package/concepts/fr/GlobalSettings.md +165 -167
- package/concepts/fr/MainAgent.md +3 -4
- package/concepts/fr/ProxySwitch.md +3 -3
- package/concepts/fr/QRCode.md +1 -1
- package/concepts/fr/ResponseFields.md +25 -37
- package/concepts/fr/SubAgent-Search.md +12 -12
- package/concepts/fr/Teammate.md +16 -16
- package/concepts/fr/ToolsFirst.md +40 -46
- package/concepts/fr/TranslateContextPollution.md +9 -9
- package/concepts/fr/UltraPlan.md +38 -38
- package/concepts/it/BodyDiffJSON.md +2 -2
- package/concepts/it/BodyFields.md +35 -38
- package/concepts/it/GlobalSettings.md +165 -167
- package/concepts/it/MainAgent.md +3 -4
- package/concepts/it/ProxySwitch.md +3 -3
- package/concepts/it/QRCode.md +1 -1
- package/concepts/it/ResponseFields.md +28 -40
- package/concepts/it/SubAgent-Search.md +12 -12
- package/concepts/it/Teammate.md +17 -17
- package/concepts/it/ToolsFirst.md +41 -47
- package/concepts/it/TranslateContextPollution.md +9 -9
- package/concepts/it/UltraPlan.md +38 -38
- package/concepts/ja/BodyDiffJSON.md +1 -1
- package/concepts/ja/BodyFields.md +30 -33
- package/concepts/ja/GlobalSettings.md +165 -167
- package/concepts/ja/MainAgent.md +3 -4
- package/concepts/ja/ProxySwitch.md +3 -3
- package/concepts/ja/QRCode.md +1 -1
- package/concepts/ja/ResponseFields.md +29 -41
- package/concepts/ja/SubAgent-Search.md +12 -12
- package/concepts/ja/Teammate.md +17 -17
- package/concepts/ja/ToolsFirst.md +41 -47
- package/concepts/ja/TranslateContextPollution.md +9 -9
- package/concepts/ja/UltraPlan.md +38 -38
- package/concepts/ko/BodyDiffJSON.md +1 -1
- package/concepts/ko/BodyFields.md +30 -33
- package/concepts/ko/GlobalSettings.md +165 -167
- package/concepts/ko/MainAgent.md +3 -4
- package/concepts/ko/ProxySwitch.md +3 -3
- package/concepts/ko/QRCode.md +1 -1
- package/concepts/ko/ResponseFields.md +29 -41
- package/concepts/ko/SubAgent-Search.md +12 -12
- package/concepts/ko/Teammate.md +17 -17
- package/concepts/ko/ToolsFirst.md +41 -47
- package/concepts/ko/TranslateContextPollution.md +9 -9
- package/concepts/ko/UltraPlan.md +38 -38
- package/concepts/no/BodyDiffJSON.md +2 -2
- package/concepts/no/BodyFields.md +30 -33
- package/concepts/no/GlobalSettings.md +165 -167
- package/concepts/no/MainAgent.md +3 -4
- package/concepts/no/ProxySwitch.md +3 -3
- package/concepts/no/QRCode.md +1 -1
- package/concepts/no/ResponseFields.md +29 -41
- package/concepts/no/SubAgent-Search.md +12 -12
- package/concepts/no/Teammate.md +17 -17
- package/concepts/no/ToolsFirst.md +42 -48
- package/concepts/no/TranslateContextPollution.md +9 -9
- package/concepts/no/UltraPlan.md +38 -38
- package/concepts/pl/BodyDiffJSON.md +2 -2
- package/concepts/pl/BodyFields.md +30 -33
- package/concepts/pl/GlobalSettings.md +165 -167
- package/concepts/pl/MainAgent.md +3 -4
- package/concepts/pl/ProxySwitch.md +3 -3
- package/concepts/pl/QRCode.md +1 -1
- package/concepts/pl/ResponseFields.md +29 -41
- package/concepts/pl/SubAgent-Search.md +12 -12
- package/concepts/pl/Teammate.md +17 -17
- package/concepts/pl/ToolsFirst.md +42 -48
- package/concepts/pl/TranslateContextPollution.md +9 -9
- package/concepts/pl/UltraPlan.md +38 -38
- package/concepts/pt-BR/BodyDiffJSON.md +2 -2
- package/concepts/pt-BR/BodyFields.md +30 -33
- package/concepts/pt-BR/GlobalSettings.md +165 -167
- package/concepts/pt-BR/MainAgent.md +3 -4
- package/concepts/pt-BR/ProxySwitch.md +3 -3
- package/concepts/pt-BR/QRCode.md +1 -1
- package/concepts/pt-BR/ResponseFields.md +29 -41
- package/concepts/pt-BR/SubAgent-Search.md +12 -12
- package/concepts/pt-BR/Teammate.md +17 -17
- package/concepts/pt-BR/ToolsFirst.md +41 -47
- package/concepts/pt-BR/TranslateContextPollution.md +9 -9
- package/concepts/pt-BR/UltraPlan.md +38 -38
- package/concepts/ru/BodyDiffJSON.md +1 -1
- package/concepts/ru/BodyFields.md +30 -33
- package/concepts/ru/GlobalSettings.md +165 -167
- package/concepts/ru/MainAgent.md +3 -4
- package/concepts/ru/ProxySwitch.md +3 -3
- package/concepts/ru/QRCode.md +1 -1
- package/concepts/ru/ResponseFields.md +29 -41
- package/concepts/ru/SubAgent-Search.md +12 -12
- package/concepts/ru/Teammate.md +17 -17
- package/concepts/ru/ToolsFirst.md +41 -47
- package/concepts/ru/TranslateContextPollution.md +9 -9
- package/concepts/ru/UltraPlan.md +38 -38
- package/concepts/th/BodyDiffJSON.md +1 -1
- package/concepts/th/BodyFields.md +30 -33
- package/concepts/th/GlobalSettings.md +165 -167
- package/concepts/th/MainAgent.md +3 -4
- package/concepts/th/ProxySwitch.md +3 -3
- package/concepts/th/QRCode.md +1 -1
- package/concepts/th/ResponseFields.md +29 -41
- package/concepts/th/SubAgent-Search.md +12 -12
- package/concepts/th/Teammate.md +17 -17
- package/concepts/th/ToolsFirst.md +42 -48
- package/concepts/th/TranslateContextPollution.md +9 -9
- package/concepts/th/UltraPlan.md +38 -38
- package/concepts/tr/BodyDiffJSON.md +1 -1
- package/concepts/tr/BodyFields.md +30 -33
- package/concepts/tr/GlobalSettings.md +165 -167
- package/concepts/tr/MainAgent.md +3 -4
- package/concepts/tr/ProxySwitch.md +3 -3
- package/concepts/tr/QRCode.md +1 -1
- package/concepts/tr/ResponseFields.md +29 -41
- package/concepts/tr/SubAgent-Search.md +12 -12
- package/concepts/tr/Teammate.md +16 -16
- package/concepts/tr/ToolsFirst.md +42 -48
- package/concepts/tr/TranslateContextPollution.md +9 -9
- package/concepts/tr/UltraPlan.md +38 -38
- package/concepts/uk/BodyDiffJSON.md +1 -1
- package/concepts/uk/BodyFields.md +30 -33
- package/concepts/uk/GlobalSettings.md +165 -167
- package/concepts/uk/MainAgent.md +3 -4
- package/concepts/uk/ProxySwitch.md +3 -3
- package/concepts/uk/QRCode.md +1 -1
- package/concepts/uk/ResponseFields.md +29 -41
- package/concepts/uk/SubAgent-Search.md +12 -12
- package/concepts/uk/Teammate.md +17 -17
- package/concepts/uk/ToolsFirst.md +42 -48
- package/concepts/uk/TranslateContextPollution.md +9 -9
- package/concepts/uk/UltraPlan.md +38 -38
- package/concepts/zh/BodyDiffJSON.md +4 -4
- package/concepts/zh/BodyFields.md +17 -20
- package/concepts/zh/GlobalSettings.md +26 -28
- package/concepts/zh/MainAgent.md +6 -6
- package/concepts/zh/ProxySwitch.md +1 -1
- package/concepts/zh/ResponseFields.md +7 -18
- package/concepts/zh/SubAgent-Search.md +2 -2
- package/concepts/zh/Teammate.md +4 -4
- package/concepts/zh/Tool-apply_patch.md +11 -0
- package/concepts/zh/Tool-create_goal.md +8 -0
- package/concepts/zh/Tool-exec.md +88 -0
- package/concepts/zh/Tool-followup_task.md +10 -0
- package/concepts/zh/Tool-get_goal.md +5 -0
- package/concepts/zh/Tool-image_generation.md +5 -0
- package/concepts/zh/Tool-interrupt_agent.md +9 -0
- package/concepts/zh/Tool-list_agents.md +9 -0
- package/concepts/zh/Tool-list_mcp_resource_templates.md +8 -0
- package/concepts/zh/Tool-list_mcp_resources.md +8 -0
- package/concepts/zh/Tool-read_mcp_resource.md +10 -0
- package/concepts/zh/Tool-request_user_input.md +9 -0
- package/concepts/zh/Tool-send_message.md +10 -0
- package/concepts/zh/Tool-shell_command.md +12 -0
- package/concepts/zh/Tool-spawn_agent.md +11 -0
- package/concepts/zh/Tool-tool_search.md +12 -0
- package/concepts/zh/Tool-update_goal.md +9 -0
- package/concepts/zh/Tool-update_plan.md +9 -0
- package/concepts/zh/Tool-view_image.md +10 -0
- package/concepts/zh/Tool-wait.md +12 -0
- package/concepts/zh/Tool-wait_agent.md +9 -0
- package/concepts/zh/Tool-web_search.md +11 -0
- package/concepts/zh/Tools.md +29 -58
- package/concepts/zh/ToolsFirst.md +21 -27
- package/concepts/zh/TranslateContextPollution.md +10 -38
- package/concepts/zh/UltraPlan.md +7 -7
- package/concepts/zh-TW/BodyFields.md +27 -30
- package/concepts/zh-TW/GlobalSettings.md +26 -28
- package/concepts/zh-TW/MainAgent.md +0 -1
- package/concepts/zh-TW/ProxySwitch.md +22 -22
- package/concepts/zh-TW/ResponseFields.md +29 -40
- package/concepts/zh-TW/SubAgent-Search.md +12 -12
- package/concepts/zh-TW/Teammate.md +16 -16
- package/concepts/zh-TW/ToolsFirst.md +41 -47
- package/concepts/zh-TW/UltraPlan.md +38 -38
- package/dist/assets/App-BZ9oclBj.js +2 -0
- package/dist/assets/App-rWON7PuS.css +1 -0
- package/dist/assets/MdxEditorPanel-B4lZ7Z_A.js +1 -0
- package/dist/assets/MdxEditorPanel-Da_ceLFo.css +1 -0
- package/dist/assets/Mobile-B5V_-6Hc.css +1 -0
- package/dist/assets/Mobile-C7Hy-TIM.js +1 -0
- package/dist/assets/abnfDiagram-VRR7QNED-DYYppIQV.js +1 -0
- package/dist/assets/apl-B8ZmNFqK.js +1 -0
- package/dist/assets/arc-BT3um7r4.js +1 -0
- package/dist/assets/architectureDiagram-ZJ3FMSHR-uweBqnh-.js +1 -0
- package/dist/assets/asciiarmor-CIte6N-S.js +1 -0
- package/dist/assets/asn1-Ax1-z1nk.js +1 -0
- package/dist/assets/asterisk-CLHf-iRz.js +1 -0
- package/dist/assets/blockDiagram-677ZJIJ3-DzeI4GyV.js +1 -0
- package/dist/assets/brainfuck-CdLnmYpe.js +1 -0
- package/dist/assets/c4Diagram-LMCZKHZV-DBI0K7wS.js +1 -0
- package/dist/assets/channel-BrN6yx0e.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-DK9JA8Ct.js +1 -0
- package/dist/assets/chunk-32BRIVSS-VNg3Ylzs.js +1 -0
- package/dist/assets/chunk-5VM5RSS4-DFhufY9o.js +1 -0
- package/dist/assets/chunk-EX3LRPZG-B93MHNBT.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-C54OW1iN.js +1 -0
- package/dist/assets/chunk-MOJQB5TN-lKhWWIDe.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-BK2jDXwB.js +1 -0
- package/dist/assets/chunk-V7JOEXUC-aLHBmfOc.js +1 -0
- package/dist/assets/chunk-VR4S4FIN-Bomuor1h.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-BPj05P7g.js +1 -0
- package/dist/assets/classDiagram-OUVF2IWQ-COm0X4uJ.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-COm0X4uJ.js +1 -0
- package/dist/assets/clike-BDgEuk0n.js +1 -0
- package/dist/assets/clojure-CrPxgQZl.js +1 -0
- package/dist/assets/cmake-BknZiLBu.js +1 -0
- package/dist/assets/cobol-DN1mHbgw.js +1 -0
- package/dist/assets/coffeescript-DTAwRC7M.js +1 -0
- package/dist/assets/commonlisp-B4DJ--Bo.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-DU44QHSb.js +1 -0
- package/dist/assets/crystal-DoipYOOv.js +1 -0
- package/dist/assets/css-BeQRP3p5.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-C0dowxsF.js +15 -0
- package/dist/assets/cynefinDiagram-TSTJHNR4-C6vNWPUV.js +1 -0
- package/dist/assets/cypher-BzELKutH.js +1 -0
- package/dist/assets/cytoscape.esm-D0POlJZ1.js +9 -0
- package/dist/assets/d-Mh2Rawoc.js +1 -0
- package/dist/assets/dagre-VKFMJZFB-aLG4uEb-.js +1 -0
- package/dist/assets/defaultLocale-DZ7E7S9R.js +1 -0
- package/dist/assets/diagram-FQU43EPY-D5C8GfdU.js +1 -0
- package/dist/assets/diagram-G47NLZAW-C9tRw_xi.js +1 -0
- package/dist/assets/diagram-NH7WQ7WH-AYxTxwJ2.js +1 -0
- package/dist/assets/diagram-OA4YK3LP-BuBE5n9n.js +1 -0
- package/dist/assets/diagram-WEI45ONY-Cda7zYeO.js +1 -0
- package/dist/assets/diff-B1OtBbfz.js +1 -0
- package/dist/assets/dockerfile-Du7niAGi.js +1 -0
- package/dist/assets/dtd-So5hjSa8.js +1 -0
- package/dist/assets/dylan-Co15g-RX.js +1 -0
- package/dist/assets/ebnf-DWrAnno4.js +1 -0
- package/dist/assets/ebnfDiagram-CCIWWBDH-DC_OGyoh.js +1 -0
- package/dist/assets/ecl-8tbf0rUT.js +1 -0
- package/dist/assets/eiffel-BXtlUtEg.js +1 -0
- package/dist/assets/elm-BwiUluiH.js +1 -0
- package/dist/assets/erDiagram-Q63AITRT-D2G21PVY.js +1 -0
- package/dist/assets/erlang-CrHBLi-m.js +1 -0
- package/dist/assets/factor-C7nC06Ta.js +1 -0
- package/dist/assets/fcl-DKWfryFT.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-CEQPYHrk.js +1 -0
- package/dist/assets/forth-Cg8ASoTj.js +1 -0
- package/dist/assets/fortran-BHf_qfFx.js +1 -0
- package/dist/assets/ganttDiagram-NO4QXBWP-JStxUtFR.js +1 -0
- package/dist/assets/gas-cj_2N_z1.js +1 -0
- package/dist/assets/gherkin-BiuTaH40.js +1 -0
- package/dist/assets/gitGraphDiagram-IHSO6WYX-BiojPH-V.js +1 -0
- package/dist/assets/graph-CnJdn47S.js +1 -0
- package/dist/assets/groovy-Bq8cXYUU.js +1 -0
- package/dist/assets/haskell-1ANuaeie.js +1 -0
- package/dist/assets/haxe-BUNSvWf_.js +1 -0
- package/dist/assets/html2canvas.esm-HFdlUwfn.js +20 -0
- package/dist/assets/http-DPC2IRQA.js +1 -0
- package/dist/assets/idl-DDbIciu_.js +1 -0
- package/dist/assets/index-BEyKG3O5.css +1 -0
- package/dist/assets/index-BduJoLQ5.js +1 -0
- package/dist/assets/index-BqwR8F0v.js +1 -0
- package/dist/assets/index-BsycQyrv.js +1 -0
- package/dist/assets/index-CFSDxZyA.js +1 -0
- package/dist/assets/index-CunNLTHc.js +1 -0
- package/dist/assets/index-OGkDFNTK.js +1 -0
- package/dist/assets/index-oxOyrL07.js +2 -0
- package/dist/assets/index-wvaycTGT.js +1 -0
- package/dist/assets/infoDiagram-FWYZ7A6U-Csl_aBIC.js +1 -0
- package/dist/assets/init-DMZMl1PR.js +1 -0
- package/dist/assets/ishikawaDiagram-FXEZZL3T-CjgGuMwr.js +1 -0
- package/dist/assets/javascript-Cet1BUca.js +1 -0
- package/dist/assets/journeyDiagram-5HDEW3XC-DUOy1PCq.js +1 -0
- package/dist/assets/jszip.min-BxIXdRg4.js +12 -0
- package/dist/assets/julia-CdxLCr68.js +1 -0
- package/dist/assets/kanban-definition-HUTT4EX6-Cx7GSrSj.js +1 -0
- package/dist/assets/katex-CfXf84Fz.js +1 -0
- package/dist/assets/layout-CCUE9ZRw.js +1 -0
- package/dist/assets/linear-GeBi6jJp.js +1 -0
- package/dist/assets/livescript-Bw1Vw7Ae.js +1 -0
- package/dist/assets/lua-DM1wbcnc.js +1 -0
- package/dist/assets/map-DQeg2hXV.js +1 -0
- package/dist/assets/mathematica-BXSv1_O1.js +1 -0
- package/dist/assets/mbox-CWY0keg3.js +1 -0
- package/dist/assets/mermaid.core-Cy9iVxov.js +7 -0
- package/dist/assets/mindmap-definition-LN4V7U3C-CUVxVkK5.js +1 -0
- package/dist/assets/mirc-CMLrLVMT.js +1 -0
- package/dist/assets/mllike-naqhAFf1.js +1 -0
- package/dist/assets/modelica-BvadsaHY.js +1 -0
- package/dist/assets/mscgen-DOr-Q10p.js +1 -0
- package/dist/assets/mumps-DKTl5Nyj.js +1 -0
- package/dist/assets/nginx-DWR90-d1.js +1 -0
- package/dist/assets/nsis-ooFGduGn.js +1 -0
- package/dist/assets/ntriples-q5jkU4X1.js +1 -0
- package/dist/assets/octave-Deu9Ci7_.js +1 -0
- package/dist/assets/orbiting-BCD4Vuij.svg +7 -0
- package/dist/assets/ordinal-DA-_8pMy.js +1 -0
- package/dist/assets/oz-cTQm1aoB.js +1 -0
- package/dist/assets/pascal-Oh3VmczP.js +1 -0
- package/dist/assets/pegDiagram-2B236MQR-BCF1byLY.js +1 -0
- package/dist/assets/perl-BddWF1HQ.js +1 -0
- package/dist/assets/pieDiagram-ENE6RG2P-D2cZTo8-.js +1 -0
- package/dist/assets/pig-g5ZQNIDr.js +1 -0
- package/dist/assets/powershell-CNmQNFVJ.js +1 -0
- package/dist/assets/properties-B41DXfv6.js +1 -0
- package/dist/assets/protobuf-eEBl31OJ.js +1 -0
- package/dist/assets/pug-DRSo4eCK.js +1 -0
- package/dist/assets/puppet-ClfRubAD.js +1 -0
- package/dist/assets/python-BSflc812.js +1 -0
- package/dist/assets/q-BBDx3N9Y.js +1 -0
- package/dist/assets/quadrantDiagram-ABIIQ3AL-Bwsn2--F.js +1 -0
- package/dist/assets/r-CByC1eO8.js +1 -0
- package/dist/assets/railroadDiagram-RFXS5EU6-CLGLpWHs.js +1 -0
- package/dist/assets/requirementDiagram-TGXJPOKE-BfmBYwZl.js +1 -0
- package/dist/assets/rpm-8t935XkD.js +1 -0
- package/dist/assets/ruby-CAgbkLZh.js +1 -0
- package/dist/assets/sankeyDiagram-HTMAVEWB-CqZhbyFn.js +1 -0
- package/dist/assets/sas-C-JWcRvo.js +1 -0
- package/dist/assets/scheme-D7o9RjUX.js +1 -0
- package/dist/assets/seqResourceLoaders-Bkh2Rb9i.js +2 -0
- package/dist/assets/seqResourceLoaders-Do7Kqg3B.css +41 -0
- package/dist/assets/sequenceDiagram-DBY2YBRQ-CcOdXkN2.js +1 -0
- package/dist/assets/shell-BXA79DV6.js +1 -0
- package/dist/assets/shimmer-BBDejmDf.svg +7 -0
- package/dist/assets/sieve-DNwyl0rd.js +1 -0
- package/dist/assets/simple-mode-CoQRusFX.js +1 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-B4OVmPqu.js +1 -0
- package/dist/assets/smalltalk-DggktV6S.js +1 -0
- package/dist/assets/solr-D7NRuIPM.js +1 -0
- package/dist/assets/sparql-Cr71deJQ.js +1 -0
- package/dist/assets/spreadsheet-BMD3tYqw.js +1 -0
- package/dist/assets/sql-CbOjXfBR.js +1 -0
- package/dist/assets/stateDiagram-2N3HPSRC-Dysun0oO.js +1 -0
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BFet8xAz.js +1 -0
- package/dist/assets/stex-iDPIkRmr.js +1 -0
- package/dist/assets/stylus-nWZizI-c.js +1 -0
- package/dist/assets/swift-DOhLK5SG.js +1 -0
- package/dist/assets/swimlanes-5IMT3BWC-_30OXwXy.js +2 -0
- package/dist/assets/swimlanesDiagram-G3AALYLV-C19viY0G.js +1 -0
- package/dist/assets/tcl-CQhJd1WY.js +1 -0
- package/dist/assets/textile-D9XqfVtL.js +1 -0
- package/dist/assets/tiddlywiki-DfOYgVky.js +1 -0
- package/dist/assets/tiki-a0_YLWvV.js +1 -0
- package/dist/assets/timeline-definition-FHXFAJF6-X1Xk3onD.js +1 -0
- package/dist/assets/toml-ekl3F1YB.js +1 -0
- package/dist/assets/troff-Bh_pcXF1.js +1 -0
- package/dist/assets/ttcn-M6363IRW.js +1 -0
- package/dist/assets/ttcn-cfg-7I2DgJEN.js +1 -0
- package/dist/assets/turtle-B5jdxH6D.js +1 -0
- package/dist/assets/vb-DM-xPmVv.js +1 -0
- package/dist/assets/vbscript-B0qu41Yc.js +1 -0
- package/dist/assets/velocity-FCgsuVHf.js +1 -0
- package/dist/assets/vendor-antd-Bzi1WuBb.js +17 -0
- package/dist/assets/vendor-codemirror-CKTrJd47.js +1 -0
- package/dist/assets/vendor-highlight-IFTJ9nxW.js +1 -0
- package/dist/assets/vendor-markdown-BN3QLsBu.js +3 -0
- package/dist/assets/vendor-mdxeditor-4PbKH6K7.js +2 -0
- package/dist/assets/vendor-qrcode-Bnjm0rcu.js +6 -0
- package/dist/assets/vendor-react-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-virtuoso-2P4SowHC.js +1 -0
- package/dist/assets/vendor-xterm-DckPrCx1.js +37 -0
- package/dist/assets/vennDiagram-L72KCM5P-D3fQnGq7.js +1 -0
- package/dist/assets/verilog-nrfFvR12.js +1 -0
- package/dist/assets/vhdl-WlDXtV-W.js +1 -0
- package/dist/assets/wardleyDiagram-EHGQE667-CRhqI6-g.js +1 -0
- package/dist/assets/webidl-RIQoSXDx.js +1 -0
- package/dist/assets/xquery-BN45G3lJ.js +1 -0
- package/dist/assets/xychartDiagram-FW5EYKEG-Cmtgmmtg.js +1 -0
- package/dist/assets/yacas-U6gaIVhD.js +1 -0
- package/dist/assets/z80-CFe5BHbK.js +1 -0
- package/dist/index.html +25 -6
- package/dist/voice-packs/default/A_choice_for_your_consideration_sir.MP3 +0 -0
- package/dist/voice-packs/default/It_is_done_sir.MP3 +0 -0
- package/dist/voice-packs/default/The_plan_awaits_your_approval_sir.MP3 +0 -0
- package/dist/voice-packs/default/pack.json +24 -0
- package/dist/voice-packs/sanguo/ask.MP3 +0 -0
- package/dist/voice-packs/sanguo/end.MP3 +0 -0
- package/dist/voice-packs/sanguo/pack.json +24 -0
- package/dist/voice-packs/sanguo/plan.MP3 +0 -0
- package/electron/diag.js +110 -0
- package/electron/main.js +1432 -0
- package/electron/menu-model.js +107 -0
- package/electron/tab-bar.html +575 -0
- package/electron/tab-content-preload.js +66 -0
- package/electron/tab-preload.js +24 -0
- package/electron/tab-worker.js +142 -0
- package/electron/window-state.js +60 -0
- package/electron/workspace-preload.js +19 -0
- package/findcx.js +4 -0
- package/i18n.js +3 -423
- package/index.html +30 -0
- package/interceptor.js +144 -70
- package/jsconfig.json +25 -0
- package/lib/approval-reviewer.js +23 -0
- package/lib/appserver-bridge.js +1753 -237
- package/lib/ask-bridge.js +5 -5
- package/lib/cli-args.js +57 -0
- package/lib/codex-config.js +157 -18
- package/lib/context-watcher.js +6 -65
- package/lib/delta-reconstructor.js +74 -52
- package/lib/ensure-hooks.js +37 -30
- package/lib/interceptor-core.js +490 -43
- package/lib/log-watcher.js +3 -8
- package/lib/main-agent-entry.js +68 -0
- package/lib/openai-body.js +95 -0
- package/lib/otel-adapter.js +3 -2
- package/lib/otel-receiver.js +1 -2
- package/lib/perm-bridge.js +84 -44
- package/lib/proxy-env.js +45 -1
- package/lib/sdk-adapter.js +241 -51
- package/lib/sdk-manager.js +523 -370
- package/lib/skills-api.js +364 -0
- package/lib/stats-worker.js +21 -21
- package/lib/token-usage.js +29 -0
- package/lib/updater.js +3 -7
- package/package.json +27 -6
- package/plugins/.gitkeep +0 -0
- package/plugins/README.md +8 -0
- package/proxy.js +187 -241
- package/pty-manager.js +17 -1
- package/public/favicon.ico +0 -0
- package/public/voice-packs/default/A_choice_for_your_consideration_sir.MP3 +0 -0
- package/public/voice-packs/default/It_is_done_sir.MP3 +0 -0
- package/public/voice-packs/default/The_plan_awaits_your_approval_sir.MP3 +0 -0
- package/public/voice-packs/default/pack.json +24 -0
- package/public/voice-packs/sanguo/ask.MP3 +0 -0
- package/public/voice-packs/sanguo/end.MP3 +0 -0
- package/public/voice-packs/sanguo/pack.json +24 -0
- package/public/voice-packs/sanguo/plan.MP3 +0 -0
- package/scripts/check-no-control-bytes.js +130 -0
- package/scripts/check-sign-config.sh +85 -0
- package/scripts/gen-default-voicepack.js +176 -0
- package/scripts/mac-sign.sh +47 -0
- package/scripts/npm-pack-dry-run.mjs +20 -0
- package/server/lib/approval-modal-prefs.js +72 -0
- package/server/lib/context-rules.js +70 -0
- package/server/lib/delta-reconstructor.js +403 -0
- package/server/lib/tools-xml-formatter.js +91 -0
- package/server/lib/voice-pack-events.js +79 -0
- package/server.js +420 -86
- package/src/App.jsx +638 -0
- package/src/App.module.css +1088 -0
- package/src/AppBase.jsx +2803 -0
- package/src/Mobile.jsx +1348 -0
- package/src/components/approval/ApprovalModal.jsx +319 -0
- package/src/components/approval/ApprovalModal.module.css +160 -0
- package/src/components/approval/ToolApprovalPanel.jsx +111 -0
- package/src/components/approval/ToolApprovalPanel.module.css +250 -0
- package/src/components/chat/ApprovalPortalContext.js +26 -0
- package/src/components/chat/AskQuestionForm.jsx +279 -0
- package/src/components/chat/AskTimeoutCountdown.jsx +113 -0
- package/src/components/chat/ChatInputBar.jsx +584 -0
- package/src/components/chat/ChatInputBar.module.css +656 -0
- package/src/components/chat/ChatMessage.jsx +1587 -0
- package/src/components/chat/ChatMessage.module.css +1560 -0
- package/src/components/chat/ChatView.jsx +3533 -0
- package/src/components/chat/ChatView.module.css +418 -0
- package/src/components/chat/DiffView.jsx +111 -0
- package/src/components/chat/DiffView.module.css +141 -0
- package/src/components/chat/RoleFilterBar.jsx +35 -0
- package/src/components/chat/RoleFilterBar.module.css +62 -0
- package/src/components/chat/controllers/askFlowController.js +789 -0
- package/src/components/chat/controllers/permissionController.js +84 -0
- package/src/components/chat/controllers/ptyPromptController.js +166 -0
- package/src/components/chat/controllers/scrollHighlightController.js +50 -0
- package/src/components/chat/controllers/splitDragController.js +135 -0
- package/src/components/chat/controllers/toolFileChangeController.js +216 -0
- package/src/components/chat/interactionOwnership.js +106 -0
- package/src/components/chat/uploadDeferLogic.js +46 -0
- package/src/components/common/ConceptHelp.jsx +97 -0
- package/src/components/common/ConceptHelp.module.css +151 -0
- package/src/components/common/ConfirmRemoveButton.jsx +78 -0
- package/src/components/common/CountryFlag.jsx +105 -0
- package/src/components/common/CountryFlag.module.css +36 -0
- package/src/components/common/DingTalkIcon.jsx +20 -0
- package/src/components/common/DiscordIcon.jsx +21 -0
- package/src/components/common/FeishuIcon.jsx +23 -0
- package/src/components/common/HoverPreviewTip.jsx +62 -0
- package/src/components/common/HoverPreviewTip.module.css +21 -0
- package/src/components/common/ImageLightbox.jsx +244 -0
- package/src/components/common/ImageLightbox.module.css +120 -0
- package/src/components/common/MemoryDetailModal.jsx +75 -0
- package/src/components/common/OpenFolderIcon.jsx +53 -0
- package/src/components/common/OpenFolderIcon.module.css +7 -0
- package/src/components/common/PanelResizer.jsx +48 -0
- package/src/components/common/PanelResizer.module.css +11 -0
- package/src/components/common/QuickAutoApproveRows.jsx +66 -0
- package/src/components/common/RefreshIcon.jsx +50 -0
- package/src/components/common/RefreshIcon.module.css +26 -0
- package/src/components/common/SnapLineOverlay.jsx +47 -0
- package/src/components/common/SnapLineOverlay.module.css +31 -0
- package/src/components/common/ToolsHelp.jsx +113 -0
- package/src/components/common/ToolsHelp.module.css +85 -0
- package/src/components/common/WeComIcon.jsx +22 -0
- package/src/components/common/quickMenuIcons.jsx +99 -0
- package/src/components/common/sharedChrome.module.css +274 -0
- package/src/components/dashboard/AppHeader.jsx +2085 -0
- package/src/components/dashboard/AppHeader.module.css +676 -0
- package/src/components/dashboard/CachePopoverContent.jsx +538 -0
- package/src/components/dashboard/CachePopoverContent.module.css +191 -0
- package/src/components/dashboard/ContextTab.jsx +769 -0
- package/src/components/dashboard/ContextTab.module.css +565 -0
- package/src/components/dashboard/DetailPanel.jsx +813 -0
- package/src/components/dashboard/DetailPanel.module.css +513 -0
- package/src/components/dashboard/LiveTagPopover.jsx +89 -0
- package/src/components/dashboard/LiveTagPopover.module.css +67 -0
- package/src/components/dashboard/RequestList.jsx +146 -0
- package/src/components/dashboard/RequestList.module.css +146 -0
- package/src/components/dashboard/TeamSessionPanel.jsx +584 -0
- package/src/components/dashboard/TeamSessionPanel.module.css +457 -0
- package/src/components/dashboard/UsageWindowPill.jsx +166 -0
- package/src/components/dashboard/UsageWindowPill.module.css +140 -0
- package/src/components/dashboard/WorkflowRunsPanel.jsx +108 -0
- package/src/components/dashboard/WorkflowRunsPanel.module.css +72 -0
- package/src/components/dashboard/WorkspaceList.jsx +410 -0
- package/src/components/dashboard/WorkspaceList.module.css +254 -0
- package/src/components/files/FileContentView.jsx +1013 -0
- package/src/components/files/FileContentView.module.css +418 -0
- package/src/components/files/FileExplorer.jsx +937 -0
- package/src/components/files/FileExplorer.module.css +173 -0
- package/src/components/files/MdxEditorPanel.base.css +9 -0
- package/src/components/files/MdxEditorPanel.jsx +181 -0
- package/src/components/files/MdxEditorPanel.module.css +917 -0
- package/src/components/git/DiffMiniMap.jsx +98 -0
- package/src/components/git/DiffMiniMap.module.css +17 -0
- package/src/components/git/FullFileDiffView.jsx +265 -0
- package/src/components/git/FullFileDiffView.module.css +176 -0
- package/src/components/git/GitChanges.jsx +461 -0
- package/src/components/git/GitChanges.module.css +337 -0
- package/src/components/git/GitDiffView.jsx +161 -0
- package/src/components/git/GitDiffView.module.css +205 -0
- package/src/components/mobile/MobileDrawerCloseButton.jsx +18 -0
- package/src/components/mobile/MobileFileExplorer.jsx +231 -0
- package/src/components/mobile/MobileFileExplorer.module.css +171 -0
- package/src/components/mobile/MobileGitDiff.jsx +293 -0
- package/src/components/mobile/MobileGitDiff.module.css +317 -0
- package/src/components/mobile/MobileStats.jsx +211 -0
- package/src/components/mobile/MobileStats.module.css +75 -0
- package/src/components/settings/ImAppendSystemModal.jsx +104 -0
- package/src/components/settings/ImConversationModal.jsx +328 -0
- package/src/components/settings/ImConversationModal.module.css +49 -0
- package/src/components/settings/ImPlatformSettings.jsx +453 -0
- package/src/components/settings/ImPlatformSettings.module.css +110 -0
- package/src/components/settings/ImSkillsModal.jsx +94 -0
- package/src/components/settings/ImStatusChip.jsx +80 -0
- package/src/components/settings/ImStatusChip.module.css +36 -0
- package/src/components/settings/MessagingModal.jsx +65 -0
- package/src/components/settings/MessagingModal.module.css +95 -0
- package/src/components/settings/ModelPromptTabs.jsx +118 -0
- package/src/components/settings/PluginModal.jsx +423 -0
- package/src/components/settings/PluginModal.module.css +136 -0
- package/src/components/settings/PreferencesForm.jsx +104 -0
- package/src/components/settings/PreferencesForm.module.css +27 -0
- package/src/components/settings/ProcessModal.jsx +175 -0
- package/src/components/settings/ProcessModal.module.css +21 -0
- package/src/components/settings/ProjectAliasEditor.jsx +138 -0
- package/src/components/settings/ProjectAliasEditor.module.css +87 -0
- package/src/components/settings/ProjectPrefsManagerModal.jsx +123 -0
- package/src/components/settings/ProjectPrefsManagerModal.module.css +52 -0
- package/src/components/settings/ProxyModal.jsx +286 -0
- package/src/components/settings/ProxyModal.module.css +141 -0
- package/src/components/settings/SkillsManagerModal.jsx +143 -0
- package/src/components/settings/SkillsManagerModal.module.css +143 -0
- package/src/components/settings/SystemTextModal.jsx +312 -0
- package/src/components/settings/SystemTextModal.module.css +331 -0
- package/src/components/settings/VoicePackSettings.jsx +319 -0
- package/src/components/settings/VoicePackSettings.module.css +151 -0
- package/src/components/settings/imPlatforms.js +177 -0
- package/src/components/terminal/CustomUltraplanEditModal.jsx +227 -0
- package/src/components/terminal/CustomUltraplanEditModal.module.css +189 -0
- package/src/components/terminal/PresetExpertPickerModal.jsx +116 -0
- package/src/components/terminal/PresetExpertPickerModal.module.css +90 -0
- package/src/components/terminal/PresetModal.jsx +118 -0
- package/src/components/terminal/ScratchTerminal.jsx +359 -0
- package/src/components/terminal/TerminalPanel.jsx +2021 -0
- package/src/components/terminal/TerminalPanel.module.css +872 -0
- package/src/components/terminal/TerminalWsContext.jsx +226 -0
- package/src/components/terminal/UltraPlanModal.jsx +328 -0
- package/src/components/terminal/UltraPlanModal.module.css +527 -0
- package/src/components/terminal/UltraplanExpertManagerModal.jsx +114 -0
- package/src/components/terminal/UltraplanExpertManagerModal.module.css +91 -0
- package/src/components/terminal/UltraplanPanel.jsx +307 -0
- package/src/components/terminal/UltraplanPanel.module.css +540 -0
- package/src/components/terminal/terminalThemes.js +33 -0
- package/src/components/viewers/ImageViewer.jsx +288 -0
- package/src/components/viewers/ImageViewer.module.css +203 -0
- package/src/components/viewers/JsonViewer.jsx +40 -0
- package/src/components/viewers/JsonViewer.module.css +9 -0
- package/src/components/viewers/LogTable.jsx +131 -0
- package/src/components/viewers/MarkdownBlock.jsx +112 -0
- package/src/components/viewers/MarkdownBlock.module.css +157 -0
- package/src/components/viewers/ToolResultView.jsx +389 -0
- package/src/components/viewers/ToolResultView.module.css +108 -0
- package/src/components/viewers/WebResultsView.jsx +74 -0
- package/src/components/viewers/WebResultsView.module.css +85 -0
- package/src/components/viewers/WorkflowLiveHud.jsx +153 -0
- package/src/components/viewers/WorkflowLiveHud.module.css +256 -0
- package/src/components/viewers/WorkflowPanel.jsx +243 -0
- package/src/components/viewers/WorkflowPanel.module.css +265 -0
- package/src/components/viewers/WorkflowTimeline.jsx +83 -0
- package/src/components/viewers/WorkflowTimeline.module.css +116 -0
- package/src/contexts/SettingsContext.jsx +140 -0
- package/src/env.js +54 -0
- package/src/global.css +607 -0
- package/src/hooks/useMarkdownExport.js +146 -0
- package/src/hooks/useMermaidRender.js +193 -0
- package/src/hooks/useProjectAlias.js +29 -0
- package/src/hooks/useSessionStoragePersistedSet.js +41 -0
- package/src/i18n/mdxTranslations.js +472 -0
- package/src/i18n.js +12409 -0
- package/src/img/ccv-logo.png +0 -0
- package/src/img/codex/entrance.svg +7 -0
- package/src/img/codex/exit.svg +7 -0
- package/src/img/codex/orbiting.svg +7 -0
- package/src/img/codex/shimmer.svg +7 -0
- package/src/img/codex/thinking.svg +7 -0
- package/src/img/codex/tickle.svg +7 -0
- package/src/img/codex/waiting.svg +7 -0
- package/src/img/codex/writing.svg +7 -0
- package/src/img/default-avatar.svg +1 -0
- package/src/img/default-model-avatar.svg +1 -0
- package/src/img/loading-pet.gif +0 -0
- package/src/img/model-codex.svg +2 -0
- package/src/img/model-deepseek.svg +2 -0
- package/src/img/model-gemini.svg +2 -0
- package/src/img/model-glm.svg +2 -0
- package/src/img/model-kimi.svg +2 -0
- package/src/img/model-minimax.svg +2 -0
- package/src/img/model-openai.svg +2 -0
- package/src/img/model-qwen.svg +2 -0
- package/src/img/teammates/DESIGN.md +165 -0
- package/src/img/teammates/analyst.svg +32 -0
- package/src/img/teammates/auditor.svg +28 -0
- package/src/img/teammates/builder.svg +33 -0
- package/src/img/teammates/default.svg +25 -0
- package/src/img/teammates/designer.svg +25 -0
- package/src/img/teammates/executor.svg +31 -0
- package/src/img/teammates/expert.svg +30 -0
- package/src/img/teammates/explorer.svg +30 -0
- package/src/img/teammates/implementer.svg +34 -0
- package/src/img/teammates/investigator.svg +29 -0
- package/src/img/teammates/marvel/analyst.svg +26 -0
- package/src/img/teammates/marvel/auditor.svg +28 -0
- package/src/img/teammates/marvel/builder.svg +15 -0
- package/src/img/teammates/marvel/default.svg +25 -0
- package/src/img/teammates/marvel/designer.svg +21 -0
- package/src/img/teammates/marvel/executor.svg +25 -0
- package/src/img/teammates/marvel/expert.svg +31 -0
- package/src/img/teammates/marvel/explorer.svg +22 -0
- package/src/img/teammates/marvel/implementer.svg +30 -0
- package/src/img/teammates/marvel/investigator.svg +21 -0
- package/src/img/teammates/marvel/researcher.svg +30 -0
- package/src/img/teammates/marvel/reviewer.svg +21 -0
- package/src/img/teammates/marvel/scanner.svg +23 -0
- package/src/img/teammates/marvel/security.svg +30 -0
- package/src/img/teammates/marvel/tracer.svg +19 -0
- package/src/img/teammates/marvel/translator.svg +24 -0
- package/src/img/teammates/marvel/worker.svg +16 -0
- package/src/img/teammates/researcher.svg +35 -0
- package/src/img/teammates/reviewer.svg +23 -0
- package/src/img/teammates/scanner.svg +31 -0
- package/src/img/teammates/security.svg +31 -0
- package/src/img/teammates/tracer.svg +30 -0
- package/src/img/teammates/translator.svg +32 -0
- package/src/img/teammates/worker.svg +29 -0
- package/src/main.jsx +30 -0
- package/src/tokenInterceptor.js +60 -0
- package/src/utils/apiUrl.js +26 -0
- package/src/utils/approvalReviewerOptions.js +27 -0
- package/src/utils/askFallback.js +21 -0
- package/src/utils/askOptionDesc.js +55 -0
- package/src/utils/askPortalMatcher.js +54 -0
- package/src/utils/autoApproveOptions.js +23 -0
- package/src/utils/avatarAnimationPostPass.js +46 -0
- package/src/utils/builtinPresets.js +18 -0
- package/src/utils/clearCheckpoint.js +154 -0
- package/src/utils/commandValidator.js +45 -0
- package/src/utils/contentFilter.js +675 -0
- package/src/utils/contextRaw.js +17 -0
- package/src/utils/contextSidebarNavigation.js +16 -0
- package/src/utils/conversationEntryNormalize.js +497 -0
- package/src/utils/displayScaleHelper.js +47 -0
- package/src/utils/entry-slim.js +594 -0
- package/src/utils/entryCache.js +179 -0
- package/src/utils/errorReport.js +40 -0
- package/src/utils/fileExpandedPathsStorage.js +70 -0
- package/src/utils/fileIcons.jsx +36 -0
- package/src/utils/fileOpen.js +37 -0
- package/src/utils/formatters.js +48 -0
- package/src/utils/gitApi.js +50 -0
- package/src/utils/gitTreeBuilder.js +19 -0
- package/src/utils/helpers.js +462 -0
- package/src/utils/identityHeal.js +83 -0
- package/src/utils/imOrigin.js +24 -0
- package/src/utils/imTr.js +7 -0
- package/src/utils/imageCompress.js +83 -0
- package/src/utils/imageDownscale.js +28 -0
- package/src/utils/imageResize.js +112 -0
- package/src/utils/ingestPipeline.js +47 -0
- package/src/utils/lazyWithReload.js +53 -0
- package/src/utils/markdown.js +30 -0
- package/src/utils/markdownIncremental.js +39 -0
- package/src/utils/markdownProfiler.js +113 -0
- package/src/utils/mdExtensionDetect.js +64 -0
- package/src/utils/memoryLinkParser.js +44 -0
- package/src/utils/modalMask.js +15 -0
- package/src/utils/monotime.js +6 -0
- package/src/utils/pinnedMenu.js +55 -0
- package/src/utils/presetShortcuts.js +14 -0
- package/src/utils/projectAlias.js +135 -0
- package/src/utils/promptClassifier.js +111 -0
- package/src/utils/promptDetect.js +196 -0
- package/src/utils/promptNav.js +60 -0
- package/src/utils/ptyChunkBuilder.js +251 -0
- package/src/utils/quickMenuHoverIntent.js +35 -0
- package/src/utils/rateLimitParser.js +249 -0
- package/src/utils/readResultPool.js +106 -0
- package/src/utils/refreshCachedItemProp.js +32 -0
- package/src/utils/requestTools.js +19 -0
- package/src/utils/requestType.js +284 -0
- package/src/utils/resizeCalc.js +21 -0
- package/src/utils/resolveLocalized.js +17 -0
- package/src/utils/seqResourceLoaders.js +79 -0
- package/src/utils/sessionManager.js +694 -0
- package/src/utils/sessionMerge.js +290 -0
- package/src/utils/skillModalController.js +111 -0
- package/src/utils/skillsParser.js +116 -0
- package/src/utils/slashCommandLabels.js +80 -0
- package/src/utils/splitDragCalc.js +73 -0
- package/src/utils/stickyBottomController.js +643 -0
- package/src/utils/svgSanitize.js +48 -0
- package/src/utils/systemTags.js +47 -0
- package/src/utils/tCodex.js +20 -0
- package/src/utils/teamModalBuilder.js +347 -0
- package/src/utils/teamSessionParser.js +186 -0
- package/src/utils/teammateAvatars.js +194 -0
- package/src/utils/teammateDetector.js +106 -0
- package/src/utils/termDiag.js +114 -0
- package/src/utils/terminalClipboard.js +117 -0
- package/src/utils/terminalWriteQueue.js +398 -0
- package/src/utils/thinkingMerge.js +49 -0
- package/src/utils/tokenUsage.js +1 -0
- package/src/utils/toolCatalog.js +31 -0
- package/src/utils/toolNameAliases.js +13 -0
- package/src/utils/toolResultBuilder.js +312 -0
- package/src/utils/toolResultClassifier.js +16 -0
- package/src/utils/toolResultCore.js +161 -0
- package/src/utils/toolsDiff.js +44 -0
- package/src/utils/toolsXmlFormatter.js +3 -0
- package/src/utils/ultraplanController.js +109 -0
- package/src/utils/ultraplanExperts.js +86 -0
- package/src/utils/ultraplanTemplates.js +152 -0
- package/src/utils/userImageRefs.js +49 -0
- package/src/utils/voicePackPlayer.js +233 -0
- package/src/utils/webResultGrouping.js +111 -0
- package/src/utils/workflowFormat.js +52 -0
- package/src/utils/workflowRuns.js +118 -0
- package/src/utils/workflowStore.js +94 -0
- package/vite.config.js +106 -0
- package/concepts/ar/CacheRebuild.md +0 -36
- package/concepts/ar/KVCacheContent.md +0 -49
- package/concepts/ar/Tool-Agent.md +0 -125
- package/concepts/ar/Tool-AskUserQuestion.md +0 -75
- package/concepts/ar/Tool-Bash.md +0 -167
- package/concepts/ar/Tool-Edit.md +0 -46
- package/concepts/ar/Tool-EnterPlanMode.md +0 -128
- package/concepts/ar/Tool-EnterWorktree.md +0 -56
- package/concepts/ar/Tool-ExitPlanMode.md +0 -62
- package/concepts/ar/Tool-Glob.md +0 -39
- package/concepts/ar/Tool-Grep.md +0 -57
- package/concepts/ar/Tool-NotebookEdit.md +0 -33
- package/concepts/ar/Tool-Read.md +0 -56
- package/concepts/ar/Tool-SendMessage.md +0 -41
- package/concepts/ar/Tool-Skill.md +0 -56
- package/concepts/ar/Tool-Task.md +0 -51
- package/concepts/ar/Tool-TaskCreate.md +0 -78
- package/concepts/ar/Tool-TaskGet.md +0 -56
- package/concepts/ar/Tool-TaskList.md +0 -56
- package/concepts/ar/Tool-TaskOutput.md +0 -40
- package/concepts/ar/Tool-TaskStop.md +0 -32
- package/concepts/ar/Tool-TaskUpdate.md +0 -123
- package/concepts/ar/Tool-TeamCreate.md +0 -38
- package/concepts/ar/Tool-TeamDelete.md +0 -30
- package/concepts/ar/Tool-WebFetch.md +0 -55
- package/concepts/ar/Tool-WebSearch.md +0 -58
- package/concepts/ar/Tool-Write.md +0 -39
- package/concepts/ar/Tool-executeCode.md +0 -41
- package/concepts/ar/Tool-getDiagnostics.md +0 -32
- package/concepts/ar/Tools.md +0 -67
- package/concepts/da/CacheRebuild.md +0 -36
- package/concepts/da/KVCacheContent.md +0 -49
- package/concepts/da/Tool-Agent.md +0 -125
- package/concepts/da/Tool-AskUserQuestion.md +0 -75
- package/concepts/da/Tool-Bash.md +0 -167
- package/concepts/da/Tool-Edit.md +0 -46
- package/concepts/da/Tool-EnterPlanMode.md +0 -128
- package/concepts/da/Tool-EnterWorktree.md +0 -56
- package/concepts/da/Tool-ExitPlanMode.md +0 -62
- package/concepts/da/Tool-Glob.md +0 -39
- package/concepts/da/Tool-Grep.md +0 -57
- package/concepts/da/Tool-NotebookEdit.md +0 -33
- package/concepts/da/Tool-Read.md +0 -56
- package/concepts/da/Tool-SendMessage.md +0 -41
- package/concepts/da/Tool-Skill.md +0 -56
- package/concepts/da/Tool-Task.md +0 -51
- package/concepts/da/Tool-TaskCreate.md +0 -78
- package/concepts/da/Tool-TaskGet.md +0 -56
- package/concepts/da/Tool-TaskList.md +0 -56
- package/concepts/da/Tool-TaskOutput.md +0 -40
- package/concepts/da/Tool-TaskStop.md +0 -32
- package/concepts/da/Tool-TaskUpdate.md +0 -123
- package/concepts/da/Tool-TeamCreate.md +0 -38
- package/concepts/da/Tool-TeamDelete.md +0 -30
- package/concepts/da/Tool-WebFetch.md +0 -55
- package/concepts/da/Tool-WebSearch.md +0 -58
- package/concepts/da/Tool-Write.md +0 -39
- package/concepts/da/Tool-executeCode.md +0 -41
- package/concepts/da/Tool-getDiagnostics.md +0 -32
- package/concepts/da/Tools.md +0 -67
- package/concepts/de/CacheRebuild.md +0 -36
- package/concepts/de/KVCacheContent.md +0 -49
- package/concepts/de/Tool-Agent.md +0 -125
- package/concepts/de/Tool-AskUserQuestion.md +0 -75
- package/concepts/de/Tool-Bash.md +0 -167
- package/concepts/de/Tool-Edit.md +0 -46
- package/concepts/de/Tool-EnterPlanMode.md +0 -128
- package/concepts/de/Tool-EnterWorktree.md +0 -56
- package/concepts/de/Tool-ExitPlanMode.md +0 -62
- package/concepts/de/Tool-Glob.md +0 -39
- package/concepts/de/Tool-Grep.md +0 -57
- package/concepts/de/Tool-NotebookEdit.md +0 -33
- package/concepts/de/Tool-Read.md +0 -56
- package/concepts/de/Tool-SendMessage.md +0 -41
- package/concepts/de/Tool-Skill.md +0 -56
- package/concepts/de/Tool-Task.md +0 -51
- package/concepts/de/Tool-TaskCreate.md +0 -78
- package/concepts/de/Tool-TaskGet.md +0 -56
- package/concepts/de/Tool-TaskList.md +0 -56
- package/concepts/de/Tool-TaskOutput.md +0 -40
- package/concepts/de/Tool-TaskStop.md +0 -32
- package/concepts/de/Tool-TaskUpdate.md +0 -123
- package/concepts/de/Tool-TeamCreate.md +0 -38
- package/concepts/de/Tool-TeamDelete.md +0 -30
- package/concepts/de/Tool-WebFetch.md +0 -55
- package/concepts/de/Tool-WebSearch.md +0 -58
- package/concepts/de/Tool-Write.md +0 -39
- package/concepts/de/Tool-executeCode.md +0 -41
- package/concepts/de/Tool-getDiagnostics.md +0 -32
- package/concepts/de/Tools.md +0 -67
- package/concepts/en/CacheRebuild.md +0 -36
- package/concepts/en/KVCacheContent.md +0 -49
- package/concepts/en/Tool-Agent.md +0 -125
- package/concepts/en/Tool-AskUserQuestion.md +0 -75
- package/concepts/en/Tool-Bash.md +0 -167
- package/concepts/en/Tool-Edit.md +0 -46
- package/concepts/en/Tool-EnterPlanMode.md +0 -128
- package/concepts/en/Tool-EnterWorktree.md +0 -56
- package/concepts/en/Tool-ExitPlanMode.md +0 -62
- package/concepts/en/Tool-Glob.md +0 -39
- package/concepts/en/Tool-Grep.md +0 -57
- package/concepts/en/Tool-NotebookEdit.md +0 -33
- package/concepts/en/Tool-Read.md +0 -56
- package/concepts/en/Tool-SendMessage.md +0 -41
- package/concepts/en/Tool-Skill.md +0 -56
- package/concepts/en/Tool-Task.md +0 -51
- package/concepts/en/Tool-TaskCreate.md +0 -78
- package/concepts/en/Tool-TaskGet.md +0 -56
- package/concepts/en/Tool-TaskList.md +0 -56
- package/concepts/en/Tool-TaskOutput.md +0 -40
- package/concepts/en/Tool-TaskStop.md +0 -32
- package/concepts/en/Tool-TaskUpdate.md +0 -123
- package/concepts/en/Tool-TeamCreate.md +0 -38
- package/concepts/en/Tool-TeamDelete.md +0 -30
- package/concepts/en/Tool-WebFetch.md +0 -55
- package/concepts/en/Tool-WebSearch.md +0 -58
- package/concepts/en/Tool-Write.md +0 -39
- package/concepts/en/Tool-executeCode.md +0 -41
- package/concepts/en/Tool-getDiagnostics.md +0 -32
- package/concepts/es/CacheRebuild.md +0 -36
- package/concepts/es/KVCacheContent.md +0 -49
- package/concepts/es/Tool-Agent.md +0 -125
- package/concepts/es/Tool-AskUserQuestion.md +0 -75
- package/concepts/es/Tool-Bash.md +0 -167
- package/concepts/es/Tool-Edit.md +0 -46
- package/concepts/es/Tool-EnterPlanMode.md +0 -128
- package/concepts/es/Tool-EnterWorktree.md +0 -56
- package/concepts/es/Tool-ExitPlanMode.md +0 -62
- package/concepts/es/Tool-Glob.md +0 -39
- package/concepts/es/Tool-Grep.md +0 -57
- package/concepts/es/Tool-NotebookEdit.md +0 -33
- package/concepts/es/Tool-Read.md +0 -56
- package/concepts/es/Tool-SendMessage.md +0 -41
- package/concepts/es/Tool-Skill.md +0 -56
- package/concepts/es/Tool-Task.md +0 -51
- package/concepts/es/Tool-TaskCreate.md +0 -78
- package/concepts/es/Tool-TaskGet.md +0 -56
- package/concepts/es/Tool-TaskList.md +0 -56
- package/concepts/es/Tool-TaskOutput.md +0 -40
- package/concepts/es/Tool-TaskStop.md +0 -32
- package/concepts/es/Tool-TaskUpdate.md +0 -123
- package/concepts/es/Tool-TeamCreate.md +0 -38
- package/concepts/es/Tool-TeamDelete.md +0 -30
- package/concepts/es/Tool-WebFetch.md +0 -55
- package/concepts/es/Tool-WebSearch.md +0 -58
- package/concepts/es/Tool-Write.md +0 -39
- package/concepts/es/Tool-executeCode.md +0 -41
- package/concepts/es/Tool-getDiagnostics.md +0 -32
- package/concepts/es/Tools.md +0 -67
- package/concepts/fr/CacheRebuild.md +0 -36
- package/concepts/fr/KVCacheContent.md +0 -49
- package/concepts/fr/Tool-Agent.md +0 -125
- package/concepts/fr/Tool-AskUserQuestion.md +0 -75
- package/concepts/fr/Tool-Bash.md +0 -167
- package/concepts/fr/Tool-Edit.md +0 -46
- package/concepts/fr/Tool-EnterPlanMode.md +0 -128
- package/concepts/fr/Tool-EnterWorktree.md +0 -56
- package/concepts/fr/Tool-ExitPlanMode.md +0 -62
- package/concepts/fr/Tool-Glob.md +0 -39
- package/concepts/fr/Tool-Grep.md +0 -57
- package/concepts/fr/Tool-NotebookEdit.md +0 -33
- package/concepts/fr/Tool-Read.md +0 -56
- package/concepts/fr/Tool-SendMessage.md +0 -41
- package/concepts/fr/Tool-Skill.md +0 -56
- package/concepts/fr/Tool-Task.md +0 -51
- package/concepts/fr/Tool-TaskCreate.md +0 -78
- package/concepts/fr/Tool-TaskGet.md +0 -56
- package/concepts/fr/Tool-TaskList.md +0 -56
- package/concepts/fr/Tool-TaskOutput.md +0 -40
- package/concepts/fr/Tool-TaskStop.md +0 -32
- package/concepts/fr/Tool-TaskUpdate.md +0 -123
- package/concepts/fr/Tool-TeamCreate.md +0 -38
- package/concepts/fr/Tool-TeamDelete.md +0 -30
- package/concepts/fr/Tool-WebFetch.md +0 -55
- package/concepts/fr/Tool-WebSearch.md +0 -58
- package/concepts/fr/Tool-Write.md +0 -39
- package/concepts/fr/Tool-executeCode.md +0 -41
- package/concepts/fr/Tool-getDiagnostics.md +0 -32
- package/concepts/fr/Tools.md +0 -67
- package/concepts/it/CacheRebuild.md +0 -36
- package/concepts/it/KVCacheContent.md +0 -49
- package/concepts/it/Tool-Agent.md +0 -125
- package/concepts/it/Tool-AskUserQuestion.md +0 -75
- package/concepts/it/Tool-Bash.md +0 -167
- package/concepts/it/Tool-Edit.md +0 -46
- package/concepts/it/Tool-EnterPlanMode.md +0 -128
- package/concepts/it/Tool-EnterWorktree.md +0 -56
- package/concepts/it/Tool-ExitPlanMode.md +0 -62
- package/concepts/it/Tool-Glob.md +0 -39
- package/concepts/it/Tool-Grep.md +0 -57
- package/concepts/it/Tool-NotebookEdit.md +0 -33
- package/concepts/it/Tool-Read.md +0 -56
- package/concepts/it/Tool-SendMessage.md +0 -41
- package/concepts/it/Tool-Skill.md +0 -56
- package/concepts/it/Tool-Task.md +0 -51
- package/concepts/it/Tool-TaskCreate.md +0 -78
- package/concepts/it/Tool-TaskGet.md +0 -56
- package/concepts/it/Tool-TaskList.md +0 -56
- package/concepts/it/Tool-TaskOutput.md +0 -40
- package/concepts/it/Tool-TaskStop.md +0 -32
- package/concepts/it/Tool-TaskUpdate.md +0 -123
- package/concepts/it/Tool-TeamCreate.md +0 -38
- package/concepts/it/Tool-TeamDelete.md +0 -30
- package/concepts/it/Tool-WebFetch.md +0 -55
- package/concepts/it/Tool-WebSearch.md +0 -58
- package/concepts/it/Tool-Write.md +0 -39
- package/concepts/it/Tool-executeCode.md +0 -41
- package/concepts/it/Tool-getDiagnostics.md +0 -32
- package/concepts/it/Tools.md +0 -67
- package/concepts/ja/CacheRebuild.md +0 -36
- package/concepts/ja/KVCacheContent.md +0 -49
- package/concepts/ja/Tool-Agent.md +0 -125
- package/concepts/ja/Tool-AskUserQuestion.md +0 -75
- package/concepts/ja/Tool-Bash.md +0 -167
- package/concepts/ja/Tool-Edit.md +0 -46
- package/concepts/ja/Tool-EnterPlanMode.md +0 -128
- package/concepts/ja/Tool-EnterWorktree.md +0 -56
- package/concepts/ja/Tool-ExitPlanMode.md +0 -62
- package/concepts/ja/Tool-Glob.md +0 -39
- package/concepts/ja/Tool-Grep.md +0 -57
- package/concepts/ja/Tool-NotebookEdit.md +0 -33
- package/concepts/ja/Tool-Read.md +0 -56
- package/concepts/ja/Tool-SendMessage.md +0 -41
- package/concepts/ja/Tool-Skill.md +0 -56
- package/concepts/ja/Tool-Task.md +0 -51
- package/concepts/ja/Tool-TaskCreate.md +0 -78
- package/concepts/ja/Tool-TaskGet.md +0 -56
- package/concepts/ja/Tool-TaskList.md +0 -56
- package/concepts/ja/Tool-TaskOutput.md +0 -40
- package/concepts/ja/Tool-TaskStop.md +0 -32
- package/concepts/ja/Tool-TaskUpdate.md +0 -123
- package/concepts/ja/Tool-TeamCreate.md +0 -38
- package/concepts/ja/Tool-TeamDelete.md +0 -30
- package/concepts/ja/Tool-WebFetch.md +0 -55
- package/concepts/ja/Tool-WebSearch.md +0 -58
- package/concepts/ja/Tool-Write.md +0 -39
- package/concepts/ja/Tool-executeCode.md +0 -41
- package/concepts/ja/Tool-getDiagnostics.md +0 -32
- package/concepts/ja/Tools.md +0 -67
- package/concepts/ko/CacheRebuild.md +0 -36
- package/concepts/ko/KVCacheContent.md +0 -49
- package/concepts/ko/Tool-Agent.md +0 -125
- package/concepts/ko/Tool-AskUserQuestion.md +0 -75
- package/concepts/ko/Tool-Bash.md +0 -167
- package/concepts/ko/Tool-Edit.md +0 -46
- package/concepts/ko/Tool-EnterPlanMode.md +0 -128
- package/concepts/ko/Tool-EnterWorktree.md +0 -56
- package/concepts/ko/Tool-ExitPlanMode.md +0 -62
- package/concepts/ko/Tool-Glob.md +0 -39
- package/concepts/ko/Tool-Grep.md +0 -57
- package/concepts/ko/Tool-NotebookEdit.md +0 -33
- package/concepts/ko/Tool-Read.md +0 -56
- package/concepts/ko/Tool-SendMessage.md +0 -41
- package/concepts/ko/Tool-Skill.md +0 -56
- package/concepts/ko/Tool-Task.md +0 -51
- package/concepts/ko/Tool-TaskCreate.md +0 -78
- package/concepts/ko/Tool-TaskGet.md +0 -56
- package/concepts/ko/Tool-TaskList.md +0 -56
- package/concepts/ko/Tool-TaskOutput.md +0 -40
- package/concepts/ko/Tool-TaskStop.md +0 -32
- package/concepts/ko/Tool-TaskUpdate.md +0 -123
- package/concepts/ko/Tool-TeamCreate.md +0 -38
- package/concepts/ko/Tool-TeamDelete.md +0 -30
- package/concepts/ko/Tool-WebFetch.md +0 -55
- package/concepts/ko/Tool-WebSearch.md +0 -58
- package/concepts/ko/Tool-Write.md +0 -39
- package/concepts/ko/Tool-executeCode.md +0 -41
- package/concepts/ko/Tool-getDiagnostics.md +0 -32
- package/concepts/ko/Tools.md +0 -67
- package/concepts/no/CacheRebuild.md +0 -36
- package/concepts/no/KVCacheContent.md +0 -49
- package/concepts/no/Tool-Agent.md +0 -125
- package/concepts/no/Tool-AskUserQuestion.md +0 -75
- package/concepts/no/Tool-Bash.md +0 -167
- package/concepts/no/Tool-Edit.md +0 -46
- package/concepts/no/Tool-EnterPlanMode.md +0 -128
- package/concepts/no/Tool-EnterWorktree.md +0 -56
- package/concepts/no/Tool-ExitPlanMode.md +0 -62
- package/concepts/no/Tool-Glob.md +0 -39
- package/concepts/no/Tool-Grep.md +0 -57
- package/concepts/no/Tool-NotebookEdit.md +0 -33
- package/concepts/no/Tool-Read.md +0 -56
- package/concepts/no/Tool-SendMessage.md +0 -41
- package/concepts/no/Tool-Skill.md +0 -56
- package/concepts/no/Tool-Task.md +0 -51
- package/concepts/no/Tool-TaskCreate.md +0 -78
- package/concepts/no/Tool-TaskGet.md +0 -56
- package/concepts/no/Tool-TaskList.md +0 -56
- package/concepts/no/Tool-TaskOutput.md +0 -40
- package/concepts/no/Tool-TaskStop.md +0 -32
- package/concepts/no/Tool-TaskUpdate.md +0 -123
- package/concepts/no/Tool-TeamCreate.md +0 -38
- package/concepts/no/Tool-TeamDelete.md +0 -30
- package/concepts/no/Tool-WebFetch.md +0 -55
- package/concepts/no/Tool-WebSearch.md +0 -58
- package/concepts/no/Tool-Write.md +0 -39
- package/concepts/no/Tool-executeCode.md +0 -41
- package/concepts/no/Tool-getDiagnostics.md +0 -32
- package/concepts/no/Tools.md +0 -67
- package/concepts/pl/CacheRebuild.md +0 -36
- package/concepts/pl/KVCacheContent.md +0 -49
- package/concepts/pl/Tool-Agent.md +0 -125
- package/concepts/pl/Tool-AskUserQuestion.md +0 -75
- package/concepts/pl/Tool-Bash.md +0 -167
- package/concepts/pl/Tool-Edit.md +0 -46
- package/concepts/pl/Tool-EnterPlanMode.md +0 -128
- package/concepts/pl/Tool-EnterWorktree.md +0 -56
- package/concepts/pl/Tool-ExitPlanMode.md +0 -62
- package/concepts/pl/Tool-Glob.md +0 -39
- package/concepts/pl/Tool-Grep.md +0 -57
- package/concepts/pl/Tool-NotebookEdit.md +0 -33
- package/concepts/pl/Tool-Read.md +0 -56
- package/concepts/pl/Tool-SendMessage.md +0 -41
- package/concepts/pl/Tool-Skill.md +0 -56
- package/concepts/pl/Tool-Task.md +0 -51
- package/concepts/pl/Tool-TaskCreate.md +0 -78
- package/concepts/pl/Tool-TaskGet.md +0 -56
- package/concepts/pl/Tool-TaskList.md +0 -56
- package/concepts/pl/Tool-TaskOutput.md +0 -40
- package/concepts/pl/Tool-TaskStop.md +0 -32
- package/concepts/pl/Tool-TaskUpdate.md +0 -123
- package/concepts/pl/Tool-TeamCreate.md +0 -38
- package/concepts/pl/Tool-TeamDelete.md +0 -30
- package/concepts/pl/Tool-WebFetch.md +0 -55
- package/concepts/pl/Tool-WebSearch.md +0 -58
- package/concepts/pl/Tool-Write.md +0 -39
- package/concepts/pl/Tool-executeCode.md +0 -41
- package/concepts/pl/Tool-getDiagnostics.md +0 -32
- package/concepts/pl/Tools.md +0 -67
- package/concepts/pt-BR/CacheRebuild.md +0 -36
- package/concepts/pt-BR/KVCacheContent.md +0 -49
- package/concepts/pt-BR/Tool-Agent.md +0 -125
- package/concepts/pt-BR/Tool-AskUserQuestion.md +0 -75
- package/concepts/pt-BR/Tool-Bash.md +0 -167
- package/concepts/pt-BR/Tool-Edit.md +0 -46
- package/concepts/pt-BR/Tool-EnterPlanMode.md +0 -128
- package/concepts/pt-BR/Tool-EnterWorktree.md +0 -56
- package/concepts/pt-BR/Tool-ExitPlanMode.md +0 -62
- package/concepts/pt-BR/Tool-Glob.md +0 -39
- package/concepts/pt-BR/Tool-Grep.md +0 -57
- package/concepts/pt-BR/Tool-NotebookEdit.md +0 -33
- package/concepts/pt-BR/Tool-Read.md +0 -56
- package/concepts/pt-BR/Tool-SendMessage.md +0 -41
- package/concepts/pt-BR/Tool-Skill.md +0 -56
- package/concepts/pt-BR/Tool-Task.md +0 -51
- package/concepts/pt-BR/Tool-TaskCreate.md +0 -78
- package/concepts/pt-BR/Tool-TaskGet.md +0 -56
- package/concepts/pt-BR/Tool-TaskList.md +0 -56
- package/concepts/pt-BR/Tool-TaskOutput.md +0 -40
- package/concepts/pt-BR/Tool-TaskStop.md +0 -32
- package/concepts/pt-BR/Tool-TaskUpdate.md +0 -123
- package/concepts/pt-BR/Tool-TeamCreate.md +0 -38
- package/concepts/pt-BR/Tool-TeamDelete.md +0 -30
- package/concepts/pt-BR/Tool-WebFetch.md +0 -55
- package/concepts/pt-BR/Tool-WebSearch.md +0 -58
- package/concepts/pt-BR/Tool-Write.md +0 -39
- package/concepts/pt-BR/Tool-executeCode.md +0 -41
- package/concepts/pt-BR/Tool-getDiagnostics.md +0 -32
- package/concepts/pt-BR/Tools.md +0 -67
- package/concepts/ru/CacheRebuild.md +0 -36
- package/concepts/ru/KVCacheContent.md +0 -49
- package/concepts/ru/Tool-Agent.md +0 -125
- package/concepts/ru/Tool-AskUserQuestion.md +0 -75
- package/concepts/ru/Tool-Bash.md +0 -167
- package/concepts/ru/Tool-Edit.md +0 -46
- package/concepts/ru/Tool-EnterPlanMode.md +0 -128
- package/concepts/ru/Tool-EnterWorktree.md +0 -56
- package/concepts/ru/Tool-ExitPlanMode.md +0 -62
- package/concepts/ru/Tool-Glob.md +0 -39
- package/concepts/ru/Tool-Grep.md +0 -57
- package/concepts/ru/Tool-NotebookEdit.md +0 -33
- package/concepts/ru/Tool-Read.md +0 -56
- package/concepts/ru/Tool-SendMessage.md +0 -41
- package/concepts/ru/Tool-Skill.md +0 -56
- package/concepts/ru/Tool-Task.md +0 -51
- package/concepts/ru/Tool-TaskCreate.md +0 -78
- package/concepts/ru/Tool-TaskGet.md +0 -56
- package/concepts/ru/Tool-TaskList.md +0 -56
- package/concepts/ru/Tool-TaskOutput.md +0 -40
- package/concepts/ru/Tool-TaskStop.md +0 -32
- package/concepts/ru/Tool-TaskUpdate.md +0 -123
- package/concepts/ru/Tool-TeamCreate.md +0 -38
- package/concepts/ru/Tool-TeamDelete.md +0 -30
- package/concepts/ru/Tool-WebFetch.md +0 -55
- package/concepts/ru/Tool-WebSearch.md +0 -58
- package/concepts/ru/Tool-Write.md +0 -39
- package/concepts/ru/Tool-executeCode.md +0 -41
- package/concepts/ru/Tool-getDiagnostics.md +0 -32
- package/concepts/ru/Tools.md +0 -67
- package/concepts/th/CacheRebuild.md +0 -36
- package/concepts/th/KVCacheContent.md +0 -49
- package/concepts/th/Tool-Agent.md +0 -125
- package/concepts/th/Tool-AskUserQuestion.md +0 -75
- package/concepts/th/Tool-Bash.md +0 -167
- package/concepts/th/Tool-Edit.md +0 -46
- package/concepts/th/Tool-EnterPlanMode.md +0 -128
- package/concepts/th/Tool-EnterWorktree.md +0 -56
- package/concepts/th/Tool-ExitPlanMode.md +0 -62
- package/concepts/th/Tool-Glob.md +0 -39
- package/concepts/th/Tool-Grep.md +0 -57
- package/concepts/th/Tool-NotebookEdit.md +0 -33
- package/concepts/th/Tool-Read.md +0 -56
- package/concepts/th/Tool-SendMessage.md +0 -41
- package/concepts/th/Tool-Skill.md +0 -56
- package/concepts/th/Tool-Task.md +0 -51
- package/concepts/th/Tool-TaskCreate.md +0 -78
- package/concepts/th/Tool-TaskGet.md +0 -56
- package/concepts/th/Tool-TaskList.md +0 -56
- package/concepts/th/Tool-TaskOutput.md +0 -40
- package/concepts/th/Tool-TaskStop.md +0 -32
- package/concepts/th/Tool-TaskUpdate.md +0 -123
- package/concepts/th/Tool-TeamCreate.md +0 -38
- package/concepts/th/Tool-TeamDelete.md +0 -30
- package/concepts/th/Tool-WebFetch.md +0 -55
- package/concepts/th/Tool-WebSearch.md +0 -58
- package/concepts/th/Tool-Write.md +0 -39
- package/concepts/th/Tool-executeCode.md +0 -41
- package/concepts/th/Tool-getDiagnostics.md +0 -32
- package/concepts/th/Tools.md +0 -67
- package/concepts/tr/CacheRebuild.md +0 -36
- package/concepts/tr/KVCacheContent.md +0 -49
- package/concepts/tr/Tool-Agent.md +0 -125
- package/concepts/tr/Tool-AskUserQuestion.md +0 -75
- package/concepts/tr/Tool-Bash.md +0 -167
- package/concepts/tr/Tool-Edit.md +0 -46
- package/concepts/tr/Tool-EnterPlanMode.md +0 -128
- package/concepts/tr/Tool-EnterWorktree.md +0 -56
- package/concepts/tr/Tool-ExitPlanMode.md +0 -62
- package/concepts/tr/Tool-Glob.md +0 -39
- package/concepts/tr/Tool-Grep.md +0 -57
- package/concepts/tr/Tool-NotebookEdit.md +0 -33
- package/concepts/tr/Tool-Read.md +0 -56
- package/concepts/tr/Tool-SendMessage.md +0 -41
- package/concepts/tr/Tool-Skill.md +0 -56
- package/concepts/tr/Tool-Task.md +0 -51
- package/concepts/tr/Tool-TaskCreate.md +0 -78
- package/concepts/tr/Tool-TaskGet.md +0 -56
- package/concepts/tr/Tool-TaskList.md +0 -56
- package/concepts/tr/Tool-TaskOutput.md +0 -40
- package/concepts/tr/Tool-TaskStop.md +0 -32
- package/concepts/tr/Tool-TaskUpdate.md +0 -123
- package/concepts/tr/Tool-TeamCreate.md +0 -38
- package/concepts/tr/Tool-TeamDelete.md +0 -30
- package/concepts/tr/Tool-WebFetch.md +0 -55
- package/concepts/tr/Tool-WebSearch.md +0 -58
- package/concepts/tr/Tool-Write.md +0 -39
- package/concepts/tr/Tool-executeCode.md +0 -41
- package/concepts/tr/Tool-getDiagnostics.md +0 -32
- package/concepts/tr/Tools.md +0 -67
- package/concepts/uk/CacheRebuild.md +0 -36
- package/concepts/uk/KVCacheContent.md +0 -49
- package/concepts/uk/Tool-Agent.md +0 -125
- package/concepts/uk/Tool-AskUserQuestion.md +0 -75
- package/concepts/uk/Tool-Bash.md +0 -167
- package/concepts/uk/Tool-Edit.md +0 -46
- package/concepts/uk/Tool-EnterPlanMode.md +0 -128
- package/concepts/uk/Tool-EnterWorktree.md +0 -56
- package/concepts/uk/Tool-ExitPlanMode.md +0 -62
- package/concepts/uk/Tool-Glob.md +0 -39
- package/concepts/uk/Tool-Grep.md +0 -57
- package/concepts/uk/Tool-NotebookEdit.md +0 -33
- package/concepts/uk/Tool-Read.md +0 -56
- package/concepts/uk/Tool-SendMessage.md +0 -41
- package/concepts/uk/Tool-Skill.md +0 -56
- package/concepts/uk/Tool-Task.md +0 -51
- package/concepts/uk/Tool-TaskCreate.md +0 -78
- package/concepts/uk/Tool-TaskGet.md +0 -56
- package/concepts/uk/Tool-TaskList.md +0 -56
- package/concepts/uk/Tool-TaskOutput.md +0 -40
- package/concepts/uk/Tool-TaskStop.md +0 -32
- package/concepts/uk/Tool-TaskUpdate.md +0 -123
- package/concepts/uk/Tool-TeamCreate.md +0 -38
- package/concepts/uk/Tool-TeamDelete.md +0 -30
- package/concepts/uk/Tool-WebFetch.md +0 -55
- package/concepts/uk/Tool-WebSearch.md +0 -58
- package/concepts/uk/Tool-Write.md +0 -39
- package/concepts/uk/Tool-executeCode.md +0 -41
- package/concepts/uk/Tool-getDiagnostics.md +0 -32
- package/concepts/uk/Tools.md +0 -67
- package/concepts/zh/CacheRebuild.md +0 -36
- package/concepts/zh/KVCacheContent.md +0 -49
- package/concepts/zh/Tool-Agent.md +0 -125
- package/concepts/zh/Tool-AskUserQuestion.md +0 -75
- package/concepts/zh/Tool-Bash.md +0 -167
- package/concepts/zh/Tool-Edit.md +0 -46
- package/concepts/zh/Tool-EnterPlanMode.md +0 -128
- package/concepts/zh/Tool-EnterWorktree.md +0 -56
- package/concepts/zh/Tool-ExitPlanMode.md +0 -62
- package/concepts/zh/Tool-Glob.md +0 -39
- package/concepts/zh/Tool-Grep.md +0 -57
- package/concepts/zh/Tool-NotebookEdit.md +0 -33
- package/concepts/zh/Tool-Read.md +0 -56
- package/concepts/zh/Tool-SendMessage.md +0 -41
- package/concepts/zh/Tool-Skill.md +0 -56
- package/concepts/zh/Tool-Task.md +0 -51
- package/concepts/zh/Tool-TaskCreate.md +0 -78
- package/concepts/zh/Tool-TaskGet.md +0 -56
- package/concepts/zh/Tool-TaskList.md +0 -56
- package/concepts/zh/Tool-TaskOutput.md +0 -40
- package/concepts/zh/Tool-TaskStop.md +0 -32
- package/concepts/zh/Tool-TaskUpdate.md +0 -123
- package/concepts/zh/Tool-TeamCreate.md +0 -38
- package/concepts/zh/Tool-TeamDelete.md +0 -30
- package/concepts/zh/Tool-WebFetch.md +0 -55
- package/concepts/zh/Tool-WebSearch.md +0 -58
- package/concepts/zh/Tool-Write.md +0 -39
- package/concepts/zh/Tool-executeCode.md +0 -41
- package/concepts/zh/Tool-getDiagnostics.md +0 -32
- package/concepts/zh-TW/CacheRebuild.md +0 -36
- package/concepts/zh-TW/KVCacheContent.md +0 -49
- package/concepts/zh-TW/Tool-Agent.md +0 -125
- package/concepts/zh-TW/Tool-AskUserQuestion.md +0 -75
- package/concepts/zh-TW/Tool-Bash.md +0 -167
- package/concepts/zh-TW/Tool-Edit.md +0 -46
- package/concepts/zh-TW/Tool-EnterPlanMode.md +0 -128
- package/concepts/zh-TW/Tool-EnterWorktree.md +0 -56
- package/concepts/zh-TW/Tool-ExitPlanMode.md +0 -62
- package/concepts/zh-TW/Tool-Glob.md +0 -39
- package/concepts/zh-TW/Tool-Grep.md +0 -57
- package/concepts/zh-TW/Tool-NotebookEdit.md +0 -33
- package/concepts/zh-TW/Tool-Read.md +0 -56
- package/concepts/zh-TW/Tool-SendMessage.md +0 -41
- package/concepts/zh-TW/Tool-Skill.md +0 -56
- package/concepts/zh-TW/Tool-Task.md +0 -51
- package/concepts/zh-TW/Tool-TaskCreate.md +0 -78
- package/concepts/zh-TW/Tool-TaskGet.md +0 -56
- package/concepts/zh-TW/Tool-TaskList.md +0 -56
- package/concepts/zh-TW/Tool-TaskOutput.md +0 -40
- package/concepts/zh-TW/Tool-TaskStop.md +0 -32
- package/concepts/zh-TW/Tool-TaskUpdate.md +0 -123
- package/concepts/zh-TW/Tool-TeamCreate.md +0 -38
- package/concepts/zh-TW/Tool-TeamDelete.md +0 -30
- package/concepts/zh-TW/Tool-WebFetch.md +0 -55
- package/concepts/zh-TW/Tool-WebSearch.md +0 -58
- package/concepts/zh-TW/Tool-Write.md +0 -39
- package/concepts/zh-TW/Tool-executeCode.md +0 -41
- package/concepts/zh-TW/Tool-getDiagnostics.md +0 -32
- package/concepts/zh-TW/Tools.md +0 -67
- package/dist/assets/App-CF-qZ3B6.js +0 -41
- package/dist/assets/App-fG2r3jFq.css +0 -1
- package/dist/assets/AppHeader-BqUGBbBx.css +0 -41
- package/dist/assets/AppHeader.module-BjoBBbCB.js +0 -1250
- package/dist/assets/Mobile-BnDkLcBv.css +0 -1
- package/dist/assets/Mobile-ByhBkyqb.js +0 -10
- package/dist/assets/_baseUniq-5XIuSb_1.js +0 -1
- package/dist/assets/arc-V3mJhKFJ.js +0 -1
- package/dist/assets/architectureDiagram-Q4EWVU46-22bjxOE1.js +0 -36
- package/dist/assets/blockDiagram-DXYQGD6D-Brshek1N.js +0 -132
- package/dist/assets/c4Diagram-AHTNJAMY-DQHIuxkd.js +0 -10
- package/dist/assets/channel-SmGaOM6g.js +0 -1
- package/dist/assets/chunk-4BX2VUAB-BQE7pifU.js +0 -1
- package/dist/assets/chunk-4TB4RGXK-DcZwv_PV.js +0 -206
- package/dist/assets/chunk-55IACEB6-Gd_3-vV5.js +0 -1
- package/dist/assets/chunk-EDXVE4YY-BbnYdg9M.js +0 -1
- package/dist/assets/chunk-FMBD7UC4-NSh7z90L.js +0 -15
- package/dist/assets/chunk-OYMX7WX6-DdFwjHsc.js +0 -231
- package/dist/assets/chunk-QZHKN3VN-C-1z3DA3.js +0 -1
- package/dist/assets/chunk-YZCP3GAM-w_NiY6dL.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Bu96zciS.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-Bu96zciS.js +0 -1
- package/dist/assets/clone-u2hQVO7u.js +0 -1
- package/dist/assets/cose-bilkent-S5V4N54A-bHlM-ydV.js +0 -1
- package/dist/assets/cytoscape.esm-DxGcaOPV.js +0 -331
- package/dist/assets/dagre-KV5264BT-2cCXOuNw.js +0 -4
- package/dist/assets/defaultLocale-DX6XiGOO.js +0 -1
- package/dist/assets/diagram-5BDNPKRD-C70P_Nd1.js +0 -10
- package/dist/assets/diagram-G4DWMVQ6-C2JvwT8U.js +0 -24
- package/dist/assets/diagram-MMDJMWI5-C9ygMSLV.js +0 -43
- package/dist/assets/diagram-TYMM5635-x-SMPmqb.js +0 -24
- package/dist/assets/erDiagram-SMLLAGMA-BGIbL82W.js +0 -85
- package/dist/assets/flowDiagram-DWJPFMVM-Dg0qwguE.js +0 -162
- package/dist/assets/ganttDiagram-T4ZO3ILL-XX5qCwKD.js +0 -292
- package/dist/assets/gitGraphDiagram-UUTBAWPF-DFil4bpA.js +0 -106
- package/dist/assets/graph-DJ6egHOd.js +0 -1
- package/dist/assets/html2canvas.esm-QH1iLAAe.js +0 -22
- package/dist/assets/index-DCtZ2KSX.css +0 -1
- package/dist/assets/index-DYIYWRaO.js +0 -41
- package/dist/assets/infoDiagram-42DDH7IO-yB7VRrpO.js +0 -2
- package/dist/assets/init-Gi6I4Gst.js +0 -1
- package/dist/assets/ishikawaDiagram-UXIWVN3A-DZ_lPco0.js +0 -70
- package/dist/assets/journeyDiagram-VCZTEJTY-B-FlPR2W.js +0 -139
- package/dist/assets/kanban-definition-6JOO6SKY-mgJwVxKf.js +0 -89
- package/dist/assets/katex-DkKDou_j.js +0 -257
- package/dist/assets/layout-BjzpJ8ZU.js +0 -1
- package/dist/assets/linear-CqOq1rA7.js +0 -1
- package/dist/assets/mermaid.core-CZyQhtml.js +0 -308
- package/dist/assets/min-B_tad8Qz.js +0 -1
- package/dist/assets/mindmap-definition-QFDTVHPH-CgBeHk8K.js +0 -96
- package/dist/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/assets/pieDiagram-DEJITSTG-CHENa0KJ.js +0 -30
- package/dist/assets/quadrantDiagram-34T5L4WZ-BUwZwCpc.js +0 -7
- package/dist/assets/requirementDiagram-MS252O5E-H7OaNjNn.js +0 -84
- package/dist/assets/sankeyDiagram-XADWPNL6-6UqaFrBf.js +0 -10
- package/dist/assets/sequenceDiagram-FGHM5R23-DBgpLSsr.js +0 -157
- package/dist/assets/stateDiagram-FHFEXIEX-D_6TWlLl.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-Cg06jbzN.js +0 -1
- package/dist/assets/timeline-definition-GMOUNBTQ-9_saaHvl.js +0 -120
- package/dist/assets/vennDiagram-DHZGUBPP-DMUS1fiT.js +0 -34
- package/dist/assets/wardley-RL74JXVD-Y-_Nnpu2.js +0 -162
- package/dist/assets/wardleyDiagram-NUSXRM2D-DIh62HY7.js +0 -20
- package/dist/assets/xychartDiagram-5P7HB3ND-B_CLrnpt.js +0 -7
- package/lib/kv-cache-analyzer.js +0 -164
package/electron/main.js
ADDED
|
@@ -0,0 +1,1432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CX Viewer Electron — Multi-Tab Architecture
|
|
3
|
+
*
|
|
4
|
+
* BaseWindow with:
|
|
5
|
+
* - tabBarView (50px, tab-bar.html)
|
|
6
|
+
* - workspaceView (project selector, shown when no tabs / adding new)
|
|
7
|
+
* - per-tab WebContentsView (each loads its own server port)
|
|
8
|
+
*
|
|
9
|
+
* Each tab = fork('tab-worker.js') → isolated proxy + server + PTY
|
|
10
|
+
*/
|
|
11
|
+
import { app, BaseWindow, WebContentsView, Menu, ipcMain, dialog, Notification, screen, clipboard } from 'electron';
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
13
|
+
import { dirname, join, basename, delimiter } from 'path';
|
|
14
|
+
import { fork, execSync } from 'child_process';
|
|
15
|
+
import { realpathSync, existsSync, readFileSync, writeFileSync, watch, mkdirSync, createWriteStream, readdirSync, statSync, unlinkSync } from 'fs';
|
|
16
|
+
import { initDiag, appendDiag, attachDiagListeners, diagFlush } from './diag.js';
|
|
17
|
+
import { buildMenuModel, serializeMenuModel } from './menu-model.js';
|
|
18
|
+
import { loadState, saveState, validateState } from './window-state.js';
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
const rootDir = join(__dirname, '..');
|
|
23
|
+
|
|
24
|
+
// ─── 主进程防阻塞:console.* 守卫 ───────────────────────────
|
|
25
|
+
// Windows 上写控制台是同步内核调用:从终端启动 + 用户在黑窗口里点选文字(QuickEdit
|
|
26
|
+
// 选择模式)时,所有写 stdout/stderr 的进程被内核级无限期阻塞——主进程一旦中招即
|
|
27
|
+
// "整窗永久冻结、hover 仍有高亮、永不恢复"(按 Esc 才解锁)。打包版 GUI 没有可见
|
|
28
|
+
// 控制台,stdout 输出毫无价值 → 打包版一律静默;关键事件本就走 appendDiag 异步落盘。
|
|
29
|
+
// dev(npx electron .)保留 console 输出不变。
|
|
30
|
+
const _isDev = !app.isPackaged;
|
|
31
|
+
const devLog = (...a) => { if (_isDev) console.log(...a); };
|
|
32
|
+
const devWarn = (...a) => { if (_isDev) console.warn(...a); };
|
|
33
|
+
const devError = (...a) => { if (_isDev) console.error(...a); };
|
|
34
|
+
// Windows 下 import(绝对路径) 会被 Node 把 'c:' 当 URL scheme 拒绝 (ERR_UNSUPPORTED_ESM_URL_SCHEME)。
|
|
35
|
+
// pathToFileURL(p).href 在 POSIX 产出 file:///abs/.. 在 Windows 产出 file:///C:/.. —— 两平台 ESM 等价。
|
|
36
|
+
const { t, setLang } = await import(pathToFileURL(join(rootDir, 'server', 'i18n.js')).href);
|
|
37
|
+
const { getCodexConfigDir, LOG_DIR } = await import(pathToFileURL(join(rootDir, 'findcx.js')).href);
|
|
38
|
+
const { isStaleLocalCodexBaseUrl } = await import(pathToFileURL(join(rootDir, 'lib', 'codex-config.js')).href);
|
|
39
|
+
|
|
40
|
+
// 白屏诊断日志(实现在 electron/diag.js)。
|
|
41
|
+
initDiag(LOG_DIR);
|
|
42
|
+
// 主进程兜底:仅 log 不 exit,先观察分布再决定是否升级为终止。
|
|
43
|
+
process.on('uncaughtException', (e) => appendDiag('main:uncaughtException', e));
|
|
44
|
+
process.on('unhandledRejection', (r) => appendDiag('main:unhandledRejection', r));
|
|
45
|
+
|
|
46
|
+
// --- Resolve shell environment (Finder-launched Electron has minimal env) ---
|
|
47
|
+
// When launched from Finder/dock, process.env lacks shell profile vars (HTTP_PROXY, PATH, LANG, etc.)
|
|
48
|
+
// Spawn a login shell to capture the full environment, then inject missing/enriched vars.
|
|
49
|
+
const _shellVars = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy', 'no_proxy', 'NO_PROXY', 'ALL_PROXY', 'all_proxy', 'OPENAI_BASE_URL', 'LANG'];
|
|
50
|
+
const _hasShellEnv = _shellVars.some(k => process.env[k]);
|
|
51
|
+
if (!_hasShellEnv && process.platform !== 'win32') {
|
|
52
|
+
try {
|
|
53
|
+
const _shell = process.env.SHELL || '/bin/zsh';
|
|
54
|
+
// Use -i (interactive) to ensure .zshrc is loaded, not just .zprofile
|
|
55
|
+
const _envOutput = execSync(`${_shell} -l -i -c 'env' 2>/dev/null`, {
|
|
56
|
+
encoding: 'utf-8',
|
|
57
|
+
timeout: 5000,
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
env: { ...process.env, TERM: process.env.TERM || 'xterm-256color' },
|
|
60
|
+
});
|
|
61
|
+
let _shellPath = null;
|
|
62
|
+
for (const line of _envOutput.split('\n')) {
|
|
63
|
+
const eq = line.indexOf('=');
|
|
64
|
+
if (eq <= 0) continue;
|
|
65
|
+
const key = line.slice(0, eq);
|
|
66
|
+
const val = line.slice(eq + 1);
|
|
67
|
+
if (key === 'PATH') {
|
|
68
|
+
_shellPath = val; // Save for merging below
|
|
69
|
+
} else if (_shellVars.includes(key) && !process.env[key]) {
|
|
70
|
+
process.env[key] = val;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Merge shell PATH into process PATH (prepend shell paths for priority)
|
|
74
|
+
// 分隔符用 path.delimiter: POSIX 下 ':' (等价于原硬编码), Windows 下 ';'
|
|
75
|
+
if (_shellPath) {
|
|
76
|
+
const existing = new Set((process.env.PATH || '').split(delimiter));
|
|
77
|
+
const merged = _shellPath.split(delimiter).filter(p => !existing.has(p));
|
|
78
|
+
if (merged.length) {
|
|
79
|
+
process.env.PATH = _shellPath + delimiter + process.env.PATH;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (process.env.HTTP_PROXY || process.env.HTTPS_PROXY) {
|
|
83
|
+
devError('[Electron] Injected proxy from shell profile:', process.env.HTTP_PROXY || process.env.HTTPS_PROXY);
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
devError('[Electron] Failed to resolve shell env:', err.message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --- Ensure PATH includes common node/npm binary locations ---
|
|
91
|
+
// 分隔符用 path.delimiter (POSIX ':', Windows ';'). POSIX 硬编码路径在 Windows 下会被拼入 PATH 但无效,无副作用。
|
|
92
|
+
const home = app.getPath('home');
|
|
93
|
+
const pathDirs = (process.env.PATH || '').split(delimiter);
|
|
94
|
+
const extraPaths = ['/usr/local/bin', '/opt/homebrew/bin', join(home, '.npm-global', 'bin'), join(home, '.nvm', 'versions', 'node')];
|
|
95
|
+
for (const p of extraPaths) {
|
|
96
|
+
if (!pathDirs.includes(p)) pathDirs.push(p);
|
|
97
|
+
}
|
|
98
|
+
process.env.PATH = pathDirs.join(delimiter);
|
|
99
|
+
|
|
100
|
+
// --- Resolve real Node.js path (Electron's process.execPath is the Electron binary) ---
|
|
101
|
+
let _nodePath = process.execPath;
|
|
102
|
+
if (process.versions.electron) {
|
|
103
|
+
try {
|
|
104
|
+
_nodePath = execSync(process.platform === 'win32' ? 'where node' : 'which node', { encoding: 'utf-8', windowsHide: true }).trim();
|
|
105
|
+
if (process.platform === 'win32') _nodePath = _nodePath.split('\n')[0].trim();
|
|
106
|
+
} catch { _nodePath = process.platform === 'win32' ? 'node' : '/usr/local/bin/node'; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { resolveNpmCodexPath, resolveNativePath } = await import(pathToFileURL(join(rootDir, 'findcx.js')).href);
|
|
110
|
+
let codexPath = resolveNpmCodexPath();
|
|
111
|
+
let isNpmVersion = !!codexPath;
|
|
112
|
+
if (!codexPath) codexPath = resolveNativePath();
|
|
113
|
+
|
|
114
|
+
// Fallback: directly check known npm global locations
|
|
115
|
+
if (!codexPath) {
|
|
116
|
+
const knownPaths = [
|
|
117
|
+
join(home, '.npm-global', 'lib', 'node_modules', '@openai', 'codex', 'cli.js'),
|
|
118
|
+
'/usr/local/lib/node_modules/@openai/codex/cli.js',
|
|
119
|
+
'/opt/homebrew/lib/node_modules/@openai/codex/cli.js',
|
|
120
|
+
];
|
|
121
|
+
for (const p of knownPaths) {
|
|
122
|
+
if (existsSync(p)) {
|
|
123
|
+
codexPath = p;
|
|
124
|
+
isNpmVersion = true;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!codexPath) {
|
|
131
|
+
process.env.CXV_CODEX_MISSING = '1';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// --- Management server for workspace selector ---
|
|
135
|
+
process.env.CXV_CLI_MODE = '1';
|
|
136
|
+
process.env.CXV_WORKSPACE_MODE = '1';
|
|
137
|
+
process.env.CXV_ELECTRON_MULTITAB = '1'; // Tell server not to spawn Codex on launch
|
|
138
|
+
|
|
139
|
+
let mgmtServerMod = null;
|
|
140
|
+
let mgmtPort = null;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 终止 child process —— Windows 上 SIGTERM 是 noop(要等满 escalateMs 才走 SIGKILL,tab close 卡);
|
|
144
|
+
* 直接 kill() 让 Node 走 TerminateProcess 立即结束。POSIX 保留 SIGTERM → escalateMs → SIGKILL 升级。
|
|
145
|
+
* 返回 Promise 在子进程退出或超时后 resolve。
|
|
146
|
+
*/
|
|
147
|
+
function killChildEscalating(child, escalateMs = 3000) {
|
|
148
|
+
if (process.platform === 'win32') {
|
|
149
|
+
try { child.kill(); } catch {}
|
|
150
|
+
return Promise.resolve();
|
|
151
|
+
}
|
|
152
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
153
|
+
return new Promise(resolve => {
|
|
154
|
+
const t = setTimeout(() => { try { child.kill('SIGKILL'); } catch {} resolve(); }, escalateMs);
|
|
155
|
+
child.on('exit', () => { clearTimeout(t); resolve(); });
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function startMgmtServer() {
|
|
160
|
+
try {
|
|
161
|
+
const { startProxy } = await import(pathToFileURL(join(rootDir, 'server', 'proxy.js')).href);
|
|
162
|
+
const proxyPort = await startProxy();
|
|
163
|
+
process.env.CXV_PROXY_PORT = String(proxyPort);
|
|
164
|
+
mgmtServerMod = await import(pathToFileURL(join(rootDir, 'server', 'server.js')).href);
|
|
165
|
+
await mgmtServerMod.startViewer();
|
|
166
|
+
mgmtPort = mgmtServerMod.getPort();
|
|
167
|
+
if (!mgmtPort) {
|
|
168
|
+
// 端口段 [7048, 7099) 全占 / startViewer 早返回 null —— 后续 loadURL 会拼出
|
|
169
|
+
// `http://127.0.0.1:null` 直接白屏。日志里把 mgmtPort 显式记下来定位用。
|
|
170
|
+
appendDiag('main:startMgmtServer', { error: 'getPort() returned null', portRangeHint: '7048-7099' });
|
|
171
|
+
throw new Error('mgmt server port unavailable');
|
|
172
|
+
}
|
|
173
|
+
if (codexPath) {
|
|
174
|
+
mgmtServerMod.setWorkspaceCodexArgs([]);
|
|
175
|
+
mgmtServerMod.setWorkspaceCodexPath(codexPath, isNpmVersion);
|
|
176
|
+
}
|
|
177
|
+
mgmtServerMod.setLaunchCallback((path, extraArgs) => createTab(path, extraArgs));
|
|
178
|
+
} catch (err) {
|
|
179
|
+
// mgmt server 起不来后续所有 loadURL 会拼出 `http://127.0.0.1:null` 白屏。
|
|
180
|
+
// 先落 diag 日志,再弹 errorBox 告知用户,最后 exit(1) 让用户能立刻定位到日志而非
|
|
181
|
+
// 面对白屏发呆。dialog 是阻塞的,用户点确认后才会 app.exit。
|
|
182
|
+
appendDiag('main:startMgmtServer', { err, mgmtPort, codexPath, isNpmVersion, proxyPort: process.env.CXV_PROXY_PORT });
|
|
183
|
+
try {
|
|
184
|
+
dialog.showErrorBox(
|
|
185
|
+
'CX Viewer 启动失败',
|
|
186
|
+
`管理服务无法启动:${err && err.message ? err.message : String(err)}\n\n` +
|
|
187
|
+
`详细日志:~/.codex/cx-viewer/electron-diag.log`,
|
|
188
|
+
);
|
|
189
|
+
} catch { /* dialog 在 ready 前调用会抛,忽略后直接退出 */ }
|
|
190
|
+
app.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// --- Tab state ---
|
|
195
|
+
const TAB_BAR_HEIGHT = 50;
|
|
196
|
+
// debug worker 日志保留窗口(CXV_DEBUG_WORKER_LOGS=1 时使用)
|
|
197
|
+
const LOG_RETENTION_MS = 7 * 24 * 3600 * 1000;
|
|
198
|
+
const tabs = new Map(); // tabId -> { child, port, token, projectName, realPath, view, status }
|
|
199
|
+
let nextTabId = 1;
|
|
200
|
+
let activeTabId = null;
|
|
201
|
+
|
|
202
|
+
// --- Window ---
|
|
203
|
+
let mainWindow = null;
|
|
204
|
+
let tabBarView = null;
|
|
205
|
+
let workspaceView = null;
|
|
206
|
+
|
|
207
|
+
// --- iPad/device mode (window resize) ---
|
|
208
|
+
// 状态由 main 进程持有,跨 tab 切换不丢失。deviceMode = 当前处于 iPad 模式;
|
|
209
|
+
// savedBounds = 进入 iPad 模式前记下的窗口尺寸,退出时还原。
|
|
210
|
+
// iPad 模式只是把窗口收窄到 DEVICE_WIDTH,并把最小宽度放宽到 DEVICE_WIDTH,
|
|
211
|
+
// 因此用户仍可手动把窗口再拉大。
|
|
212
|
+
const DEVICE_WIDTH = 500; // iPad 预览默认宽度
|
|
213
|
+
const DEVICE_MIN_WIDTH = 460; // iPad 模式下允许手动缩到的最小宽度
|
|
214
|
+
const PC_MIN_WIDTH = 800; // 原始 minWidth(见 BaseWindow 创建处)
|
|
215
|
+
const WIN_MIN_HEIGHT = 600;
|
|
216
|
+
let deviceMode = false;
|
|
217
|
+
let savedBounds = null;
|
|
218
|
+
|
|
219
|
+
// --- Workspace selector popup mode ---
|
|
220
|
+
// 工作区选择器有两种呈现:整页(零 tab 的初始化界面)/ 浮层(已有 tab 时点「+」,叠在当前
|
|
221
|
+
// tab 之上居中弹出,当前界面变暗保留在后面)。workspacePopupOpen = 当前处于浮层模式。
|
|
222
|
+
let workspacePopupOpen = false;
|
|
223
|
+
|
|
224
|
+
// --- Pending-approval aggregation across tabs ---
|
|
225
|
+
// pendingByTab: tabId -> { permission?: Map<id,payload>, plan?: Map<id,payload>, ask?: Map<id,payload>, projectName }
|
|
226
|
+
const pendingByTab = new Map();
|
|
227
|
+
// notifiedKeys: dedupe Notification + flashFrame triggers across WS reconnects.
|
|
228
|
+
// Key form: `${tabId}|${kind}|${id}` — cleared when the same tuple goes through pending-remove.
|
|
229
|
+
const notifiedKeys = new Set();
|
|
230
|
+
let _isFlashing = false;
|
|
231
|
+
// 用户偏好:仅窗口失焦时弹通知。默认 true 保留历史行为(失焦才通知);
|
|
232
|
+
// 关掉后窗口聚焦时也通知。renderer 通过 set-approval-pref IPC 推过来,首次 mount 也会推一次同步初值。
|
|
233
|
+
// 启动时同步从 preferences.json 读初值,消除"默认 true → renderer hydrate 后才生效"的 race window。
|
|
234
|
+
// 读失败/字段缺失则保留 true(向后兼容旧 preferences.json)。
|
|
235
|
+
let _notifyOnlyWhenHidden = true;
|
|
236
|
+
try {
|
|
237
|
+
const _prefsPath = join(LOG_DIR, 'preferences.json');
|
|
238
|
+
if (existsSync(_prefsPath)) {
|
|
239
|
+
const _prefs = JSON.parse(readFileSync(_prefsPath, 'utf-8'));
|
|
240
|
+
if (_prefs?.approvalModal && typeof _prefs.approvalModal.notifyOnlyWhenHidden === 'boolean') {
|
|
241
|
+
_notifyOnlyWhenHidden = _prefs.approvalModal.notifyOnlyWhenHidden;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} catch (e) {
|
|
245
|
+
devWarn('[main] failed to load notifyOnlyWhenHidden from preferences.json:', e?.message || e);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// --- 启动期主题/语言(窗口创建前必须确定) ---
|
|
249
|
+
// backgroundColor(防启动白屏闪烁)与 win32 titleBarOverlay 配色要在 new BaseWindow 时给出;
|
|
250
|
+
// 菜单语言要在 buildMenu 前 setLang。preferences.json 由 server 进程写,这里只读。
|
|
251
|
+
// winBg = 内容区底色(src/global.css --bg-base),barBg/sym = tab bar 底色/前景(tab-bar.html --bg/--text)。
|
|
252
|
+
// ⚠ 这些值与上述 CSS 变量是两处维护:改任一侧配色必须同步另一侧,否则 win32 窗控区/启动首帧会露出色差。
|
|
253
|
+
const THEME_COLORS = {
|
|
254
|
+
dark: { winBg: '#0a0a0a', barBg: '#1a1a1a', sym: '#aaa' },
|
|
255
|
+
light: { winBg: '#FAFAFA', barBg: '#f0f0f0', sym: '#666' },
|
|
256
|
+
};
|
|
257
|
+
let _startupTheme = 'dark';
|
|
258
|
+
try {
|
|
259
|
+
const _prefsPath = join(LOG_DIR, 'preferences.json');
|
|
260
|
+
if (existsSync(_prefsPath)) {
|
|
261
|
+
const _prefs = JSON.parse(readFileSync(_prefsPath, 'utf-8'));
|
|
262
|
+
if (_prefs?.themeColor === 'light') _startupTheme = 'light';
|
|
263
|
+
// setLang 自带 locale 校验,非法值回落 en;缺失时保留 i18n.js 模块加载期的 detectLanguage() 结果。
|
|
264
|
+
if (_prefs?.lang) setLang(_prefs.lang);
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
devWarn('[main] failed to load theme/lang from preferences.json:', e?.message || e);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// --- 窗口几何持久化(electron/window-state.js,纯逻辑;这里只做去抖与事件接线) ---
|
|
271
|
+
// 存 LOG_DIR/window-state.json —— 不写 preferences.json(server 进程拥有写权,同写会竞争覆盖)。
|
|
272
|
+
const WINDOW_STATE_FILE = join(LOG_DIR, 'window-state.json');
|
|
273
|
+
let _winStateTimer = null;
|
|
274
|
+
function saveWinStateNow() {
|
|
275
|
+
// deviceMode 下窗口被人为收窄到 DEVICE_WIDTH,写入会污染下次恢复值 → 跳过。
|
|
276
|
+
if (!mainWindow || mainWindow.isDestroyed() || deviceMode) return;
|
|
277
|
+
try {
|
|
278
|
+
const b = typeof mainWindow.getNormalBounds === 'function' ? mainWindow.getNormalBounds() : mainWindow.getBounds();
|
|
279
|
+
saveState(writeFileSync, WINDOW_STATE_FILE, {
|
|
280
|
+
x: b.x, y: b.y, width: b.width, height: b.height,
|
|
281
|
+
maximized: mainWindow.isMaximized(),
|
|
282
|
+
});
|
|
283
|
+
} catch { /* best-effort */ }
|
|
284
|
+
}
|
|
285
|
+
function scheduleWinStateSave() {
|
|
286
|
+
clearTimeout(_winStateTimer);
|
|
287
|
+
// 400ms 去抖:一次拖拽/缩放含连续多个 move/resize 事件,合并为一次磁盘写;cleanupAll 另有同步兜底。
|
|
288
|
+
_winStateTimer = setTimeout(saveWinStateNow, 400);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function _kindCount(tabState) {
|
|
292
|
+
if (!tabState) return 0;
|
|
293
|
+
// Sum sizes of every Map field; 'projectName' (string) is skipped automatically.
|
|
294
|
+
let n = 0;
|
|
295
|
+
for (const k of Object.keys(tabState)) {
|
|
296
|
+
if (tabState[k] instanceof Map) n += tabState[k].size;
|
|
297
|
+
}
|
|
298
|
+
return n;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function _totalPendingCount() {
|
|
302
|
+
let total = 0;
|
|
303
|
+
for (const tabState of pendingByTab.values()) total += _kindCount(tabState);
|
|
304
|
+
return total;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function broadcastApproval() {
|
|
308
|
+
// Send aggregated state to every tab content view so they can render chips and route jumps.
|
|
309
|
+
const others = [];
|
|
310
|
+
for (const [tabId, st] of pendingByTab) {
|
|
311
|
+
const count = _kindCount(st);
|
|
312
|
+
if (count > 0) others.push({ tabId, projectName: st.projectName || '', count });
|
|
313
|
+
}
|
|
314
|
+
for (const [tabId, t] of tabs) {
|
|
315
|
+
if (!t.view || t.view.webContents.isDestroyed()) continue;
|
|
316
|
+
const ownState = pendingByTab.get(tabId);
|
|
317
|
+
const ownPending = ownState ? {
|
|
318
|
+
ptyPlan: ownState.ptyPlan ? [...ownState.ptyPlan.entries()].map(([id, p]) => ({ id, ...p })) : [],
|
|
319
|
+
ask: ownState.ask ? [...ownState.ask.entries()].map(([id, p]) => ({ id, ...p })) : [],
|
|
320
|
+
} : { ptyPlan: [], ask: [] };
|
|
321
|
+
const otherTabs = others.filter(o => o.tabId !== tabId);
|
|
322
|
+
try { t.view.webContents.send('approval-broadcast', { ownTabId: tabId, ownPending, others: otherTabs }); } catch {}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 审批级联去抖:每条 pending-add/remove 都全量跑 setBadgeCount(任务栏 COM 同步调用) +
|
|
327
|
+
// flashFrame + 对 N 个 tab + tab 栏的 N+3 次 IPC 广播。审批风暴(队列式 ask/plan)下
|
|
328
|
+
// 主进程被级联放大持续占满;100ms 合并窗口内多次状态变更只跑一次——badge/广播读的是
|
|
329
|
+
// 当前最终状态,本就幂等,延迟 ≤100ms 无感知。
|
|
330
|
+
let _aggregateTimer = null;
|
|
331
|
+
function scheduleAggregateApproval() {
|
|
332
|
+
if (_aggregateTimer) return;
|
|
333
|
+
_aggregateTimer = setTimeout(() => {
|
|
334
|
+
_aggregateTimer = null;
|
|
335
|
+
aggregateApproval();
|
|
336
|
+
}, 100);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function aggregateApproval() {
|
|
340
|
+
const total = _totalPendingCount();
|
|
341
|
+
// Dock badge / Windows taskbar overlay
|
|
342
|
+
try { app.setBadgeCount(total); } catch {}
|
|
343
|
+
// flashFrame transitions: 0→≥1 start; ≥1→0 stop. Window focus also stops (handled in mainWindow.on('focus'))
|
|
344
|
+
if (total > 0 && !_isFlashing) {
|
|
345
|
+
if (mainWindow && !mainWindow.isDestroyed() && !mainWindow.isFocused()) {
|
|
346
|
+
try { mainWindow.flashFrame(true); _isFlashing = true; } catch {}
|
|
347
|
+
}
|
|
348
|
+
} else if (total === 0 && _isFlashing) {
|
|
349
|
+
if (mainWindow && !mainWindow.isDestroyed()) try { mainWindow.flashFrame(false); } catch {}
|
|
350
|
+
_isFlashing = false;
|
|
351
|
+
}
|
|
352
|
+
broadcastApproval();
|
|
353
|
+
// tab-bar 上的小圆环依赖 tab 列表里的 pending 字段,pending 状态变了要让 tab-bar 重渲染。
|
|
354
|
+
broadcastTabs();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function maybeNotify(tabId, kind, id, payload) {
|
|
358
|
+
const key = `${tabId}|${kind}|${id}`;
|
|
359
|
+
if (notifiedKeys.has(key)) return; // dedupe across reconnects
|
|
360
|
+
notifiedKeys.add(key);
|
|
361
|
+
// 受 _notifyOnlyWhenHidden(用户偏好)控制:开启时窗口聚焦则不通知;关掉后聚焦也通知。
|
|
362
|
+
if (_notifyOnlyWhenHidden && mainWindow && !mainWindow.isDestroyed() && mainWindow.isFocused()) return;
|
|
363
|
+
const projectName = payload?.projectName || pendingByTab.get(tabId)?.projectName || 'CX Viewer';
|
|
364
|
+
// i18n with safe fallback: t() returns the key itself when missing — detect that and substitute defaults.
|
|
365
|
+
const _tr = (key, params, fallback) => {
|
|
366
|
+
try {
|
|
367
|
+
const r = t(key, params);
|
|
368
|
+
return (r && r !== key) ? r : fallback;
|
|
369
|
+
} catch { return fallback; }
|
|
370
|
+
};
|
|
371
|
+
let title = '';
|
|
372
|
+
let body = '';
|
|
373
|
+
if (kind === 'ask') {
|
|
374
|
+
title = _tr('electron.approval.notify.title.ask', null, 'Question');
|
|
375
|
+
body = _tr('electron.approval.notify.body.ask', { project: projectName }, `Question in ${projectName}`);
|
|
376
|
+
} else if (kind === 'ptyPlan') {
|
|
377
|
+
title = _tr('electron.approval.notify.title.ptyPlan', null, 'Plan review');
|
|
378
|
+
body = _tr('electron.approval.notify.body.ptyPlan', { project: projectName }, `Plan in ${projectName}`);
|
|
379
|
+
}
|
|
380
|
+
// Defensive: unknown kind (e.g. stale message after rollback) → drop silently rather than show empty notification.
|
|
381
|
+
if (!title) return;
|
|
382
|
+
if (!Notification.isSupported || !Notification.isSupported()) return;
|
|
383
|
+
try {
|
|
384
|
+
const n = new Notification({ title, body, silent: false });
|
|
385
|
+
n.on('click', () => {
|
|
386
|
+
try {
|
|
387
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
388
|
+
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
389
|
+
if (process.platform === 'darwin' && app.dock) try { app.dock.show(); } catch {}
|
|
390
|
+
if (typeof app.show === 'function') try { app.show(); } catch {}
|
|
391
|
+
mainWindow.show();
|
|
392
|
+
mainWindow.focus();
|
|
393
|
+
}
|
|
394
|
+
} catch {}
|
|
395
|
+
try { switchTab(tabId); } catch {}
|
|
396
|
+
});
|
|
397
|
+
n.show();
|
|
398
|
+
} catch {}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function recordPendingAdd(tabId, kind, id, payload) {
|
|
402
|
+
if (!pendingByTab.has(tabId)) pendingByTab.set(tabId, { projectName: payload?.projectName || '' });
|
|
403
|
+
const tabState = pendingByTab.get(tabId);
|
|
404
|
+
if (payload?.projectName) tabState.projectName = payload.projectName;
|
|
405
|
+
if (!tabState[kind]) tabState[kind] = new Map();
|
|
406
|
+
if (tabState[kind].has(id)) {
|
|
407
|
+
// 占位 id `__ask__` 在 PTY hook 复用 — 比对 payload 决定是 WS 重连重发(dedupe)
|
|
408
|
+
// 还是新一轮 ask(替换 + 清 notifiedKey 让重新弹通知)。其它 id 直接 dedupe。
|
|
409
|
+
if (id === '__ask__') {
|
|
410
|
+
const prev = tabState[kind].get(id);
|
|
411
|
+
const sameContent = JSON.stringify(prev?.questions || null) === JSON.stringify(payload?.questions || null);
|
|
412
|
+
if (sameContent) return;
|
|
413
|
+
notifiedKeys.delete(`${tabId}|${kind}|${id}`);
|
|
414
|
+
} else {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
tabState[kind].set(id, payload || {});
|
|
419
|
+
maybeNotify(tabId, kind, id, payload);
|
|
420
|
+
scheduleAggregateApproval();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function recordPendingRemove(tabId, kind, id) {
|
|
424
|
+
const tabState = pendingByTab.get(tabId);
|
|
425
|
+
if (!tabState) { scheduleAggregateApproval(); return; }
|
|
426
|
+
const sub = tabState[kind];
|
|
427
|
+
if (sub) sub.delete(id);
|
|
428
|
+
notifiedKeys.delete(`${tabId}|${kind}|${id}`);
|
|
429
|
+
// Cleanup empty submaps to keep state lean — generic so new kinds (ptyPlan, etc.) are handled
|
|
430
|
+
// without per-kind branching. 'projectName' is a string, not a Map, so it's correctly skipped.
|
|
431
|
+
for (const k of Object.keys(tabState)) {
|
|
432
|
+
if (tabState[k] instanceof Map && tabState[k].size === 0) delete tabState[k];
|
|
433
|
+
}
|
|
434
|
+
if (_kindCount(tabState) === 0) {
|
|
435
|
+
pendingByTab.delete(tabId);
|
|
436
|
+
}
|
|
437
|
+
scheduleAggregateApproval();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function clearPendingForTab(tabId) {
|
|
441
|
+
if (pendingByTab.delete(tabId)) {
|
|
442
|
+
// Also clear any notifiedKeys belonging to this tab
|
|
443
|
+
for (const k of [...notifiedKeys]) {
|
|
444
|
+
if (k.startsWith(`${tabId}|`)) notifiedKeys.delete(k);
|
|
445
|
+
}
|
|
446
|
+
scheduleAggregateApproval();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function getTabList() {
|
|
451
|
+
return [...tabs.entries()].map(([id, t]) => ({
|
|
452
|
+
id, name: t.projectName || basename(t.realPath || ''), status: t.status,
|
|
453
|
+
// tab-bar 用来在 tab 左侧空槽里渲染"待审批提醒小圆环"。聚合了
|
|
454
|
+
// ptyPlan / ask / permission 等所有待审批 kind,>0 表示这个 tab 里有东西需要用户决策。
|
|
455
|
+
pending: _kindCount(pendingByTab.get(id)),
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function broadcastTabs() {
|
|
460
|
+
if (tabBarView?.webContents && !tabBarView.webContents.isDestroyed()) {
|
|
461
|
+
tabBarView.webContents.send('tabs-updated', getTabList());
|
|
462
|
+
tabBarView.webContents.send('tab-activated', activeTabId);
|
|
463
|
+
// 同步当前活动 tab 的 header 模型(迁移到 tab bar 的那批控件);无活动 tab → null 清空。
|
|
464
|
+
const hm = activeTabId != null ? (tabs.get(activeTabId)?.headerModel ?? null) : null;
|
|
465
|
+
tabBarView.webContents.send('header-model', hm, activeTabId);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function updateWindowTitle() {
|
|
470
|
+
if (!mainWindow) return;
|
|
471
|
+
const tab = tabs.get(activeTabId);
|
|
472
|
+
mainWindow.setTitle(tab ? `${tab.projectName} - CX Viewer` : 'CX Viewer');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// --- Layout ---
|
|
476
|
+
let resizeTimer;
|
|
477
|
+
function updateLayout() {
|
|
478
|
+
if (!mainWindow) return;
|
|
479
|
+
const bounds = mainWindow.getContentBounds();
|
|
480
|
+
const w = bounds.width;
|
|
481
|
+
const h = bounds.height;
|
|
482
|
+
|
|
483
|
+
if (tabBarView) tabBarView.setBounds({ x: 0, y: 0, width: w, height: TAB_BAR_HEIGHT });
|
|
484
|
+
if (workspaceView) workspaceView.setBounds({ x: 0, y: TAB_BAR_HEIGHT, width: w, height: h - TAB_BAR_HEIGHT });
|
|
485
|
+
for (const tab of tabs.values()) {
|
|
486
|
+
if (tab.view) tab.view.setBounds({ x: 0, y: TAB_BAR_HEIGHT, width: w, height: h - TAB_BAR_HEIGHT });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 把主窗口在 PC / iPad 模式间切换。进入时记下当前尺寸并收窄到 DEVICE_WIDTH(高度、位置不变),
|
|
491
|
+
// 退出时还原到记下的尺寸。窗口最小宽度随模式调整:进入前先放宽到 DEVICE_MIN_WIDTH(460),否则 setBounds 会被
|
|
492
|
+
// 原 minWidth(800) 钳制;退出后再恢复到 800。programmatic setBounds 会触发 'resize' → updateLayout,
|
|
493
|
+
// 但 updateLayout 只重排子视图、不动窗口,无回环,这里再同步调一次确保即时生效。
|
|
494
|
+
function setDeviceMode(on) {
|
|
495
|
+
if (!mainWindow) return;
|
|
496
|
+
if (on === deviceMode) { broadcastDeviceMode(); return; }
|
|
497
|
+
if (mainWindow.isFullScreen && mainWindow.isFullScreen()) { broadcastDeviceMode(); return; } // 全屏下无法 resize:回弹按钮态,不改 deviceMode
|
|
498
|
+
if (on) {
|
|
499
|
+
if (mainWindow.isMaximized && mainWindow.isMaximized()) mainWindow.unmaximize();
|
|
500
|
+
// getNormalBounds() 返回「非最大化」几何,避免从最大化进入时把全屏尺寸误记为 savedBounds。
|
|
501
|
+
const b = (typeof mainWindow.getNormalBounds === 'function' ? mainWindow.getNormalBounds() : mainWindow.getBounds());
|
|
502
|
+
savedBounds = { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
503
|
+
mainWindow.setMinimumSize(DEVICE_MIN_WIDTH, WIN_MIN_HEIGHT); // 先放宽 min 到 460,否则被原 minWidth 钳制
|
|
504
|
+
mainWindow.setBounds({ x: b.x, y: b.y, width: DEVICE_WIDTH, height: b.height });
|
|
505
|
+
deviceMode = true;
|
|
506
|
+
} else {
|
|
507
|
+
const cur = mainWindow.getBounds();
|
|
508
|
+
const tgt = savedBounds || { width: 1400, height: cur.height };
|
|
509
|
+
mainWindow.setBounds({ x: cur.x, y: cur.y, width: tgt.width, height: tgt.height });
|
|
510
|
+
mainWindow.setMinimumSize(PC_MIN_WIDTH, WIN_MIN_HEIGHT); // 放大后再恢复 min
|
|
511
|
+
savedBounds = null;
|
|
512
|
+
deviceMode = false;
|
|
513
|
+
}
|
|
514
|
+
updateLayout();
|
|
515
|
+
broadcastDeviceMode();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// 把当前 deviceMode 推给 tab bar(按钮所在的窗口级 chrome),让图标与窗口状态一致。
|
|
519
|
+
function broadcastDeviceMode() {
|
|
520
|
+
const wc = tabBarView && tabBarView.webContents;
|
|
521
|
+
if (wc && !wc.isDestroyed()) wc.send('device-mode-changed', deviceMode);
|
|
522
|
+
sendDeviceModeToTab(activeTabId); // 仅活动 tab:React 据 device mode 状态切 viewMode(pad⇄pc),不依赖宽度
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// 把当前 deviceMode 推给指定 tab 的 content(React 据此对齐 viewMode)。只推活动/被切到的 tab:
|
|
526
|
+
// 不全量广播——会话在 tab-worker 后端不受前端 reload 影响,但避免后台 tab 无谓 reload 丢前端草稿。
|
|
527
|
+
function sendDeviceModeToTab(tabId) {
|
|
528
|
+
const tab = tabId != null ? tabs.get(tabId) : null;
|
|
529
|
+
const twc = tab && tab.view && tab.view.webContents;
|
|
530
|
+
if (twc && !twc.isDestroyed()) twc.send('device-mode-changed', deviceMode);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// --- Tab management ---
|
|
534
|
+
function createTab(projectPath, extraArgs = []) {
|
|
535
|
+
devLog('[main] createTab:', projectPath, extraArgs);
|
|
536
|
+
let realPath;
|
|
537
|
+
try { realPath = realpathSync(projectPath); } catch { realPath = projectPath; }
|
|
538
|
+
|
|
539
|
+
// Deduplicate: if already open, switch to it
|
|
540
|
+
for (const [id, tab] of tabs) {
|
|
541
|
+
if (tab.realPath === realPath) {
|
|
542
|
+
switchTab(id);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const tabId = nextTabId++;
|
|
548
|
+
const projectName = basename(realPath);
|
|
549
|
+
|
|
550
|
+
// Register immediately (loading state) 并立刻置为 active —— tab-bar 里的关闭按钮
|
|
551
|
+
// 由 `.tab.active .tab-close { opacity: 1 }` 控制,loading 期间若 activeTabId 还指向
|
|
552
|
+
// 旧 tab / null,新 tab 上的 × 会全程不可见,用户无法在加载阶段取消。
|
|
553
|
+
tabs.set(tabId, { child: null, port: null, token: null, projectName, realPath, view: null, status: 'loading' });
|
|
554
|
+
activeTabId = tabId;
|
|
555
|
+
broadcastTabs();
|
|
556
|
+
hideWorkspaceSelector();
|
|
557
|
+
|
|
558
|
+
// Fork child process with CLEAN env — remove parent cxv/proxy routing.
|
|
559
|
+
// pty-manager injects the per-tab OPENAI_BASE_URL for Codex when the proxy starts,
|
|
560
|
+
// while the tab proxy can still use a non-local OPENAI_BASE_URL as the upstream.
|
|
561
|
+
const childEnv = { ...process.env };
|
|
562
|
+
delete childEnv.CXV_WORKSPACE_MODE;
|
|
563
|
+
delete childEnv.CXV_PROXY_PORT;
|
|
564
|
+
delete childEnv.CXV_PROXY_MODE;
|
|
565
|
+
delete childEnv.CXV_ELECTRON_MULTITAB;
|
|
566
|
+
if (isStaleLocalCodexBaseUrl(childEnv.OPENAI_BASE_URL)) delete childEnv.OPENAI_BASE_URL;
|
|
567
|
+
// Legacy cc-viewer env may still be present in a user's shell; keep clearing it.
|
|
568
|
+
delete childEnv.ANTHROPIC_BASE_URL;
|
|
569
|
+
childEnv.CXV_PROJECT_DIR = realPath;
|
|
570
|
+
|
|
571
|
+
// worker stdio:默认 inherit(行为与原版一致,零 IO 开销);
|
|
572
|
+
// CXV_DEBUG_WORKER_LOGS=1 时切到 pipe + 写文件(便于排查打包后从 Finder 启动的问题)
|
|
573
|
+
// — Finder 启动 .app 时 inherit 等于丢弃 worker 输出,开关打开后日志落到
|
|
574
|
+
// ${CXV_LOG_DIR || ~/.codex/cx-viewer}/electron-debug-{ts}-tab{N}.log,自动清理 7 天前旧文件
|
|
575
|
+
const _debugWorkerLogs = process.env.CXV_DEBUG_WORKER_LOGS === '1';
|
|
576
|
+
let _logStream = null;
|
|
577
|
+
if (_debugWorkerLogs) {
|
|
578
|
+
const _logDir = process.env.CXV_LOG_DIR || join(home, '.codex', 'cx-viewer');
|
|
579
|
+
try { mkdirSync(_logDir, { recursive: true }); } catch (err) { devError('[Electron] mkdir log dir failed:', err.message); }
|
|
580
|
+
try {
|
|
581
|
+
const cutoff = Date.now() - LOG_RETENTION_MS;
|
|
582
|
+
for (const f of readdirSync(_logDir)) {
|
|
583
|
+
if (!f.startsWith('electron-debug-') || !f.endsWith('.log')) continue;
|
|
584
|
+
const fp = join(_logDir, f);
|
|
585
|
+
if (statSync(fp).mtimeMs < cutoff) unlinkSync(fp);
|
|
586
|
+
}
|
|
587
|
+
} catch (err) { devError('[Electron] cleanup old debug logs failed:', err.message); }
|
|
588
|
+
const _logPath = join(_logDir, `electron-debug-${Date.now()}-tab${tabId}.log`);
|
|
589
|
+
_logStream = createWriteStream(_logPath, { flags: 'a' });
|
|
590
|
+
devError(`[Electron] tab ${tabId} debug log → ${_logPath}`);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// stdio 策略:debug 模式 pipe 到文件日志;dev 模式 inherit 方便终端观察;
|
|
594
|
+
// 打包版一律 ignore——worker 若继承控制台句柄,从终端启动 + QuickEdit 点选黑窗口时
|
|
595
|
+
// 写 stdout 的 worker 会被内核级无限期阻塞(单 tab 永久卡死面),且打包版输出本就无处可看。
|
|
596
|
+
const child = fork(join(__dirname, 'tab-worker.js'), [], {
|
|
597
|
+
execPath: _nodePath,
|
|
598
|
+
cwd: realPath,
|
|
599
|
+
env: childEnv,
|
|
600
|
+
stdio: _debugWorkerLogs
|
|
601
|
+
? ['ignore', 'pipe', 'pipe', 'ipc']
|
|
602
|
+
: (_isDev ? ['inherit', 'inherit', 'inherit', 'ipc'] : ['ignore', 'ignore', 'ignore', 'ipc']),
|
|
603
|
+
silent: _debugWorkerLogs,
|
|
604
|
+
// Windows:execPath 是真实 node.exe(console-subsystem),GUI 父进程不带 windowsHide
|
|
605
|
+
// 启动它会分配一个可见控制台窗口(用户报告的"多出来的 Node.js 窗口")。POSIX 上为 no-op。
|
|
606
|
+
windowsHide: true,
|
|
607
|
+
});
|
|
608
|
+
if (_logStream) {
|
|
609
|
+
child.stdout?.pipe(_logStream, { end: false });
|
|
610
|
+
child.stderr?.pipe(_logStream, { end: false });
|
|
611
|
+
child.on('exit', () => { try { _logStream.end(); } catch {} });
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
tabs.get(tabId).child = child;
|
|
615
|
+
|
|
616
|
+
// Timeout
|
|
617
|
+
const timeout = setTimeout(() => {
|
|
618
|
+
if (tabs.get(tabId)?.status === 'loading') {
|
|
619
|
+
appendDiag('tab:ready-timeout', { tabId, project: tabs.get(tabId)?.projectName, ms: 30000 });
|
|
620
|
+
tabs.get(tabId).status = 'error';
|
|
621
|
+
broadcastTabs();
|
|
622
|
+
}
|
|
623
|
+
}, 30000);
|
|
624
|
+
|
|
625
|
+
// fork 失败(execPath 不存在 / 权限)—— 之前完全无人接,主进程直接 crash。
|
|
626
|
+
child.on('error', (err) => {
|
|
627
|
+
appendDiag('tab:child-error', { tabId, project: tabs.get(tabId)?.projectName, err });
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
child.on('message', (msg) => {
|
|
631
|
+
devLog(`[main] child msg for tab ${tabId}:`, msg.type, msg.port || '', msg.projectName || '', msg.message || '');
|
|
632
|
+
if (msg.type === 'ready') {
|
|
633
|
+
clearTimeout(timeout);
|
|
634
|
+
const tab = tabs.get(tabId);
|
|
635
|
+
if (!tab) return;
|
|
636
|
+
tab.port = msg.port;
|
|
637
|
+
tab.token = msg.token;
|
|
638
|
+
tab.projectName = msg.projectName || projectName;
|
|
639
|
+
tab.status = 'ready';
|
|
640
|
+
|
|
641
|
+
// Create WebContentsView (don't add to content yet — switchTab will manage it)
|
|
642
|
+
const view = new WebContentsView({
|
|
643
|
+
webPreferences: {
|
|
644
|
+
nodeIntegration: false,
|
|
645
|
+
contextIsolation: true,
|
|
646
|
+
preload: join(__dirname, 'tab-content-preload.js'),
|
|
647
|
+
autoplayPolicy: 'no-user-gesture-required',
|
|
648
|
+
},
|
|
649
|
+
});
|
|
650
|
+
const url = `http://127.0.0.1:${msg.port}${msg.token ? `?token=${msg.token}` : ''}`;
|
|
651
|
+
attachDiagListeners(view.webContents, 'tab', { tabId, port: msg.port, project: tab.projectName });
|
|
652
|
+
attachContextMenu(view.webContents);
|
|
653
|
+
view.webContents.loadURL(url);
|
|
654
|
+
tab.view = view;
|
|
655
|
+
|
|
656
|
+
// Push tabId so the renderer can self-identify in approval-broadcast routing.
|
|
657
|
+
view.webContents.once('did-finish-load', () => {
|
|
658
|
+
try { view.webContents.send('tab-id-init', tabId); } catch {}
|
|
659
|
+
// Also send any current aggregated approval state so a reload doesn't lose context.
|
|
660
|
+
broadcastApproval();
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
switchTab(tabId);
|
|
664
|
+
broadcastTabs();
|
|
665
|
+
}
|
|
666
|
+
if (msg.type === 'pty-exit') {
|
|
667
|
+
const tab = tabs.get(tabId);
|
|
668
|
+
if (tab) { tab.status = 'exited'; broadcastTabs(); }
|
|
669
|
+
clearPendingForTab(tabId);
|
|
670
|
+
}
|
|
671
|
+
if (msg.type === 'error') {
|
|
672
|
+
clearTimeout(timeout);
|
|
673
|
+
const tab = tabs.get(tabId);
|
|
674
|
+
if (tab) { tab.status = 'error'; broadcastTabs(); }
|
|
675
|
+
}
|
|
676
|
+
// Pending state changes bubbled up by tab-worker's server.js (see _notifyParentPending).
|
|
677
|
+
if (msg.type === 'pending-add' && msg.kind && msg.id != null) {
|
|
678
|
+
recordPendingAdd(tabId, msg.kind, String(msg.id), msg.payload);
|
|
679
|
+
} else if (msg.type === 'pending-remove' && msg.kind && msg.id != null) {
|
|
680
|
+
recordPendingRemove(tabId, msg.kind, String(msg.id));
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
child.on('exit', (code, signal) => {
|
|
685
|
+
clearTimeout(timeout);
|
|
686
|
+
const tab = tabs.get(tabId);
|
|
687
|
+
// 非正常退出(segfault / OOM / spawnCodex 失败)记 diag;正常 shutdown 安静。
|
|
688
|
+
if (code !== 0 && code !== null) {
|
|
689
|
+
appendDiag('tab:child-exit', { tabId, project: tab?.projectName, code, signal, status: tab?.status });
|
|
690
|
+
} else if (signal && signal !== 'SIGTERM') {
|
|
691
|
+
appendDiag('tab:child-exit', { tabId, project: tab?.projectName, code, signal, status: tab?.status });
|
|
692
|
+
}
|
|
693
|
+
if (tab && tab.status === 'loading') {
|
|
694
|
+
tab.status = 'error';
|
|
695
|
+
broadcastTabs();
|
|
696
|
+
}
|
|
697
|
+
clearPendingForTab(tabId);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// Send launch command
|
|
701
|
+
child.send({
|
|
702
|
+
type: 'launch',
|
|
703
|
+
path: realPath,
|
|
704
|
+
extraArgs,
|
|
705
|
+
codexPath,
|
|
706
|
+
isNpmVersion,
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function switchTab(tabId) {
|
|
711
|
+
const target = tabs.get(tabId);
|
|
712
|
+
if (!target) return;
|
|
713
|
+
|
|
714
|
+
// If target tab is still loading (no view yet), just mark it active but keep workspace visible.
|
|
715
|
+
// 但浮层模式必须先收起:否则切到加载中的 tab 会让浮层悬空(视图仍叠着、workspacePopupOpen 仍为 true、activeTabId 已变)。
|
|
716
|
+
if (!target.view) {
|
|
717
|
+
if (workspacePopupOpen) hideWorkspaceSelector();
|
|
718
|
+
activeTabId = tabId;
|
|
719
|
+
broadcastTabs();
|
|
720
|
+
updateWindowTitle();
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Remove workspace view and all other tab views from content, show only the target tab
|
|
725
|
+
if (workspaceView && !workspaceView.webContents.isDestroyed()) {
|
|
726
|
+
try { mainWindow.contentView.removeChildView(workspaceView); } catch {}
|
|
727
|
+
}
|
|
728
|
+
if (workspacePopupOpen) { workspacePopupOpen = false; sendWorkspaceMode('full'); } // 切 tab 时浮层让位 + 复位 React 浮层态
|
|
729
|
+
for (const [id, tab] of tabs) {
|
|
730
|
+
if (tab.view) {
|
|
731
|
+
if (id === tabId) {
|
|
732
|
+
// Ensure target is attached and visible
|
|
733
|
+
try { mainWindow.contentView.removeChildView(tab.view); } catch {}
|
|
734
|
+
mainWindow.contentView.addChildView(tab.view);
|
|
735
|
+
tab.view.setVisible(true);
|
|
736
|
+
} else {
|
|
737
|
+
try { mainWindow.contentView.removeChildView(tab.view); } catch {}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
updateLayout();
|
|
742
|
+
activeTabId = tabId;
|
|
743
|
+
broadcastTabs();
|
|
744
|
+
sendDeviceModeToTab(tabId); // 切到的 tab 同步 device mode → 对齐 viewMode(仅不匹配时 reload)
|
|
745
|
+
updateWindowTitle();
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async function closeTab(tabId) {
|
|
749
|
+
const tab = tabs.get(tabId);
|
|
750
|
+
if (!tab) return;
|
|
751
|
+
|
|
752
|
+
// Confirmation dialog
|
|
753
|
+
const { response } = await dialog.showMessageBox(mainWindow, {
|
|
754
|
+
type: 'question',
|
|
755
|
+
buttons: ['Close', 'Cancel'],
|
|
756
|
+
defaultId: 1,
|
|
757
|
+
cancelId: 1,
|
|
758
|
+
title: 'Close Tab',
|
|
759
|
+
message: `Close "${tab.projectName}"?`,
|
|
760
|
+
detail: 'The Codex session will be terminated.',
|
|
761
|
+
});
|
|
762
|
+
if (response !== 0) return;
|
|
763
|
+
|
|
764
|
+
// Kill child process
|
|
765
|
+
if (tab.child) {
|
|
766
|
+
if (tab.child.connected) {
|
|
767
|
+
try { tab.child.send({ type: 'shutdown' }); } catch {}
|
|
768
|
+
const forceTimer = setTimeout(() => {
|
|
769
|
+
try { tab.child.kill('SIGKILL'); } catch {}
|
|
770
|
+
}, 5000);
|
|
771
|
+
tab.child.on('exit', () => clearTimeout(forceTimer));
|
|
772
|
+
} else {
|
|
773
|
+
// IPC channel closed, kill with platform-aware escalation
|
|
774
|
+
killChildEscalating(tab.child, 3000);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Remove view
|
|
779
|
+
if (tab.view) {
|
|
780
|
+
mainWindow.contentView.removeChildView(tab.view);
|
|
781
|
+
tab.view.webContents.close();
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
tabs.delete(tabId);
|
|
785
|
+
clearPendingForTab(tabId);
|
|
786
|
+
|
|
787
|
+
// Switch to another tab or show workspace
|
|
788
|
+
if (tabs.size > 0) {
|
|
789
|
+
const nextId = tabs.keys().next().value;
|
|
790
|
+
switchTab(nextId);
|
|
791
|
+
} else {
|
|
792
|
+
activeTabId = null;
|
|
793
|
+
showWorkspaceSelector();
|
|
794
|
+
}
|
|
795
|
+
broadcastTabs();
|
|
796
|
+
updateWindowTitle();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// 懒创建并复用 workspaceView。背景设透明('#00000000'),让浮层模式下 React 画的半透明遮罩
|
|
800
|
+
// 能透出后面的 tab;整页模式下 React 自身画不透明背景,透明视图无副作用。
|
|
801
|
+
function ensureWorkspaceView() {
|
|
802
|
+
if (!workspaceView || workspaceView.webContents.isDestroyed()) {
|
|
803
|
+
workspaceView = new WebContentsView({
|
|
804
|
+
webPreferences: {
|
|
805
|
+
nodeIntegration: false,
|
|
806
|
+
contextIsolation: true,
|
|
807
|
+
preload: join(__dirname, 'workspace-preload.js'),
|
|
808
|
+
autoplayPolicy: 'no-user-gesture-required',
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
try { workspaceView.setBackgroundColor('#00000000'); } catch {}
|
|
812
|
+
const token = mgmtServerMod.getAccessToken();
|
|
813
|
+
attachDiagListeners(workspaceView.webContents, 'workspace');
|
|
814
|
+
attachContextMenu(workspaceView.webContents);
|
|
815
|
+
// 加载完成后重推当前模式:首次开浮层时 sendWorkspaceMode('popup') 早于 loadURL 完成会丢,靠这里兜底(不再只依赖 React mount 端 request)。
|
|
816
|
+
workspaceView.webContents.on('did-finish-load', () => sendWorkspaceMode(workspacePopupOpen ? 'popup' : 'full'));
|
|
817
|
+
// 渲染进程崩溃:复位浮层标志并丢弃视图,下次开 picker 由 ensureWorkspaceView 重建,避免「崩溃后首次点 + 只清状态不打开」的双击 papercut。
|
|
818
|
+
workspaceView.webContents.on('render-process-gone', () => { workspacePopupOpen = false; workspaceView = null; });
|
|
819
|
+
workspaceView.webContents.loadURL(`http://127.0.0.1:${mgmtPort}${token ? `?token=${token}` : ''}`);
|
|
820
|
+
}
|
|
821
|
+
return workspaceView;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// 把当前工作区选择器模式推给 workspaceView 的 React 端('full' 整页 / 'popup' 浮层)。
|
|
825
|
+
function sendWorkspaceMode(mode) {
|
|
826
|
+
const wc = workspaceView && workspaceView.webContents;
|
|
827
|
+
if (wc && !wc.isDestroyed()) wc.send('workspace-mode', mode);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// 「+」/Cmd+T 入口:有 tab → 浮层;无 tab → 整页(理论上无 tab 时 tab bar 不显示「+」,这里兜底)。
|
|
831
|
+
function openProjectPicker() {
|
|
832
|
+
if (tabs.size > 0) showWorkspacePopup();
|
|
833
|
+
else showWorkspaceSelector();
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function showWorkspaceSelector() {
|
|
837
|
+
ensureWorkspaceView();
|
|
838
|
+
// Remove all tab views, then add workspace view on top
|
|
839
|
+
for (const tab of tabs.values()) {
|
|
840
|
+
if (tab.view) {
|
|
841
|
+
try { mainWindow.contentView.removeChildView(tab.view); } catch {}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
try { mainWindow.contentView.removeChildView(workspaceView); } catch {}
|
|
845
|
+
mainWindow.contentView.addChildView(workspaceView);
|
|
846
|
+
workspaceView.setVisible(true);
|
|
847
|
+
updateLayout();
|
|
848
|
+
activeTabId = null;
|
|
849
|
+
workspacePopupOpen = false;
|
|
850
|
+
broadcastTabs();
|
|
851
|
+
sendWorkspaceMode('full');
|
|
852
|
+
updateWindowTitle();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// 浮层模式:保留当前活动 tab 视图不移除,把 workspaceView 叠在其上置顶(透明背景),
|
|
856
|
+
// activeTabId 不变 → tab bar 仍高亮当前 tab、保留「+」与 header 控件。
|
|
857
|
+
function showWorkspacePopup() {
|
|
858
|
+
if (tabs.size === 0) { showWorkspaceSelector(); return; } // 无 tab 兜底回整页,避免浮层后无内容
|
|
859
|
+
ensureWorkspaceView();
|
|
860
|
+
try { mainWindow.contentView.removeChildView(workspaceView); } catch {}
|
|
861
|
+
mainWindow.contentView.addChildView(workspaceView);
|
|
862
|
+
workspaceView.setVisible(true);
|
|
863
|
+
updateLayout();
|
|
864
|
+
workspacePopupOpen = true;
|
|
865
|
+
sendWorkspaceMode('popup');
|
|
866
|
+
broadcastTabs();
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function hideWorkspaceSelector() {
|
|
870
|
+
if (workspaceView && !workspaceView.webContents.isDestroyed()) {
|
|
871
|
+
try { mainWindow.contentView.removeChildView(workspaceView); } catch {}
|
|
872
|
+
}
|
|
873
|
+
workspacePopupOpen = false;
|
|
874
|
+
sendWorkspaceMode('full'); // 收起即把 React 浮层态 / cxv-ws-popup body class 复位,避免隐藏视图上的悬挂状态
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// --- IPC handlers ---
|
|
878
|
+
ipcMain.on('tab-switch', (_, tabId) => switchTab(tabId));
|
|
879
|
+
ipcMain.on('tab-close', (_, tabId) => closeTab(tabId));
|
|
880
|
+
// 「+」点击:浮层已开 → 再点收起(toggle);否则按是否有 tab 弹浮层/整页。
|
|
881
|
+
ipcMain.on('tab-new', () => {
|
|
882
|
+
if (workspacePopupOpen) { hideWorkspaceSelector(); broadcastTabs(); }
|
|
883
|
+
else openProjectPicker();
|
|
884
|
+
});
|
|
885
|
+
// 浮层内的关闭按钮 / 点遮罩 / Esc → 收起浮层,露出原 tab。
|
|
886
|
+
ipcMain.on('workspace-popup-close', () => {
|
|
887
|
+
if (workspacePopupOpen) { hideWorkspaceSelector(); broadcastTabs(); }
|
|
888
|
+
});
|
|
889
|
+
// React 端挂载即拉取当前模式,消除首帧竞态(镜像 device-mode 的 request/response)。
|
|
890
|
+
ipcMain.on('request-workspace-mode', (event) => {
|
|
891
|
+
if (event.sender && !event.sender.isDestroyed()) {
|
|
892
|
+
event.sender.send('workspace-mode', workspacePopupOpen ? 'popup' : 'full');
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
ipcMain.on('workspace-launch', (_, data) => {
|
|
896
|
+
devLog('[main] workspace-launch IPC:', data);
|
|
897
|
+
createTab(data.path, data.extraArgs);
|
|
898
|
+
});
|
|
899
|
+
ipcMain.on('approval-jump', (_, tabId) => {
|
|
900
|
+
if (tabId != null && tabs.has(tabId)) switchTab(tabId);
|
|
901
|
+
});
|
|
902
|
+
// iPad 模式:renderer 发无状态 toggle,main 翻转权威状态并应用;新挂载的 header 用 request 同步初值。
|
|
903
|
+
ipcMain.on('toggle-device-mode', () => setDeviceMode(!deviceMode));
|
|
904
|
+
ipcMain.on('request-device-mode', (event) => {
|
|
905
|
+
if (event.sender && !event.sender.isDestroyed()) event.sender.send('device-mode-changed', deviceMode);
|
|
906
|
+
});
|
|
907
|
+
// tab bar 挂载即拉取当前活动 tab 已缓存的 header 模型,消除 did-finish-load 与首次 push 的竞态。
|
|
908
|
+
ipcMain.on('request-header-model', (event) => {
|
|
909
|
+
if (!event.sender || event.sender.isDestroyed()) return;
|
|
910
|
+
const hm = activeTabId != null ? (tabs.get(activeTabId)?.headerModel ?? null) : null;
|
|
911
|
+
event.sender.send('header-model', hm, activeTabId);
|
|
912
|
+
});
|
|
913
|
+
// Header 控件迁移:活动 tab 的 React header 把「迁移到 tab bar 的控件」模型推上来,main 缓存到该 tab,
|
|
914
|
+
// 若它是活动 tab 则转发给 tab bar 渲染。tab bar 的点击回传 header-action → 转发给当前活动 tab 执行。
|
|
915
|
+
ipcMain.on('set-header-model', (event, model) => {
|
|
916
|
+
const id = _resolveSenderTabId(event.sender);
|
|
917
|
+
if (id == null) return;
|
|
918
|
+
const tab = tabs.get(id);
|
|
919
|
+
if (!tab) return;
|
|
920
|
+
tab.headerModel = model;
|
|
921
|
+
if (id === activeTabId && tabBarView?.webContents && !tabBarView.webContents.isDestroyed()) {
|
|
922
|
+
tabBarView.webContents.send('header-model', model, id);
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
ipcMain.on('header-action', (_event, payload) => {
|
|
926
|
+
// 优先投递给「按钮所属 tab」(tab bar 随模型回传 tabId);切 tab 竞态下不会误投到新活动 tab。
|
|
927
|
+
// 无 tabId(兜底/旧路径)时退回当前活动 tab。
|
|
928
|
+
const targetId = (payload && payload.tabId != null && tabs.has(payload.tabId)) ? payload.tabId : activeTabId;
|
|
929
|
+
const tab = targetId != null ? tabs.get(targetId) : null;
|
|
930
|
+
const wc = tab && tab.view && tab.view.webContents;
|
|
931
|
+
// win32 HTML 菜单栏:点 File/Edit/View/Window 时附上已翻译的菜单模型,
|
|
932
|
+
// React 端(AppHeader)据此渲染跟随皮肤的下拉,前端零 i18n key。展开拷贝、不原地改入参。
|
|
933
|
+
const fwd = (payload && payload.type === 'menuBarOpen')
|
|
934
|
+
? { ...payload, menus: serializeMenuModel(t, process.platform) }
|
|
935
|
+
: payload;
|
|
936
|
+
if (wc && !wc.isDestroyed()) wc.send('header-action', fwd);
|
|
937
|
+
});
|
|
938
|
+
// win32 HTML 菜单下拉的叶子点击(React 端经 tab-content-preload 发来)。
|
|
939
|
+
// sender 校验对标 set-approval-pref:挡掉 tab 销毁后的延迟事件触发 close-window 等全局命令。
|
|
940
|
+
ipcMain.on('menu-command', (event, id) => {
|
|
941
|
+
if (!event.sender || event.sender.isDestroyed()) return;
|
|
942
|
+
dispatchMenuCommand(id);
|
|
943
|
+
});
|
|
944
|
+
// React 下拉开/关状态 → tab bar(打开期间 hover 相邻顶级菜单按钮即切换,原生菜单栏惯例)。
|
|
945
|
+
ipcMain.on('menu-bar-state', (event, open) => {
|
|
946
|
+
if (!event.sender || event.sender.isDestroyed()) return;
|
|
947
|
+
if (tabBarView?.webContents && !tabBarView.webContents.isDestroyed()) {
|
|
948
|
+
tabBarView.webContents.send('menu-bar-state', !!open);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
// Resolve sender's tabId by reverse-scanning the tabs Map. O(n) but n is small (<10).
|
|
953
|
+
// Used for PTY plan IPC where the sender (chat WebContentsView) is the authority on which tab
|
|
954
|
+
// owns the message. Falls back to client-supplied tabId if reverse lookup fails (e.g. early init).
|
|
955
|
+
function _resolveSenderTabId(sender) {
|
|
956
|
+
if (!sender) return null;
|
|
957
|
+
for (const [id, t] of tabs) {
|
|
958
|
+
if (t.view && t.view.webContents === sender) return id;
|
|
959
|
+
}
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
ipcMain.on('pty-plan-pending', (event, msg) => {
|
|
964
|
+
if (!msg || msg.id == null) return;
|
|
965
|
+
const tabId = _resolveSenderTabId(event.sender) ?? (msg.tabId ?? null);
|
|
966
|
+
if (tabId == null) return;
|
|
967
|
+
recordPendingAdd(tabId, 'ptyPlan', String(msg.id), msg.payload || {});
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
ipcMain.on('pty-plan-resolved', (event, msg) => {
|
|
971
|
+
if (!msg || msg.id == null) return;
|
|
972
|
+
const tabId = _resolveSenderTabId(event.sender) ?? (msg.tabId ?? null);
|
|
973
|
+
if (tabId == null) return;
|
|
974
|
+
recordPendingRemove(tabId, 'ptyPlan', String(msg.id));
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
// 渲染端兜底:WS 断连 / ChatView unmount 时 server 不一定推 ask-hook-resolved,
|
|
978
|
+
// renderer 通过该 IPC 让 main 同步清 pendingByTab[tabId].ask。
|
|
979
|
+
// 与 server.js 的 ask-hook-resolved/sdk-ask-resolved 路径并行;recordPendingRemove 对不存在的 id 是 no-op,重复调用安全。
|
|
980
|
+
ipcMain.on('ask-resolved', (event, msg) => {
|
|
981
|
+
if (!msg || msg.id == null) return;
|
|
982
|
+
const tabId = _resolveSenderTabId(event.sender) ?? (msg.tabId ?? null);
|
|
983
|
+
if (tabId == null) return;
|
|
984
|
+
recordPendingRemove(tabId, 'ask', String(msg.id));
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
// Renderer 同步用户偏好(目前仅 notifyOnlyWhenHidden 影响 main 进程的通知行为;
|
|
988
|
+
// 其他字段如 modalEnabled / soundEnabled 仅在 renderer 内消费,这里 forward-compatible 接收但不使用)。
|
|
989
|
+
// 任何 tab 改了都会推同一份(prefs 全局共享单一 preferences.json),最后一次 win;无 tab 隔离需求。
|
|
990
|
+
ipcMain.on('set-approval-pref', (event, prefs) => {
|
|
991
|
+
// 防御加固:contextIsolation 已经隔离了 renderer/main world,这里再校验 sender 还在/未销毁,
|
|
992
|
+
// 避免 webview tab 销毁后的延迟事件继续修改全局偏好。
|
|
993
|
+
if (!event.sender || event.sender.isDestroyed()) return;
|
|
994
|
+
if (!prefs || typeof prefs !== 'object') return;
|
|
995
|
+
if (typeof prefs.notifyOnlyWhenHidden === 'boolean') {
|
|
996
|
+
_notifyOnlyWhenHidden = prefs.notifyOnlyWhenHidden;
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// --- Cleanup ---
|
|
1001
|
+
let isQuitting = false;
|
|
1002
|
+
async function cleanupAll() {
|
|
1003
|
+
if (isQuitting) return;
|
|
1004
|
+
isQuitting = true;
|
|
1005
|
+
|
|
1006
|
+
// 退出兜底:窗口还活着就把最终几何同步落盘(去抖 timer 可能还没到点)。
|
|
1007
|
+
if (_winStateTimer) { clearTimeout(_winStateTimer); _winStateTimer = null; }
|
|
1008
|
+
saveWinStateNow();
|
|
1009
|
+
|
|
1010
|
+
const promises = [];
|
|
1011
|
+
for (const [id, tab] of tabs) {
|
|
1012
|
+
if (tab.child && tab.child.connected) {
|
|
1013
|
+
try { tab.child.send({ type: 'shutdown' }); } catch {}
|
|
1014
|
+
promises.push(new Promise(resolve => {
|
|
1015
|
+
const timer = setTimeout(() => { try { tab.child.kill('SIGKILL'); } catch {} resolve(); }, 5000);
|
|
1016
|
+
tab.child.on('exit', () => { clearTimeout(timer); resolve(); });
|
|
1017
|
+
}));
|
|
1018
|
+
} else if (tab.child) {
|
|
1019
|
+
// Child process exists but IPC channel is closed, kill with platform-aware escalation
|
|
1020
|
+
promises.push(killChildEscalating(tab.child, 3000));
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
await Promise.all(promises);
|
|
1024
|
+
if (mgmtServerMod) await mgmtServerMod.stopViewer().catch(() => {});
|
|
1025
|
+
// 退出收尾:清掉去抖 timer(防悬挂回调对已 destroy 的 view 做无谓广播),
|
|
1026
|
+
// 并等 diag 异步队列落盘——否则退出前最后几条诊断(child-exit 等)会丢。
|
|
1027
|
+
if (_aggregateTimer) { clearTimeout(_aggregateTimer); _aggregateTimer = null; }
|
|
1028
|
+
await diagFlush().catch(() => {});
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// --- App menu ---
|
|
1032
|
+
// 菜单结构的单一数据源在 electron/menu-model.js(原生菜单与 win32 HTML 菜单栏共用)。
|
|
1033
|
+
// 文案经 server/i18n.js t() 翻译;preferences.json 的 lang 变化时 watchTheme 会重建。
|
|
1034
|
+
// 缩放(Cmd/Ctrl +/-/0)仍由 renderer 的「显示大小」接管,模型里不含 zoom 条目,
|
|
1035
|
+
// 否则两处各调 setZoomFactor 会叠加导致双重缩放。
|
|
1036
|
+
function buildMenu() {
|
|
1037
|
+
const isMac = process.platform === 'darwin';
|
|
1038
|
+
const template = [
|
|
1039
|
+
...(isMac ? [{
|
|
1040
|
+
label: app.name,
|
|
1041
|
+
submenu: [
|
|
1042
|
+
{ role: 'about' },
|
|
1043
|
+
{ type: 'separator' },
|
|
1044
|
+
{ role: 'hide' },
|
|
1045
|
+
{ role: 'hideOthers' },
|
|
1046
|
+
{ role: 'unhide' },
|
|
1047
|
+
{ type: 'separator' },
|
|
1048
|
+
{ role: 'quit' },
|
|
1049
|
+
],
|
|
1050
|
+
}] : []),
|
|
1051
|
+
...buildMenuModel().map((menu) => ({
|
|
1052
|
+
label: t(menu.labelKey),
|
|
1053
|
+
submenu: [
|
|
1054
|
+
...menu.items.map((it) => {
|
|
1055
|
+
if (it.type === 'separator') return { type: 'separator' };
|
|
1056
|
+
// edit 类条目走 role:作用于「当前聚焦」webContents(workspace 选择器里也要能粘贴);
|
|
1057
|
+
// darwinRole:macOS 保留原生语义(zoom ≠ maximize,不改变 mac 既有行为)。
|
|
1058
|
+
if (it.role) return { role: it.role, label: t(it.labelKey), accelerator: it.accel };
|
|
1059
|
+
if (it.darwinRole && isMac) return { role: it.darwinRole, label: t(it.labelKey) };
|
|
1060
|
+
return { label: t(it.labelKey), accelerator: it.accel, click: () => dispatchMenuCommand(it.id) };
|
|
1061
|
+
}),
|
|
1062
|
+
// Tab switching shortcuts: Cmd+1-9(仅挂在 Window 菜单,隐藏条目只为注册 accelerator)
|
|
1063
|
+
...(menu.id === 'window' ? Array.from({ length: 9 }, (_, i) => ({
|
|
1064
|
+
label: `Tab ${i + 1}`,
|
|
1065
|
+
accelerator: `CmdOrCtrl+${i + 1}`,
|
|
1066
|
+
visible: false,
|
|
1067
|
+
click: () => {
|
|
1068
|
+
const ids = [...tabs.keys()];
|
|
1069
|
+
if (ids[i]) switchTab(ids[i]);
|
|
1070
|
+
},
|
|
1071
|
+
})) : []),
|
|
1072
|
+
],
|
|
1073
|
+
})),
|
|
1074
|
+
];
|
|
1075
|
+
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// 菜单命令统一派发:原生菜单 click 与 win32 HTML 菜单(React 下拉经 menu-command IPC)共用。
|
|
1079
|
+
// 编辑/视图类命令显式指向「活动 tab 的内容视图」—— 点 HTML 菜单时焦点在 tab-bar view,
|
|
1080
|
+
// 若走 role 派发会作用到 tab-bar 自己(reload 会把 tab 栏刷掉)。
|
|
1081
|
+
// 零 tab(workspace 选择器)时 tab 类命令为 no-op —— 有意行为:选择器不需要 reload/编辑命令,
|
|
1082
|
+
// 与改前 role 派发(刷聚焦视图)的差异已接受。
|
|
1083
|
+
function dispatchMenuCommand(id) {
|
|
1084
|
+
const wc = activeTabId != null ? tabs.get(activeTabId)?.view?.webContents : null;
|
|
1085
|
+
const tabWc = (wc && !wc.isDestroyed()) ? wc : null;
|
|
1086
|
+
switch (id) {
|
|
1087
|
+
case 'new-tab': openProjectPicker(); break;
|
|
1088
|
+
case 'close-tab': if (activeTabId != null) closeTab(activeTabId); break;
|
|
1089
|
+
case 'undo': case 'redo': case 'cut': case 'copy': case 'paste': case 'selectAll':
|
|
1090
|
+
if (tabWc) tabWc[id]();
|
|
1091
|
+
break;
|
|
1092
|
+
case 'reload': if (tabWc) tabWc.reload(); break;
|
|
1093
|
+
case 'force-reload': if (tabWc) tabWc.reloadIgnoringCache(); break;
|
|
1094
|
+
case 'toggle-devtools': if (tabWc) tabWc.toggleDevTools(); break;
|
|
1095
|
+
case 'toggle-fullscreen': if (mainWindow) mainWindow.setFullScreen(!mainWindow.isFullScreen()); break;
|
|
1096
|
+
case 'minimize': if (mainWindow) mainWindow.minimize(); break;
|
|
1097
|
+
case 'maximize':
|
|
1098
|
+
if (mainWindow) { if (mainWindow.isMaximized()) mainWindow.unmaximize(); else mainWindow.maximize(); }
|
|
1099
|
+
break;
|
|
1100
|
+
case 'close-window': if (mainWindow) mainWindow.close(); break;
|
|
1101
|
+
case 'prev-tab': cycleTab(-1); break;
|
|
1102
|
+
case 'next-tab': cycleTab(1); break;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// 已翻译的菜单模型 + tab-bar 文案,经 IPC 发给 tab-bar(win32 渲染 HTML 菜单栏)。
|
|
1107
|
+
// lang 变化时 watchTheme 会重发。
|
|
1108
|
+
function menuModelPayload() {
|
|
1109
|
+
return {
|
|
1110
|
+
menus: serializeMenuModel(t, process.platform),
|
|
1111
|
+
uiStrings: {
|
|
1112
|
+
newTab: t('electron.tabbar.newTab'),
|
|
1113
|
+
toIpad: t('electron.tabbar.toIpad'),
|
|
1114
|
+
toPc: t('electron.tabbar.toPc'),
|
|
1115
|
+
menu: t('electron.tabbar.menu'),
|
|
1116
|
+
},
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function broadcastMenuModel() {
|
|
1121
|
+
if (tabBarView?.webContents && !tabBarView.webContents.isDestroyed()) {
|
|
1122
|
+
try { tabBarView.webContents.send('menu-model', menuModelPayload()); } catch {}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// --- 右键菜单 ---
|
|
1127
|
+
// Electron 默认没有任何右键菜单(浏览器版才有 Chrome 的)。给内容视图补一份原生编辑菜单,
|
|
1128
|
+
// 文案走 server/i18n.js。页面里自带 React 右键菜单的区域(文件树等)会 preventDefault,
|
|
1129
|
+
// 此时 context-menu 事件不触发,不会出现双重菜单。原生 popup 样式跟随 OS,无法跟随应用皮肤(已接受)。
|
|
1130
|
+
function attachContextMenu(wc) {
|
|
1131
|
+
wc.on('context-menu', (_e, params) => {
|
|
1132
|
+
const items = [];
|
|
1133
|
+
const ef = params.editFlags || {};
|
|
1134
|
+
if (params.isEditable) {
|
|
1135
|
+
items.push(
|
|
1136
|
+
{ label: t('electron.menu.undo'), role: 'undo', enabled: !!ef.canUndo },
|
|
1137
|
+
{ label: t('electron.menu.redo'), role: 'redo', enabled: !!ef.canRedo },
|
|
1138
|
+
{ type: 'separator' },
|
|
1139
|
+
{ label: t('electron.menu.cut'), role: 'cut', enabled: !!ef.canCut },
|
|
1140
|
+
{ label: t('electron.menu.copy'), role: 'copy', enabled: !!ef.canCopy },
|
|
1141
|
+
{ label: t('electron.menu.paste'), role: 'paste', enabled: !!ef.canPaste },
|
|
1142
|
+
{ label: t('electron.menu.selectAll'), role: 'selectAll', enabled: !!ef.canSelectAll },
|
|
1143
|
+
);
|
|
1144
|
+
} else if (params.selectionText && params.selectionText.trim()) {
|
|
1145
|
+
items.push({ label: t('electron.menu.copy'), role: 'copy' });
|
|
1146
|
+
}
|
|
1147
|
+
if (params.linkURL) {
|
|
1148
|
+
if (items.length) items.push({ type: 'separator' });
|
|
1149
|
+
items.push({ label: t('electron.menu.copyLink'), click: () => clipboard.writeText(params.linkURL) });
|
|
1150
|
+
}
|
|
1151
|
+
if (!items.length) return;
|
|
1152
|
+
try { Menu.buildFromTemplate(items).popup({ window: mainWindow }); } catch {}
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function cycleTab(direction) {
|
|
1157
|
+
const ids = [...tabs.keys()];
|
|
1158
|
+
if (ids.length === 0) return;
|
|
1159
|
+
const idx = ids.indexOf(activeTabId);
|
|
1160
|
+
const next = (idx + direction + ids.length) % ids.length;
|
|
1161
|
+
switchTab(ids[next]);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// --- Theme watching ---
|
|
1165
|
+
// 之前用 watchFile(interval:2000) 轮询 prefs,多 tab 切主题 → 其它 tab 的 tab-bar 最多滞后 2s。
|
|
1166
|
+
// fs.watch 走 kqueue/inotify 事件驱动,~0ms 滞后,且不再周期性 stat 浪费资源。
|
|
1167
|
+
// 生命周期:watcher 跟随 main process 进程结束自动清理,不显式 close —— 单一全局实例,
|
|
1168
|
+
// watchTheme() 不会重入(在 createWindow → ready 路径里只调用一次)。
|
|
1169
|
+
function watchTheme() {
|
|
1170
|
+
let warnedCorrupt = false;
|
|
1171
|
+
try {
|
|
1172
|
+
const prefsPath = join(getCodexConfigDir(), 'cx-viewer', 'preferences.json');
|
|
1173
|
+
if (!existsSync(prefsPath)) {
|
|
1174
|
+
// 首装尚无 preferences.json:直接 return 会让 watcher 永不启动(主题/语言热切换失效到重启)。
|
|
1175
|
+
// 改为 watch 所在目录,等文件出现后再切到文件级 watch。
|
|
1176
|
+
const dir = dirname(prefsPath);
|
|
1177
|
+
if (!existsSync(dir)) return; // 目录都没有(异常环境),放弃热切换,不影响主流程
|
|
1178
|
+
const dirWatcher = watch(dir, () => {
|
|
1179
|
+
if (existsSync(prefsPath)) {
|
|
1180
|
+
try { dirWatcher.close(); } catch {}
|
|
1181
|
+
watchTheme();
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
// 读取 theme + lang:写入瞬间可能拿到不完整 JSON,沿用「读失败回落上次已知值」策略。
|
|
1187
|
+
const readPrefs = () => {
|
|
1188
|
+
try {
|
|
1189
|
+
const prefs = JSON.parse(readFileSync(prefsPath, 'utf-8'));
|
|
1190
|
+
warnedCorrupt = false;
|
|
1191
|
+
return {
|
|
1192
|
+
theme: prefs.themeColor === 'light' ? 'light' : 'dark',
|
|
1193
|
+
lang: typeof prefs.lang === 'string' ? prefs.lang : null,
|
|
1194
|
+
};
|
|
1195
|
+
} catch (err) {
|
|
1196
|
+
// prefs 写入瞬间被读到可能拿到不完整 JSON;仅首次 warn,避免连续事件刷屏
|
|
1197
|
+
if (!warnedCorrupt) {
|
|
1198
|
+
warnedCorrupt = true;
|
|
1199
|
+
devWarn('[watchTheme] preferences.json read/parse failed, falling back:', err.message);
|
|
1200
|
+
}
|
|
1201
|
+
return null;
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
const applyOverlayTheme = (theme) => {
|
|
1205
|
+
// win32:原生 最小化/最大化/关闭 按钮区域跟随皮肤换色;backgroundColor 同步防止 resize 露底色。
|
|
1206
|
+
if (process.platform !== 'win32' || !mainWindow || mainWindow.isDestroyed()) return;
|
|
1207
|
+
const c = THEME_COLORS[theme] || THEME_COLORS.dark;
|
|
1208
|
+
try {
|
|
1209
|
+
if (typeof mainWindow.setTitleBarOverlay === 'function') {
|
|
1210
|
+
mainWindow.setTitleBarOverlay({ color: c.barBg, symbolColor: c.sym, height: TAB_BAR_HEIGHT });
|
|
1211
|
+
}
|
|
1212
|
+
if (typeof mainWindow.setBackgroundColor === 'function') mainWindow.setBackgroundColor(c.winBg);
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
devWarn('[watchTheme] setTitleBarOverlay failed:', err.message);
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
let current = readPrefs() || { theme: 'dark', lang: null };
|
|
1218
|
+
if (tabBarView?.webContents) tabBarView.webContents.send('theme-changed', current.theme);
|
|
1219
|
+
applyOverlayTheme(current.theme);
|
|
1220
|
+
// writeFileSync 在写入过程中可能触发多次 change 事件(特别是 macOS 下原子替换路径);
|
|
1221
|
+
// 用 readPrefs + diff 自己做幂等,重复事件无副作用。
|
|
1222
|
+
watch(prefsPath, () => {
|
|
1223
|
+
const next = readPrefs();
|
|
1224
|
+
if (!next) return;
|
|
1225
|
+
if (next.theme !== current.theme) {
|
|
1226
|
+
current = { ...current, theme: next.theme };
|
|
1227
|
+
if (tabBarView?.webContents && !tabBarView.webContents.isDestroyed()) {
|
|
1228
|
+
tabBarView.webContents.send('theme-changed', current.theme);
|
|
1229
|
+
}
|
|
1230
|
+
applyOverlayTheme(current.theme);
|
|
1231
|
+
}
|
|
1232
|
+
// UI 切语言 → server 已 setLang 并写入 prefs;主进程跟随:重建原生菜单 + 重发 HTML 菜单模型。
|
|
1233
|
+
if (next.lang && next.lang !== current.lang) {
|
|
1234
|
+
current = { ...current, lang: next.lang };
|
|
1235
|
+
setLang(next.lang);
|
|
1236
|
+
buildMenu();
|
|
1237
|
+
broadcastMenuModel();
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
} catch (err) {
|
|
1241
|
+
devWarn('[watchTheme] init failed:', err.message);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// --- Single instance lock ---
|
|
1246
|
+
const gotLock = app.requestSingleInstanceLock();
|
|
1247
|
+
if (!gotLock) {
|
|
1248
|
+
app.quit();
|
|
1249
|
+
} else {
|
|
1250
|
+
app.on('second-instance', () => {
|
|
1251
|
+
if (mainWindow) {
|
|
1252
|
+
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
1253
|
+
mainWindow.focus();
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
app.on('before-quit', async (e) => {
|
|
1258
|
+
if (!isQuitting) {
|
|
1259
|
+
e.preventDefault();
|
|
1260
|
+
// 有打开的 tab 时,弹确认框
|
|
1261
|
+
if (tabs.size > 0 && mainWindow && !mainWindow.isDestroyed()) {
|
|
1262
|
+
const names = [...tabs.values()].map(tb => tb.projectName).join(', ');
|
|
1263
|
+
const { response } = await dialog.showMessageBox(mainWindow, {
|
|
1264
|
+
type: 'question',
|
|
1265
|
+
buttons: [t('electron.quit.buttonQuit'), t('electron.quit.buttonCancel')],
|
|
1266
|
+
defaultId: 1,
|
|
1267
|
+
cancelId: 1,
|
|
1268
|
+
title: t('electron.quit.title'),
|
|
1269
|
+
message: t('electron.quit.message', { count: tabs.size }),
|
|
1270
|
+
detail: `${names}\n\n${t('electron.quit.detail')}`,
|
|
1271
|
+
});
|
|
1272
|
+
if (response !== 0) return; // 用户取消
|
|
1273
|
+
}
|
|
1274
|
+
await cleanupAll();
|
|
1275
|
+
app.exit(0);
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
app.whenReady().then(async () => {
|
|
1280
|
+
// Windows 任务栏分组/通知身份:须与 electron-builder.yml 的 appId 一致,否则任务栏按钮分裂。
|
|
1281
|
+
if (process.platform === 'win32') app.setAppUserModelId('com.cxviewer.app');
|
|
1282
|
+
|
|
1283
|
+
// Start management server
|
|
1284
|
+
await startMgmtServer();
|
|
1285
|
+
|
|
1286
|
+
buildMenu();
|
|
1287
|
+
|
|
1288
|
+
// 恢复上次窗口几何(显示器布局可能已变,validateState 越界则回落默认居中)。
|
|
1289
|
+
let savedWinState = null;
|
|
1290
|
+
try {
|
|
1291
|
+
savedWinState = validateState(
|
|
1292
|
+
loadState(readFileSync, WINDOW_STATE_FILE),
|
|
1293
|
+
screen.getAllDisplays().map((d) => d.workArea),
|
|
1294
|
+
);
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
devWarn('[main] restore window state failed:', err?.message || err);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// Create window
|
|
1300
|
+
// - backgroundColor:首帧用主题底色绘制,消除暗色皮肤下启动白屏闪烁;
|
|
1301
|
+
// - win32:隐藏原生标题栏,titleBarOverlay 只保留原生 最小化/最大化/关闭(保住 Win11
|
|
1302
|
+
// Snap Layouts 与双击最大化),配色跟随皮肤,高度对齐 50px tab bar —— tab bar 即标题栏;
|
|
1303
|
+
// - macOS/Linux:维持原 hiddenInset 行为不变。
|
|
1304
|
+
const _themeColors = THEME_COLORS[_startupTheme];
|
|
1305
|
+
mainWindow = new BaseWindow({
|
|
1306
|
+
width: savedWinState?.width ?? 1400,
|
|
1307
|
+
height: savedWinState?.height ?? 900,
|
|
1308
|
+
...(savedWinState ? { x: savedWinState.x, y: savedWinState.y } : {}),
|
|
1309
|
+
minWidth: 800,
|
|
1310
|
+
minHeight: 600,
|
|
1311
|
+
title: 'CX Viewer',
|
|
1312
|
+
backgroundColor: _themeColors.winBg,
|
|
1313
|
+
...(process.platform === 'win32' ? {
|
|
1314
|
+
titleBarStyle: 'hidden',
|
|
1315
|
+
titleBarOverlay: { color: _themeColors.barBg, symbolColor: _themeColors.sym, height: TAB_BAR_HEIGHT },
|
|
1316
|
+
} : {
|
|
1317
|
+
titleBarStyle: 'hiddenInset',
|
|
1318
|
+
trafficLightPosition: { x: 16, y: 17 },
|
|
1319
|
+
}),
|
|
1320
|
+
});
|
|
1321
|
+
if (savedWinState?.maximized) mainWindow.maximize();
|
|
1322
|
+
// 原生菜单栏只隐藏不注销:accelerator 仍生效(setMenu(null) 会连快捷键一起杀掉;
|
|
1323
|
+
// autoHideMenuBar 则按 Alt 会唤回原生英文菜单,与 HTML 菜单栏打架)。
|
|
1324
|
+
if (process.platform === 'win32') {
|
|
1325
|
+
try { mainWindow.setMenuBarVisibility(false); } catch {}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// Tab bar
|
|
1329
|
+
tabBarView = new WebContentsView({
|
|
1330
|
+
webPreferences: {
|
|
1331
|
+
nodeIntegration: false,
|
|
1332
|
+
contextIsolation: true,
|
|
1333
|
+
preload: join(__dirname, 'tab-preload.js'),
|
|
1334
|
+
autoplayPolicy: 'no-user-gesture-required',
|
|
1335
|
+
},
|
|
1336
|
+
});
|
|
1337
|
+
attachDiagListeners(tabBarView.webContents, 'tabBar');
|
|
1338
|
+
tabBarView.webContents.loadFile(join(__dirname, 'tab-bar.html'));
|
|
1339
|
+
mainWindow.contentView.addChildView(tabBarView);
|
|
1340
|
+
|
|
1341
|
+
// 全屏状态广播:进入全屏后 macOS 红黄绿按钮隐藏,tab-bar 需移除占位
|
|
1342
|
+
const sendFullscreenState = () => {
|
|
1343
|
+
if (!tabBarView?.webContents || tabBarView.webContents.isDestroyed()) return;
|
|
1344
|
+
try { tabBarView.webContents.send('fullscreen-changed', !!mainWindow?.isFullScreen?.()); } catch {}
|
|
1345
|
+
};
|
|
1346
|
+
tabBarView.webContents.on('did-finish-load', sendFullscreenState);
|
|
1347
|
+
// 菜单模型(已翻译)推给 tab bar:win32 渲染 HTML 菜单栏 + 各平台 tooltip 文案。
|
|
1348
|
+
tabBarView.webContents.on('did-finish-load', broadcastMenuModel);
|
|
1349
|
+
mainWindow.on('enter-full-screen', sendFullscreenState);
|
|
1350
|
+
mainWindow.on('leave-full-screen', sendFullscreenState);
|
|
1351
|
+
// 设备模式下经历 OS 全屏往返:离开全屏后系统还原「进全屏前」的窄窗,但若期间宽度被改,
|
|
1352
|
+
// 据权威 deviceMode 重新收窄,保证 deviceMode=true ⇔ 窗口=DEVICE_WIDTH 不漂移。
|
|
1353
|
+
mainWindow.on('leave-full-screen', () => {
|
|
1354
|
+
if (deviceMode && mainWindow && !mainWindow.isFullScreen()) {
|
|
1355
|
+
const b = mainWindow.getBounds();
|
|
1356
|
+
if (b.width !== DEVICE_WIDTH) mainWindow.setBounds({ x: b.x, y: b.y, width: DEVICE_WIDTH, height: b.height });
|
|
1357
|
+
mainWindow.setMinimumSize(DEVICE_MIN_WIDTH, WIN_MIN_HEIGHT);
|
|
1358
|
+
updateLayout();
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
// When the user brings the window back to focus, stop the taskbar/dock flash and clear notifications already opened on screen.
|
|
1363
|
+
mainWindow.on('focus', () => {
|
|
1364
|
+
if (_isFlashing) {
|
|
1365
|
+
try { mainWindow.flashFrame(false); } catch {}
|
|
1366
|
+
_isFlashing = false;
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
// Show workspace selector
|
|
1371
|
+
showWorkspaceSelector();
|
|
1372
|
+
updateLayout();
|
|
1373
|
+
|
|
1374
|
+
// Watch theme
|
|
1375
|
+
watchTheme();
|
|
1376
|
+
|
|
1377
|
+
// Resize handler
|
|
1378
|
+
mainWindow.on('resize', () => {
|
|
1379
|
+
clearTimeout(resizeTimer);
|
|
1380
|
+
resizeTimer = setTimeout(updateLayout, 16);
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
// 窗口几何持久化:resize/move/最大化切换 都去抖落盘(退出路径常走 app.exit,
|
|
1384
|
+
// 不能只依赖 close 时机;cleanupAll 里另有一次兜底同步保存)。
|
|
1385
|
+
mainWindow.on('resize', scheduleWinStateSave);
|
|
1386
|
+
mainWindow.on('move', scheduleWinStateSave);
|
|
1387
|
+
mainWindow.on('maximize', scheduleWinStateSave);
|
|
1388
|
+
mainWindow.on('unmaximize', scheduleWinStateSave);
|
|
1389
|
+
|
|
1390
|
+
mainWindow.on('close', async (e) => {
|
|
1391
|
+
if (isQuitting) return; // before-quit 已处理
|
|
1392
|
+
if (tabs.size > 0 && mainWindow && !mainWindow.isDestroyed()) {
|
|
1393
|
+
e.preventDefault();
|
|
1394
|
+
const names = [...tabs.values()].map(tb => tb.projectName).join(', ');
|
|
1395
|
+
const { response } = await dialog.showMessageBox(mainWindow, {
|
|
1396
|
+
type: 'question',
|
|
1397
|
+
buttons: [t('electron.quit.buttonQuit'), t('electron.quit.buttonCancel')],
|
|
1398
|
+
defaultId: 1,
|
|
1399
|
+
cancelId: 1,
|
|
1400
|
+
title: t('electron.quit.title'),
|
|
1401
|
+
message: t('electron.quit.message', { count: tabs.size }),
|
|
1402
|
+
detail: `${names}\n\n${t('electron.quit.detail')}`,
|
|
1403
|
+
});
|
|
1404
|
+
if (response !== 0) return;
|
|
1405
|
+
await cleanupAll();
|
|
1406
|
+
app.exit(0);
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
mainWindow.on('closed', () => {
|
|
1411
|
+
mainWindow = null;
|
|
1412
|
+
});
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
app.on('window-all-closed', async () => {
|
|
1416
|
+
await cleanupAll();
|
|
1417
|
+
app.quit();
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
app.on('activate', () => {
|
|
1421
|
+
if (!mainWindow) {
|
|
1422
|
+
// Re-create window — but mgmt server is already running
|
|
1423
|
+
app.whenReady().then(() => {
|
|
1424
|
+
// Simplified: just quit if window was closed
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// .finally 而非 .then:cleanupAll reject 时 .then 链断裂会让 app.exit 永不执行(进程吊死)。
|
|
1431
|
+
process.on('SIGINT', () => { cleanupAll().finally(() => app.exit(0)); });
|
|
1432
|
+
process.on('SIGTERM', () => { cleanupAll().finally(() => app.exit(0)); });
|