@youweichen/pi-harness 0.0.0-stage → 1.1.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 (456) 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 +3131 -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 +178 -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 +188 -0
  30. package/dist/server/files-service.js +787 -0
  31. package/dist/server/index.js +1033 -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-agent.js +262 -0
  40. package/dist/server/node-sources.js +142 -0
  41. package/dist/server/node-workbench.js +729 -0
  42. package/dist/server/origin-policy.js +50 -0
  43. package/dist/server/patch-node-pty.js +96 -0
  44. package/dist/server/pi-environment.js +203 -0
  45. package/dist/server/plan/extension.js +74 -0
  46. package/dist/server/plan/progress.js +90 -0
  47. package/dist/server/plan/settings.js +41 -0
  48. package/dist/server/plan/state.js +123 -0
  49. package/dist/server/plugin-facilities.js +304 -0
  50. package/dist/server/plugin-updater.js +245 -0
  51. package/dist/server/plugins.js +712 -0
  52. package/dist/server/private-file.js +28 -0
  53. package/dist/server/process-utils.js +112 -0
  54. package/dist/server/prompt-delivery.js +80 -0
  55. package/dist/server/protocol-version.js +11 -0
  56. package/dist/server/protocol.js +6 -0
  57. package/dist/server/provider-auth.js +99 -0
  58. package/dist/server/query-cache.js +45 -0
  59. package/dist/server/recovery-state.js +37 -0
  60. package/dist/server/scm.js +459 -0
  61. package/dist/server/security-headers.js +24 -0
  62. package/dist/server/serialize.js +254 -0
  63. package/dist/server/session-file-read.js +225 -0
  64. package/dist/server/session-record-worker.js +21 -0
  65. package/dist/server/session-reload.js +13 -0
  66. package/dist/server/session-tree-controller.js +250 -0
  67. package/dist/server/session-tree.js +191 -0
  68. package/dist/server/settings-service.js +53 -0
  69. package/dist/server/skills-service.js +87 -0
  70. package/dist/server/slash-commands.js +286 -0
  71. package/dist/server/sqlite-preview.js +58 -0
  72. package/dist/server/sqlite-query.js +67 -0
  73. package/dist/server/sqlite-worker.js +12 -0
  74. package/dist/server/system-prompt-files.js +158 -0
  75. package/dist/server/system-prompt-routes.js +40 -0
  76. package/dist/server/system-prompt-view.js +49 -0
  77. package/dist/server/task-progress.js +233 -0
  78. package/dist/server/terminals.js +833 -0
  79. package/dist/server/text-sniff.js +269 -0
  80. package/dist/server/thinking-timing.js +91 -0
  81. package/dist/server/token-auth.js +37 -0
  82. package/dist/server/tool-output.js +97 -0
  83. package/dist/server/tool-text-incidents.js +82 -0
  84. package/dist/server/tool-text.js +67 -0
  85. package/dist/server/uploads.js +112 -0
  86. package/dist/server/user-attachments.js +96 -0
  87. package/dist/server/webui-context.js +257 -0
  88. package/dist/server/wiki-links.js +136 -0
  89. package/dist/server/wiki-pdf-worker.js +23 -0
  90. package/dist/server/wiki-pdf.js +36 -0
  91. package/dist/server/wiki-routes.js +184 -0
  92. package/dist/server/wiki-service.js +524 -0
  93. package/electron/main.mjs +607 -0
  94. package/extensions/webui.ts +192 -0
  95. package/package.json +122 -4
  96. package/web/dist/apple-touch-icon.png +0 -0
  97. package/web/dist/assets/NodeWorkbench-BiP2ktQP.js +3 -0
  98. package/web/dist/assets/TerminalPanel-CKaSUsN-.js +2 -0
  99. package/web/dist/assets/abnfDiagram-VCTEODGH-CnwRJDRL.js +1 -0
  100. package/web/dist/assets/arc-CG4Q7-W_.js +1 -0
  101. package/web/dist/assets/architectureDiagram-5GKGNRK7-BrGqX_Rg.js +36 -0
  102. package/web/dist/assets/blockDiagram-I7D4REHJ-C2t0PjZT.js +129 -0
  103. package/web/dist/assets/c4Diagram-7LVT6UL2-CadrcgGL.js +38 -0
  104. package/web/dist/assets/channel-C27Jizb6.js +1 -0
  105. package/web/dist/assets/chunk-2Q5K7J3B-CC3V2ITZ.js +1 -0
  106. package/web/dist/assets/chunk-5VM5RSS4-DqKSbGmE.js +15 -0
  107. package/web/dist/assets/chunk-F27PBJKO-_CDr3JbY.js +1 -0
  108. package/web/dist/assets/chunk-IMKFNOWR-BIR4H4fX.js +231 -0
  109. package/web/dist/assets/chunk-JWPE2WC7-CrsdBGXT.js +1 -0
  110. package/web/dist/assets/chunk-POPQ4Y6H-C6q9kQmf.js +1 -0
  111. package/web/dist/assets/chunk-SVP7TREG-l2XXTr2v.js +88 -0
  112. package/web/dist/assets/chunk-TICWLB2K-29I_mZgB.js +206 -0
  113. package/web/dist/assets/chunk-XXDRQBXY-htV9FBqi.js +1 -0
  114. package/web/dist/assets/classDiagram-ZZMXUADV-DYTBrAEP.js +1 -0
  115. package/web/dist/assets/classDiagram-v2-VYDZK3BY-DYTBrAEP.js +1 -0
  116. package/web/dist/assets/cose-bilkent-JH36ORCC-Cop-ihyx.js +1 -0
  117. package/web/dist/assets/cynefin-OW5HDTMX-IEk8qBRq.js +178 -0
  118. package/web/dist/assets/cynefinDiagram-5FMLGOSQ-Bmzt6Qb5.js +62 -0
  119. package/web/dist/assets/cytoscape.esm-Bch-eiPH.js +331 -0
  120. package/web/dist/assets/dagre-GXQ25YYZ-DiXTJYYU.js +4 -0
  121. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  122. package/web/dist/assets/diagram-S7CK7UJ4-BT7CLuNk.js +30 -0
  123. package/web/dist/assets/diagram-UQ7AKVKN-BZZ3nTCO.js +41 -0
  124. package/web/dist/assets/diagram-VSXAHHWV-Dal8LeZh.js +3 -0
  125. package/web/dist/assets/diagram-VX7I27RA-D6V9FfNA.js +24 -0
  126. package/web/dist/assets/diagram-Z3DM3KII-KUAIcFiD.js +24 -0
  127. package/web/dist/assets/ebnfDiagram-PWID7BFC-s0FEPZrV.js +1 -0
  128. package/web/dist/assets/erDiagram-RLTQ6QDP-CiIQPFCD.js +99 -0
  129. package/web/dist/assets/flowDiagram-HODETNUW-CvG4MYWn.js +168 -0
  130. package/web/dist/assets/ganttDiagram-EL5Y4UJY-CUf115ii.js +292 -0
  131. package/web/dist/assets/gitGraphDiagram-WWUBYQGX-o3K1r6fL.js +106 -0
  132. package/web/dist/assets/index-C3vDFSEV.css +10 -0
  133. package/web/dist/assets/index-C5or_GAq.js +276 -0
  134. package/web/dist/assets/infoDiagram-27XIBGKW-BxiBeQ8E.js +2 -0
  135. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  136. package/web/dist/assets/ishikawaDiagram-5VMMS53U-B1ONuCkH.js +70 -0
  137. package/web/dist/assets/journeyDiagram-3NMN7TZE-DwmuoXcX.js +139 -0
  138. package/web/dist/assets/kanban-definition-UXKFOSKX-pMKHubxH.js +89 -0
  139. package/web/dist/assets/katex-C5jXJg4s.js +257 -0
  140. package/web/dist/assets/layout-CEwfjQR0.js +1 -0
  141. package/web/dist/assets/linear-D22Gdbt1.js +1 -0
  142. package/web/dist/assets/markdown-BhJ_LInD.js +51 -0
  143. package/web/dist/assets/mermaid.core-CO2nrD04.js +312 -0
  144. package/web/dist/assets/mindmap-definition-YA3MSWOX-9MQn9ZAp.js +96 -0
  145. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  146. package/web/dist/assets/pegDiagram-XKGWAZYB-Dy6fLjM-.js +1 -0
  147. package/web/dist/assets/pieDiagram-E7YTZNPT-EDLez6nA.js +39 -0
  148. package/web/dist/assets/quadrantDiagram-AXDQQJYC-Cgcd9mvy.js +7 -0
  149. package/web/dist/assets/railroadDiagram-O6MQD6OU-B4Td0tjj.js +1 -0
  150. package/web/dist/assets/react-BddUxV-9.js +24 -0
  151. package/web/dist/assets/requirementDiagram-BXWQKSXE-aF2QNZ78.js +84 -0
  152. package/web/dist/assets/sankeyDiagram-P5KCCOFB-DgTYwYMa.js +40 -0
  153. package/web/dist/assets/sequenceDiagram-WJ2MYXX4-EUcddWUc.js +162 -0
  154. package/web/dist/assets/sizeCapture-INFHLROL-CXHOv9Sx.js +1 -0
  155. package/web/dist/assets/stateDiagram-D77RDMKH-B-xlgfaK.js +1 -0
  156. package/web/dist/assets/stateDiagram-v2-MP3YSRHH-kwAwAlWX.js +1 -0
  157. package/web/dist/assets/swimlanes-42K2YHIH-BtFBOCDh.js +1 -0
  158. package/web/dist/assets/swimlanesDiagram-VR7AAH4N-D5j5Cj3S.js +8 -0
  159. package/web/dist/assets/timeline-definition-24CTP7MA-CZlQY6pb.js +120 -0
  160. package/web/dist/assets/vennDiagram-4TSXK5OY-Cn61dJvL.js +34 -0
  161. package/web/dist/assets/wardleyDiagram-VM6X3IG4-fiMNrWpr.js +78 -0
  162. package/web/dist/assets/xterm-6GBZ9nXN.css +32 -0
  163. package/web/dist/assets/xterm-D1D2FVe3.js +38 -0
  164. package/web/dist/assets/xychartDiagram-S5SC5T6Z-CuzM_sof.js +7 -0
  165. package/web/dist/brand-mark.svg +5 -0
  166. package/web/dist/favicon-32.png +0 -0
  167. package/web/dist/favicon.svg +5 -0
  168. package/web/dist/fonts/WIKI-FONTS.md +5 -0
  169. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  170. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  171. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  172. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  173. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  174. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  175. package/web/dist/fonts/ibm-plex-sans/LICENSE +93 -0
  176. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  177. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  178. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  179. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  180. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  181. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  182. package/web/dist/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  183. package/web/dist/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  184. package/web/dist/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  185. package/web/dist/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  186. package/web/dist/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  187. package/web/dist/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  188. package/web/dist/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  189. package/web/dist/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  190. package/web/dist/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  191. package/web/dist/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  192. package/web/dist/fonts/jetbrains-mono/LICENSE +93 -0
  193. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  194. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
  195. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
  196. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  197. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  198. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
  199. package/web/dist/fonts/noto-serif-sc/LICENSE +94 -0
  200. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
  201. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
  202. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
  203. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
  204. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
  205. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
  206. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
  207. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
  208. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
  209. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
  210. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
  211. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
  212. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
  213. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
  214. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
  215. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
  216. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
  217. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
  218. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
  219. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
  220. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
  221. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
  222. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
  223. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
  224. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
  225. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
  226. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
  227. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
  228. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
  229. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
  230. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
  231. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
  232. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
  233. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
  234. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
  235. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
  236. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
  237. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
  238. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
  239. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
  240. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
  241. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
  242. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
  243. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
  244. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
  245. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
  246. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
  247. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
  248. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
  249. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
  250. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
  251. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
  252. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
  253. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
  254. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
  255. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
  256. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
  257. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
  258. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
  259. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
  260. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
  261. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
  262. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
  263. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
  264. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
  265. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
  266. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
  267. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
  268. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
  269. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
  270. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
  271. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
  272. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
  273. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
  274. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
  275. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
  276. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
  277. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
  278. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
  279. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
  280. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
  281. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
  282. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
  283. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
  284. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
  285. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
  286. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
  287. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
  288. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
  289. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
  290. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
  291. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
  292. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
  293. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
  294. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
  295. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
  296. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
  297. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
  298. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
  299. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
  300. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
  301. package/web/dist/fonts/source-serif-4/LICENSE +93 -0
  302. package/web/dist/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
  303. package/web/dist/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
  304. package/web/dist/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
  305. package/web/dist/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
  306. package/web/dist/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
  307. package/web/dist/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
  308. package/web/dist/icon/1a-mark.svg +1 -0
  309. package/web/dist/icon.ico +0 -0
  310. package/web/dist/index.html +18 -0
  311. package/web/public/apple-touch-icon.png +0 -0
  312. package/web/public/brand-mark.svg +5 -0
  313. package/web/public/favicon-32.png +0 -0
  314. package/web/public/favicon.svg +5 -0
  315. package/web/public/fonts/WIKI-FONTS.md +5 -0
  316. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  317. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  318. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  319. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  320. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  321. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  322. package/web/public/fonts/ibm-plex-sans/LICENSE +93 -0
  323. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  324. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  325. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  326. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  327. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  328. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  329. package/web/public/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  330. package/web/public/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  331. package/web/public/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  332. package/web/public/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  333. package/web/public/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  334. package/web/public/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  335. package/web/public/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  336. package/web/public/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  337. package/web/public/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  338. package/web/public/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  339. package/web/public/fonts/jetbrains-mono/LICENSE +93 -0
  340. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  341. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
  342. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
  343. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  344. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  345. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
  346. package/web/public/fonts/noto-serif-sc/LICENSE +94 -0
  347. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
  348. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
  349. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
  350. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
  351. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
  352. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
  353. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
  354. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
  355. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
  356. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
  357. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
  358. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
  359. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
  360. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
  361. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
  362. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
  363. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
  364. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
  365. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
  366. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
  367. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
  368. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
  369. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
  370. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
  371. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
  372. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
  373. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
  374. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
  375. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
  376. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
  377. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
  378. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
  379. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
  380. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
  381. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
  382. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
  383. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
  384. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
  385. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
  386. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
  387. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
  388. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
  389. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
  390. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
  391. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
  392. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
  393. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
  394. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
  395. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
  396. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
  397. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
  398. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
  399. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
  400. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
  401. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
  402. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
  403. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
  404. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
  405. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
  406. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
  407. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
  408. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
  409. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
  410. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
  411. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
  412. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
  413. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
  414. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
  415. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
  416. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
  417. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
  418. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
  419. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
  420. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
  421. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
  422. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
  423. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
  424. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
  425. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
  426. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
  427. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
  428. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
  429. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
  430. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
  431. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
  432. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
  433. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
  434. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
  435. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
  436. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
  437. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
  438. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
  439. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
  440. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
  441. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
  442. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
  443. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
  444. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
  445. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
  446. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
  447. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
  448. package/web/public/fonts/source-serif-4/LICENSE +93 -0
  449. package/web/public/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
  450. package/web/public/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
  451. package/web/public/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
  452. package/web/public/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
  453. package/web/public/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
  454. package/web/public/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
  455. package/web/public/icon/1a-mark.svg +1 -0
  456. package/web/public/icon.ico +0 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Local control socket for the pi-harness server.
3
+ *
4
+ * Lets the CLI (and humans) query status and quiesce/unquiesce the server
5
+ * WITHOUT opening a network port or exposing an unauthenticated HTTP
6
+ * endpoint. Only the local OS user can reach it:
7
+ * - POSIX: a mode-0600 Unix domain socket at <dataDir>/pi-harness.sock
8
+ * - Windows: a named pipe \\.\pipe\pi-harness-<port>
9
+ *
10
+ * Protocol: one JSON object per line.
11
+ * → {"cmd":"status"} ← {"ok":true, ...serviceStatus}
12
+ * → {"cmd":"quiesce"} ← {"ok":true}
13
+ * → {"cmd":"unquiesce"} ← {"ok":true}
14
+ * → anything else ← {"ok":false,"error":"..."}
15
+ *
16
+ * Idle connections are closed after a short timeout so a stuck CLI never
17
+ * holds the socket.
18
+ */
19
+ import { createServer, createConnection } from "node:net";
20
+ import { chmodSync, existsSync, rmSync } from "node:fs";
21
+ import { join } from "node:path";
22
+ /** How long a control connection may sit idle before the server closes it. */
23
+ const CONTROL_IDLE_TIMEOUT_MS = 5_000;
24
+ /** How long the CLI waits for a reply before giving up. */
25
+ const CONTROL_CLIENT_TIMEOUT_MS = 3_000;
26
+ /** Socket path (POSIX) or pipe name (Windows). */
27
+ export function controlPath(dataDir, port) {
28
+ return process.platform === "win32"
29
+ ? `\\\\.\\pipe\\pi-harness-${port}`
30
+ : join(dataDir, "pi-harness.sock");
31
+ }
32
+ /** Start the control socket; returns a stop function. */
33
+ export function startControlServer(opts) {
34
+ const { service, dataDir, port } = opts;
35
+ const path = controlPath(dataDir, port);
36
+ let server;
37
+ let stop = false;
38
+ if (process.platform === "win32") {
39
+ server = createServer(handleConnection);
40
+ }
41
+ else {
42
+ // Remove a stale socket left by a previous crash (only if it's ours —
43
+ // an existing socket file that refuses connections is stale).
44
+ if (existsSync(path)) {
45
+ try {
46
+ rmSync(path);
47
+ }
48
+ catch {
49
+ /* best-effort */
50
+ }
51
+ }
52
+ server = createServer(handleConnection);
53
+ }
54
+ // A second instance on the same data dir / port would fail to bind — don't
55
+ // crash the server over it, just log and run without a control socket.
56
+ server.on("error", (err) => {
57
+ if (err.code === "EADDRINUSE") {
58
+ console.warn(`[control] socket ${path} already in use — control socket disabled`);
59
+ }
60
+ else {
61
+ console.warn(`[control] socket error: ${err.message}`);
62
+ }
63
+ });
64
+ function handleConnection(sock) {
65
+ let buf = "";
66
+ const timer = setTimeout(() => {
67
+ sock.destroy();
68
+ }, CONTROL_IDLE_TIMEOUT_MS);
69
+ sock.on("data", (chunk) => {
70
+ buf += chunk.toString("utf8");
71
+ let nl;
72
+ while ((nl = buf.indexOf("\n")) >= 0) {
73
+ const line = buf.slice(0, nl).trim();
74
+ buf = buf.slice(nl + 1);
75
+ if (!line)
76
+ continue;
77
+ timer.refresh();
78
+ let req;
79
+ try {
80
+ req = JSON.parse(line);
81
+ }
82
+ catch {
83
+ sock.write(JSON.stringify({ ok: false, error: "bad json" }) + "\n");
84
+ continue;
85
+ }
86
+ let resp;
87
+ switch (req.cmd) {
88
+ case "status":
89
+ resp = { ok: true, ...service.serviceStatus() };
90
+ resp.activeConversations = (resp.activeConversations ?? 0) + (opts.additionalActiveConversations?.() ?? 0);
91
+ break;
92
+ case "quiesce":
93
+ service.quiesce();
94
+ resp = { ok: true };
95
+ break;
96
+ case "unquiesce":
97
+ service.unquiesce();
98
+ resp = { ok: true };
99
+ break;
100
+ default:
101
+ resp = { ok: false, error: `unknown cmd: ${String(req.cmd)}` };
102
+ break;
103
+ }
104
+ sock.write(JSON.stringify(resp) + "\n");
105
+ }
106
+ });
107
+ sock.on("error", () => {
108
+ /* client vanished */
109
+ });
110
+ sock.on("close", () => clearTimeout(timer));
111
+ }
112
+ if (process.platform === "win32") {
113
+ // net.Server on a named pipe: listen on the pipe name directly.
114
+ server.listen(path, () => {
115
+ console.log(` control : ${path}`);
116
+ });
117
+ }
118
+ else {
119
+ server.listen(path, () => {
120
+ try {
121
+ chmodSync(path, 0o600);
122
+ }
123
+ catch {
124
+ /* best-effort */
125
+ }
126
+ console.log(` control : ${path}`);
127
+ });
128
+ }
129
+ return () => {
130
+ stop = true;
131
+ server.close();
132
+ try {
133
+ if (process.platform !== "win32" && existsSync(path))
134
+ rmSync(path);
135
+ }
136
+ catch {
137
+ /* best-effort */
138
+ }
139
+ };
140
+ }
141
+ /**
142
+ * CLI-side client: send one command and return the parsed reply (or null if
143
+ * the server is unreachable / timed out).
144
+ */
145
+ export function sendControlCommand(dataDir, port, cmd) {
146
+ const path = controlPath(dataDir, port);
147
+ return new Promise((resolve) => {
148
+ const sock = createConnection(path);
149
+ let done = false;
150
+ const finish = (v) => {
151
+ if (done)
152
+ return;
153
+ done = true;
154
+ clearTimeout(timer);
155
+ sock.destroy();
156
+ resolve(v);
157
+ };
158
+ const timer = setTimeout(() => finish(null), CONTROL_CLIENT_TIMEOUT_MS);
159
+ let buf = "";
160
+ sock.on("connect", () => {
161
+ sock.write(JSON.stringify({ cmd }) + "\n");
162
+ });
163
+ sock.on("data", (chunk) => {
164
+ buf += chunk.toString("utf8");
165
+ const nl = buf.indexOf("\n");
166
+ if (nl >= 0) {
167
+ try {
168
+ finish(JSON.parse(buf.slice(0, nl)));
169
+ }
170
+ catch {
171
+ finish(null);
172
+ }
173
+ }
174
+ });
175
+ sock.on("error", () => finish(null));
176
+ sock.on("close", () => finish(null));
177
+ });
178
+ }
@@ -0,0 +1,65 @@
1
+ /** Thrown when the service is quiesced (draining) and the request is NEW work
2
+ * the admission controller refuses: a brand-new client attach, a prompt,
3
+ * a fork, a session resume, or a goal wizard start. index.ts closes the
4
+ * WebSocket with 4403 so the browser reconnect loop can retry after the
5
+ * server reopens admission (see AgentService.quiesce). */
6
+ export class QuiesceRejectedError extends Error {
7
+ code = "QUIESCED";
8
+ constructor(detail) {
9
+ super(`服务器正在排空存量工作(quiesce)——${detail}`);
10
+ this.name = "QuiesceRejectedError";
11
+ }
12
+ }
13
+ /** Cap on simultaneously open conversations of ONE project (each keeps a full
14
+ * runtime alive; conversations of other projects keep their own lists). */
15
+ export const MAX_OPEN_CONVERSATIONS = 8;
16
+ export const DEFAULT_CONV_TITLE = "新对话";
17
+ // Mirrors web/src/skill-block.ts's parseSkillBlock (which itself mirrors the
18
+ // pi SDK's dist/core/agent-session.js) — kept in sync by hand, server and
19
+ // web can't share a module across the tsconfig split. When the user sends
20
+ // /skill:name args, the SDK expands the prompt into
21
+ // `<skill name="..." location="...">\n...SKILL.md body...\n</skill>\n\n<args>`;
22
+ // using that raw text as a conversation title would dump (and mid-sentence
23
+ // truncate) the entire skill body instead of something readable.
24
+ const SKILL_BLOCK_TITLE_RE = /^<skill name="([^"]+)" location="[^"]+">\n[\s\S]*?\n<\/skill>(?:\n\n([\s\S]+))?$/;
25
+ export function skillAwareTitleText(text) {
26
+ const m = text.match(SKILL_BLOCK_TITLE_RE);
27
+ if (!m)
28
+ return text;
29
+ const name = m[1];
30
+ const args = m[2]?.trim();
31
+ return `skill:${name}` + (args ? ` · ${args}` : "");
32
+ }
33
+ /** First user text in a session, truncated for the conversation list. */
34
+ export function conversationTitle(session) {
35
+ try {
36
+ for (const m of session.agent.state.messages) {
37
+ if (m.role !== "user")
38
+ continue;
39
+ const content = m.content;
40
+ let text = "";
41
+ if (typeof content === "string") {
42
+ text = content;
43
+ }
44
+ else if (Array.isArray(content)) {
45
+ for (const p of content) {
46
+ if (p &&
47
+ typeof p === "object" &&
48
+ p.type === "text" &&
49
+ typeof p.text === "string") {
50
+ text = p.text;
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ const trimmed = skillAwareTitleText(text).trim().replace(/\s+/g, " ");
56
+ if (trimmed.length > 0) {
57
+ return trimmed.length > 30 ? `${trimmed.slice(0, 30)}…` : trimmed;
58
+ }
59
+ }
60
+ }
61
+ catch {
62
+ // best-effort
63
+ }
64
+ return DEFAULT_CONV_TITLE;
65
+ }
@@ -0,0 +1,77 @@
1
+ import { access, readdir } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, resolve } from "node:path";
4
+ /** List subdirectories for the workspace picker (`browse_dirs`).
5
+ *
6
+ * Deliberately NOT routed through FilesService.listFiles(), which pins
7
+ * every listing inside the current workspace — choosing a new workspace
8
+ * is exactly the case that has to look outside it. Scope is kept narrow
9
+ * instead: directory *names* only, never file contents, and the same
10
+ * loopback binding + PI_WEB_TOKEN auth as every other message guards it.
11
+ * (The agent can already shell out with bash, so this exposes nothing it
12
+ * could not already reach — it just makes it clickable.)
13
+ */
14
+ export async function browseDirs(path, emit) {
15
+ const MAX = 500;
16
+ const target = resolve(path?.trim() || homedir());
17
+ try {
18
+ const dirents = await readdir(target, { withFileTypes: true });
19
+ const dirs = [];
20
+ for (const d of dirents) {
21
+ // Symlinked directories are worth following (project checkouts
22
+ // are often symlinked), but a broken link must not abort the
23
+ // whole listing — isDirectory() is false for those, which is
24
+ // the behaviour we want anyway.
25
+ if (!d.isDirectory())
26
+ continue;
27
+ if (d.name.startsWith("."))
28
+ continue; // dotfolders: noise here
29
+ dirs.push(d.name);
30
+ if (dirs.length >= MAX)
31
+ break;
32
+ }
33
+ dirs.sort((a, b) => a.localeCompare(b));
34
+ const parent = dirname(target);
35
+ emit({
36
+ type: "dir_browse",
37
+ path: target,
38
+ parent: parent === target ? null : parent,
39
+ dirs,
40
+ truncated: dirs.length >= MAX,
41
+ drives: await listWindowsDrives(),
42
+ });
43
+ }
44
+ catch (err) {
45
+ emit({
46
+ type: "notice",
47
+ level: "error",
48
+ text: `无法读取目录:${err.message}`,
49
+ });
50
+ }
51
+ }
52
+ /**
53
+ * Windows drive roots that currently exist ("C:\\", "D:\\", …); empty on
54
+ * POSIX (where "/" already reaches everything).
55
+ *
56
+ * Needed because Windows has no unified filesystem root: dirname("C:\\") is
57
+ * "C:\\", so the picker's walk-up hits a ceiling on the boot drive and can
58
+ * never reach D:. Probing A–Z with access() avoids shelling out to wmic /
59
+ * PowerShell (both slow to spawn, and wmic is gone on recent Windows).
60
+ * Missing/empty drives simply reject, so they drop out.
61
+ */
62
+ async function listWindowsDrives() {
63
+ if (process.platform !== "win32")
64
+ return undefined;
65
+ const letters = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i));
66
+ const found = await Promise.all(letters.map(async (letter) => {
67
+ const root = `${letter}:\\`;
68
+ try {
69
+ await access(root);
70
+ return root;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }));
76
+ return found.filter((d) => d !== null);
77
+ }
@@ -0,0 +1,38 @@
1
+ import { realpathSync, statSync, openSync, readSync, closeSync } from "node:fs";
2
+ import { resolve, relative, isAbsolute, sep } from "node:path";
3
+ import { looksLikeText, previewKind } from "./text-sniff.js";
4
+ /** Validate every snapshot before any attachment side effects or history fork. */
5
+ export function validateEditorSnapshots(cwd, attachments) {
6
+ for (const att of attachments ?? []) {
7
+ if (att.editorSnapshot === undefined)
8
+ continue;
9
+ const s = att.editorSnapshot;
10
+ if (!s || typeof s.cwd !== "string" || s.cwd !== cwd || typeof s.text !== "string" ||
11
+ typeof s.dirty !== "boolean" || (s.version !== undefined && typeof s.version !== "string") ||
12
+ typeof att.path !== "string" || !att.path || att.mode === "lines" || att.imageData || att.fileData || att.uploadPath) {
13
+ throw new Error("当前文件快照身份或类型无效,请重新打开文件");
14
+ }
15
+ if (Buffer.byteLength(s.text, "utf8") > 512 * 1024)
16
+ throw new Error("当前文件快照超过 512 KiB,请缩减内容或移除标签");
17
+ const root = realpathSync(cwd);
18
+ const abs = resolve(cwd, att.path);
19
+ for (const [boundary, path] of [[resolve(cwd), abs], [root, realpathSync(abs)]]) {
20
+ const rel = relative(boundary, path);
21
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel))
22
+ throw new Error("当前文件路径超出工作区");
23
+ }
24
+ if (!statSync(abs).isFile() || !["text", "none"].includes(previewKind(att.path)) || !looksLikeText(Buffer.from(s.text))) {
25
+ throw new Error("当前文件快照必须是文本文件");
26
+ }
27
+ const fd = openSync(abs, "r");
28
+ try {
29
+ const sample = Buffer.alloc(4096);
30
+ const length = readSync(fd, sample, 0, sample.length, 0);
31
+ if (!looksLikeText(sample.subarray(0, length)))
32
+ throw new Error("当前文件不是可编辑文本");
33
+ }
34
+ finally {
35
+ closeSync(fd);
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Lightweight bash fallback for Windows.
3
+ *
4
+ * When neither Git Bash nor a bash on PATH exists, download busybox-w32
5
+ * (single self-contained ~1.5MB exe, no installer) into <home>/.pi-web/bin/
6
+ * and expose it as bash.exe — busybox dispatches on argv[0], so `bash.exe`
7
+ * runs its bash (ash) applet. The terminal panel (terminals.ts) and the SDK
8
+ * bash tool (via PATH) then both resolve to it, so the agent never silently
9
+ * falls back to cmd/PowerShell syntax on a bare Windows box.
10
+ *
11
+ * Download is fire-and-forget at server start and never throws: on failure
12
+ * the terminal simply falls back to $COMSPEC (cmd.exe) as before.
13
+ */
14
+ import { copyFileSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
15
+ import { homedir } from "node:os";
16
+ import { join } from "node:path";
17
+ /** Official busybox-w32 64-bit Unicode build (Win10 1903+ / Win11). */
18
+ const BUSYBOX_URL = "https://frippery.org/files/busybox/busybox64u.exe";
19
+ /** busybox.exe is ~660KB; anything far smaller is an error page, not a binary. */
20
+ const MIN_SIZE = 500_000;
21
+ /** Download cap — a stalled connection must not block startup forever. */
22
+ const DOWNLOAD_TIMEOUT_MS = 60_000;
23
+ /** Directory holding the busybox fallback (shared with terminals.ts / PATH). */
24
+ export function windowsBashDir() {
25
+ return join(homedir(), ".pi-web", "bin");
26
+ }
27
+ /** bash.exe (busybox bash applet) used by the terminal and the SDK bash tool. */
28
+ export function windowsBashPath() {
29
+ return join(windowsBashDir(), "bash.exe");
30
+ }
31
+ /** True when a standard Git Bash install exists (SDK's preferred shell). */
32
+ export function hasGitBash() {
33
+ const pf = process.env.ProgramFiles;
34
+ const pf86 = process.env["ProgramFiles(x86)"];
35
+ for (const cand of [
36
+ pf ? join(pf, "Git", "bin", "bash.exe") : "",
37
+ pf86 ? join(pf86, "Git", "bin", "bash.exe") : "",
38
+ ]) {
39
+ if (cand && existsSync(cand))
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ /**
45
+ * Ensure a bash.exe exists on Windows. No-op when Git Bash is already
46
+ * installed (it is strictly preferred) or the fallback is already present.
47
+ * Never throws — failures degrade silently to the previous behaviour.
48
+ *
49
+ * @returns the bash.exe path when ready, null otherwise.
50
+ */
51
+ export async function ensureWindowsBash() {
52
+ if (process.platform !== "win32")
53
+ return null;
54
+ const target = windowsBashPath();
55
+ if (existsSync(target))
56
+ return target;
57
+ if (hasGitBash())
58
+ return null; // Git Bash preferred — nothing to install.
59
+ const dir = windowsBashDir();
60
+ mkdirSync(dir, { recursive: true });
61
+ const tmp = join(dir, `busybox-${process.pid}.tmp`);
62
+ try {
63
+ const res = await fetch(BUSYBOX_URL, {
64
+ signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
65
+ });
66
+ if (!res.ok)
67
+ return null;
68
+ const buf = Buffer.from(await res.arrayBuffer());
69
+ if (buf.length < MIN_SIZE)
70
+ return null; // error/HTML page, not the exe.
71
+ writeFileSync(tmp, buf);
72
+ renameSync(tmp, join(dir, "busybox.exe"));
73
+ copyFileSync(join(dir, "busybox.exe"), target);
74
+ return target;
75
+ }
76
+ catch {
77
+ rmSync(tmp, { force: true });
78
+ return null;
79
+ }
80
+ }
@@ -0,0 +1,23 @@
1
+ /** Display-only metadata. Toggle/uninstall identities remain owned by the loader. */
2
+ export function extensionDisplay(path, source) {
3
+ const normalized = path.replaceAll("\\", "/").replace(/\/$/, "");
4
+ if (source?.startsWith("npm:"))
5
+ return { name: source.slice(4) };
6
+ const marker = "/node_modules/";
7
+ const packagePath = normalized.slice(normalized.lastIndexOf(marker) + marker.length);
8
+ if (normalized.includes(marker)) {
9
+ const parts = packagePath.split("/");
10
+ const name = parts[0]?.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
11
+ if (name)
12
+ return { name };
13
+ }
14
+ const parts = normalized.split("/").filter(Boolean);
15
+ const filename = parts.pop() ?? "";
16
+ const stem = filename.replace(/\.(?:[cm]?[jt]s|tsx|jsx)$/i, "");
17
+ if (!/^(?:index|main|extension|plugin)$/i.test(stem))
18
+ return { name: stem || source || path };
19
+ // Entry points commonly sit in build/source folders beneath the extension.
20
+ while (parts.length && /^(?:src|dist|lib|build|extensions?|\.pi)$/i.test(parts.at(-1)))
21
+ parts.pop();
22
+ return { name: parts.at(-1) || source || filename };
23
+ }
@@ -0,0 +1,64 @@
1
+ const cache = new Map();
2
+ export async function cachedFetch(url, json = true) {
3
+ const old = cache.get(url);
4
+ if (old && old.until > Date.now())
5
+ return old.value;
6
+ const value = (async () => { const response = await fetch(url, { signal: AbortSignal.timeout(15000), headers: { Accept: json ? "application/json" : "text/html" } }); if (!response.ok)
7
+ throw Error(`Package catalog: HTTP ${response.status}`); const text = await response.text(); if (text.length > 8_000_000)
8
+ throw Error("Catalog response too large"); return json ? JSON.parse(text) : text; })();
9
+ cache.set(url, { until: Date.now() + 300000, value });
10
+ if (cache.size > 128)
11
+ cache.delete(cache.keys().next().value);
12
+ try {
13
+ return await value;
14
+ }
15
+ catch (error) {
16
+ cache.delete(url);
17
+ throw error;
18
+ }
19
+ }
20
+ export function safeUrl(value) { if (typeof value !== "string")
21
+ return; try {
22
+ const u = new URL(value.replace(/^git\+/, ""));
23
+ if (["https:", "http:"].includes(u.protocol) && !u.username && !u.password)
24
+ return u.href;
25
+ }
26
+ catch { } }
27
+ const decode = (text) => text.replace(/&(?:amp|quot|lt|gt|apos|#39|#x[0-9a-f]+|#\d+);/gi, entity => ({ "&amp;": "&", "&quot;": '"', "&lt;": "<", "&gt;": ">", "&apos;": "'", "&#39;": "'" }[entity] ?? String.fromCodePoint(Number(entity.startsWith("&#x") ? `0x${entity.slice(3, -1)}` : entity.slice(2, -1)))));
28
+ const plain = (s) => decode(s.replace(/<[^>]*>/g, "")).trim();
29
+ /** Pi currently serves its catalog as HTML. Extract only data attributes/text, never render remote HTML. */
30
+ export function parseCatalog(html, page) {
31
+ const items = [];
32
+ for (const match of html.matchAll(/<article\b[^>]*data-package-card="true"[\s\S]*?<\/article>/g)) {
33
+ const text = match[0], attr = (name) => decode(new RegExp(`${name}="([^"]*)"`).exec(text)?.[1] ?? "");
34
+ const name = attr("data-package-name");
35
+ if (!name)
36
+ continue;
37
+ const meta = /class="packages-meta">([\s\S]*?)<\/div>/.exec(text)?.[1] ?? "";
38
+ items.push({ name, description: plain(/class="packages-desc">([\s\S]*?)<\/p>/.exec(text)?.[1] ?? ""), author: plain(/<span>(.*?)<\/span>/.exec(meta)?.[1] ?? ""), downloads: attr("data-package-downloads") && Number.isFinite(Number(attr("data-package-downloads"))) ? Number(attr("data-package-downloads")) : undefined, date: Number(attr("data-package-date")) || undefined, types: attr("data-package-types").split(/[ ,]+/).filter(Boolean), url: `https://pi.dev/packages/${name}`, version: /package-version=([^"&]+)/.exec(text)?.[1], image: safeUrl(/<img[^>]*src="([^"]+)"/.exec(text)?.[1]), repository: safeUrl(/href="(https:\/\/github.com\/(?!earendil-works\/pi\/issues)[^"]+)"/.exec(text)?.[1]) });
39
+ }
40
+ if (!items.length && !/packages-grid|No packages|No results/i.test(html))
41
+ throw Error("Pi catalog format changed; open pi.dev/packages");
42
+ const pages = Math.max(page, ...[...html.matchAll(/[?&](?:amp;)?page=(\d+)/g)].map(m => Number(m[1])));
43
+ const count = /class="packages-count">\s*[\d,]+-[\d,]+\s*\/\s*([\d,]+)/.exec(html)?.[1];
44
+ return { items, page, pages, ...(count ? { total: Number(count.replaceAll(",", "")) } : {}) };
45
+ }
46
+ export async function browseCatalog(query, type, sort, page) {
47
+ const params = new URLSearchParams({ name: query.slice(0, 200), type: ["extension", "skill", "prompt", "theme"].includes(type) ? type : "", sort: sort === "recent" ? "recent" : "downloads", page: String(Math.max(1, Math.min(1000, page))) });
48
+ return parseCatalog(await cachedFetch(`https://pi.dev/packages?${params}`, false), page);
49
+ }
50
+ export async function packageReleaseNotes(name) {
51
+ const meta = await cachedFetch(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`);
52
+ const repository = safeUrl(typeof meta.repository === "string" ? meta.repository : meta.repository?.url);
53
+ const match = repository && /^https:\/\/github.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(repository);
54
+ if (!match)
55
+ return { title: meta.version ?? "", notes: [] };
56
+ try {
57
+ const release = await cachedFetch(`https://api.github.com/repos/${match[1]}/${match[2]}/releases/latest`);
58
+ const notes = typeof release.body === "string" ? release.body.split("\n").map((line) => line.trim()).filter((line) => /^[-*] /.test(line)).slice(0, 3).map((line) => line.slice(2, 800)) : [];
59
+ return { title: String(release.tag_name ?? meta.version), notes, url: safeUrl(release.html_url) };
60
+ }
61
+ catch {
62
+ return { title: meta.version ?? "", notes: [], url: `${repository?.replace(/\.git$/, "")}/releases` };
63
+ }
64
+ }
@@ -0,0 +1,46 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { homedir } from "node:os";
5
+ export const textField = (value) => typeof value === "string" ? value.slice(0, 4000) : undefined;
6
+ export const resourceTypes = ["extensions", "skills", "prompts", "themes"];
7
+ export const sourceOf = (entry) => typeof entry === "string" ? entry : entry.source;
8
+ export function readJson(path) {
9
+ try {
10
+ return JSON.parse(readFileSync(path, "utf8"));
11
+ }
12
+ catch (error) {
13
+ if (error.code === "ENOENT")
14
+ return {};
15
+ throw error;
16
+ }
17
+ }
18
+ export function settingsVersion(cwd, agentDir) {
19
+ return createHash("sha256").update(JSON.stringify([readJson(join(agentDir, "settings.json")), readJson(join(cwd, ".pi/settings.json"))])).digest("hex");
20
+ }
21
+ export function packageDisabled(entry) { return typeof entry !== "string" && resourceTypes.every(type => Array.isArray(entry[type]) && entry[type].length === 0); }
22
+ export function sourceInfo(source) {
23
+ const npm = /^npm:((?:@[a-z0-9._-]+\/)?[a-z0-9._-]+)(?:@([^\s]+))?$/i.exec(source);
24
+ if (npm)
25
+ return { kind: "npm", name: npm[1], ref: npm[2], pinned: /^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(npm[2] ?? ""), unpinned: `npm:${npm[1]}` };
26
+ if (/^(git:|https?:\/\/|git@|ssh:\/\/)/.test(source)) {
27
+ const ref = /(?:\.git|\/[^/@]+)@(.+)$/.exec(source)?.[1] ?? /#([^#]+)$/.exec(source)?.[1];
28
+ return { kind: "git", name: source, ref, pinned: !!ref, unpinned: ref ? source.slice(0, -(ref.length + 1)) : source };
29
+ }
30
+ return { kind: "local", name: source.split(/[\\/]/).at(-1) || source, pinned: false, unpinned: source };
31
+ }
32
+ export function validateSource(value, cwd) {
33
+ if (typeof value !== "string" || !value.trim() || value.length > 2048 || /[\x00-\x1f]/.test(value))
34
+ throw Error("Invalid package source");
35
+ const source = value.trim();
36
+ if (source.startsWith("-"))
37
+ throw Error("Invalid package source");
38
+ if (source.startsWith("npm:")) {
39
+ if (sourceInfo(source).kind !== "npm")
40
+ throw Error("Invalid npm source");
41
+ return source;
42
+ }
43
+ if (/^(git:|https?:\/\/|git@|ssh:\/\/)/.test(source))
44
+ return source;
45
+ return resolve(cwd, source.startsWith("~/") ? join(homedir(), source.slice(2)) : source);
46
+ }