@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,459 @@
1
+ /**
2
+ * Read-only git queries backing the source-control panel.
3
+ *
4
+ * Replaces the old hidden-PTY + text-scraping approach: every query is a
5
+ * plain `execFile("git", …)` — no shell, no prompts, no echo, no sentinel
6
+ * parsing. Output is parsed into structured JSON on the server; the client
7
+ * just renders it.
8
+ */
9
+ import { execFile } from "node:child_process";
10
+ import { lstat, open, realpath, stat } from "node:fs/promises";
11
+ import { isAbsolute, relative, resolve, sep } from "node:path";
12
+ import { promisify } from "node:util";
13
+ import { decodeText, looksLikeText } from "./text-sniff.js";
14
+ const exec = promisify(execFile);
15
+ /** Per-command timeout / output cap — a stuck repo must not hang the panel. */
16
+ const GIT_TIMEOUT_MS = 15_000;
17
+ const MAX_GIT_OUTPUT = 16 * 1024 * 1024;
18
+ const MAX_UNTRACKED_PREVIEW_BYTES = 512 * 1024;
19
+ /** Untracked files shown in the whole-scope diff; the rest are only counted. */
20
+ const MAX_DIFF_UNTRACKED_FILES = 500;
21
+ class GitOutputTooLarge extends Error {
22
+ }
23
+ /** Run one git command; throws Error with a readable message on failure. */
24
+ async function git(cwd, args) {
25
+ try {
26
+ const command = exec("git", ["-c", "core.quotepath=false", ...args], {
27
+ cwd,
28
+ timeout: GIT_TIMEOUT_MS,
29
+ maxBuffer: MAX_GIT_OUTPUT,
30
+ windowsHide: true,
31
+ });
32
+ command.child.stdin?.end();
33
+ const { stdout } = await command;
34
+ return stdout;
35
+ }
36
+ catch (err) {
37
+ const e = err;
38
+ if (e.code === "ENOENT")
39
+ throw new Error("未找到 git 命令——请确认已安装 Git 并在 PATH 中");
40
+ if (e.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER")
41
+ throw new GitOutputTooLarge("git 输出超过 16 MiB");
42
+ if (e.killed)
43
+ throw new Error("git 命令超时");
44
+ const detail = (e.stderr ?? e.message ?? "").trim().split("\n")[0];
45
+ throw new Error(detail || "git 命令失败");
46
+ }
47
+ }
48
+ /** Absolute path of the repo's git dir (handles worktrees/submodules), or
49
+ * null when cwd isn't inside a repository. */
50
+ export async function gitDirOf(cwd) {
51
+ try {
52
+ const { stdout } = await exec("git", ["rev-parse", "--absolute-git-dir"], {
53
+ cwd,
54
+ timeout: 5_000,
55
+ windowsHide: true,
56
+ });
57
+ return stdout.trim() || null;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ /** True when the error is "this directory isn't a git repository". */
64
+ export function isNotRepoError(err) {
65
+ return /not a git repository/i.test(err instanceof Error ? err.message : String(err));
66
+ }
67
+ /* ------------------------------------------------------------------ */
68
+ /* parsers */
69
+ /* ------------------------------------------------------------------ */
70
+ /** Undo git's C-style quoting for unusual file names ("a\tb" → a<TAB>b).
71
+ * core.quotepath=false already handles non-ASCII; this covers control chars. */
72
+ function unquotePath(s) {
73
+ if (!s.startsWith('"'))
74
+ return s;
75
+ const inner = s.endsWith('"') ? s.slice(1, -1) : s.slice(1);
76
+ return inner.replace(/\\(.)/g, (_m, c) => {
77
+ switch (c) {
78
+ case "n": return "\n";
79
+ case "t": return "\t";
80
+ case "r": return "\r";
81
+ case "b": return "\b";
82
+ case "a": return "\a";
83
+ case "f": return "\f";
84
+ case "v": return "\v";
85
+ case "\\": return "\\";
86
+ case '"': return '"';
87
+ default: return c;
88
+ }
89
+ });
90
+ }
91
+ function parseStatusHeader(rest) {
92
+ const out = {
93
+ branch: "HEAD",
94
+ detached: false,
95
+ upstream: null,
96
+ ahead: 0,
97
+ behind: 0,
98
+ upstreamGone: false,
99
+ };
100
+ let branchPart = rest;
101
+ let flags = "";
102
+ const bi = rest.indexOf(" [");
103
+ if (bi >= 0) {
104
+ branchPart = rest.slice(0, bi);
105
+ flags = rest.slice(bi + 2);
106
+ if (flags.endsWith("]"))
107
+ flags = flags.slice(0, -1);
108
+ }
109
+ if (branchPart === "HEAD (no branch)" || branchPart === "HEAD") {
110
+ out.detached = true;
111
+ }
112
+ else {
113
+ if (branchPart.startsWith("No commits yet on "))
114
+ branchPart = branchPart.slice("No commits yet on ".length);
115
+ const up = branchPart.indexOf("...");
116
+ if (up >= 0) {
117
+ out.branch = branchPart.slice(0, up);
118
+ out.upstream = branchPart.slice(up + 3);
119
+ }
120
+ else {
121
+ out.branch = branchPart;
122
+ }
123
+ }
124
+ if (flags) {
125
+ for (const part of flags.split(",")) {
126
+ const p = part.trim();
127
+ const m = p.match(/^(ahead|behind) (\d+)$/);
128
+ if (m) {
129
+ if (m[1] === "ahead")
130
+ out.ahead = Number(m[2]);
131
+ else
132
+ out.behind = Number(m[2]);
133
+ }
134
+ else if (p === "gone") {
135
+ out.upstreamGone = true;
136
+ }
137
+ }
138
+ }
139
+ return out;
140
+ }
141
+ function parseStatusFiles(text) {
142
+ const out = [];
143
+ for (const rawLine of text.split("\n")) {
144
+ const line = rawLine.trimEnd();
145
+ if (!line || line.startsWith("## "))
146
+ continue;
147
+ if (line.length >= 3) {
148
+ let path = line.slice(3);
149
+ const arrow = path.indexOf(" -> "); // rename: "R old -> new"
150
+ if (arrow >= 0)
151
+ path = path.slice(arrow + 4);
152
+ out.push({ path: unquotePath(path), x: line[0], y: line[1] });
153
+ }
154
+ }
155
+ return out;
156
+ }
157
+ function parseBranches(text) {
158
+ const out = [];
159
+ for (const line of text.split("\n")) {
160
+ const parts = line.split("\t");
161
+ if (parts.length < 2)
162
+ continue;
163
+ const ref = parts[0];
164
+ const isHead = parts[1] === "*";
165
+ if (ref.startsWith("refs/heads/")) {
166
+ out.push({ name: ref.slice("refs/heads/".length), current: isHead });
167
+ }
168
+ else if (ref.startsWith("refs/remotes/")) {
169
+ const short = ref.slice("refs/remotes/".length);
170
+ // Skip the "origin/HEAD -> origin/main" symlink.
171
+ if (short.endsWith("/HEAD"))
172
+ continue;
173
+ const slash = short.indexOf("/");
174
+ out.push({
175
+ name: short,
176
+ current: false,
177
+ remote: slash > 0 ? short.slice(0, slash) : true,
178
+ });
179
+ }
180
+ }
181
+ return out;
182
+ }
183
+ /** Parse `git log --graph --pretty=format:%H%x09…` preserving graph prefixes. */
184
+ function parseCommitHistory(text) {
185
+ const out = [];
186
+ for (const line of text.split("\n")) {
187
+ const tab = line.indexOf("\t");
188
+ if (tab < 0)
189
+ continue; // connector-only graph line
190
+ const prefixAndHash = line.slice(0, tab);
191
+ const match = prefixAndHash.match(/([0-9a-f]{7,40})$/i);
192
+ if (!match || match.index === undefined)
193
+ continue;
194
+ const fields = line.slice(tab + 1).split("\t");
195
+ // An empty decoration field removes the final tab — four fields is a
196
+ // valid undecorated commit.
197
+ if (fields.length < 4)
198
+ continue;
199
+ out.push({
200
+ hash: match[1],
201
+ shortHash: fields[0],
202
+ author: fields[1],
203
+ date: fields[2],
204
+ subject: fields[3],
205
+ decorations: fields[4] ?? "",
206
+ graph: prefixAndHash.slice(0, match.index),
207
+ });
208
+ }
209
+ return out;
210
+ }
211
+ /** Parse `git diff --numstat` output: "12\t3\tpath" → { path: [add, del] }. */
212
+ function parseNumStat(text) {
213
+ const stats = {};
214
+ for (const line of text.split("\n")) {
215
+ if (!line.trim())
216
+ continue;
217
+ const tab1 = line.indexOf("\t");
218
+ const tab2 = tab1 < 0 ? -1 : line.indexOf("\t", tab1 + 1);
219
+ if (tab2 < 0)
220
+ continue;
221
+ let add = Number(line.slice(0, tab1));
222
+ let del = Number(line.slice(tab1 + 1, tab2));
223
+ if (!Number.isFinite(add))
224
+ add = 0; // binary file → "-"
225
+ if (!Number.isFinite(del))
226
+ del = 0;
227
+ const path = unquotePath(line.slice(tab2 + 1).trim());
228
+ const prev = stats[path];
229
+ stats[path] = [
230
+ (prev?.[0] ?? 0) + add,
231
+ (prev?.[1] ?? 0) + del,
232
+ ];
233
+ }
234
+ return stats;
235
+ }
236
+ /* ------------------------------------------------------------------ */
237
+ /* queries */
238
+ /* ------------------------------------------------------------------ */
239
+ const HISTORY_ARGS = [
240
+ "log",
241
+ "--all",
242
+ "--graph",
243
+ "--decorate=short",
244
+ "--date=short",
245
+ "--pretty=format:%H%x09%h%x09%an%x09%ad%x09%s%x09%D",
246
+ "-n",
247
+ "120",
248
+ ];
249
+ /** Commit graph for the history tab — fetched lazily so the common
250
+ * "changes" view never pays for it on huge repos. */
251
+ export async function scmHistory(cwd) {
252
+ return parseCommitHistory(await git(cwd, HISTORY_ARGS));
253
+ }
254
+ /** Status refresh payload (status + branches + numstat) — parallel. */
255
+ export async function scmStatus(cwd) {
256
+ const [statusText, branchText, statText, cachedStatText] = await Promise.all([
257
+ git(cwd, ["status", "--porcelain=v1", "-b", "--find-renames"]),
258
+ git(cwd, [
259
+ "for-each-ref",
260
+ "refs/heads",
261
+ "refs/remotes",
262
+ "--format=%(refname)%09%(HEAD)",
263
+ ]),
264
+ git(cwd, ["diff", "--numstat"]),
265
+ git(cwd, ["diff", "--cached", "--numstat"]),
266
+ ]);
267
+ const header = parseStatusHeader(statusText.split("\n").find((l) => l.startsWith("## "))?.slice(3) ?? "");
268
+ // Worktree + staged line counts, merged per path.
269
+ const merged = {};
270
+ for (const [path, pair] of Object.entries(parseNumStat(statText))) {
271
+ merged[path] = pair;
272
+ }
273
+ for (const [path, pair] of Object.entries(parseNumStat(cachedStatText))) {
274
+ const prev = merged[path];
275
+ merged[path] = [(prev?.[0] ?? 0) + pair[0], (prev?.[1] ?? 0) + pair[1]];
276
+ }
277
+ const branches = parseBranches(branchText);
278
+ const base = (await git(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]).catch(() => "")).trim() || ["main", "master", "develop"].find(name => branches.some(branch => branch.name === name)) || "HEAD";
279
+ return {
280
+ base,
281
+ notRepo: false,
282
+ branch: header.branch,
283
+ detached: header.detached,
284
+ upstream: header.upstream,
285
+ ahead: header.ahead,
286
+ behind: header.behind,
287
+ upstreamGone: header.upstreamGone,
288
+ files: parseStatusFiles(statusText),
289
+ branches,
290
+ stats: merged,
291
+ };
292
+ }
293
+ /** Staged + worktree diffs, or a bounded preview for an untracked file. */
294
+ export async function scmFileDiff(cwd, path) {
295
+ const [staged, worktree] = await Promise.all([
296
+ git(cwd, ["diff", "--cached", "--no-color", "--no-ext-diff", "--", path]).catch(() => ""),
297
+ git(cwd, ["diff", "--no-color", "--no-ext-diff", "--", path]).catch(() => ""),
298
+ ]);
299
+ if (staged || worktree)
300
+ return { staged, worktree };
301
+ const others = await git(cwd, ["ls-files", "--others", "--exclude-standard", "--", path]);
302
+ if (!others.trim())
303
+ return { staged, worktree };
304
+ const abs = resolve(cwd, path);
305
+ const root = await realpath(cwd);
306
+ const target = await realpath(abs);
307
+ const rel = relative(root, target);
308
+ if (isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`))
309
+ throw new Error("路径超出工作区");
310
+ const info = await stat(target);
311
+ if (!info.isFile()) {
312
+ return { staged, worktree, untracked: true, untrackedKind: "directory" };
313
+ }
314
+ const handle = await open(target, "r");
315
+ try {
316
+ const buf = Buffer.alloc(Math.min(info.size, MAX_UNTRACKED_PREVIEW_BYTES));
317
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
318
+ const data = buf.subarray(0, bytesRead);
319
+ const isText = looksLikeText(data);
320
+ return {
321
+ staged,
322
+ worktree,
323
+ untracked: true,
324
+ untrackedKind: isText ? "text" : "binary",
325
+ untrackedText: isText ? decodeText(data) : undefined,
326
+ untrackedTruncated: bytesRead < info.size,
327
+ };
328
+ }
329
+ finally {
330
+ await handle.close();
331
+ }
332
+ }
333
+ /** Full patch of one commit (`git show`). */
334
+ export async function scmCommitDetail(cwd, hash) {
335
+ return git(cwd, [
336
+ "show",
337
+ "--no-color",
338
+ "--no-ext-diff",
339
+ "--find-renames",
340
+ "--format=fuller",
341
+ "--stat",
342
+ "--patch",
343
+ hash,
344
+ ]);
345
+ }
346
+ /** Footer metadata only. symbolic-ref also works before the first commit. */
347
+ export async function scmCurrentBranch(cwd) {
348
+ try {
349
+ return { branch: (await git(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"])).trim(), detached: false };
350
+ }
351
+ catch {
352
+ try {
353
+ return { branch: (await git(cwd, ["rev-parse", "--short", "HEAD"])).trim(), detached: true };
354
+ }
355
+ catch {
356
+ return { branch: null, detached: false, notRepo: (await gitDirOf(cwd)) === null };
357
+ }
358
+ }
359
+ }
360
+ /** Untracked file as a unified "new file" patch. Each file is read directly: the
361
+ * listing already proved it untracked, so no git process is needed per file.
362
+ * Symlinks are not followed and unreadable files become binary entries, so one
363
+ * odd file never fails the whole view. */
364
+ async function untrackedPatch(root, path) {
365
+ const a = JSON.stringify(`a/${path}`), b = JSON.stringify(`b/${path}`);
366
+ const header = `diff --git ${a} ${b}\nnew file mode 100644\n--- /dev/null\n+++ ${b}\n`;
367
+ const binary = `${header}Binary files /dev/null and ${b} differ\n`;
368
+ try {
369
+ const absolute = resolve(root, path);
370
+ const rel = relative(root, absolute);
371
+ if (isAbsolute(rel) || rel === ".." || rel.startsWith(`..${sep}`))
372
+ return binary;
373
+ const info = await lstat(absolute);
374
+ if (!info.isFile())
375
+ return binary;
376
+ const handle = await open(absolute, "r");
377
+ try {
378
+ const buf = Buffer.alloc(Math.min(info.size, MAX_UNTRACKED_PREVIEW_BYTES));
379
+ const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
380
+ const data = buf.subarray(0, bytesRead);
381
+ if (!looksLikeText(data))
382
+ return binary;
383
+ const lines = decodeText(data).replace(/\n$/, "").split("\n");
384
+ let text = header;
385
+ if (bytesRead > 0)
386
+ text += `@@ -0,0 +1,${lines.length} @@\n${lines.map(line => `+${line}`).join("\n")}\n`;
387
+ if (bytesRead < info.size)
388
+ text += "\\ Preview truncated\n";
389
+ return text;
390
+ }
391
+ finally {
392
+ await handle.close();
393
+ }
394
+ }
395
+ catch {
396
+ return binary;
397
+ }
398
+ }
399
+ /** Whole-scope patches for the changes panel. Resolve refs before constructing
400
+ * revision expressions: user input can never become an option or pathspec. */
401
+ export async function scmDiff(cwd, options) {
402
+ const commit = async (ref) => (await git(cwd, ["rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`])).trim();
403
+ // Full-file context lets the panel unfold unchanged lines. Large files can
404
+ // exceed the output cap that way; fall back to normal context instead of failing.
405
+ const diff = async (range) => {
406
+ const args = (context) => ["diff", "--no-color", "--no-ext-diff", "--no-textconv", "--find-renames", "--relative", `--unified=${context}`, ...range, "--", "."];
407
+ try {
408
+ return { text: await git(cwd, args("100000")), reducedContext: false };
409
+ }
410
+ catch (error) {
411
+ if (!(error instanceof GitOutputTooLarge))
412
+ throw error;
413
+ }
414
+ try {
415
+ return { text: await git(cwd, args("3")), reducedContext: true };
416
+ }
417
+ catch (error) {
418
+ if (error instanceof GitOutputTooLarge)
419
+ throw new Error("Diff exceeds 16 MiB; narrow the changes before retrying");
420
+ throw error;
421
+ }
422
+ };
423
+ if (options.scope === "branch") {
424
+ let base = options.base;
425
+ if (!base) {
426
+ base = (await git(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]).catch(() => "")).trim();
427
+ if (!base)
428
+ for (const candidate of ["main", "master", "develop", "HEAD"]) {
429
+ if (await commit(candidate).catch(() => "")) {
430
+ base = candidate;
431
+ break;
432
+ }
433
+ }
434
+ }
435
+ if (!base || base.length > 1024 || base.includes("\0"))
436
+ throw new Error("Invalid base branch");
437
+ const [baseHash, head] = await Promise.all([commit(base), commit("HEAD")]);
438
+ const result = await diff([`${baseHash}...${head}`]);
439
+ return { text: result.text, base, ...(result.reducedContext ? { reducedContext: true } : {}) };
440
+ }
441
+ if (options.scope !== "work")
442
+ throw new Error("Invalid diff scope");
443
+ const head = await commit("HEAD").catch(async () => (await git(cwd, ["hash-object", "-t", "tree", "--stdin"])).trim());
444
+ // Git recognizes the empty tree without writing an object, including SHA-256 repos.
445
+ const result = await diff([head]);
446
+ const parts = [result.text];
447
+ let bytes = Buffer.byteLength(result.text, "utf8");
448
+ const paths = (await git(cwd, ["ls-files", "--others", "--exclude-standard", "-z", "--", "."])).split("\0").filter(Boolean);
449
+ const root = await realpath(cwd);
450
+ const shown = paths.slice(0, MAX_DIFF_UNTRACKED_FILES);
451
+ for (const path of shown) {
452
+ const patch = await untrackedPatch(root, path);
453
+ bytes += Buffer.byteLength(patch, "utf8");
454
+ if (bytes > MAX_GIT_OUTPUT)
455
+ throw new Error("Diff exceeds 16 MiB; narrow the changes before retrying");
456
+ parts.push(patch);
457
+ }
458
+ return { text: parts.join(""), ...(paths.length > shown.length ? { omittedUntracked: paths.length - shown.length } : {}), ...(result.reducedContext ? { reducedContext: true } : {}) };
459
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Baseline response headers for every route.
3
+ *
4
+ * The UI can drive an agent that runs commands, so a foreign page must not be
5
+ * able to frame it (clickjacking on confirm buttons). Same-origin framing stays
6
+ * allowed because the Wiki previews PDFs in a same-origin iframe. X-Frame-Options
7
+ * is set as well because /api/wiki-media replaces the Content-Security-Policy
8
+ * header with its own `sandbox`.
9
+ *
10
+ * No `script-src` policy here: it needs a full pass over pdf.js, mermaid, KaTeX
11
+ * and plugin bundles before it can be enforced without breaking features.
12
+ */
13
+ export const SECURITY_HEADERS = {
14
+ "Content-Security-Policy": "frame-ancestors 'self'",
15
+ "X-Frame-Options": "SAMEORIGIN",
16
+ "X-Content-Type-Options": "nosniff",
17
+ // The first navigation may carry ?token=; never leak the URL to external links.
18
+ "Referrer-Policy": "no-referrer",
19
+ };
20
+ export function securityHeaders(_req, res, next) {
21
+ for (const [name, value] of Object.entries(SECURITY_HEADERS))
22
+ res.setHeader(name, value);
23
+ next();
24
+ }