@youweichen/pi-harness 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -2
  3. package/bin/pi-harness.mjs +1819 -0
  4. package/deploy/com.youweichen.pi-harness.plist +48 -0
  5. package/deploy/nginx-subpath.conf +88 -0
  6. package/deploy/pi-harness-task.xml +71 -0
  7. package/deploy/pi-harness.service +31 -0
  8. package/dist/server/agent-service.js +3051 -0
  9. package/dist/server/app-version.js +19 -0
  10. package/dist/server/attachments.js +526 -0
  11. package/dist/server/auth-file.js +61 -0
  12. package/dist/server/bg-servers.js +154 -0
  13. package/dist/server/client-eviction.js +19 -0
  14. package/dist/server/client-id.js +9 -0
  15. package/dist/server/client-state.js +202 -0
  16. package/dist/server/codemode-image-routes.js +36 -0
  17. package/dist/server/codemode-presentation.js +19 -0
  18. package/dist/server/component-updates.js +135 -0
  19. package/dist/server/context-breakdown.js +56 -0
  20. package/dist/server/control-socket.js +177 -0
  21. package/dist/server/conversation.js +65 -0
  22. package/dist/server/dir-browser.js +77 -0
  23. package/dist/server/editor-snapshot.js +38 -0
  24. package/dist/server/ensure-bash.js +80 -0
  25. package/dist/server/extension-display.js +23 -0
  26. package/dist/server/extensions-catalog.js +64 -0
  27. package/dist/server/extensions-model.js +46 -0
  28. package/dist/server/extensions-routes.js +255 -0
  29. package/dist/server/extensions-worker.js +187 -0
  30. package/dist/server/files-service.js +795 -0
  31. package/dist/server/index.js +1029 -0
  32. package/dist/server/markdown-images.js +42 -0
  33. package/dist/server/model-admin.js +801 -0
  34. package/dist/server/model-config-merge.js +32 -0
  35. package/dist/server/native-mcp-config.js +103 -0
  36. package/dist/server/native-mcp-presentation.js +12 -0
  37. package/dist/server/native-prompt-template.js +12 -0
  38. package/dist/server/native-tools.js +32 -0
  39. package/dist/server/node-sources.js +142 -0
  40. package/dist/server/node-workbench.js +668 -0
  41. package/dist/server/origin-policy.js +50 -0
  42. package/dist/server/patch-node-pty.js +96 -0
  43. package/dist/server/pi-environment.js +203 -0
  44. package/dist/server/plan/extension.js +74 -0
  45. package/dist/server/plan/progress.js +90 -0
  46. package/dist/server/plan/settings.js +41 -0
  47. package/dist/server/plan/state.js +113 -0
  48. package/dist/server/plugin-facilities.js +304 -0
  49. package/dist/server/plugin-updater.js +245 -0
  50. package/dist/server/plugins.js +712 -0
  51. package/dist/server/private-file.js +28 -0
  52. package/dist/server/process-utils.js +112 -0
  53. package/dist/server/prompt-delivery.js +80 -0
  54. package/dist/server/protocol-version.js +11 -0
  55. package/dist/server/protocol.js +6 -0
  56. package/dist/server/provider-auth.js +99 -0
  57. package/dist/server/query-cache.js +45 -0
  58. package/dist/server/recovery-state.js +37 -0
  59. package/dist/server/scm.js +459 -0
  60. package/dist/server/security-headers.js +24 -0
  61. package/dist/server/serialize.js +254 -0
  62. package/dist/server/session-file-read.js +225 -0
  63. package/dist/server/session-record-worker.js +21 -0
  64. package/dist/server/session-reload.js +13 -0
  65. package/dist/server/session-tree-controller.js +250 -0
  66. package/dist/server/session-tree.js +191 -0
  67. package/dist/server/settings-service.js +53 -0
  68. package/dist/server/skills-service.js +87 -0
  69. package/dist/server/slash-commands.js +280 -0
  70. package/dist/server/sqlite-preview.js +58 -0
  71. package/dist/server/sqlite-query.js +67 -0
  72. package/dist/server/sqlite-worker.js +12 -0
  73. package/dist/server/system-prompt-files.js +158 -0
  74. package/dist/server/system-prompt-routes.js +40 -0
  75. package/dist/server/system-prompt-view.js +49 -0
  76. package/dist/server/task-progress.js +233 -0
  77. package/dist/server/terminals.js +833 -0
  78. package/dist/server/text-sniff.js +269 -0
  79. package/dist/server/thinking-timing.js +91 -0
  80. package/dist/server/token-auth.js +37 -0
  81. package/dist/server/tool-output.js +97 -0
  82. package/dist/server/tool-text-incidents.js +82 -0
  83. package/dist/server/tool-text.js +67 -0
  84. package/dist/server/uploads.js +112 -0
  85. package/dist/server/user-attachments.js +96 -0
  86. package/dist/server/webui-context.js +257 -0
  87. package/dist/server/wiki-links.js +136 -0
  88. package/dist/server/wiki-pdf-worker.js +23 -0
  89. package/dist/server/wiki-pdf.js +36 -0
  90. package/dist/server/wiki-routes.js +177 -0
  91. package/dist/server/wiki-service.js +511 -0
  92. package/electron/main.mjs +607 -0
  93. package/extensions/webui.ts +192 -0
  94. package/package.json +122 -4
  95. package/web/dist/apple-touch-icon.png +0 -0
  96. package/web/dist/assets/NodeWorkbench-C4U1PldE.js +3 -0
  97. package/web/dist/assets/TerminalPanel-CWh6vw_N.js +2 -0
  98. package/web/dist/assets/abnfDiagram-VCTEODGH-D5Ti0Kpa.js +1 -0
  99. package/web/dist/assets/arc-DKJilLd6.js +1 -0
  100. package/web/dist/assets/architectureDiagram-5GKGNRK7-Cf0w38g-.js +36 -0
  101. package/web/dist/assets/blockDiagram-I7D4REHJ-BOTLWLIL.js +129 -0
  102. package/web/dist/assets/c4Diagram-7LVT6UL2-CLCCoJs3.js +38 -0
  103. package/web/dist/assets/channel-D8Hd7T4P.js +1 -0
  104. package/web/dist/assets/chunk-2Q5K7J3B-BHjga2Ge.js +1 -0
  105. package/web/dist/assets/chunk-5VM5RSS4-CQJ5VfDZ.js +15 -0
  106. package/web/dist/assets/chunk-F27PBJKO-C67qoPJH.js +1 -0
  107. package/web/dist/assets/chunk-IMKFNOWR-CgXXGwI8.js +231 -0
  108. package/web/dist/assets/chunk-JWPE2WC7-D0RPyByP.js +1 -0
  109. package/web/dist/assets/chunk-POPQ4Y6H-BeYC9s9x.js +1 -0
  110. package/web/dist/assets/chunk-SVP7TREG-eQPl4UBQ.js +88 -0
  111. package/web/dist/assets/chunk-TICWLB2K-DT73arPU.js +206 -0
  112. package/web/dist/assets/chunk-XXDRQBXY-B4FwQcP-.js +1 -0
  113. package/web/dist/assets/classDiagram-ZZMXUADV-B_btgYRB.js +1 -0
  114. package/web/dist/assets/classDiagram-v2-VYDZK3BY-B_btgYRB.js +1 -0
  115. package/web/dist/assets/cose-bilkent-JH36ORCC-Bnn5nlQN.js +1 -0
  116. package/web/dist/assets/cynefin-OW5HDTMX-CStWX9kE.js +178 -0
  117. package/web/dist/assets/cynefinDiagram-5FMLGOSQ-BMVHC03f.js +62 -0
  118. package/web/dist/assets/cytoscape.esm-Bch-eiPH.js +331 -0
  119. package/web/dist/assets/dagre-GXQ25YYZ-ClSaO4ml.js +4 -0
  120. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  121. package/web/dist/assets/diagram-S7CK7UJ4-C43LL08C.js +30 -0
  122. package/web/dist/assets/diagram-UQ7AKVKN-BPVn_ySW.js +41 -0
  123. package/web/dist/assets/diagram-VSXAHHWV-C34FFhRi.js +3 -0
  124. package/web/dist/assets/diagram-VX7I27RA-BRk4bncf.js +24 -0
  125. package/web/dist/assets/diagram-Z3DM3KII-O5hYPLEd.js +24 -0
  126. package/web/dist/assets/ebnfDiagram-PWID7BFC-DVGEBm99.js +1 -0
  127. package/web/dist/assets/erDiagram-RLTQ6QDP-M-BLpTEV.js +99 -0
  128. package/web/dist/assets/flowDiagram-HODETNUW-BwdLopEx.js +168 -0
  129. package/web/dist/assets/ganttDiagram-EL5Y4UJY-DYxx2ppA.js +292 -0
  130. package/web/dist/assets/gitGraphDiagram-WWUBYQGX-DUU0wMYB.js +106 -0
  131. package/web/dist/assets/index-CI2KfVSr.css +10 -0
  132. package/web/dist/assets/index-DQcpggFU.js +275 -0
  133. package/web/dist/assets/infoDiagram-27XIBGKW-CFaW5C0C.js +2 -0
  134. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  135. package/web/dist/assets/ishikawaDiagram-5VMMS53U--ENP1MTB.js +70 -0
  136. package/web/dist/assets/journeyDiagram-3NMN7TZE-DgnAzSTj.js +139 -0
  137. package/web/dist/assets/kanban-definition-UXKFOSKX-_STZN1vU.js +89 -0
  138. package/web/dist/assets/katex-C5jXJg4s.js +257 -0
  139. package/web/dist/assets/layout-CkUClkXt.js +1 -0
  140. package/web/dist/assets/linear-sYHiUOxa.js +1 -0
  141. package/web/dist/assets/markdown-vNTCZJ6d.js +51 -0
  142. package/web/dist/assets/mermaid.core-B8fKGWsc.js +312 -0
  143. package/web/dist/assets/mindmap-definition-YA3MSWOX-DP0BYOEx.js +96 -0
  144. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  145. package/web/dist/assets/pegDiagram-XKGWAZYB-DkWTmZre.js +1 -0
  146. package/web/dist/assets/pieDiagram-E7YTZNPT-DfZgMV4u.js +39 -0
  147. package/web/dist/assets/quadrantDiagram-AXDQQJYC-Djl4TT7q.js +7 -0
  148. package/web/dist/assets/railroadDiagram-O6MQD6OU-DbKF6zXn.js +1 -0
  149. package/web/dist/assets/react-BNj3n7jX.js +24 -0
  150. package/web/dist/assets/requirementDiagram-BXWQKSXE-idola5qb.js +84 -0
  151. package/web/dist/assets/sankeyDiagram-P5KCCOFB-KA0hQOle.js +40 -0
  152. package/web/dist/assets/sequenceDiagram-WJ2MYXX4-BXSKvxzP.js +162 -0
  153. package/web/dist/assets/sizeCapture-INFHLROL-C_Ewv3S5.js +1 -0
  154. package/web/dist/assets/stateDiagram-D77RDMKH-DIf5X8oo.js +1 -0
  155. package/web/dist/assets/stateDiagram-v2-MP3YSRHH-DYwe_2mB.js +1 -0
  156. package/web/dist/assets/swimlanes-42K2YHIH-Cx_Rc8M2.js +1 -0
  157. package/web/dist/assets/swimlanesDiagram-VR7AAH4N-52Y8tgRV.js +8 -0
  158. package/web/dist/assets/timeline-definition-24CTP7MA-BRrDhWVz.js +120 -0
  159. package/web/dist/assets/vennDiagram-4TSXK5OY-Ad6G0YwD.js +34 -0
  160. package/web/dist/assets/wardleyDiagram-VM6X3IG4-X6-ENpSi.js +78 -0
  161. package/web/dist/assets/xterm-6GBZ9nXN.css +32 -0
  162. package/web/dist/assets/xterm-D1D2FVe3.js +38 -0
  163. package/web/dist/assets/xychartDiagram-S5SC5T6Z-DhegNS6d.js +7 -0
  164. package/web/dist/brand-mark.svg +5 -0
  165. package/web/dist/favicon-32.png +0 -0
  166. package/web/dist/favicon.svg +5 -0
  167. package/web/dist/fonts/WIKI-FONTS.md +5 -0
  168. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  169. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  170. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  171. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  172. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  173. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  174. package/web/dist/fonts/ibm-plex-sans/LICENSE +93 -0
  175. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  176. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  177. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  178. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  179. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  180. package/web/dist/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  181. package/web/dist/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  182. package/web/dist/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  183. package/web/dist/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  184. package/web/dist/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  185. package/web/dist/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  186. package/web/dist/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  187. package/web/dist/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  188. package/web/dist/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  189. package/web/dist/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  190. package/web/dist/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  191. package/web/dist/fonts/jetbrains-mono/LICENSE +93 -0
  192. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  193. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
  194. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
  195. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  196. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  197. package/web/dist/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
  198. package/web/dist/fonts/noto-serif-sc/LICENSE +94 -0
  199. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
  200. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
  201. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
  202. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
  203. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
  204. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
  205. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
  206. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
  207. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
  208. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
  209. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
  210. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
  211. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
  212. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
  213. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
  214. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
  215. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
  216. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
  217. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
  218. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
  219. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
  220. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
  221. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
  222. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
  223. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
  224. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
  225. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
  226. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
  227. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
  228. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
  229. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
  230. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
  231. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
  232. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
  233. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
  234. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
  235. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
  236. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
  237. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
  238. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
  239. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
  240. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
  241. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
  242. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
  243. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
  244. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
  245. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
  246. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
  247. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
  248. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
  249. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
  250. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
  251. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
  252. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
  253. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
  254. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
  255. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
  256. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
  257. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
  258. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
  259. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
  260. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
  261. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
  262. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
  263. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
  264. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
  265. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
  266. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
  267. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
  268. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
  269. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
  270. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
  271. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
  272. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
  273. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
  274. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
  275. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
  276. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
  277. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
  278. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
  279. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
  280. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
  281. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
  282. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
  283. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
  284. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
  285. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
  286. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
  287. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
  288. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
  289. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
  290. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
  291. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
  292. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
  293. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
  294. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
  295. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
  296. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
  297. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
  298. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
  299. package/web/dist/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
  300. package/web/dist/fonts/source-serif-4/LICENSE +93 -0
  301. package/web/dist/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
  302. package/web/dist/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
  303. package/web/dist/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
  304. package/web/dist/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
  305. package/web/dist/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
  306. package/web/dist/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
  307. package/web/dist/icon/1a-mark.svg +1 -0
  308. package/web/dist/icon.ico +0 -0
  309. package/web/dist/index.html +18 -0
  310. package/web/public/apple-touch-icon.png +0 -0
  311. package/web/public/brand-mark.svg +5 -0
  312. package/web/public/favicon-32.png +0 -0
  313. package/web/public/favicon.svg +5 -0
  314. package/web/public/fonts/WIKI-FONTS.md +5 -0
  315. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  316. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  317. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  318. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  319. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  320. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  321. package/web/public/fonts/ibm-plex-sans/LICENSE +93 -0
  322. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  323. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  324. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  325. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  326. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  327. package/web/public/fonts/ibm-plex-sans/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  328. package/web/public/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  329. package/web/public/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  330. package/web/public/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  331. package/web/public/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  332. package/web/public/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  333. package/web/public/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  334. package/web/public/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  335. package/web/public/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  336. package/web/public/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  337. package/web/public/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  338. package/web/public/fonts/jetbrains-mono/LICENSE +93 -0
  339. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-400-normal.woff2 +0 -0
  340. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-500-normal.woff2 +0 -0
  341. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-600-normal.woff2 +0 -0
  342. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
  343. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
  344. package/web/public/fonts/jetbrains-mono/jetbrains-mono-latin-ext-600-normal.woff2 +0 -0
  345. package/web/public/fonts/noto-serif-sc/LICENSE +94 -0
  346. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-0.woff2 +0 -0
  347. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-1.woff2 +0 -0
  348. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-10.woff2 +0 -0
  349. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-100.woff2 +0 -0
  350. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-11.woff2 +0 -0
  351. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-12.woff2 +0 -0
  352. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-13.woff2 +0 -0
  353. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-14.woff2 +0 -0
  354. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-15.woff2 +0 -0
  355. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-16.woff2 +0 -0
  356. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-17.woff2 +0 -0
  357. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-18.woff2 +0 -0
  358. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-19.woff2 +0 -0
  359. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-2.woff2 +0 -0
  360. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-20.woff2 +0 -0
  361. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-21.woff2 +0 -0
  362. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-22.woff2 +0 -0
  363. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-23.woff2 +0 -0
  364. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-24.woff2 +0 -0
  365. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-25.woff2 +0 -0
  366. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-26.woff2 +0 -0
  367. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-27.woff2 +0 -0
  368. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-28.woff2 +0 -0
  369. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-29.woff2 +0 -0
  370. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-3.woff2 +0 -0
  371. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-30.woff2 +0 -0
  372. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-31.woff2 +0 -0
  373. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-32.woff2 +0 -0
  374. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-33.woff2 +0 -0
  375. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-34.woff2 +0 -0
  376. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-35.woff2 +0 -0
  377. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-36.woff2 +0 -0
  378. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-37.woff2 +0 -0
  379. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-38.woff2 +0 -0
  380. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-39.woff2 +0 -0
  381. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-4.woff2 +0 -0
  382. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-40.woff2 +0 -0
  383. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-41.woff2 +0 -0
  384. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-42.woff2 +0 -0
  385. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-43.woff2 +0 -0
  386. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-44.woff2 +0 -0
  387. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-45.woff2 +0 -0
  388. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-46.woff2 +0 -0
  389. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-47.woff2 +0 -0
  390. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-48.woff2 +0 -0
  391. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-49.woff2 +0 -0
  392. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-5.woff2 +0 -0
  393. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-50.woff2 +0 -0
  394. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-51.woff2 +0 -0
  395. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-52.woff2 +0 -0
  396. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-53.woff2 +0 -0
  397. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-54.woff2 +0 -0
  398. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-55.woff2 +0 -0
  399. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-56.woff2 +0 -0
  400. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-57.woff2 +0 -0
  401. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-58.woff2 +0 -0
  402. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-59.woff2 +0 -0
  403. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-6.woff2 +0 -0
  404. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-60.woff2 +0 -0
  405. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-61.woff2 +0 -0
  406. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-62.woff2 +0 -0
  407. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-63.woff2 +0 -0
  408. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-64.woff2 +0 -0
  409. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-65.woff2 +0 -0
  410. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-66.woff2 +0 -0
  411. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-67.woff2 +0 -0
  412. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-68.woff2 +0 -0
  413. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-69.woff2 +0 -0
  414. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-7.woff2 +0 -0
  415. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-70.woff2 +0 -0
  416. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-71.woff2 +0 -0
  417. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-72.woff2 +0 -0
  418. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-73.woff2 +0 -0
  419. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-74.woff2 +0 -0
  420. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-75.woff2 +0 -0
  421. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-76.woff2 +0 -0
  422. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-77.woff2 +0 -0
  423. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-78.woff2 +0 -0
  424. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-79.woff2 +0 -0
  425. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-8.woff2 +0 -0
  426. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-80.woff2 +0 -0
  427. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-81.woff2 +0 -0
  428. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-82.woff2 +0 -0
  429. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-83.woff2 +0 -0
  430. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-84.woff2 +0 -0
  431. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-85.woff2 +0 -0
  432. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-86.woff2 +0 -0
  433. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-87.woff2 +0 -0
  434. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-88.woff2 +0 -0
  435. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-89.woff2 +0 -0
  436. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-9.woff2 +0 -0
  437. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-90.woff2 +0 -0
  438. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-91.woff2 +0 -0
  439. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-92.woff2 +0 -0
  440. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-93.woff2 +0 -0
  441. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-94.woff2 +0 -0
  442. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-95.woff2 +0 -0
  443. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-96.woff2 +0 -0
  444. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-97.woff2 +0 -0
  445. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-98.woff2 +0 -0
  446. package/web/public/fonts/noto-serif-sc/noto-serif-sc-500-99.woff2 +0 -0
  447. package/web/public/fonts/source-serif-4/LICENSE +93 -0
  448. package/web/public/fonts/source-serif-4/source-serif-4-500-0.woff2 +0 -0
  449. package/web/public/fonts/source-serif-4/source-serif-4-500-1.woff2 +0 -0
  450. package/web/public/fonts/source-serif-4/source-serif-4-500-2.woff2 +0 -0
  451. package/web/public/fonts/source-serif-4/source-serif-4-500-3.woff2 +0 -0
  452. package/web/public/fonts/source-serif-4/source-serif-4-500-4.woff2 +0 -0
  453. package/web/public/fonts/source-serif-4/source-serif-4-500-5.woff2 +0 -0
  454. package/web/public/icon/1a-mark.svg +1 -0
  455. package/web/public/icon.ico +0 -0
@@ -0,0 +1,269 @@
1
+ /**
2
+ * text-sniff — 文件预览相关的纯函数:扩展名分类、内容嗅探、文本解码
3
+ * (UTF-8 → GBK → latin1 回退)、十六进制视图、行数统计。
4
+ *
5
+ * 全部无副作用、不碰 fs —— 便于单元测试(tests/unit/text-sniff.test.ts)。
6
+ * 从 agent-service.ts 抽出,行为保持不变。
7
+ */
8
+ const PREVIEW_IMAGE_EXTS = new Set([
9
+ "png",
10
+ "jpg",
11
+ "jpeg",
12
+ "gif",
13
+ "webp",
14
+ "svg",
15
+ "bmp",
16
+ "ico",
17
+ "avif",
18
+ "jfif",
19
+ "tif",
20
+ "tiff",
21
+ ]);
22
+ const PREVIEW_VIDEO_EXTS = new Set([
23
+ "mp4",
24
+ "webm",
25
+ "mov",
26
+ "mkv",
27
+ "avi",
28
+ "m4v",
29
+ "ogv",
30
+ "mpg",
31
+ "mpeg",
32
+ "wmv",
33
+ "flv",
34
+ ]);
35
+ const PREVIEW_TEXT_EXTS = new Set([
36
+ // code
37
+ "ts",
38
+ "tsx",
39
+ "js",
40
+ "jsx",
41
+ "mjs",
42
+ "cjs",
43
+ "jsm",
44
+ "es6",
45
+ "vue",
46
+ "svelte",
47
+ "py",
48
+ "pyw",
49
+ "ipynb",
50
+ "go",
51
+ "rs",
52
+ "c",
53
+ "h",
54
+ "cpp",
55
+ "hpp",
56
+ "cc",
57
+ "cxx",
58
+ "hh",
59
+ "csh",
60
+ "java",
61
+ "kt",
62
+ "kts",
63
+ "scala",
64
+ "sc",
65
+ "cs",
66
+ "fs",
67
+ "fsx",
68
+ "fsi",
69
+ "sh",
70
+ "bash",
71
+ "zsh",
72
+ "fish",
73
+ "bat",
74
+ "cmd",
75
+ "ps1",
76
+ "psd1",
77
+ "psm1",
78
+ "rb",
79
+ "php",
80
+ "pl",
81
+ "pm",
82
+ "tcl",
83
+ "lua",
84
+ "r",
85
+ "rmd",
86
+ "sql",
87
+ "swift",
88
+ "dart",
89
+ "groovy",
90
+ "gradle",
91
+ "tf",
92
+ "tfvars",
93
+ "hcl",
94
+ "nim",
95
+ "zig",
96
+ "v",
97
+ "vala",
98
+ "d",
99
+ "clj",
100
+ "cljs",
101
+ "cljc",
102
+ "edn",
103
+ "ex",
104
+ "exs",
105
+ "erl",
106
+ "hrl",
107
+ "ml",
108
+ "mli",
109
+ // markup / config / data
110
+ "json",
111
+ "jsonc",
112
+ "json5",
113
+ "jsonl",
114
+ "md",
115
+ "mdx",
116
+ "markdown",
117
+ "html",
118
+ "htm",
119
+ "xhtml",
120
+ "css",
121
+ "scss",
122
+ "sass",
123
+ "less",
124
+ "styl",
125
+ "xml",
126
+ "dtd",
127
+ "yaml",
128
+ "yml",
129
+ "toml",
130
+ "ini",
131
+ "cfg",
132
+ "conf",
133
+ "properties",
134
+ "env",
135
+ "log",
136
+ "txt",
137
+ "text",
138
+ "csv",
139
+ "tsv",
140
+ "lock",
141
+ "graphql",
142
+ "gql",
143
+ "proto",
144
+ "prisma",
145
+ "asm",
146
+ "s",
147
+ ]);
148
+ /**
149
+ * Classify a file name into its preview category. Files with no extension
150
+ * (README, Makefile, .gitignore, …) are treated as text. Everything not in an
151
+ * allowlist (exe, jar, dll, zip, …) is "none" — never previewed.
152
+ */
153
+ export function previewKind(name) {
154
+ const dot = name.lastIndexOf(".");
155
+ // A leading dot with nothing after it (.gitignore, .env) counts as no ext.
156
+ const ext = dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
157
+ if (["db", "sqlite", "sqlite3", "db3"].includes(ext))
158
+ return "sqlite";
159
+ if (PREVIEW_IMAGE_EXTS.has(ext))
160
+ return "image";
161
+ if (PREVIEW_VIDEO_EXTS.has(ext))
162
+ return "video";
163
+ if (ext === "" || PREVIEW_TEXT_EXTS.has(ext))
164
+ return "text";
165
+ return "none";
166
+ }
167
+ /**
168
+ * Content sniff for the preview: any data that has no NUL bytes and no
169
+ * meaningful control-char ratio is treated as text — so files with unknown
170
+ * or absent extensions (jsonl, .log.1, …) still open as text. NULs catch
171
+ * zip/sqlite/png/… even when the extension claims text.
172
+ */
173
+ export function looksLikeText(buf) {
174
+ if (buf.length === 0)
175
+ return true;
176
+ if (buf.includes(0))
177
+ return false;
178
+ const text = buf.toString("utf8");
179
+ let control = 0;
180
+ for (const ch of text) {
181
+ const c = ch.charCodeAt(0);
182
+ // Keep \t \n \r \f (and \b); everything else < 0x20 is binary-ish.
183
+ if (c < 0x20 && c !== 9 && c !== 10 && c !== 12 && c !== 13)
184
+ control++;
185
+ }
186
+ return control / Math.max(text.length, 1) < 0.02;
187
+ }
188
+ /** Decode bytes: strict UTF-8 first, falling back to GBK (Windows legacy
189
+ * Chinese files), then latin1 as a last resort — so previews and inline
190
+ * attachments never show mojibake for GBK/GB2312 encoded files. */
191
+ export function decodeText(buf) {
192
+ try {
193
+ return new TextDecoder("utf-8", { fatal: true }).decode(buf);
194
+ }
195
+ catch {
196
+ try {
197
+ return new TextDecoder("gbk").decode(buf);
198
+ }
199
+ catch {
200
+ return buf.toString("latin1");
201
+ }
202
+ }
203
+ }
204
+ /** Sniff an image MIME type from magic bytes (extension is only a hint).
205
+ * Returns null when the bytes don't look like a known raster format —
206
+ * callers keep such files as plain path references. */
207
+ export function sniffImageMime(buf, ext) {
208
+ if (buf.length >= 8 &&
209
+ buf[0] === 0x89 &&
210
+ buf[1] === 0x50 &&
211
+ buf[2] === 0x4e &&
212
+ buf[3] === 0x47) {
213
+ return "image/png";
214
+ }
215
+ if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
216
+ return "image/jpeg";
217
+ }
218
+ const head = buf.slice(0, 6).toString("ascii");
219
+ if (head === "GIF87a" || head === "GIF89a")
220
+ return "image/gif";
221
+ if (buf.length >= 12 &&
222
+ buf.slice(0, 4).toString("ascii") === "RIFF" &&
223
+ buf.slice(8, 12).toString("ascii") === "WEBP") {
224
+ return "image/webp";
225
+ }
226
+ if (buf.length >= 2 && buf[0] === 0x42 && buf[1] === 0x4d)
227
+ return "image/bmp";
228
+ // Unknown but raster-looking extension — trust the extension so existing
229
+ // image attachments keep working.
230
+ const known = {
231
+ ".png": "image/png",
232
+ ".jpg": "image/jpeg",
233
+ ".jpeg": "image/jpeg",
234
+ ".gif": "image/gif",
235
+ ".webp": "image/webp",
236
+ ".bmp": "image/bmp",
237
+ };
238
+ return known[ext] ?? null;
239
+ }
240
+ /** First few KB of binary data as a classic hex + ASCII dump (preview only). */
241
+ export function hexDump(buf, maxBytes = 4096) {
242
+ const data = buf.subarray(0, Math.min(buf.length, maxBytes));
243
+ const rows = [];
244
+ for (let off = 0; off < data.length; off += 16) {
245
+ const chunk = data.subarray(off, off + 16);
246
+ const hex = [...chunk]
247
+ .map((b) => b.toString(16).padStart(2, "0"))
248
+ .join(" ");
249
+ const ascii = [...chunk]
250
+ .map((b) => (b >= 0x20 && b < 0x7f ? String.fromCharCode(b) : "."))
251
+ .join("");
252
+ rows.push(`${off.toString(16).padStart(8, "0")} ${hex.padEnd(47, " ")} ${ascii}`);
253
+ }
254
+ return rows.join("\n");
255
+ }
256
+ /** Count lines in a buffer; a trailing newline terminates the last line
257
+ * instead of starting an empty one — matches the client preview's
258
+ * split-based line numbering. */
259
+ export function countLines(buf) {
260
+ if (buf.length === 0)
261
+ return 0;
262
+ const hasTrailingNewline = buf[buf.length - 1] === 10; /* \n */
263
+ let lines = 0;
264
+ for (let i = 0; i < buf.length; i++) {
265
+ if (buf[i] === 10)
266
+ lines++;
267
+ }
268
+ return hasTrailingNewline ? lines : lines + 1;
269
+ }
@@ -0,0 +1,91 @@
1
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ /** Per-conversation observed timings. Historical blocks without events stay unknown. */
4
+ export class ThinkingTimings {
5
+ messages = new Map();
6
+ observe(timestamp, event, now = Date.now()) {
7
+ if (!event.type.startsWith("thinking_") || event.contentIndex === undefined)
8
+ return;
9
+ let blocks = this.messages.get(timestamp);
10
+ if (!blocks) {
11
+ blocks = new Map();
12
+ this.messages.set(timestamp, blocks);
13
+ if (this.messages.size > 256)
14
+ this.messages.delete(this.messages.keys().next().value);
15
+ }
16
+ const index = event.contentIndex;
17
+ let timing = blocks.get(index);
18
+ if (!timing && event.type !== "thinking_end") {
19
+ timing = { start: now };
20
+ blocks.set(index, timing);
21
+ }
22
+ if (timing && event.type === "thinking_end")
23
+ timing.end = now;
24
+ }
25
+ finish(timestamp, now = Date.now()) {
26
+ for (const timing of this.messages.get(timestamp)?.values() ?? [])
27
+ timing.end ??= now;
28
+ }
29
+ finishedDurations(timestamp) {
30
+ const result = {};
31
+ for (const [index, timing] of this.messages.get(timestamp) ?? []) {
32
+ if (timing.end !== undefined)
33
+ result[index] = Math.max(0, timing.end - timing.start);
34
+ }
35
+ return result;
36
+ }
37
+ annotate(message, timestamp, now = Date.now()) {
38
+ const blocks = this.messages.get(timestamp);
39
+ if (!message || !blocks)
40
+ return message;
41
+ return { ...message, content: message.content.map((block, index) => {
42
+ const timing = blocks.get(index);
43
+ return block.type === "thinking" && timing ? { ...block, durationMs: Math.max(0, (timing.end ?? now) - timing.start) } : block;
44
+ }) };
45
+ }
46
+ }
47
+ /** Preserve measured durations across server restarts without changing SDK transcripts. */
48
+ export class ThinkingDurationStore {
49
+ filePath;
50
+ values;
51
+ constructor(filePath) {
52
+ this.filePath = filePath;
53
+ try {
54
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
55
+ this.values = parsed && typeof parsed === "object" && !Array.isArray(parsed)
56
+ ? parsed : {};
57
+ }
58
+ catch {
59
+ this.values = {};
60
+ }
61
+ }
62
+ key(sessionFile, timestamp) { return `${sessionFile}\u0000${timestamp}`; }
63
+ save(sessionFile, timestamp, durations) {
64
+ if (!sessionFile || !Number.isFinite(timestamp) || timestamp <= 0 || Object.keys(durations).length === 0)
65
+ return;
66
+ const key = this.key(sessionFile, timestamp);
67
+ this.values[key] = durations;
68
+ const keys = Object.keys(this.values);
69
+ for (const old of keys.slice(0, Math.max(0, keys.length - 2048)))
70
+ delete this.values[old];
71
+ try {
72
+ mkdirSync(dirname(this.filePath), { recursive: true });
73
+ const tmp = `${this.filePath}.${process.pid}.tmp`;
74
+ writeFileSync(tmp, JSON.stringify(this.values));
75
+ renameSync(tmp, this.filePath);
76
+ }
77
+ catch { /* Duration metadata must never interrupt a reply. */ }
78
+ }
79
+ annotate(message, sessionFile, timestamp) {
80
+ if (!message || !sessionFile || !Number.isFinite(timestamp))
81
+ return message;
82
+ const durations = this.values[this.key(sessionFile, timestamp)];
83
+ if (!durations || typeof durations !== "object")
84
+ return message;
85
+ return { ...message, content: message.content.map((block, index) => {
86
+ const durationMs = durations[index];
87
+ return block.type === "thinking" && block.durationMs === undefined && typeof durationMs === "number" && Number.isFinite(durationMs) && durationMs >= 0
88
+ ? { ...block, durationMs } : block;
89
+ }) };
90
+ }
91
+ }
@@ -0,0 +1,37 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ /** Constant-time check that any candidate equals the expected shared token. */
3
+ export function tokenMatches(candidates, expected) {
4
+ const want = Buffer.from(expected);
5
+ let ok = false;
6
+ for (const candidate of candidates) {
7
+ const got = Buffer.from(candidate);
8
+ // Length is not secret; equal-length buffers are compared in constant time.
9
+ if (got.length === want.length && timingSafeEqual(got, want))
10
+ ok = true;
11
+ }
12
+ return ok;
13
+ }
14
+ /** Cookie handed to the browser after a successful token login. */
15
+ export function tokenCookie(token, secure) {
16
+ return `pi_web_token=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Strict; Max-Age=2592000${secure ? "; Secure" : ""}`;
17
+ }
18
+ /**
19
+ * Cookies are not scoped by port, so a cookie from an earlier run (other port or
20
+ * rotated token) would shadow a fresh login and make asset requests fail.
21
+ * Re-issue the cookie unless the browser already holds the current token.
22
+ */
23
+ export function needsTokenCookie(cookieHeader, token) {
24
+ for (const part of (cookieHeader ?? "").split(";")) {
25
+ const [name, ...rest] = part.trim().split("=");
26
+ if (name !== "pi_web_token")
27
+ continue;
28
+ try {
29
+ if (decodeURIComponent(rest.join("=")) === token)
30
+ return false;
31
+ }
32
+ catch {
33
+ /* malformed value: replace it */
34
+ }
35
+ }
36
+ return true;
37
+ }
@@ -0,0 +1,97 @@
1
+ /** SDK tool updates are output snapshots; user_bash has a separate delta event. */
2
+ export function toolOutputUpdate(partial) {
3
+ const content = partial?.content;
4
+ if (!Array.isArray(content))
5
+ return null;
6
+ const text = content.map((c) => c?.type === "text" && typeof c.text === "string" ? c.text : "").join("");
7
+ return { delta: text, replace: true };
8
+ }
9
+ import { constants } from "node:fs";
10
+ import { lstat, open, realpath } from "node:fs/promises";
11
+ import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+ /** Open only a transcript-referenced workspace file or native bash/MCP/codemode spill file. */
14
+ export async function openToolOutput(cwd, path) {
15
+ const root = await realpath(cwd);
16
+ const candidate = resolve(cwd, path);
17
+ const canonical = await realpath(candidate);
18
+ const rel = relative(root, canonical);
19
+ const workspace = rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && !isAbsolute(rel);
20
+ const temporary = dirname(canonical) === await realpath(tmpdir()) && /^pi-(?:(?:bash|powershell)-[a-f0-9]{16}\.log|mcp-[a-f0-9]{16}\.[A-Za-z0-9]{1,8}|codemode-[a-f0-9]{16}\.(?:txt|png|jpg|gif|webp))$/.test(basename(canonical));
21
+ if (!workspace && !temporary)
22
+ throw Object.assign(new Error("Output access denied"), { code: "EACCES" });
23
+ if ((await lstat(candidate)).isSymbolicLink())
24
+ throw Object.assign(new Error("Output access denied"), { code: "EACCES" });
25
+ const handle = await open(canonical, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
26
+ try {
27
+ const stat = await handle.stat();
28
+ if (!stat.isFile() || await realpath(candidate) !== canonical)
29
+ throw Object.assign(new Error("Output access denied"), { code: "EACCES" });
30
+ return handle;
31
+ }
32
+ catch (error) {
33
+ await handle.close();
34
+ throw error;
35
+ }
36
+ }
37
+ import { createHash } from "node:crypto";
38
+ import { StringDecoder } from "node:string_decoder";
39
+ export const toolOutputId = (path) => createHash("sha256").update(path).digest("hex");
40
+ /** Native 1.0.4 model-facing markers, not arbitrary paths from browser requests. */
41
+ export function outputReferences(text) {
42
+ return [...text.matchAll(/\[Image saved to ([^\r\n]+?) \(image\/(?:png|jpeg|gif|webp), [^\r\n]*?\)\]|\[Binary resource [^\r\n]*? saved to ([^\r\n]+?)\]/g)].map(m => m[1] ?? m[2]);
43
+ }
44
+ /** Bound I/O as well as memory; the final partial line may contain complete markers. */
45
+ async function scanOutputReferences(handle) {
46
+ const limit = 8 * 1024 * 1024;
47
+ const buffer = Buffer.alloc(64 * 1024), decoder = new StringDecoder("utf8");
48
+ const paths = [];
49
+ let position = 0, fragments = [];
50
+ while (position < limit) {
51
+ const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, limit - position), position);
52
+ if (!bytesRead)
53
+ break;
54
+ position += bytesRead;
55
+ const text = decoder.write(buffer.subarray(0, bytesRead));
56
+ let start = 0, newline;
57
+ while ((newline = text.indexOf("\n", start)) !== -1) {
58
+ fragments.push(text.slice(start, newline));
59
+ for (const path of outputReferences(fragments.join("")))
60
+ paths.push(path);
61
+ fragments = [];
62
+ start = newline + 1;
63
+ }
64
+ if (start < text.length)
65
+ fragments.push(text.slice(start));
66
+ }
67
+ // Do not flush an incomplete UTF-8 sequence at the scan limit.
68
+ for (const path of outputReferences(fragments.join("")))
69
+ paths.push(path);
70
+ return paths;
71
+ }
72
+ export async function toolOutputManifest(cwd, toolName, text, fullPath) {
73
+ const paths = new Set();
74
+ if (fullPath)
75
+ paths.add(fullPath);
76
+ const nativeResources = toolName === "codemode" || toolName === "read_mcp_resource" || /^mcp__.+?__.+$/.test(toolName);
77
+ if (nativeResources) {
78
+ for (const path of outputReferences(text))
79
+ paths.add(path);
80
+ if (fullPath) {
81
+ let handle;
82
+ try {
83
+ handle = await openToolOutput(cwd, fullPath);
84
+ for (const path of await scanOutputReferences(handle))
85
+ paths.add(path);
86
+ }
87
+ catch (error) {
88
+ if (error.code !== "ENOENT")
89
+ throw error;
90
+ }
91
+ finally {
92
+ await handle?.close();
93
+ }
94
+ }
95
+ }
96
+ return [...paths].map(path => ({ id: toolOutputId(path), name: basename(path), path, default: path === fullPath }));
97
+ }
@@ -0,0 +1,82 @@
1
+ import { isToolTextContinuation, unexecutedToolText } from "./tool-text.js";
2
+ const analyzed = new WeakMap();
3
+ function analyze(message) {
4
+ const cached = analyzed.get(message);
5
+ if (cached)
6
+ return cached;
7
+ const text = message.content.map(b => b.type === "text" ? b.text : "").join("\n");
8
+ const value = { text, leak: unexecutedToolText(text) };
9
+ analyzed.set(message, value);
10
+ return value;
11
+ }
12
+ /** Replay native history without modifying it. IDs survive reload and branch replay. */
13
+ export function toolTextIncidents(messages, streaming = false) {
14
+ const incidents = [];
15
+ let user, current;
16
+ let sinceReminderTool = false;
17
+ for (const [index, message] of messages.entries()) {
18
+ if (message.role === "user") {
19
+ if (current && isToolTextContinuation(messages, index)) {
20
+ current.reminders.push(message);
21
+ current.state = "reminding";
22
+ sinceReminderTool = false;
23
+ }
24
+ else {
25
+ user = message;
26
+ current = undefined;
27
+ sinceReminderTool = false;
28
+ }
29
+ continue;
30
+ }
31
+ if (message.role !== "assistant" || !user)
32
+ continue;
33
+ const hasTool = message.content.some(b => b.type === "toolCall");
34
+ if (hasTool) {
35
+ if (current)
36
+ current.state = "recovered";
37
+ sinceReminderTool = true;
38
+ continue;
39
+ }
40
+ const { text, leak } = analyze(message);
41
+ const stoppedByUser = message.stopReason === "aborted" || message.stopReason === "error";
42
+ if (leak && !stoppedByUser && !current) {
43
+ current = { incidentId: `tool-text:${user.entryId ?? user.id}:${message.entryId ?? message.id}`, user, first: message, last: message, reminders: [], leaks: [], outputs: [], state: "stopped" };
44
+ incidents.push(current);
45
+ }
46
+ if (!current)
47
+ continue;
48
+ if (!leak && !current.reminders.length)
49
+ current.state = "recovered";
50
+ if (leak)
51
+ current.leaks.push(message);
52
+ if (leak || current.reminders.length && !sinceReminderTool) {
53
+ current.last = message;
54
+ current.outputs.push({ message, raw: current.reminders.length ? text : leak?.raw ?? text, attempt: current.reminders.length });
55
+ current.state = stoppedByUser ? "recovered" : "stopped";
56
+ }
57
+ }
58
+ if (current && current.state !== "recovered")
59
+ current.state = streaming ? "reminding" : "stopped";
60
+ return { incidents, current };
61
+ }
62
+ const projected = new WeakMap();
63
+ /** Optional wire metadata; cached objects preserve append-only snapshot identity. */
64
+ export function projectToolTextMessages(messages) {
65
+ const metadata = new Map();
66
+ for (const incident of toolTextIncidents(messages).incidents) {
67
+ for (const output of incident.outputs)
68
+ metadata.set(output.message.id, { toolText: { incidentId: incident.incidentId, attempt: output.attempt } });
69
+ incident.reminders.forEach((message, index) => metadata.set(message.id, { origin: "auto-reminder", toolText: { incidentId: incident.incidentId, attempt: index + 1 } }));
70
+ }
71
+ return messages.map(message => {
72
+ const fields = metadata.get(message.id);
73
+ if (!fields)
74
+ return message;
75
+ const key = JSON.stringify(fields), cached = projected.get(message);
76
+ if (cached?.key === key)
77
+ return cached.message;
78
+ const value = { ...message, ...fields };
79
+ projected.set(message, { key, message: value });
80
+ return value;
81
+ });
82
+ }
@@ -0,0 +1,67 @@
1
+ export const TOOL_TEXT_CONTINUE_PROMPT = "你上一条回复里的工具调用是以普通文本输出的,没有被执行。请通过工具调用(不要写成文本)重新发起它,然后继续完成任务。";
2
+ /**
3
+ * Detection of tool calls a model wrote as plain text instead of calling the
4
+ * tool (DSML or `<invoke>` markup on the text channel). Pure: shared by the
5
+ * server (auto-continue) and the web UI (recovery card). Detection only: text
6
+ * is never promoted to an executable call.
7
+ */
8
+ /** Match protocol markers outside Markdown code, retaining offsets into the original. */
9
+ export function maskMarkdownCode(text) {
10
+ let fence;
11
+ const masked = text.split("\n").map(line => {
12
+ const marker = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
13
+ const inCode = !!fence || !!marker;
14
+ if (marker) {
15
+ if (!fence)
16
+ fence = { marker: marker[1][0], length: marker[1].length };
17
+ else if (marker[1][0] === fence.marker && marker[1].length >= fence.length && !marker[2].trim())
18
+ fence = undefined;
19
+ }
20
+ return inCode ? line.replace(/</g, "\0") : line;
21
+ }).join("\n");
22
+ return masked.replace(/(`+)([\s\S]*?)\1(?!`)/g, code => code.replace(/</g, "\0"));
23
+ }
24
+ const D = "(?:[||]DSML[||])?";
25
+ const INVOKE_OPEN = new RegExp(`<${D}invoke\\s+name=["']([\\w.-]+)["']\\s*>`, "g");
26
+ const PARAMETER = `<${D}parameter\\s+name=["'][\\w.-]+["'][^>]*>[\\s\\S]*?<\\/${D}parameter>`;
27
+ const WRAPPER_CLOSE = `(?:<\\/${D}(?:tool_calls|function_calls)>\\s*)?`;
28
+ /**
29
+ * The text ENDS with one or more parameters closed by </invoke>. Anchored at the
30
+ * end, so a call inside a closed code block (text ends with ```) or inline code
31
+ * (ends with `) never matches, and neither does a call followed by prose.
32
+ */
33
+ const CALL_TAIL = new RegExp(`(?:${PARAMETER}\\s*)+<\\/${D}invoke>\\s*${WRAPPER_CLOSE}$`);
34
+ const STRICT_START = new RegExp(`(?:<${D}(?:tool_calls|function_calls)>\\s*)?<${D}invoke\\s+name=["'][\\w.-]+["']\\s*>`);
35
+ export function unexecutedToolText(text) {
36
+ const masked = maskMarkdownCode(text);
37
+ const start = STRICT_START.exec(masked);
38
+ if (start) {
39
+ const tail = masked.slice(start.index);
40
+ const closed = new RegExp(`<\\/${D}invoke>\\s*${WRAPPER_CLOSE}$`).test(tail);
41
+ const calls = [...tail.matchAll(new RegExp(`<${D}invoke\\s+name=["']([\\w.-]+)["']\\s*>([\\s\\S]*?)<\\/${D}invoke>`, "g"))];
42
+ if (closed && calls.length && calls.every(call => new RegExp(PARAMETER).test(call[2]))) {
43
+ return { before: text.slice(0, start.index).trimEnd(), raw: text.slice(start.index).trim(), tools: [...new Set(calls.map(call => call[1]))] };
44
+ }
45
+ }
46
+ // Fallback on the raw text: the call can still be recognised from its closing
47
+ // tail when the opening tag is masked (an unclosed ``` earlier in the reply) or
48
+ // was emitted elsewhere (inside leaked reasoning, or a separate thinking block).
49
+ const tail = CALL_TAIL.exec(text);
50
+ if (!tail)
51
+ return null;
52
+ const opens = [...text.matchAll(INVOKE_OPEN)];
53
+ const first = opens.find(open => open.index !== undefined && open.index < tail.index)?.index ?? tail.index;
54
+ const begin = Math.min(first, tail.index);
55
+ return { before: text.slice(0, begin).trimEnd(), raw: text.slice(begin).trim(), tools: [...new Set(opens.map(open => open[1]))] };
56
+ }
57
+ /** Recognize the persisted recovery exchange, including pre-fix sessions. This
58
+ * only preserves task projection; it never grants execution authorization. */
59
+ export function isToolTextContinuation(messages, index) {
60
+ const message = messages[index], previous = messages[index - 1];
61
+ if (message?.role !== "user" || message.content.length !== 1 || message.content[0].type !== "text" || message.content[0].text !== TOOL_TEXT_CONTINUE_PROMPT)
62
+ return false;
63
+ if (previous?.role !== "assistant" || previous.stopReason === "aborted" || previous.stopReason === "error" || previous.content.some(block => block.type === "toolCall"))
64
+ return false;
65
+ const text = previous.content.map(block => block.type === "text" ? block.text : "").join("\n");
66
+ return !!unexecutedToolText(text);
67
+ }