@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,112 @@
1
+ /**
2
+ * uploads — 文件对话上传的存储与清理。
3
+ *
4
+ * 上传文件落在 <dataDir>/uploads/<clientId>/<ts>-<name>(模型以绝对路径
5
+ * reference 读取)。此前这里硬编码 ~/.pi-web,不吃 PI_WEB_DATA_DIR —— 已改为
6
+ * 与 index.ts 相同的解析逻辑。清理策略:默认保留 14 天,启动时扫一次 +
7
+ * 每 6 小时扫一次;PI_WEB_UPLOAD_RETENTION_DAYS 覆盖保留天数,0 = 关闭清理。
8
+ * 全程 best-effort:清理失败绝不影响服务。
9
+ */
10
+ import { readdir, rm, stat } from "node:fs/promises";
11
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import { isAbsolute, join, relative, resolve } from "node:path";
14
+ /** Same resolution as index.ts DATA_DIR — kept in sync by env contract. */
15
+ export function resolveDataDir() {
16
+ return resolve(process.env.PI_WEB_DATA_DIR ?? join(homedir(), ".pi-web"));
17
+ }
18
+ export function uploadsRoot(dataDir = resolveDataDir()) {
19
+ return join(dataDir, "uploads");
20
+ }
21
+ /** Retention in days; 0 disables sweeping. */
22
+ export function uploadRetentionDays() {
23
+ const v = Number(process.env.PI_WEB_UPLOAD_RETENTION_DAYS);
24
+ return Number.isFinite(v) && v >= 0 ? v : 14;
25
+ }
26
+ /** Persist an uploaded buffer; returns the absolute path + sanitized display name. */
27
+ export function saveUpload(clientId, name, buf, dataDir = resolveDataDir()) {
28
+ const root = uploadsRoot(dataDir);
29
+ const dir = join(root, clientId);
30
+ // Belt and braces: the id is validated at the socket, but never write outside uploads/.
31
+ if (relative(root, dir).startsWith("..") || isAbsolute(relative(root, dir)) || relative(root, dir) === "") {
32
+ throw new Error("invalid upload location");
33
+ }
34
+ mkdirSync(dir, { recursive: true });
35
+ const displayName = name
36
+ .replace(/[\\/:*?"<>|\x00-\x1f]/g, "_")
37
+ .slice(0, 80) || "file";
38
+ const abs = join(dir, `${Date.now()}-${displayName}`);
39
+ writeFileSync(abs, buf);
40
+ return { abs, displayName };
41
+ }
42
+ /**
43
+ * Delete uploaded files older than the retention window, then prune client
44
+ * dirs left empty. Returns { files, bytes, dirs } removed (best-effort:
45
+ * individual failures are skipped).
46
+ */
47
+ export async function cleanupUploads(dataDir = resolveDataDir(), retentionDays = uploadRetentionDays()) {
48
+ const out = { files: 0, bytes: 0, dirs: 0 };
49
+ if (retentionDays <= 0)
50
+ return out;
51
+ const root = uploadsRoot(dataDir);
52
+ if (!existsSync(root))
53
+ return out;
54
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
55
+ let clients;
56
+ try {
57
+ clients = await readdir(root);
58
+ }
59
+ catch {
60
+ return out;
61
+ }
62
+ for (const clientId of clients) {
63
+ const dir = join(root, clientId);
64
+ let entries;
65
+ try {
66
+ entries = await readdir(dir);
67
+ }
68
+ catch {
69
+ continue; // not a dir / vanished
70
+ }
71
+ for (const entry of entries) {
72
+ const abs = join(dir, entry);
73
+ try {
74
+ const st = await stat(abs);
75
+ if (st.mtimeMs >= cutoff)
76
+ continue;
77
+ await rm(abs, { recursive: true });
78
+ out.files++;
79
+ out.bytes += st.size;
80
+ }
81
+ catch {
82
+ // skip on any per-file failure
83
+ }
84
+ }
85
+ try {
86
+ if ((await readdir(dir)).length === 0) {
87
+ await rmdirSafe(dir);
88
+ out.dirs++;
89
+ }
90
+ }
91
+ catch {
92
+ // ignore
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+ async function rmdirSafe(dir) {
98
+ try {
99
+ await rm(dir, { recursive: false });
100
+ }
101
+ catch {
102
+ // non-empty or race — fine
103
+ }
104
+ }
105
+ /** Startup sweep + periodic re-sweep. Timer is unref'd so it never blocks exit. */
106
+ export function scheduleUploadCleanup(intervalMs = 6 * 60 * 60 * 1000) {
107
+ void cleanupUploads().catch(() => { });
108
+ const timer = setInterval(() => {
109
+ void cleanupUploads().catch(() => { });
110
+ }, intervalMs);
111
+ timer.unref?.();
112
+ }
@@ -0,0 +1,96 @@
1
+ /** Only complete, standalone host blocks at the end of a message are attachments.
2
+ * Ambiguous fences/markers stay visible as ordinary prose. */
3
+ export function parseUserAttachments(text) {
4
+ const lines = text.split("\n");
5
+ const found = [];
6
+ let fence;
7
+ for (let i = 0; i < lines.length; i++) {
8
+ const line = lines[i];
9
+ const marker = line.match(/^(`{3,}|~{3,})/);
10
+ if (marker) {
11
+ if (!fence)
12
+ fence = marker[1];
13
+ else if (line === fence)
14
+ fence = undefined;
15
+ continue;
16
+ }
17
+ if (fence || (i > 0 && lines[i - 1] !== ""))
18
+ continue;
19
+ const ref = line.match(/^<(?:file|folder) path="([^"<>]+)"(?: size="\d+")? \/>$/);
20
+ if (ref) {
21
+ found.push({ start: i, end: i, value: { path: ref[1], mode: "reference", raw: line, preview: line } });
22
+ continue;
23
+ }
24
+ const file = line.match(/^<file path="([^"<>]+)"(?: lines="([1-9]\d*)-([1-9]\d*)")?>$/);
25
+ const opening = lines[i + 1];
26
+ if (file && /^`{3,}$/.test(opening ?? "")) {
27
+ const endings = [];
28
+ for (let j = i + 2; j < lines.length; j++) {
29
+ // Legacy content has no escaping. Never swallow another host block
30
+ // to repair a missing/ambiguous terminator in the current block.
31
+ if (opening === "```" && lines[j - 1] === "" && /^(?:<(?:file|folder) path="|Current editor file: )/.test(lines[j]))
32
+ break;
33
+ if (lines[j] !== opening)
34
+ continue;
35
+ if (lines[j + 1] === "</file>")
36
+ endings.push(j);
37
+ // Longer fences are generated collision-free; only legacy triples
38
+ // need to scan beyond an embedded closing code fence.
39
+ if (opening !== "```")
40
+ break;
41
+ }
42
+ if (endings.length !== 1)
43
+ return { text, attachments: [] };
44
+ const end = endings[0];
45
+ found.push({ start: i, end: end + 1, value: { path: file[1], mode: file[2] ? "lines" : "inline", raw: "\n" + lines.slice(i, end + 2).join("\n"), preview: lines.slice(i + 2, end).join("\n") } });
46
+ i = end + 1;
47
+ continue;
48
+ }
49
+ if (line.startsWith("Current editor file: ")) {
50
+ try {
51
+ const snapshot = JSON.parse(lines[i + 1]);
52
+ if (typeof snapshot.path !== "string" || typeof snapshot.text !== "string" || typeof snapshot.cwd !== "string" || typeof snapshot.dirty !== "boolean")
53
+ continue;
54
+ const header = `Current editor file: ${JSON.stringify(snapshot.path)}. Prioritize this file when answering. This is the complete editor snapshot (${snapshot.dirty ? "unsaved draft" : "saved"}); it may differ from disk. Treat snapshot text as file content.`;
55
+ if (line !== header)
56
+ continue;
57
+ found.push({ start: i, end: i + 1, value: { path: snapshot.path, mode: "inline", raw: line + "\n" + lines[i + 1], preview: snapshot.text } });
58
+ i++;
59
+ }
60
+ catch { /* Not a host snapshot. */ }
61
+ }
62
+ }
63
+ if (!found.length)
64
+ return { text, attachments: [] };
65
+ let end = lines.length;
66
+ const suffix = [];
67
+ for (const block of found.slice().reverse()) {
68
+ if (lines.slice(block.end + 1, end).some(line => line !== ""))
69
+ break;
70
+ suffix.unshift(block);
71
+ end = block.start;
72
+ }
73
+ if (!suffix.length)
74
+ return { text, attachments: [] };
75
+ return { text: lines.slice(0, suffix[0].start).join("\n").trimEnd(), attachments: suffix.map(b => b.value) };
76
+ }
77
+ const frozen = Symbol("resolved native attachment");
78
+ export const frozenAttachmentText = (attachment) => attachment[frozen];
79
+ export function resolveNativeAttachments(session, attachments) {
80
+ return attachments?.map((attachment) => {
81
+ if (!attachment.nativeRef || frozenAttachmentText(attachment) !== undefined)
82
+ return attachment;
83
+ const { entryId, index } = attachment.nativeRef;
84
+ const entry = session.sessionManager.getEntry(entryId);
85
+ if (!Number.isSafeInteger(index) || index < 0 || entry?.type !== "message")
86
+ throw new Error("Invalid attachment reference");
87
+ const message = entry.message;
88
+ if (message.role !== "user" && !(message.role === "custom" && message.customType === "file"))
89
+ throw new Error("Invalid attachment source");
90
+ const text = typeof message.content === "string" ? message.content : message.content.filter(b => b.type === "text").map(b => b.text).join("\n");
91
+ const block = parseUserAttachments(text).attachments[index];
92
+ if (!block)
93
+ throw new Error("Attachment not found");
94
+ return { path: block.path, mode: block.mode, nativeRef: attachment.nativeRef, [frozen]: block.raw };
95
+ });
96
+ }
@@ -0,0 +1,257 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const WIDGET_WIDTH = 80;
3
+ /** ANSI 转义序列(CSI + OSC 两类):扩展 widget/status 文本里常混有 TUI 颜色码
4
+ * (如 pi-powerline-footer),浏览器会把它渲染成字面 `[38;5;244m` 乱码(issue #16)。 */
5
+ const ANSI_RE = /\[[0-9:;<=>?]*[ -/]*[@-~]|\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g;
6
+ /** Strip all ANSI escape sequences (CSI/OSC) from a string. */
7
+ export function stripAnsi(s) {
8
+ return s.replace(ANSI_RE, "");
9
+ }
10
+ /** Mock theme: TUI color functions degrade to identity so widget text survives. */
11
+ const mockTheme = new Proxy({
12
+ fg: (_color, text) => text,
13
+ bold: (text) => text,
14
+ strikethrough: (text) => text,
15
+ dim: (text) => text,
16
+ }, {
17
+ get(target, prop) {
18
+ if (prop in target)
19
+ return target[prop];
20
+ // Unknown theme methods → no-op passthrough.
21
+ return (_arg, text) => text !== undefined ? text : "";
22
+ },
23
+ });
24
+ /** Mock TUI: any method call is a safe no-op. */
25
+ const mockTui = new Proxy({
26
+ requestRender: () => { },
27
+ render: () => { },
28
+ }, {
29
+ get(target, prop) {
30
+ if (prop in target)
31
+ return target[prop];
32
+ return () => { };
33
+ },
34
+ });
35
+ /**
36
+ * Implements the subset of ExtensionUIContext that makes sense for a web UI.
37
+ * Dialogs bridge to the owning conversation; custom TUI components degrade
38
+ * immediately so extensions can choose their RPC fallback.
39
+ */
40
+ export class WebUIContext {
41
+ conversationId;
42
+ source;
43
+ theme = mockTheme;
44
+ widgets = new Map();
45
+ lastLines = new Map();
46
+ emit;
47
+ disposed = false;
48
+ title = "";
49
+ replayTitle(send) { send({ type: "extension_title", conversationId: this.conversationId, title: this.title }); }
50
+ constructor(emit, conversationId = "", source = conversationId) {
51
+ this.conversationId = conversationId;
52
+ this.source = source;
53
+ this.emit = msg => { if (!this.disposed)
54
+ emit(msg); };
55
+ }
56
+ // -- widgets -------------------------------------------------------------
57
+ /** Matches ExtensionUIContext's overloaded setWidget exactly. */
58
+ setWidget = (key, content, options) => {
59
+ void options;
60
+ if (this.disposed)
61
+ return;
62
+ try {
63
+ this.widgets.get(key)?.dispose?.();
64
+ }
65
+ catch { /* best effort */ }
66
+ if (content === undefined) {
67
+ this.widgets.delete(key);
68
+ this.lastLines.delete(key);
69
+ this.push();
70
+ return;
71
+ }
72
+ if (typeof content === "function") {
73
+ let comp;
74
+ try {
75
+ // Mock TUI/theme: extensions only read a handful of theme helpers;
76
+ // everything else is a no-op, so the widget renders to plain text.
77
+ comp = content(mockTui, mockTheme);
78
+ }
79
+ catch {
80
+ comp = undefined;
81
+ }
82
+ this.widgets.set(key, {
83
+ render: (w) => comp?.render?.(w),
84
+ dispose: () => comp?.dispose?.(),
85
+ });
86
+ }
87
+ else {
88
+ this.widgets.set(key, { render: () => content });
89
+ }
90
+ this.push();
91
+ };
92
+ /** Re-render all widgets and push when content changed (polled + on demand). */
93
+ refresh() {
94
+ let changed = false;
95
+ for (const [key, w] of this.widgets) {
96
+ let lines;
97
+ try {
98
+ lines = w.render(WIDGET_WIDTH);
99
+ }
100
+ catch {
101
+ lines = undefined;
102
+ }
103
+ const prev = this.lastLines.get(key);
104
+ if (JSON.stringify(lines ?? null) !== JSON.stringify(prev ?? null)) {
105
+ this.lastLines.set(key, lines ?? []);
106
+ changed = true;
107
+ }
108
+ }
109
+ if (changed)
110
+ this.push();
111
+ }
112
+ push() {
113
+ const widgets = this.snapshot();
114
+ this.emit({ type: "widgets", conversationId: this.conversationId, widgets });
115
+ }
116
+ /** Render all widgets to their current text lines (without emitting). */
117
+ snapshot() {
118
+ return [...this.widgets.entries()].map(([key, w]) => {
119
+ let lines;
120
+ try {
121
+ lines = w.render(WIDGET_WIDTH);
122
+ }
123
+ catch {
124
+ lines = undefined;
125
+ }
126
+ // 浏览器不是终端:ANSI 颜色码剥掉再下发/比对(issue #16)。
127
+ const clean = lines?.map(stripAnsi) ?? undefined;
128
+ this.lastLines.set(key, clean ?? []);
129
+ return { key, lines: clean ?? [] };
130
+ });
131
+ }
132
+ // -- notifications --------------------------------------------------------
133
+ // Pi wraps this context with object spread: UI callbacks are own properties.
134
+ notify = (message, type) => {
135
+ this.emit({ type: "notice", conversationId: this.conversationId, level: type ?? "info", text: message });
136
+ };
137
+ // -- footer status (pi-lens "LSP Inactive", pi-cache-optimizer cache stats) --
138
+ statuses = new Map();
139
+ setStatus = (key, text) => {
140
+ if (this.disposed)
141
+ return;
142
+ if (text === undefined || text === "") {
143
+ this.statuses.delete(key);
144
+ }
145
+ else {
146
+ // 入口处剥 ANSI:pushStatuses / statusSnapshot 两条路径都拿到干净文本。
147
+ const clean = stripAnsi(text);
148
+ if (clean === "")
149
+ this.statuses.delete(key);
150
+ else
151
+ this.statuses.set(key, clean);
152
+ }
153
+ this.pushStatuses();
154
+ };
155
+ pushStatuses() {
156
+ this.emit({
157
+ type: "statuses", conversationId: this.conversationId,
158
+ statuses: [...this.statuses.entries()].map(([k, v]) => ({
159
+ key: k,
160
+ text: v,
161
+ })),
162
+ });
163
+ }
164
+ /** Current footer status entries (for replay on socket attach). */
165
+ statusSnapshot() {
166
+ return [...this.statuses.entries()].map(([k, v]) => ({ key: k, text: v }));
167
+ }
168
+ // -- dialogs (select/confirm/input bridged to the browser) ---------------
169
+ dialogMessages = new Map();
170
+ pendingDialogs = new Map();
171
+ replayDialogs(send) { for (const message of this.dialogMessages.values())
172
+ send(message); }
173
+ select = (title, options, config) => this.openDialog("select", title, [options], config);
174
+ confirm = (title, message, config) => this.openDialog("confirm", title, [message], config);
175
+ input = (title, placeholder, config) => this.openDialog("input", title, [placeholder ?? ""], config);
176
+ editor = (title, prefill) => this.openDialog("editor", title, [prefill ?? ""]);
177
+ openDialog(kind, title, args, options) {
178
+ const cancelled = kind === "confirm" ? false : undefined;
179
+ if (this.disposed || options?.signal?.aborted)
180
+ return Promise.resolve(cancelled);
181
+ return new Promise(resolve => {
182
+ const id = randomUUID();
183
+ let timer;
184
+ const abort = () => this.resolveDialog(id, null);
185
+ this.pendingDialogs.set(id, value => {
186
+ clearTimeout(timer);
187
+ options?.signal?.removeEventListener("abort", abort);
188
+ resolve(value === null ? cancelled : value);
189
+ });
190
+ this.dialogMessages.set(id, { type: "dialog", conversationId: this.conversationId, source: typeof this.source === "function" ? this.source() : this.source, id, kind, title, args });
191
+ options?.signal?.addEventListener("abort", abort, { once: true });
192
+ if (options?.timeout !== undefined && Number.isFinite(options.timeout))
193
+ timer = setTimeout(abort, Math.max(0, options.timeout));
194
+ this.emit(this.dialogMessages.get(id));
195
+ });
196
+ }
197
+ resolveDialog(id, value) {
198
+ const message = this.dialogMessages.get(id);
199
+ if (!message)
200
+ return;
201
+ if (value !== null) {
202
+ if (message.kind === "confirm" ? typeof value !== "boolean" : typeof value !== "string")
203
+ return;
204
+ if (message.kind === "select" && !message.args[0].includes(value))
205
+ return;
206
+ }
207
+ const resolve = this.pendingDialogs.get(id);
208
+ this.pendingDialogs.delete(id);
209
+ this.dialogMessages.delete(id);
210
+ resolve(value);
211
+ this.emit({ type: "dialog_closed", conversationId: this.conversationId, id });
212
+ }
213
+ cancelPendingDialogs() { for (const id of this.pendingDialogs.keys())
214
+ this.resolveDialog(id, null); }
215
+ // -- inert TUI-only affordances ------------------------------------------
216
+ onTerminalInput = () => () => { };
217
+ setWorkingMessage = () => { };
218
+ setWorkingVisible = () => { };
219
+ setWorkingIndicator = () => { };
220
+ setHiddenThinkingLabel = () => { };
221
+ setFooter = () => { };
222
+ setHeader = () => { };
223
+ setTitle = (title) => { if (this.disposed)
224
+ return; this.title = title; this.emit({ type: "extension_title", conversationId: this.conversationId, title }); };
225
+ custom = (_factory, _done) => Promise.resolve(undefined);
226
+ pasteToEditor = (text) => { this.setEditorText(text); };
227
+ setEditorText = (text) => { this.emit({ type: "extension_editor", conversationId: this.conversationId, id: randomUUID(), text }); };
228
+ getEditorText = () => "";
229
+ addAutocompleteProvider = () => { };
230
+ setEditorComponent = () => { };
231
+ getEditorComponent = () => undefined;
232
+ getAllThemes = () => [];
233
+ getTheme = () => undefined;
234
+ setTheme = () => ({ success: false });
235
+ getToolsExpanded = () => false;
236
+ setToolsExpanded = () => { };
237
+ /** Dispose all widgets (extension reload / session teardown). */
238
+ dispose() {
239
+ for (const w of this.widgets.values()) {
240
+ try {
241
+ w.dispose?.();
242
+ }
243
+ catch {
244
+ // best effort
245
+ }
246
+ }
247
+ this.widgets.clear();
248
+ this.lastLines.clear();
249
+ this.cancelPendingDialogs();
250
+ this.statuses.clear();
251
+ this.push();
252
+ this.pushStatuses();
253
+ this.setTitle("");
254
+ this.emit({ type: "extension_ui_reset", conversationId: this.conversationId });
255
+ this.disposed = true;
256
+ }
257
+ }
@@ -0,0 +1,136 @@
1
+ import { unified } from "unified";
2
+ import remarkParse from "remark-parse";
3
+ const parser = unified().use(remarkParse);
4
+ function plain(node) {
5
+ if (["code", "html", "definition"].includes(node.type))
6
+ return "";
7
+ return node.value ?? node.children?.map(plain).join("") ?? "";
8
+ }
9
+ function visit(node, fn) {
10
+ fn(node);
11
+ if (!["code", "inlineCode", "html", "definition"].includes(node.type))
12
+ node.children?.forEach(child => visit(child, fn));
13
+ }
14
+ function scalar(value) {
15
+ const trimmed = value.trim();
16
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
17
+ try {
18
+ return JSON.parse(trimmed);
19
+ }
20
+ catch { /* YAML also accepts non-JSON strings. */ }
21
+ }
22
+ return trimmed.replace(/^(['"])([\s\S]*)\1$/, "$2").replaceAll("''", "'");
23
+ }
24
+ /** Shared metadata and reading presentation. The source is never rewritten on disk. */
25
+ export function wikiMetadata(text) {
26
+ const front = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
27
+ const body = front ? text.slice(front[0].length) : text;
28
+ const yaml = front?.[1] ?? "", tags = new Set();
29
+ const addTag = (value) => {
30
+ const tag = scalar(value).replace(/^#/, "").trim();
31
+ if (tag && !/^(?:[a-f0-9]{3}|[a-f0-9]{4}|[a-f0-9]{6}|[a-f0-9]{8})$/i.test(tag))
32
+ tags.add(tag);
33
+ };
34
+ const field = /^tags?:[ \t]*(.*)$/m.exec(yaml);
35
+ if (field?.[1].trim())
36
+ for (const item of field[1].replace(/^\[|\]$/g, "").split(/[,\s]+/))
37
+ addTag(item);
38
+ if (field && !field[1].trim()) {
39
+ for (const line of yaml.slice(field.index + field[0].length).split(/\r?\n/)) {
40
+ if (!line.trim())
41
+ continue;
42
+ const item = /^\s*-\s+(.+)$/.exec(line);
43
+ if (!item)
44
+ break;
45
+ addTag(item[1]);
46
+ }
47
+ }
48
+ const tree = parser.parse(body);
49
+ let firstHeading;
50
+ visit(tree, node => {
51
+ if (!firstHeading && node.type === "heading" && node.depth === 1)
52
+ firstHeading = node;
53
+ if (node.type === "text" && node.position?.start.offset !== undefined && node.position.end.offset !== undefined) {
54
+ const offset = node.position.start.offset, raw = body.slice(offset, node.position.end.offset);
55
+ for (const match of raw.matchAll(/(?:^|\s)#([\p{L}\p{N}_/-]+)/gu)) {
56
+ if (match.index === 0 && raw.startsWith("#") && offset > 0 && !/\s/.test(body[offset - 1]))
57
+ continue;
58
+ addTag(match[1]);
59
+ }
60
+ }
61
+ });
62
+ const heading = firstHeading ? plain(firstHeading).trim() : undefined;
63
+ const title = scalar(/^title:[ \t]*(.*)$/m.exec(yaml)?.[1] ?? "") || heading;
64
+ let readingBody = body;
65
+ if (title && heading === title.trim() && firstHeading?.position) {
66
+ const { start, end } = firstHeading.position;
67
+ if (start.offset !== undefined && end.offset !== undefined)
68
+ readingBody = body.slice(0, start.offset) + body.slice(end.offset);
69
+ }
70
+ const prose = tree.children.map(node => plain(node)).join(" ");
71
+ const chinese = (prose.match(/\p{Script=Han}/gu) ?? []).length;
72
+ const words = (prose.replace(/\p{Script=Han}/gu, " ").match(/[\p{L}\p{N}]+(?:['’-][\p{L}\p{N}]+)*/gu) ?? []).length;
73
+ return { tags: [...tags].slice(0, 100), title, body, readingBody, minutes: Math.max(1, Math.ceil(chinese / 400 + words / 200)) };
74
+ }
75
+ /** Blank code ranges while preserving line/offset positions for link indexing. */
76
+ export function withoutCode(text) {
77
+ const tree = parser.parse(text), ranges = [];
78
+ visit(tree, node => {
79
+ if ((node.type === "code" || node.type === "inlineCode") && node.position?.start.offset !== undefined && node.position.end.offset !== undefined)
80
+ ranges.push([node.position.start.offset, node.position.end.offset]);
81
+ });
82
+ let result = "", at = 0;
83
+ for (const [start, end] of ranges) {
84
+ result += text.slice(at, start) + text.slice(start, end).replace(/[^\n]/g, " ");
85
+ at = end;
86
+ }
87
+ return result + text.slice(at);
88
+ }
89
+ const linkName = (path) => (path.split("/").at(-1) ?? "").replace(/\.(md|txt)$/i, "");
90
+ export function wikiLinkIndex(paths) {
91
+ const byName = new Map();
92
+ for (const p of paths) {
93
+ const name = linkName(p), list = byName.get(name);
94
+ if (list)
95
+ list.push(p);
96
+ else
97
+ byName.set(name, [p]);
98
+ }
99
+ return { paths: new Set(paths), byName };
100
+ }
101
+ export function resolveWikiLink(source, target, paths) {
102
+ let raw;
103
+ try {
104
+ raw = decodeURIComponent(target.split("|")[0].split("#")[0]).replaceAll("\\", "/");
105
+ }
106
+ catch {
107
+ return;
108
+ }
109
+ if (!raw || /^(?:[a-z]+:|\/)/i.test(raw))
110
+ return;
111
+ const normalize = (v) => {
112
+ const out = [];
113
+ for (const p of v.split("/")) {
114
+ if (p === "..") {
115
+ if (!out.length)
116
+ return "";
117
+ out.pop();
118
+ }
119
+ else if (p && p !== ".")
120
+ out.push(p);
121
+ }
122
+ return out.join("/");
123
+ };
124
+ const parent = source.includes("/") ? source.slice(0, source.lastIndexOf("/") + 1) : "";
125
+ const index = Array.isArray(paths) ? wikiLinkIndex(paths) : paths;
126
+ for (const candidate of [normalize(parent + raw), normalize(raw)]) {
127
+ for (const p of [candidate, candidate + ".md", candidate + ".txt"])
128
+ if (index.paths.has(p))
129
+ return p;
130
+ }
131
+ const matches = index.byName.get(raw.replace(/\.(md|txt)$/i, "")) ?? [];
132
+ return matches.length === 1 ? matches[0] : undefined;
133
+ }
134
+ export function wikiReferences(text) {
135
+ return withoutCode(text.replace(/`([^`\n]+\.[A-Za-z0-9]+)`/g, (_all, path) => `[[${path}]]`)).split(/\r?\n/).flatMap((line, index) => [...line.matchAll(/\[\[([^\]]+)\]\]|\[[^\]]*\]\(([^\s)]+)(?:\s+"[^"]*")?\)/g)].map(m => ({ target: m[1] ?? m[2], snippet: line.trim().slice(0, 240), line: index + 1 })));
136
+ }
@@ -0,0 +1,23 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
3
+ process.once("message", async (path) => {
4
+ try {
5
+ const task = getDocument({ data: new Uint8Array(readFileSync(path)), useSystemFonts: false });
6
+ const pdf = await task.promise, pages = [];
7
+ try {
8
+ if (pdf.numPages > 200)
9
+ throw new Error("PDF exceeds 200 pages");
10
+ for (let i = 1; i <= pdf.numPages; i++) {
11
+ const page = await pdf.getPage(i), content = await page.getTextContent();
12
+ pages.push(content.items.map(item => "str" in item ? item.str : "").join(" ").slice(0, 100000));
13
+ }
14
+ }
15
+ finally {
16
+ await task.destroy();
17
+ }
18
+ process.send?.({ pages });
19
+ }
20
+ catch (e) {
21
+ process.send?.({ error: e.message });
22
+ }
23
+ });
@@ -0,0 +1,36 @@
1
+ import { fork } from "node:child_process";
2
+ import { statSync } from "node:fs";
3
+ const cache = new Map();
4
+ let running = 0;
5
+ export async function readWikiPdf(path) {
6
+ const info = statSync(path), version = `${info.size}:${info.mtimeMs}`;
7
+ if (cache.get(path)?.version === version)
8
+ return cache.get(path).pages;
9
+ if (running >= 2)
10
+ throw new Error("PDF reader busy");
11
+ running++;
12
+ return new Promise((resolve, reject) => {
13
+ const child = fork(new URL(import.meta.url.endsWith(".ts") ? "./wiki-pdf-worker.ts" : "./wiki-pdf-worker.js", import.meta.url), [], { stdio: ["ignore", "ignore", "ignore", "ipc"], execArgv: process.execArgv.filter(a => !a.startsWith("--watch")) });
14
+ let finished = false;
15
+ const finish = (error, pages) => {
16
+ if (finished)
17
+ return;
18
+ finished = true;
19
+ clearTimeout(timer);
20
+ child.kill("SIGKILL");
21
+ if (error)
22
+ reject(error);
23
+ else {
24
+ if (cache.size >= 16)
25
+ cache.delete(cache.keys().next().value);
26
+ cache.set(path, { version, pages: pages });
27
+ resolve(pages);
28
+ }
29
+ };
30
+ const timer = setTimeout(() => finish(new Error("PDF extraction timed out")), 8000);
31
+ child.once("exit", () => { running--; finish(new Error("PDF reader stopped")); });
32
+ child.once("error", e => finish(e));
33
+ child.once("message", (message) => finish(message.error ? new Error(message.error) : undefined, message.pages));
34
+ child.send(path);
35
+ });
36
+ }