@youweichen/pi-harness 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -2
  3. package/bin/pi-harness.mjs +1819 -0
  4. package/deploy/com.youweichen.pi-harness.plist +48 -0
  5. package/deploy/nginx-subpath.conf +88 -0
  6. package/deploy/pi-harness-task.xml +71 -0
  7. package/deploy/pi-harness.service +31 -0
  8. package/dist/server/agent-service.js +3051 -0
  9. package/dist/server/app-version.js +19 -0
  10. package/dist/server/attachments.js +526 -0
  11. package/dist/server/auth-file.js +61 -0
  12. package/dist/server/bg-servers.js +154 -0
  13. package/dist/server/client-eviction.js +19 -0
  14. package/dist/server/client-id.js +9 -0
  15. package/dist/server/client-state.js +202 -0
  16. package/dist/server/codemode-image-routes.js +36 -0
  17. package/dist/server/codemode-presentation.js +19 -0
  18. package/dist/server/component-updates.js +135 -0
  19. package/dist/server/context-breakdown.js +56 -0
  20. package/dist/server/control-socket.js +177 -0
  21. package/dist/server/conversation.js +65 -0
  22. package/dist/server/dir-browser.js +77 -0
  23. package/dist/server/editor-snapshot.js +38 -0
  24. package/dist/server/ensure-bash.js +80 -0
  25. package/dist/server/extension-display.js +23 -0
  26. package/dist/server/extensions-catalog.js +64 -0
  27. package/dist/server/extensions-model.js +46 -0
  28. package/dist/server/extensions-routes.js +255 -0
  29. package/dist/server/extensions-worker.js +187 -0
  30. package/dist/server/files-service.js +795 -0
  31. package/dist/server/index.js +1029 -0
  32. package/dist/server/markdown-images.js +42 -0
  33. package/dist/server/model-admin.js +801 -0
  34. package/dist/server/model-config-merge.js +32 -0
  35. package/dist/server/native-mcp-config.js +103 -0
  36. package/dist/server/native-mcp-presentation.js +12 -0
  37. package/dist/server/native-prompt-template.js +12 -0
  38. package/dist/server/native-tools.js +32 -0
  39. package/dist/server/node-sources.js +142 -0
  40. package/dist/server/node-workbench.js +668 -0
  41. package/dist/server/origin-policy.js +50 -0
  42. package/dist/server/patch-node-pty.js +96 -0
  43. package/dist/server/pi-environment.js +203 -0
  44. package/dist/server/plan/extension.js +74 -0
  45. package/dist/server/plan/progress.js +90 -0
  46. package/dist/server/plan/settings.js +41 -0
  47. package/dist/server/plan/state.js +113 -0
  48. package/dist/server/plugin-facilities.js +304 -0
  49. package/dist/server/plugin-updater.js +245 -0
  50. package/dist/server/plugins.js +712 -0
  51. package/dist/server/private-file.js +28 -0
  52. package/dist/server/process-utils.js +112 -0
  53. package/dist/server/prompt-delivery.js +80 -0
  54. package/dist/server/protocol-version.js +11 -0
  55. package/dist/server/protocol.js +6 -0
  56. package/dist/server/provider-auth.js +99 -0
  57. package/dist/server/query-cache.js +45 -0
  58. package/dist/server/recovery-state.js +37 -0
  59. package/dist/server/scm.js +459 -0
  60. package/dist/server/security-headers.js +24 -0
  61. package/dist/server/serialize.js +254 -0
  62. package/dist/server/session-file-read.js +225 -0
  63. package/dist/server/session-record-worker.js +21 -0
  64. package/dist/server/session-reload.js +13 -0
  65. package/dist/server/session-tree-controller.js +250 -0
  66. package/dist/server/session-tree.js +191 -0
  67. package/dist/server/settings-service.js +53 -0
  68. package/dist/server/skills-service.js +87 -0
  69. package/dist/server/slash-commands.js +280 -0
  70. package/dist/server/sqlite-preview.js +58 -0
  71. package/dist/server/sqlite-query.js +67 -0
  72. package/dist/server/sqlite-worker.js +12 -0
  73. package/dist/server/system-prompt-files.js +158 -0
  74. package/dist/server/system-prompt-routes.js +40 -0
  75. package/dist/server/system-prompt-view.js +49 -0
  76. package/dist/server/task-progress.js +233 -0
  77. package/dist/server/terminals.js +833 -0
  78. package/dist/server/text-sniff.js +269 -0
  79. package/dist/server/thinking-timing.js +91 -0
  80. package/dist/server/token-auth.js +37 -0
  81. package/dist/server/tool-output.js +97 -0
  82. package/dist/server/tool-text-incidents.js +82 -0
  83. package/dist/server/tool-text.js +67 -0
  84. package/dist/server/uploads.js +112 -0
  85. package/dist/server/user-attachments.js +96 -0
  86. package/dist/server/webui-context.js +257 -0
  87. package/dist/server/wiki-links.js +136 -0
  88. package/dist/server/wiki-pdf-worker.js +23 -0
  89. package/dist/server/wiki-pdf.js +36 -0
  90. package/dist/server/wiki-routes.js +177 -0
  91. package/dist/server/wiki-service.js +511 -0
  92. package/electron/main.mjs +607 -0
  93. package/extensions/webui.ts +192 -0
  94. package/package.json +122 -4
  95. package/web/dist/apple-touch-icon.png +0 -0
  96. package/web/dist/assets/NodeWorkbench-C4U1PldE.js +3 -0
  97. package/web/dist/assets/TerminalPanel-CWh6vw_N.js +2 -0
  98. package/web/dist/assets/abnfDiagram-VCTEODGH-D5Ti0Kpa.js +1 -0
  99. package/web/dist/assets/arc-DKJilLd6.js +1 -0
  100. package/web/dist/assets/architectureDiagram-5GKGNRK7-Cf0w38g-.js +36 -0
  101. package/web/dist/assets/blockDiagram-I7D4REHJ-BOTLWLIL.js +129 -0
  102. package/web/dist/assets/c4Diagram-7LVT6UL2-CLCCoJs3.js +38 -0
  103. package/web/dist/assets/channel-D8Hd7T4P.js +1 -0
  104. package/web/dist/assets/chunk-2Q5K7J3B-BHjga2Ge.js +1 -0
  105. package/web/dist/assets/chunk-5VM5RSS4-CQJ5VfDZ.js +15 -0
  106. package/web/dist/assets/chunk-F27PBJKO-C67qoPJH.js +1 -0
  107. package/web/dist/assets/chunk-IMKFNOWR-CgXXGwI8.js +231 -0
  108. package/web/dist/assets/chunk-JWPE2WC7-D0RPyByP.js +1 -0
  109. package/web/dist/assets/chunk-POPQ4Y6H-BeYC9s9x.js +1 -0
  110. package/web/dist/assets/chunk-SVP7TREG-eQPl4UBQ.js +88 -0
  111. package/web/dist/assets/chunk-TICWLB2K-DT73arPU.js +206 -0
  112. package/web/dist/assets/chunk-XXDRQBXY-B4FwQcP-.js +1 -0
  113. package/web/dist/assets/classDiagram-ZZMXUADV-B_btgYRB.js +1 -0
  114. package/web/dist/assets/classDiagram-v2-VYDZK3BY-B_btgYRB.js +1 -0
  115. package/web/dist/assets/cose-bilkent-JH36ORCC-Bnn5nlQN.js +1 -0
  116. package/web/dist/assets/cynefin-OW5HDTMX-CStWX9kE.js +178 -0
  117. package/web/dist/assets/cynefinDiagram-5FMLGOSQ-BMVHC03f.js +62 -0
  118. package/web/dist/assets/cytoscape.esm-Bch-eiPH.js +331 -0
  119. package/web/dist/assets/dagre-GXQ25YYZ-ClSaO4ml.js +4 -0
  120. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  121. package/web/dist/assets/diagram-S7CK7UJ4-C43LL08C.js +30 -0
  122. package/web/dist/assets/diagram-UQ7AKVKN-BPVn_ySW.js +41 -0
  123. package/web/dist/assets/diagram-VSXAHHWV-C34FFhRi.js +3 -0
  124. package/web/dist/assets/diagram-VX7I27RA-BRk4bncf.js +24 -0
  125. package/web/dist/assets/diagram-Z3DM3KII-O5hYPLEd.js +24 -0
  126. package/web/dist/assets/ebnfDiagram-PWID7BFC-DVGEBm99.js +1 -0
  127. package/web/dist/assets/erDiagram-RLTQ6QDP-M-BLpTEV.js +99 -0
  128. package/web/dist/assets/flowDiagram-HODETNUW-BwdLopEx.js +168 -0
  129. package/web/dist/assets/ganttDiagram-EL5Y4UJY-DYxx2ppA.js +292 -0
  130. package/web/dist/assets/gitGraphDiagram-WWUBYQGX-DUU0wMYB.js +106 -0
  131. package/web/dist/assets/index-CI2KfVSr.css +10 -0
  132. package/web/dist/assets/index-DQcpggFU.js +275 -0
  133. package/web/dist/assets/infoDiagram-27XIBGKW-CFaW5C0C.js +2 -0
  134. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  135. package/web/dist/assets/ishikawaDiagram-5VMMS53U--ENP1MTB.js +70 -0
  136. package/web/dist/assets/journeyDiagram-3NMN7TZE-DgnAzSTj.js +139 -0
  137. package/web/dist/assets/kanban-definition-UXKFOSKX-_STZN1vU.js +89 -0
  138. package/web/dist/assets/katex-C5jXJg4s.js +257 -0
  139. package/web/dist/assets/layout-CkUClkXt.js +1 -0
  140. package/web/dist/assets/linear-sYHiUOxa.js +1 -0
  141. package/web/dist/assets/markdown-vNTCZJ6d.js +51 -0
  142. package/web/dist/assets/mermaid.core-B8fKGWsc.js +312 -0
  143. package/web/dist/assets/mindmap-definition-YA3MSWOX-DP0BYOEx.js +96 -0
  144. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  145. package/web/dist/assets/pegDiagram-XKGWAZYB-DkWTmZre.js +1 -0
  146. package/web/dist/assets/pieDiagram-E7YTZNPT-DfZgMV4u.js +39 -0
  147. package/web/dist/assets/quadrantDiagram-AXDQQJYC-Djl4TT7q.js +7 -0
  148. package/web/dist/assets/railroadDiagram-O6MQD6OU-DbKF6zXn.js +1 -0
  149. package/web/dist/assets/react-BNj3n7jX.js +24 -0
  150. package/web/dist/assets/requirementDiagram-BXWQKSXE-idola5qb.js +84 -0
  151. package/web/dist/assets/sankeyDiagram-P5KCCOFB-KA0hQOle.js +40 -0
  152. package/web/dist/assets/sequenceDiagram-WJ2MYXX4-BXSKvxzP.js +162 -0
  153. package/web/dist/assets/sizeCapture-INFHLROL-C_Ewv3S5.js +1 -0
  154. package/web/dist/assets/stateDiagram-D77RDMKH-DIf5X8oo.js +1 -0
  155. package/web/dist/assets/stateDiagram-v2-MP3YSRHH-DYwe_2mB.js +1 -0
  156. package/web/dist/assets/swimlanes-42K2YHIH-Cx_Rc8M2.js +1 -0
  157. package/web/dist/assets/swimlanesDiagram-VR7AAH4N-52Y8tgRV.js +8 -0
  158. package/web/dist/assets/timeline-definition-24CTP7MA-BRrDhWVz.js +120 -0
  159. package/web/dist/assets/vennDiagram-4TSXK5OY-Ad6G0YwD.js +34 -0
  160. package/web/dist/assets/wardleyDiagram-VM6X3IG4-X6-ENpSi.js +78 -0
  161. package/web/dist/assets/xterm-6GBZ9nXN.css +32 -0
  162. package/web/dist/assets/xterm-D1D2FVe3.js +38 -0
  163. package/web/dist/assets/xychartDiagram-S5SC5T6Z-DhegNS6d.js +7 -0
  164. package/web/dist/brand-mark.svg +5 -0
  165. package/web/dist/favicon-32.png +0 -0
  166. package/web/dist/favicon.svg +5 -0
  167. package/web/dist/fonts/WIKI-FONTS.md +5 -0
  168. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  169. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  170. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  171. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  172. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  173. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  174. package/web/dist/fonts/ibm-plex-sans/LICENSE +93 -0
  175. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  176. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  177. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  178. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  179. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  180. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  181. package/web/dist/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  182. package/web/dist/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  183. package/web/dist/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  184. package/web/dist/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  185. package/web/dist/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  186. package/web/dist/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  187. package/web/dist/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  188. package/web/dist/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  189. package/web/dist/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  190. package/web/dist/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  191. package/web/dist/fonts/jetbrains-mono/LICENSE +93 -0
  192. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  193. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
  194. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
  195. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  196. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  197. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
  198. package/web/dist/fonts/noto-serif-sc/LICENSE +94 -0
  199. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
  200. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
  201. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
  202. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
  203. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
  204. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
  205. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
  206. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
  207. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
  208. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
  209. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
  210. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
  211. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
  212. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
  213. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
  214. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
  215. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
  216. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
  217. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
  218. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
  219. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
  220. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
  221. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
  222. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
  223. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
  224. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
  225. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
  226. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
  227. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
  228. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
  229. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
  230. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
  231. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
  232. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
  233. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
  234. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
  235. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
  236. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
  237. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
  238. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
  239. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
  240. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
  241. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
  242. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
  243. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
  244. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
  245. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
  246. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
  247. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
  248. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
  249. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
  250. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
  251. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
  252. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
  253. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
  254. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
  255. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
  256. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
  257. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
  258. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
  259. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
  260. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
  261. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
  262. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
  263. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
  264. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
  265. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
  266. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
  267. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
  268. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
  269. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
  270. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
  271. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
  272. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
  273. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
  274. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
  275. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
  276. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
  277. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
  278. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
  279. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
  280. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
  281. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
  282. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
  283. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
  284. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
  285. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
  286. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
  287. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
  288. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
  289. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
  290. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
  291. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
  292. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
  293. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
  294. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
  295. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
  296. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
  297. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
  298. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
  299. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
  300. package/web/dist/fonts/source-serif-4/LICENSE +93 -0
  301. package/web/dist/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
  302. package/web/dist/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
  303. package/web/dist/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
  304. package/web/dist/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
  305. package/web/dist/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
  306. package/web/dist/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
  307. package/web/dist/icon/1a-mark.svg +1 -0
  308. package/web/dist/icon.ico +0 -0
  309. package/web/dist/index.html +18 -0
  310. package/web/public/apple-touch-icon.png +0 -0
  311. package/web/public/brand-mark.svg +5 -0
  312. package/web/public/favicon-32.png +0 -0
  313. package/web/public/favicon.svg +5 -0
  314. package/web/public/fonts/WIKI-FONTS.md +5 -0
  315. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  316. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  317. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  318. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  319. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  320. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  321. package/web/public/fonts/ibm-plex-sans/LICENSE +93 -0
  322. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  323. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  324. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  325. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  326. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  327. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  328. package/web/public/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  329. package/web/public/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  330. package/web/public/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  331. package/web/public/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  332. package/web/public/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  333. package/web/public/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  334. package/web/public/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  335. package/web/public/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  336. package/web/public/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  337. package/web/public/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  338. package/web/public/fonts/jetbrains-mono/LICENSE +93 -0
  339. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  340. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
  341. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
  342. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  343. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  344. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
  345. package/web/public/fonts/noto-serif-sc/LICENSE +94 -0
  346. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
  347. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
  348. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
  349. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
  350. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
  351. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
  352. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
  353. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
  354. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
  355. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
  356. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
  357. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
  358. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
  359. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
  360. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
  361. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
  362. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
  363. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
  364. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
  365. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
  366. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
  367. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
  368. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
  369. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
  370. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
  371. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
  372. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
  373. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
  374. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
  375. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
  376. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
  377. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
  378. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
  379. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
  380. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
  381. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
  382. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
  383. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
  384. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
  385. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
  386. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
  387. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
  388. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
  389. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
  390. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
  391. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
  392. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
  393. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
  394. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
  395. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
  396. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
  397. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
  398. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
  399. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
  400. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
  401. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
  402. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
  403. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
  404. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
  405. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
  406. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
  407. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
  408. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
  409. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
  410. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
  411. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
  412. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
  413. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
  414. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
  415. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
  416. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
  417. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
  418. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
  419. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
  420. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
  421. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
  422. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
  423. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
  424. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
  425. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
  426. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
  427. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
  428. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
  429. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
  430. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
  431. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
  432. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
  433. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
  434. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
  435. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
  436. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
  437. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
  438. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
  439. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
  440. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
  441. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
  442. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
  443. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
  444. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
  445. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
  446. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
  447. package/web/public/fonts/source-serif-4/LICENSE +93 -0
  448. package/web/public/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
  449. package/web/public/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
  450. package/web/public/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
  451. package/web/public/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
  452. package/web/public/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
  453. package/web/public/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
  454. package/web/public/icon/1a-mark.svg +1 -0
  455. package/web/public/icon.ico +0 -0
@@ -0,0 +1,607 @@
1
+ import { createAppUpdater } from "./app-updater.mjs";
2
+ /**
3
+ * pi-harness Electron 桌面版主进程。
4
+ *
5
+ * 架构:
6
+ * - 主进程 fork 一个隐藏子进程跑 server(ELECTRON_RUN_AS_NODE=1),
7
+ * server 使用 Electron 内置的 Node 运行时,node-pty 等原生模块
8
+ * 在 electron-builder 打包时自动 rebuild 为 Electron ABI。
9
+ * - BrowserWindow 加载 http://127.0.0.1:{PORT}(server 就绪后)。
10
+ * - 托盘:关闭窗口 → 最小化到托盘;Quit → 真正退出(杀 server 子进程)。
11
+ * - 自动更新:electron-updater + GitHub Releases;设置页发起下载,用户确认后重启安装。
12
+ *
13
+ * 开发模式(npm run dev:electron):
14
+ * 先构建 web + server(npm run build),然后 electron . 即可。
15
+ * 如有 Vite dev server (:5173),优先加载它获取 HMR。
16
+ *
17
+ * 与当前 server/index.ts 的耦合点(重构时请对照检查):
18
+ * - 就绪标记:server 启动后 stdout 打印 "⚡ pi-harness"(见 server/index.ts
19
+ * httpServer.listen 回调),本文件靠这行判断 server 已就绪。
20
+ * - PI_WEB_PKG_ROOT:告诉 server 去哪找 web/dist(resolvePkgRoot()),
21
+ * 打包后指向 process.resourcesPath(electron-builder extraResources)。
22
+ * - PI_WEB_DATA_DIR:桌面版单独用 <home>/.pi-web-desktop,与命令行版
23
+ * 的 <home>/.pi-web 分开,避免两边同时跑互相抢 client-state.json /
24
+ * terminals 等运行时状态(数据都在 SDK 的 ~/.pi/agent 里,两边共享,
25
+ * 不受影响)。
26
+ */
27
+
28
+ import { app, BrowserWindow, Tray, Menu, nativeImage, dialog, ipcMain, shell } from "electron";
29
+ import { randomUUID } from "node:crypto";
30
+ import { fork } from "node:child_process";
31
+ import { createServer } from "node:net";
32
+ import { existsSync, mkdirSync } from "node:fs";
33
+ import { join, dirname, resolve } from "node:path";
34
+ import { fileURLToPath } from "node:url";
35
+ import { desktopClientId } from "./client-identity.mjs";
36
+ import { agentRuntimeEnvironment } from "./agent-runtime-env.mjs";
37
+ import { bearerHeaders, desktopServerToken, desktopWindowUrl } from "./server-auth.mjs";
38
+ // electron-updater 是 CJS 包,Node ESM 下不能直接 named import,
39
+ // 得走默认导出再解构(Node 的 CJS→ESM 互操作不会自动分析 named exports)。
40
+ import electronUpdaterPkg from "electron-updater";
41
+ const { autoUpdater } = electronUpdaterPkg;
42
+
43
+ const __dirname = dirname(fileURLToPath(import.meta.url));
44
+ const ROOT = resolve(__dirname, "..");
45
+ const isDev = !app.isPackaged || !!process.env.DEV;
46
+
47
+ // ── 状态 ──
48
+ /** @type {BrowserWindow | null} */
49
+ let mainWindow = null;
50
+ /** @type {Tray | null} */
51
+ let tray = null;
52
+ /** @type {import("node:child_process").ChildProcess | null} */
53
+ let serverProcess = null;
54
+ let isQuitting = false;
55
+ let closeGuardReady = false, closeApproved = false, pendingClose = null;
56
+ let serverPort = 0;
57
+ // One secret per launch, kept across server restarts so the open window stays authorized.
58
+ const serverToken = desktopServerToken();
59
+ let clientId;
60
+ const dataDir = process.env.PI_WEB_DATA_DIR || join(
61
+ process.env.HOME || process.env.USERPROFILE || "~", ".pi-web-desktop",
62
+ );
63
+
64
+ // ── 路径 ──
65
+
66
+ // asar 关掉之后(见 electron-builder.yml 里的长注释),打包后的目录结构跟开发
67
+ // 时是同构的:__dirname 是 <app>/electron,ROOT 就是 <app>——开发时是仓库根,
68
+ // 打包后是 Resources/app。dist / web/dist / node_modules 全都平铺
69
+ // 在 ROOT 下面,所以这两个函数不再需要分打包和开发两种情况。
70
+
71
+ /** server 入口:<root>/dist/server/index.js */
72
+ function getServerPath() {
73
+ return join(ROOT, "dist", "server", "index.js");
74
+ }
75
+
76
+ /** server 找 web/dist 的根目录 */
77
+ function getPkgRoot() {
78
+ return ROOT;
79
+ }
80
+
81
+ // ── 端口 ──
82
+
83
+ /** 找一个随机空闲端口(防止端口冲突) */
84
+ async function findFreePort() {
85
+ return new Promise((resolve, reject) => {
86
+ const srv = createServer();
87
+ srv.listen(0, "127.0.0.1", () => {
88
+ const port = /** @type {import("node:net").AddressInfo} */ (srv.address()).port;
89
+ srv.close(() => resolve(port));
90
+ });
91
+ srv.on("error", reject);
92
+ });
93
+ }
94
+
95
+ // ── 启动 Server 子进程 ──
96
+
97
+ async function startServer(reusePort) {
98
+ serverPort = reusePort || await findFreePort();
99
+ const serverPath = getServerPath();
100
+
101
+ if (!existsSync(serverPath)) {
102
+ dialog.showErrorBox(
103
+ "pi 启动失败",
104
+ `找不到 server 入口:${serverPath}\n\n请先执行 npm run build,然后重试。`,
105
+ );
106
+ app.quit();
107
+ return;
108
+ }
109
+
110
+ // 桌面版数据目录与命令行版分开(见文件头注释),确保存在
111
+ mkdirSync(dataDir, { recursive: true, mode: 0o700 });
112
+
113
+ serverProcess = fork(serverPath, [], {
114
+ env: {
115
+ ...agentRuntimeEnvironment(getPkgRoot(), dataDir),
116
+ PORT: String(serverPort),
117
+ PI_WEB_HOST: "127.0.0.1",
118
+ PI_WEB_TOKEN: serverToken,
119
+ PI_WEB_DATA_DIR: dataDir,
120
+ PI_WEB_NO_BROWSER: "1", // 不要自动打开浏览器(本身也不会打开,桌面版有自己的窗口)
121
+ PI_WEB_PKG_ROOT: getPkgRoot(), // 告诉 server 去哪找 web/dist
122
+ ELECTRON_RUN_AS_NODE: "1", // 以 Node.js 模式运行(非 Electron)
123
+ },
124
+ stdio: ["ignore", "pipe", "pipe", "ipc"],
125
+ serialization: "json",
126
+ });
127
+
128
+ // 收集 stdout/stderr 用于调试
129
+ let serverOut = "";
130
+ serverProcess.stdout?.on("data", (chunk) => {
131
+ const text = chunk.toString();
132
+ serverOut = (serverOut + text).slice(-64 * 1024);
133
+ process.stdout.write(`[server] ${text}`);
134
+ });
135
+ serverProcess.stderr?.on("data", (chunk) => {
136
+ const text = chunk.toString();
137
+ serverOut = (serverOut + text).slice(-64 * 1024);
138
+ process.stderr.write(`[server:err] ${text}`);
139
+ });
140
+
141
+ // 等待 server 就绪:监听 stdout 中的 "⚡ pi-harness" 标记(见 server/index.ts)
142
+ await new Promise((resolvePromise, rejectPromise) => {
143
+ const timeout = setTimeout(() => {
144
+ rejectPromise(
145
+ new Error(`Server 启动超时(30s)。最后输出:\n${serverOut.slice(-500)}`),
146
+ );
147
+ }, 30_000);
148
+
149
+ const checkOutput = (chunk) => {
150
+ const text = chunk.toString();
151
+ if (text.includes("⚡ pi-harness") || text.includes("http://localhost")) {
152
+ clearTimeout(timeout);
153
+ serverProcess.stdout?.removeListener("data", checkOutput);
154
+ resolvePromise(undefined);
155
+ }
156
+ };
157
+
158
+ serverProcess.stdout?.on("data", checkOutput);
159
+ serverProcess.on("error", (err) => {
160
+ clearTimeout(timeout);
161
+ rejectPromise(err);
162
+ });
163
+ serverProcess.on("exit", (code) => {
164
+ clearTimeout(timeout);
165
+ if (code !== 0) {
166
+ // 把 server 自己最后的输出带上。只报一个 exit code=1 的话,用户在
167
+ // 弹窗里看到的就只有「启动失败」四个字,真正的原因(模块缺失、
168
+ // 端口被占、配置读不了)全留在没人看得到的 stdout 里——超时那条
169
+ // 分支早就是这么做的,退出这条漏了。
170
+ const tail = serverOut.trim().slice(-800);
171
+ rejectPromise(
172
+ new Error(
173
+ `Server 退出了 (exit code=${code})` +
174
+ (tail ? `\n\n最后输出:\n${tail}` : ""),
175
+ ),
176
+ );
177
+ }
178
+ });
179
+ });
180
+
181
+ console.log(`[electron] server 就绪,端口 ${serverPort}`);
182
+ }
183
+
184
+ /** Keep the window's URL stable so its WebSocket can reconnect after a crash. */
185
+ function watchServerExit() {
186
+ const child = serverProcess;
187
+ child?.once("exit", () => {
188
+ if (isQuitting || serverProcess !== child) return;
189
+ serverProcess = null;
190
+ void recoverServer();
191
+ });
192
+ }
193
+
194
+ async function recoverServer() {
195
+ const port = serverPort;
196
+ let lastError;
197
+ for (const delay of [1000, 2000, 4000]) {
198
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, delay));
199
+ if (isQuitting) return;
200
+ try {
201
+ await startServer(port);
202
+ watchServerExit();
203
+ return;
204
+ } catch (error) {
205
+ lastError = error;
206
+ // A failed startup can leave a child alive until its own timeout fires.
207
+ serverProcess?.kill("SIGTERM");
208
+ serverProcess = null;
209
+ }
210
+ }
211
+ if (!isQuitting) dialog.showErrorBox(
212
+ "pi 服务已断开",
213
+ `服务重启失败,请重新打开应用。\n\n${lastError instanceof Error ? lastError.message : String(lastError)}`,
214
+ );
215
+ }
216
+
217
+ // ── 窗口 ──
218
+
219
+ function createWindow() {
220
+ mainWindow = new BrowserWindow({
221
+ width: 1200,
222
+ height: 800,
223
+ minWidth: 800,
224
+ minHeight: 600,
225
+ title: "pi-harness",
226
+ backgroundColor: "#ffffff",
227
+ show: false,
228
+ ...(process.platform === "darwin"
229
+ ? { titleBarStyle: "hidden", trafficLightPosition: { x: 18, y: 17 } }
230
+ : { frame: false }),
231
+ webPreferences: {
232
+ preload: join(__dirname, "preload.cjs"),
233
+ nodeIntegration: false,
234
+ contextIsolation: true,
235
+ additionalArguments: [`--pi-desktop-client-id=${clientId}`],
236
+ },
237
+ icon: join(ROOT, "web", "dist", "apple-touch-icon.png"),
238
+ });
239
+
240
+ const publishWindowState = () => {
241
+ if (mainWindow && !mainWindow.isDestroyed()) {
242
+ mainWindow.webContents.send("pi-window-state", {
243
+ maximized: mainWindow.isMaximized(),
244
+ fullscreen: mainWindow.isFullScreen(),
245
+ });
246
+ }
247
+ };
248
+ for (const event of ["maximize", "unmaximize", "enter-full-screen", "leave-full-screen", "restore"]) {
249
+ mainWindow.on(event, publishWindowState);
250
+ }
251
+ mainWindow.webContents.on("did-finish-load", publishWindowState);
252
+ mainWindow.webContents.on("will-prevent-unload", (event) => {
253
+ const zh = app.getLocale().startsWith("zh");
254
+ const choice = dialog.showMessageBoxSync(mainWindow, {
255
+ type: "warning",
256
+ message: zh ? "文件有未保存的修改,确定放弃并离开?" : "Discard unsaved file changes and leave?",
257
+ buttons: zh ? ["取消", "放弃修改"] : ["Cancel", "Discard changes"],
258
+ defaultId: 0,
259
+ cancelId: 0,
260
+ });
261
+ // Electron allows unloading only when this event is prevented.
262
+ if (choice === 1) event.preventDefault();
263
+ else isQuitting = false;
264
+ });
265
+
266
+ // 加载 localhost 上的 server
267
+ const url = `http://127.0.0.1:${serverPort}`;
268
+ // Web links belong in the system browser, never a new privileged app window.
269
+ const openExternal = (target) => {
270
+ try {
271
+ const parsed = new URL(target);
272
+ if (!["http:", "https:"].includes(parsed.protocol)) return;
273
+ void shell.openExternal(parsed.href).catch(error => console.error("Unable to open browser:", error.message));
274
+ } catch { /* Invalid navigation targets are blocked. */ }
275
+ };
276
+ mainWindow.webContents.setWindowOpenHandler(({ url: target }) => {
277
+ openExternal(target);
278
+ return { action: "deny" };
279
+ });
280
+ const guardNavigation = (event, target) => {
281
+ try { if (new URL(target).origin === new URL(url).origin) return; } catch {}
282
+ event.preventDefault();
283
+ openExternal(target);
284
+ };
285
+ mainWindow.webContents.on("will-navigate", guardNavigation);
286
+ mainWindow.webContents.on("will-redirect", guardNavigation);
287
+ mainWindow.loadURL(desktopWindowUrl(serverPort, serverToken));
288
+
289
+ mainWindow.once("ready-to-show", () => {
290
+ mainWindow.show();
291
+ });
292
+
293
+ // Give the editor a chance to flush before hiding or quitting. Keep the server
294
+ // alive until the matching main-frame acknowledgement; failure cancels closing.
295
+ mainWindow.on("close", (event) => {
296
+ if (closeGuardReady && !closeApproved) {
297
+ event.preventDefault();
298
+ if (!pendingClose) {
299
+ const id = randomUUID();
300
+ pendingClose = { id, timer: setTimeout(() => { pendingClose = null; isQuitting = false; mainWindow?.show(); }, 30000) };
301
+ mainWindow.webContents.send("pi-window-before-close", id);
302
+ }
303
+ return;
304
+ }
305
+ closeApproved = false;
306
+ if (!isQuitting) {
307
+ event.preventDefault();
308
+ mainWindow.hide();
309
+ return false;
310
+ }
311
+ });
312
+
313
+ mainWindow.on("closed", () => {
314
+ closeGuardReady = false;
315
+ if (pendingClose) clearTimeout(pendingClose.timer);
316
+ pendingClose = null;
317
+ mainWindow = null;
318
+ });
319
+
320
+ // 开发模式打开 DevTools
321
+ if (isDev) {
322
+ mainWindow.webContents.openDevTools({ mode: "detach" });
323
+ }
324
+ }
325
+
326
+ ipcMain.on("pi-window-close-ready", (event, ready) => {
327
+ if (mainWindow && event.sender === mainWindow.webContents && event.senderFrame === mainWindow.webContents.mainFrame) closeGuardReady = ready === true;
328
+ });
329
+ ipcMain.on("pi-window-close-result", (event, result) => {
330
+ const win = mainWindow;
331
+ if (!win || event.sender !== win.webContents || event.senderFrame !== win.webContents.mainFrame || !pendingClose || result?.id !== pendingClose.id) return;
332
+ clearTimeout(pendingClose.timer); pendingClose = null;
333
+ if (result.allow !== true) { isQuitting = false; win.show(); win.focus(); return; }
334
+ closeApproved = true;
335
+ if (isQuitting) app.quit(); else win.close();
336
+ });
337
+
338
+ // Only the app's main webContents may control its own window. The renderer
339
+ // receives fixed operations, never an arbitrary Electron method or channel.
340
+ ipcMain.handle("pi-window-action", (event, action) => {
341
+ const win = mainWindow;
342
+ if (!win || win.isDestroyed() || event.sender !== win.webContents) return;
343
+ switch (action) {
344
+ case "minimize": win.minimize(); break;
345
+ case "toggle-maximize":
346
+ if (win.isFullScreen()) win.setFullScreen(false);
347
+ else if (win.isMaximized()) win.unmaximize();
348
+ else win.maximize();
349
+ break;
350
+ case "close": win.close(); break;
351
+ }
352
+ });
353
+ // The renderer supplies workspace identity, never an arbitrary executable path.
354
+ // Ask our authenticated server to resolve the active workspace's file first.
355
+ ipcMain.handle("pi-wiki-open-file", async (event, request) => {
356
+ const win = mainWindow;
357
+ if (!win || win.isDestroyed() || event.sender !== win.webContents || event.senderFrame !== win.webContents.mainFrame) throw new Error("Forbidden");
358
+ if (!request || ![request.clientId, request.cwd, request.path].every(value => typeof value === "string")) throw new Error("Invalid file request");
359
+ const response = await fetch(`http://127.0.0.1:${serverPort}/api/wiki`, {
360
+ method: "POST", headers: { "Content-Type": "application/json", ...bearerHeaders(serverToken) },
361
+ body: JSON.stringify({ clientId: request.clientId, cwd: request.cwd, path: request.path, action: "open-info" }),
362
+ });
363
+ const result = await response.json();
364
+ if (!response.ok || typeof result.absolute !== "string") throw new Error(result.error || "Unable to open file");
365
+ const error = await shell.openPath(result.absolute);
366
+ if (error) throw new Error(error);
367
+ });
368
+ ipcMain.handle("pi-extension-open-path", async (event, request) => {
369
+ const win = mainWindow;
370
+ if (!win || win.isDestroyed() || event.sender !== win.webContents || event.senderFrame !== win.webContents.mainFrame) throw new Error("Forbidden");
371
+ if (!request || ![request.clientId, request.cwd, request.id].every(value => typeof value === "string")) throw new Error("Invalid package request");
372
+ const response = await fetch(`http://127.0.0.1:${serverPort}/api/extensions`, {
373
+ method: "POST", headers: { "Content-Type": "application/json", ...bearerHeaders(serverToken) },
374
+ body: JSON.stringify({ clientId: request.clientId, cwd: request.cwd, id: request.id, action: "open-info" }),
375
+ });
376
+ const result = await response.json();
377
+ if (!response.ok || typeof result.absolute !== "string") throw new Error(result.error || "Installed path unavailable");
378
+ shell.showItemInFolder(result.absolute);
379
+ });
380
+ ipcMain.handle("pi-window-state-read", (event) => {
381
+ const win = mainWindow;
382
+ if (!win || win.isDestroyed() || event.sender !== win.webContents) return null;
383
+ return { maximized: win.isMaximized(), fullscreen: win.isFullScreen() };
384
+ });
385
+
386
+ // ── 托盘 ──
387
+
388
+ function createTray() {
389
+ const iconPath = join(ROOT, "web", "dist", "apple-touch-icon.png");
390
+ const icon = existsSync(iconPath) ? nativeImage.createFromPath(iconPath) : nativeImage.createEmpty();
391
+ tray = new Tray(icon.isEmpty() ? icon : icon.resize({ width: 16, height: 16 }));
392
+ tray.setToolTip("pi");
393
+
394
+ const contextMenu = Menu.buildFromTemplate([
395
+ {
396
+ label: "显示窗口",
397
+ click: () => {
398
+ if (mainWindow) {
399
+ mainWindow.show();
400
+ mainWindow.focus();
401
+ }
402
+ },
403
+ },
404
+ { type: "separator" },
405
+ {
406
+ label: "关于 pi",
407
+ click: () => {
408
+ dialog.showMessageBox({
409
+ type: "info",
410
+ title: "关于 pi",
411
+ message: `pi v${app.getVersion()}`,
412
+ detail: "Web chat interface for the pi coding agent.",
413
+ });
414
+ },
415
+ },
416
+ { type: "separator" },
417
+ {
418
+ label: "退出",
419
+ click: () => {
420
+ isQuitting = true;
421
+ app.quit();
422
+ },
423
+ },
424
+ ]);
425
+
426
+ tray.setContextMenu(contextMenu);
427
+
428
+ // 点击托盘图标 → 显示/隐藏窗口
429
+ tray.on("click", () => {
430
+ if (mainWindow) {
431
+ if (mainWindow.isVisible()) {
432
+ mainWindow.hide();
433
+ } else {
434
+ mainWindow.show();
435
+ mainWindow.focus();
436
+ }
437
+ }
438
+ });
439
+ }
440
+
441
+ // ── 菜单 ──
442
+
443
+ function createAppMenu() {
444
+ const template = [
445
+ {
446
+ label: "pi",
447
+ submenu: [
448
+ {
449
+ label: "关于 pi",
450
+ click: () => {
451
+ dialog.showMessageBox({
452
+ type: "info",
453
+ title: "关于 pi",
454
+ message: `pi v${app.getVersion()}`,
455
+ detail: "Web chat interface for the pi coding agent.",
456
+ });
457
+ },
458
+ },
459
+ { type: "separator" },
460
+ { role: "hide" },
461
+ { role: "hideOthers" },
462
+ { role: "unhide" },
463
+ { type: "separator" },
464
+ {
465
+ label: "退出",
466
+ accelerator: "CmdOrCtrl+Q",
467
+ click: () => {
468
+ isQuitting = true;
469
+ app.quit();
470
+ },
471
+ },
472
+ ],
473
+ },
474
+ {
475
+ label: "编辑",
476
+ submenu: [
477
+ { role: "undo" },
478
+ { role: "redo" },
479
+ { type: "separator" },
480
+ { role: "cut" },
481
+ { role: "copy" },
482
+ { role: "paste" },
483
+ { role: "selectAll" },
484
+ ],
485
+ },
486
+ {
487
+ label: "视图",
488
+ submenu: [
489
+ { role: "reload" },
490
+ { role: "forceReload" },
491
+ { role: "toggleDevTools" },
492
+ { type: "separator" },
493
+ { role: "resetZoom" },
494
+ { role: "zoomIn" },
495
+ { role: "zoomOut" },
496
+ { type: "separator" },
497
+ { role: "togglefullscreen" },
498
+ ],
499
+ },
500
+ {
501
+ label: "窗口",
502
+ submenu: [{ role: "minimize" }, { role: "close" }],
503
+ },
504
+ ];
505
+
506
+ // macOS 需要第一个菜单项是应用名
507
+ const menu = Menu.buildFromTemplate(template);
508
+ Menu.setApplicationMenu(menu);
509
+ }
510
+
511
+ // ── 自动更新 ──
512
+
513
+ function setupAutoUpdater() {
514
+ const appUpdater = createAppUpdater({
515
+ updater: autoUpdater, version: app.getVersion(), supported: !isDev,
516
+ publish: state => {
517
+ if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send("pi-app-update-state", state);
518
+ },
519
+ confirmInstall: async () => {
520
+ if (!mainWindow || mainWindow.isDestroyed()) return false;
521
+ const zh = app.getLocale().startsWith("zh");
522
+ const result = await dialog.showMessageBox(mainWindow, {
523
+ type: "question",
524
+ message: zh ? "重启应用并安装更新?请先保存正在编辑的文件。" : "Restart to install the update? Save your open files first.",
525
+ buttons: zh ? ["稍后", "重启并安装"] : ["Later", "Restart and install"], defaultId: 0, cancelId: 0,
526
+ });
527
+ return result.response === 1;
528
+ },
529
+ });
530
+ ipcMain.handle("pi-app-update", (event, action) => {
531
+ if (!mainWindow || mainWindow.isDestroyed() || event.sender !== mainWindow.webContents || event.senderFrame !== mainWindow.webContents.mainFrame) throw Error("Forbidden");
532
+ return appUpdater.run(action);
533
+ });
534
+ if (!isDev) setTimeout(() => void appUpdater.run("check"), 5000);
535
+ }
536
+
537
+ // ── 应用生命周期 ──
538
+
539
+ app.setAppUserModelId("com.youweichen.pi-harness");
540
+
541
+ // 确保只有一个实例
542
+ const gotLock = app.requestSingleInstanceLock();
543
+ if (!gotLock) {
544
+ app.quit();
545
+ } else {
546
+ app.on("second-instance", () => {
547
+ if (mainWindow) {
548
+ if (mainWindow.isMinimized()) mainWindow.restore();
549
+ mainWindow.show();
550
+ mainWindow.focus();
551
+ }
552
+ });
553
+ }
554
+
555
+ app.whenReady().then(async () => {
556
+ try {
557
+ clientId = desktopClientId(dataDir);
558
+ await startServer();
559
+ watchServerExit();
560
+ } catch (err) {
561
+ dialog.showErrorBox("pi 启动失败", err instanceof Error ? err.message : String(err));
562
+ app.quit();
563
+ return;
564
+ }
565
+
566
+ createAppMenu();
567
+ createWindow();
568
+ createTray();
569
+ setupAutoUpdater();
570
+
571
+ app.on("activate", () => {
572
+ if (!mainWindow) createWindow();
573
+ if (mainWindow) {
574
+ mainWindow.show();
575
+ mainWindow.focus();
576
+ }
577
+ });
578
+ });
579
+
580
+ app.on("window-all-closed", () => {
581
+ // macOS 不退出(dock 图标还在)
582
+ // 其他平台退出
583
+ if (process.platform !== "darwin") {
584
+ isQuitting = true;
585
+ app.quit();
586
+ }
587
+ });
588
+
589
+ app.on("before-quit", () => {
590
+ isQuitting = true;
591
+ });
592
+
593
+ app.on("will-quit", () => {
594
+ // 杀 server 子进程
595
+ if (serverProcess) {
596
+ try {
597
+ serverProcess.kill("SIGTERM");
598
+ } catch {
599
+ // 可能已经退出
600
+ }
601
+ serverProcess = null;
602
+ }
603
+ if (tray) {
604
+ tray.destroy();
605
+ tray = null;
606
+ }
607
+ });