@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,19 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /** Version of the running pi-harness package, independent of the pi SDK. */
5
+ export function appVersion(root) {
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ const candidates = root ? [root] : [process.env.PI_WEB_PKG_ROOT, resolve(here, ".."), resolve(here, "..", "..")];
8
+ for (const candidate of candidates) {
9
+ if (!candidate)
10
+ continue;
11
+ try {
12
+ const pkg = JSON.parse(readFileSync(join(candidate, "package.json"), "utf8"));
13
+ if (pkg.name === "@youweichen/pi-harness" && typeof pkg.version === "string")
14
+ return pkg.version;
15
+ }
16
+ catch { /* inspect the next package root */ }
17
+ }
18
+ return "0.0.0";
19
+ }
@@ -0,0 +1,526 @@
1
+ import { frozenAttachmentText } from "./user-attachments.js";
2
+ import { validateEditorSnapshots } from "./editor-snapshot.js";
3
+ import { countLines, decodeText, looksLikeText, sniffImageMime, } from "./text-sniff.js";
4
+ import { saveUpload, uploadsRoot } from "./uploads.js";
5
+ /** Keep the outer fence longer than every backtick run in the frozen content. */
6
+ function inlineFile(path, text, lines) {
7
+ let length = 3;
8
+ for (const match of text.matchAll(/`+/g))
9
+ length = Math.max(length, match[0].length + 1);
10
+ const fence = "`".repeat(length);
11
+ return `\n<file path="${path}"${lines ? ` lines="${lines}"` : ""}>\n${fence}\n${text}\n${fence}\n</file>`;
12
+ }
13
+ export async function buildAttachmentMessages(ctx, attachments) {
14
+ if (!attachments || attachments.length === 0)
15
+ return [];
16
+ validateEditorSnapshots(ctx.cwd, attachments);
17
+ const fs = await import("node:fs/promises");
18
+ const { resolve, sep, relative, extname, join, basename } = await import("node:path");
19
+ const root = resolve(ctx.cwd);
20
+ const MAX_ATTACHMENT_BYTES = 200 * 1024;
21
+ // Files at or below this size are inlined; larger files are referenced by
22
+ // path only (the model reads them on demand — saves tokens for small edits).
23
+ const MAX_INLINE_BYTES = Number(process.env.PI_WEB_INLINE_FILE_MAX ?? 12 * 1024);
24
+ const IMAGE_EXT = new Set([
25
+ ".png",
26
+ ".jpg",
27
+ ".jpeg",
28
+ ".gif",
29
+ ".webp",
30
+ ".bmp",
31
+ ".svg",
32
+ ]);
33
+ const MIME = {
34
+ ".png": "image/png",
35
+ ".jpg": "image/jpeg",
36
+ ".jpeg": "image/jpeg",
37
+ ".gif": "image/gif",
38
+ ".webp": "image/webp",
39
+ ".bmp": "image/bmp",
40
+ ".svg": "image/svg+xml",
41
+ };
42
+ const out = [];
43
+ /** Push the aside for a raw uploaded file (fresh fileData or a restored
44
+ * uploadPath re-read from disk). Small text files are inlined so the
45
+ * model sees them immediately; everything else becomes a path reference.
46
+ * `upload: true` marks the card as a restorable upload — the browser
47
+ * re-sends it by path when editing & re-asking a question. */
48
+ const pushUploadAside = (name, wirePath, buf) => {
49
+ if (buf.length <= MAX_INLINE_BYTES && looksLikeText(buf)) {
50
+ const lines = countLines(buf);
51
+ out.push({
52
+ message: {
53
+ customType: "file",
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: inlineFile(wirePath, decodeText(buf)),
58
+ },
59
+ ],
60
+ display: true,
61
+ details: {
62
+ name,
63
+ path: wirePath,
64
+ mode: "inline",
65
+ size: buf.length,
66
+ lines,
67
+ upload: true,
68
+ },
69
+ },
70
+ });
71
+ }
72
+ else {
73
+ out.push({
74
+ message: {
75
+ customType: "file",
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: `<file path="${wirePath}" size="${buf.length}" />`,
80
+ },
81
+ ],
82
+ display: true,
83
+ details: {
84
+ name,
85
+ path: wirePath,
86
+ mode: "reference",
87
+ size: buf.length,
88
+ upload: true,
89
+ },
90
+ },
91
+ });
92
+ }
93
+ };
94
+ /** Raw image bytes for path-referenced image files (idx → info), pre-read
95
+ * so the loop below doesn't re-read them. SVG stays a plain text file —
96
+ * the model reads its source, far more useful than a rasterized blob. */
97
+ const pathImageData = new Map();
98
+ /** Cap for path images (fully read + base64'd); larger ones fall back to
99
+ * a plain path reference (the model can still attempt to read them). */
100
+ const MAX_PATH_IMAGE_BYTES = 5 * 1024 * 1024;
101
+ for (const [idx, att] of attachments.entries()) {
102
+ if (att.imageData) {
103
+ const raw = att.imageData.replace(/^data:[^;]*;base64,/, "");
104
+ const mimeType = att.mimeType?.startsWith("image/")
105
+ ? att.mimeType
106
+ : "image/png";
107
+ const bytes = Buffer.byteLength(raw, "base64");
108
+ // Only images that would actually be sent (non-empty, under the cap).
109
+ if (bytes > 0 && bytes <= 2 * 1024 * 1024) {
110
+ }
111
+ continue;
112
+ }
113
+ if (att.nativeRef || att.editorSnapshot || att.fileData || !att.path)
114
+ continue;
115
+ const ext = extname(att.path).toLowerCase();
116
+ if (!IMAGE_EXT.has(ext) || ext === ".svg")
117
+ continue;
118
+ const abs = resolve(root, att.path);
119
+ const rawRel = relative(root, abs);
120
+ if (rawRel.startsWith("..") || rawRel.includes(`${sep}..`))
121
+ continue;
122
+ let st;
123
+ try {
124
+ st = await fs.stat(abs);
125
+ }
126
+ catch {
127
+ continue;
128
+ }
129
+ if (!st.isFile() || st.size === 0 || st.size > MAX_PATH_IMAGE_BYTES) {
130
+ continue;
131
+ }
132
+ const buf = await fs.readFile(abs);
133
+ const mime = sniffImageMime(buf, ext);
134
+ if (!mime)
135
+ continue;
136
+ const raw = buf.toString("base64");
137
+ pathImageData.set(idx, { raw, mimeType: mime, bytes: st.size });
138
+ }
139
+ /** Transcript per attachment index (filled below, keyed by bridgedImages idx). */
140
+ /** Cap for reading a file in "lines" mode (selected slice is inlined). */
141
+ const MAX_LINES_READ_BYTES = 2 * 1024 * 1024;
142
+ for (const [idx, att] of attachments.entries()) {
143
+ const frozen = frozenAttachmentText(att);
144
+ if (frozen !== undefined) {
145
+ out.push({ message: { customType: "file", display: true, content: frozen } });
146
+ continue;
147
+ }
148
+ if (att.nativeRef)
149
+ throw new Error("Unresolved attachment reference");
150
+ if (att.editorSnapshot) {
151
+ const snapshot = att.editorSnapshot;
152
+ out.push({ message: {
153
+ customType: "file", display: true,
154
+ content: [{ type: "text", text: `Current editor file: ${JSON.stringify(att.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.\n${JSON.stringify({ path: att.path, ...snapshot })}` }],
155
+ details: { name: basename(att.path), path: att.path, mode: "inline", editorSnapshot: snapshot, size: Buffer.byteLength(snapshot.text) },
156
+ } });
157
+ continue;
158
+ }
159
+ // Raw pasted/dropped/uploaded image — no workspace path involved (the
160
+ // browser downscales client-side; this guard only prevents abuse).
161
+ if (att.imageData) {
162
+ const raw = att.imageData.replace(/^data:[^;]*;base64,/, "");
163
+ const mimeType = att.mimeType?.startsWith("image/") ? att.mimeType : "image/png";
164
+ const bytes = Buffer.byteLength(raw, "base64");
165
+ const MAX_PASTED_IMAGE_BYTES = 2 * 1024 * 1024;
166
+ if (bytes === 0) {
167
+ ctx.emit({
168
+ type: "notice",
169
+ level: "error",
170
+ text: `图片数据为空,已跳过`,
171
+ });
172
+ continue;
173
+ }
174
+ if (bytes > MAX_PASTED_IMAGE_BYTES) {
175
+ ctx.emit({
176
+ type: "notice",
177
+ level: "warning",
178
+ text: `图片过大已跳过(>2MB):${att.name ?? "粘贴图片"}`,
179
+ });
180
+ continue;
181
+ }
182
+ out.push({
183
+ message: {
184
+ customType: "file",
185
+ content: [{ type: "image", data: raw, mimeType }],
186
+ display: true,
187
+ details: {
188
+ name: att.name ?? "image.png",
189
+ // No workspace path — the card renders without the path line.
190
+ path: undefined,
191
+ mode: "image",
192
+ size: bytes,
193
+ },
194
+ },
195
+ });
196
+ continue;
197
+ }
198
+ // Raw uploaded file (base64) — no workspace path involved. The bytes are
199
+ // persisted under <dataDir>/uploads/<clientId>/ so the model can read
200
+ // them on demand with its read tool (absolute path, no traversal guard
201
+ // needed — the path is server-generated). Small text uploads are inlined
202
+ // so the model sees them immediately; everything else becomes a path
203
+ // reference.
204
+ if (att.fileData) {
205
+ const buf = Buffer.from(att.fileData, "base64");
206
+ const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
207
+ if (buf.length === 0) {
208
+ ctx.emit({
209
+ type: "notice",
210
+ level: "error",
211
+ text: `文件数据为空,已跳过`,
212
+ });
213
+ continue;
214
+ }
215
+ if (buf.length > MAX_UPLOAD_BYTES) {
216
+ ctx.emit({
217
+ type: "notice",
218
+ level: "warning",
219
+ text: `文件过大已跳过(>20MB):${att.name ?? "上传文件"}`,
220
+ });
221
+ continue;
222
+ }
223
+ // Uploaded files live in a GLOBAL per-user dir (not inside the project
224
+ // or the per-client session store) so browsing a repo never picks up
225
+ // uploaded junk: <dataDir>/uploads/<clientId>/(保留期自动清理,见 uploads.ts)。
226
+ const { abs, displayName: safeName } = saveUpload(ctx.clientId, att.name ?? "file", buf);
227
+ // Wire format: forward-slash absolute path (the read tool accepts
228
+ // absolute paths; Windows uses "C:/..." — safe inside the XML-ish tag).
229
+ const wirePath = abs.split(sep).join("/");
230
+ pushUploadAside(safeName, wirePath, buf);
231
+ continue;
232
+ }
233
+ // Restored upload from edit-and-re-ask: the browser re-sends the
234
+ // server-generated absolute path of a previously uploaded (fileData)
235
+ // file instead of the original base64 (the fork drops the original
236
+ // aside card, so the bytes must be re-read from the uploads dir).
237
+ // Validate the path stays inside THIS client's uploads/ folder, then
238
+ // re-read the persisted bytes and attach by the same path — no
239
+ // re-save (the file already exists; retention sweeping governs its
240
+ // lifetime, same as the original card).
241
+ if (att.uploadPath) {
242
+ const rootDir = uploadsRoot();
243
+ const abs = resolve(rootDir, att.uploadPath);
244
+ const relToRoot = relative(rootDir, abs);
245
+ const inClientDir = !relToRoot.startsWith("..") &&
246
+ !relToRoot.includes(`${sep}..`) &&
247
+ (relToRoot === ctx.clientId ||
248
+ relToRoot.startsWith(`${ctx.clientId}${sep}`));
249
+ if (!inClientDir) {
250
+ ctx.emit({
251
+ type: "notice",
252
+ level: "warning",
253
+ text: `无法恢复已上传文件(路径不在本客户端上传目录):${att.name ?? att.uploadPath}`,
254
+ });
255
+ continue;
256
+ }
257
+ let buf;
258
+ try {
259
+ buf = await fs.readFile(abs);
260
+ }
261
+ catch {
262
+ ctx.emit({
263
+ type: "notice",
264
+ level: "warning",
265
+ text: `无法恢复已上传文件(已被清理或不可读):${att.name ?? att.uploadPath}`,
266
+ });
267
+ continue;
268
+ }
269
+ if (buf.length === 0)
270
+ continue;
271
+ pushUploadAside(att.name ?? basename(abs), abs.split(sep).join("/"), buf);
272
+ continue;
273
+ }
274
+ const abs = resolve(root, att.path);
275
+ const rawRel = relative(root, abs);
276
+ if (rawRel.startsWith("..") || rawRel.includes(`${sep}..`)) {
277
+ ctx.emit({
278
+ type: "notice",
279
+ level: "warning",
280
+ text: `附件路径超出工作区:${att.path}`,
281
+ });
282
+ continue;
283
+ }
284
+ // Normalize to forward slashes (relative() returns "\\" on Windows);
285
+ // <file path> and details.path must use the wire format.
286
+ const rel = rawRel.split(sep).join("/");
287
+ let stat;
288
+ try {
289
+ stat = await fs.stat(abs);
290
+ }
291
+ catch {
292
+ ctx.emit({
293
+ type: "notice",
294
+ level: "error",
295
+ text: `附件不存在:${att.path}`,
296
+ });
297
+ continue;
298
+ }
299
+ const name = att.path.split(/[\\/]/).pop() ?? att.path;
300
+ // Folders can't be inlined — always a path reference the model browses
301
+ // on demand with its own tools (ls/read).
302
+ if (stat.isDirectory()) {
303
+ out.push({
304
+ message: {
305
+ customType: "file",
306
+ content: [{ type: "text", text: `<folder path="${rel}" />` }],
307
+ display: true,
308
+ details: {
309
+ name,
310
+ path: rel,
311
+ mode: "reference",
312
+ type: "folder",
313
+ },
314
+ },
315
+ });
316
+ continue;
317
+ }
318
+ if (!stat.isFile()) {
319
+ ctx.emit({
320
+ type: "notice",
321
+ level: "warning",
322
+ text: `跳过非文件附件:${att.path}`,
323
+ });
324
+ continue;
325
+ }
326
+ const ext = extname(att.path).toLowerCase();
327
+ if (IMAGE_EXT.has(ext) && ext !== ".svg") {
328
+ const pathImg = pathImageData.get(idx);
329
+ if (pathImg) {
330
+ // Vision-capable main model (or bridge failed): send the raw image
331
+ // content straight from the pre-read bytes.
332
+ out.push({
333
+ message: {
334
+ customType: "file",
335
+ content: [
336
+ {
337
+ type: "image",
338
+ data: pathImg.raw,
339
+ mimeType: pathImg.mimeType,
340
+ },
341
+ ],
342
+ display: true,
343
+ details: { name, path: rel, mode: "image", size: stat.size },
344
+ },
345
+ });
346
+ continue;
347
+ }
348
+ // Pre-read failed (unsupported sniff / too large): fall back to the
349
+ // legacy inline-cap behavior.
350
+ if (stat.size > MAX_ATTACHMENT_BYTES) {
351
+ ctx.emit({
352
+ type: "notice",
353
+ level: "warning",
354
+ text: `图片附件过大已跳过(>200KB):${att.path}`,
355
+ });
356
+ continue;
357
+ }
358
+ const data = await fs.readFile(abs, "base64");
359
+ out.push({
360
+ message: {
361
+ customType: "file",
362
+ content: [
363
+ { type: "image", data, mimeType: MIME[ext] ?? "image/png" },
364
+ ],
365
+ display: true,
366
+ details: { name, path: rel, mode: "image", size: stat.size },
367
+ },
368
+ });
369
+ continue;
370
+ }
371
+ const makeReference = () => ({
372
+ message: {
373
+ customType: "file",
374
+ content: [
375
+ {
376
+ type: "text",
377
+ text: `<file path="${rel}" size="${stat.size}" />`,
378
+ },
379
+ ],
380
+ display: true,
381
+ details: { name, path: rel, mode: "reference", size: stat.size },
382
+ },
383
+ });
384
+ const makeInline = (buf) => {
385
+ const lines = countLines(buf);
386
+ return {
387
+ message: {
388
+ customType: "file",
389
+ content: [
390
+ {
391
+ type: "text",
392
+ text: inlineFile(rel, decodeText(buf)),
393
+ },
394
+ ],
395
+ display: true,
396
+ details: {
397
+ name,
398
+ path: rel,
399
+ mode: "inline",
400
+ size: stat.size,
401
+ lines,
402
+ },
403
+ },
404
+ };
405
+ };
406
+ // Reference mode is always honored and never reads the file.
407
+ if (att.mode === "reference") {
408
+ out.push(makeReference());
409
+ continue;
410
+ }
411
+ // Line-range mode: inline only the selected 1-based inclusive range.
412
+ // Reading is capped so a huge file can't exhaust memory even though
413
+ // the selected slice is small.
414
+ if (att.mode === "lines") {
415
+ const range = att.lines;
416
+ if (!range || range.start < 1 || range.end < range.start) {
417
+ ctx.emit({
418
+ type: "notice",
419
+ level: "warning",
420
+ text: `行范围无效,已改为仅引用:${att.path}`,
421
+ });
422
+ out.push(makeReference());
423
+ continue;
424
+ }
425
+ if (stat.size > MAX_LINES_READ_BYTES) {
426
+ ctx.emit({
427
+ type: "notice",
428
+ level: "warning",
429
+ text: `文件过大,已改为仅引用:${att.path}`,
430
+ });
431
+ out.push(makeReference());
432
+ continue;
433
+ }
434
+ const buf = await fs.readFile(abs);
435
+ if (buf.includes(0)) {
436
+ ctx.emit({
437
+ type: "notice",
438
+ level: "warning",
439
+ text: `二进制文件已改为仅引用:${att.path}`,
440
+ });
441
+ out.push(makeReference());
442
+ continue;
443
+ }
444
+ const parts = decodeText(buf).split("\n");
445
+ // A trailing newline yields an empty phantom line — drop it so line
446
+ // numbers match the preview panel.
447
+ if (parts.length > 0 && parts[parts.length - 1] === "")
448
+ parts.pop();
449
+ const start = Math.min(range.start, parts.length);
450
+ const end = Math.min(range.end, parts.length);
451
+ if (start < 1 || end < start) {
452
+ ctx.emit({
453
+ type: "notice",
454
+ level: "warning",
455
+ text: `选中行超出文件范围,已改为仅引用:${att.path}`,
456
+ });
457
+ out.push(makeReference());
458
+ continue;
459
+ }
460
+ const selected = parts.slice(start - 1, end).join("\n");
461
+ out.push({
462
+ message: {
463
+ customType: "file",
464
+ content: [
465
+ {
466
+ type: "text",
467
+ text: inlineFile(rel, selected, `${start}-${end}`),
468
+ },
469
+ ],
470
+ display: true,
471
+ details: {
472
+ name,
473
+ path: rel,
474
+ mode: "lines",
475
+ size: stat.size,
476
+ lines: end - start + 1,
477
+ startLine: start,
478
+ endLine: end,
479
+ },
480
+ },
481
+ });
482
+ continue;
483
+ }
484
+ // Forced inline has a hard cap to protect the model context.
485
+ if (att.mode === "inline") {
486
+ if (stat.size > MAX_INLINE_BYTES) {
487
+ ctx.emit({
488
+ type: "notice",
489
+ level: "warning",
490
+ text: `文件过大,已改为仅引用:${att.path}`,
491
+ });
492
+ out.push(makeReference());
493
+ continue;
494
+ }
495
+ const buf = await fs.readFile(abs);
496
+ if (buf.includes(0)) {
497
+ ctx.emit({
498
+ type: "notice",
499
+ level: "warning",
500
+ text: `二进制文件已改为仅引用:${att.path}`,
501
+ });
502
+ out.push(makeReference());
503
+ continue;
504
+ }
505
+ out.push(makeInline(buf));
506
+ continue;
507
+ }
508
+ // Auto: small files inline, large files reference by path.
509
+ if (stat.size > MAX_INLINE_BYTES) {
510
+ out.push(makeReference());
511
+ continue;
512
+ }
513
+ const buf = await fs.readFile(abs);
514
+ if (buf.includes(0)) {
515
+ ctx.emit({
516
+ type: "notice",
517
+ level: "warning",
518
+ text: `二进制文件已跳过(仅引用路径):${att.path}`,
519
+ });
520
+ out.push(makeReference());
521
+ continue;
522
+ }
523
+ out.push(makeInline(buf));
524
+ }
525
+ return out;
526
+ }
@@ -0,0 +1,61 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import lockfile from "proper-lockfile";
4
+ /**
5
+ * Read-modify-write of the SDK's auth.json.
6
+ *
7
+ * The SDK stores credentials (including refreshed OAuth tokens) in this file
8
+ * under a proper-lockfile lock, so writing it by hand without the same lock can
9
+ * lose a concurrent token refresh. We take the same lock (same options, same
10
+ * `<file>.lock` directory), write atomically, keep an existing file's mode and
11
+ * create new files as 0600. An unreadable file is never replaced: the caller
12
+ * gets an error instead of a silently emptied credential store.
13
+ *
14
+ * `mutate` returns the new content, or undefined to leave the file untouched.
15
+ * Resolves true when the file was rewritten.
16
+ */
17
+ export async function updateAuthFile(authPath, mutate) {
18
+ mkdirSync(dirname(authPath), { recursive: true });
19
+ if (!existsSync(authPath))
20
+ writeFileSync(authPath, "{}", { mode: 0o600, flag: "wx" });
21
+ const release = await lockfile.lock(authPath, {
22
+ realpath: false,
23
+ stale: 30_000,
24
+ // Same order of patience as the SDK (it waits up to 30s): contention is brief but real.
25
+ retries: { retries: 30, minTimeout: 20, maxTimeout: 500 },
26
+ });
27
+ try {
28
+ const raw = readFileSync(authPath, "utf8").replace(/^/, "");
29
+ let current = {};
30
+ if (raw.trim()) {
31
+ let parsed;
32
+ try {
33
+ parsed = JSON.parse(raw);
34
+ }
35
+ catch {
36
+ throw new Error("auth.json 无法解析,已保留原文件,未做任何修改");
37
+ }
38
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
39
+ throw new Error("auth.json 格式不正确,已保留原文件,未做任何修改");
40
+ }
41
+ current = parsed;
42
+ }
43
+ const next = mutate(current);
44
+ if (next === undefined)
45
+ return false;
46
+ const mode = statSync(authPath).mode & 0o777;
47
+ const tmp = `${authPath}.${process.pid}.tmp`;
48
+ writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", { mode });
49
+ try {
50
+ chmodSync(tmp, mode); // umask can narrow the create mode, never widen it here
51
+ }
52
+ catch {
53
+ /* best effort on Windows */
54
+ }
55
+ renameSync(tmp, authPath);
56
+ return true;
57
+ }
58
+ finally {
59
+ await release();
60
+ }
61
+ }