@baique/pi-web-sky 0.1.35 → 0.1.37

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 (483) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +11 -11
  3. package/.next/build-manifest.json +4 -4
  4. package/.next/next-server.js.nft.json +1 -1
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/react-loadable-manifest.json +2 -2
  7. package/.next/required-server-files.js +6 -6
  8. package/.next/required-server-files.json +6 -6
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +3 -3
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/server/app/api/agent/[id]/bash-output/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/bash-output/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/agent/[id]/events/route.js +3 -3
  26. package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/agent/[id]/route.js +2 -2
  28. package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/agent/running/route.js +1 -1
  32. package/.next/server/app/api/agent/running/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/app-update/route.js +1 -1
  34. package/.next/server/app/api/app-update/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  36. package/.next/server/app/api/auth/all-providers/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  38. package/.next/server/app/api/auth/api-key/[provider]/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/auth/login/[provider]/route.js +3 -3
  40. package/.next/server/app/api/auth/login/[provider]/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  42. package/.next/server/app/api/auth/logout/[provider]/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/auth/providers/route.js +1 -1
  44. package/.next/server/app/api/auth/providers/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/boards/[id]/canvas/route.js +1 -1
  46. package/.next/server/app/api/boards/[id]/canvas/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/boards/[id]/edges/[eid]/route.js +1 -1
  48. package/.next/server/app/api/boards/[id]/edges/[eid]/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/boards/[id]/edges/route.js +1 -1
  50. package/.next/server/app/api/boards/[id]/edges/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/boards/[id]/nodes/[nid]/route.js +1 -1
  52. package/.next/server/app/api/boards/[id]/nodes/[nid]/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/boards/[id]/nodes/route.js +1 -1
  54. package/.next/server/app/api/boards/[id]/nodes/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/boards/[id]/reconcile-task-edges/route.js +1 -1
  56. package/.next/server/app/api/boards/[id]/reconcile-task-edges/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/boards/[id]/route.js +1 -1
  58. package/.next/server/app/api/boards/[id]/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/boards/purge-orphans/route.js +1 -1
  60. package/.next/server/app/api/boards/purge-orphans/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/boards/reorder/route.js +1 -1
  62. package/.next/server/app/api/boards/reorder/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/boards/route.js +1 -1
  64. package/.next/server/app/api/boards/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/cwd/browse/route.js +1 -1
  66. package/.next/server/app/api/cwd/browse/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/cwd/validate/route.js +2 -2
  68. package/.next/server/app/api/cwd/validate/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/default-cwd/route.js +2 -2
  70. package/.next/server/app/api/default-cwd/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/file-index/route.js +2 -2
  72. package/.next/server/app/api/file-index/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/files/[...path]/route.js +2 -2
  74. package/.next/server/app/api/files/[...path]/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/git/diff/route.js +1 -1
  76. package/.next/server/app/api/git/diff/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/git/status/route.js +1 -1
  78. package/.next/server/app/api/git/status/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/home/route.js +1 -1
  80. package/.next/server/app/api/home/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/mcp/route.js +2 -2
  82. package/.next/server/app/api/mcp/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/models/route.js +2 -2
  84. package/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  86. package/.next/server/app/api/models-config/catalog/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/models-config/discover/route.js +1 -1
  88. package/.next/server/app/api/models-config/discover/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/models-config/route.js +1 -1
  90. package/.next/server/app/api/models-config/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/models-config/test/route.js +1 -1
  92. package/.next/server/app/api/models-config/test/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/plugins/route.js +2 -2
  94. package/.next/server/app/api/plugins/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/project-trust/route.js +2 -2
  96. package/.next/server/app/api/project-trust/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/quota/route.js +1 -1
  98. package/.next/server/app/api/quota/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/search/route.js +2 -2
  100. package/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/sessions/[id]/auto-name/route.js +3 -3
  102. package/.next/server/app/api/sessions/[id]/auto-name/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/sessions/[id]/context/route.js +2 -2
  104. package/.next/server/app/api/sessions/[id]/context/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +2 -2
  106. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/sessions/[id]/export/route.js +2 -2
  108. package/.next/server/app/api/sessions/[id]/export/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  110. package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/sessions/[id]/state/route.js +2 -2
  112. package/.next/server/app/api/sessions/[id]/state/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/sessions/[id]/todos/route.js +2 -2
  114. package/.next/server/app/api/sessions/[id]/todos/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/sessions/route.js +2 -2
  116. package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/skills/check/route.js +1 -1
  118. package/.next/server/app/api/skills/check/route.js.nft.json +1 -1
  119. package/.next/server/app/api/skills/check/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/skills/install/route.js +2 -2
  121. package/.next/server/app/api/skills/install/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/skills/route.js +2 -31
  123. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  124. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/skills/search/route.js +1 -1
  126. package/.next/server/app/api/skills/search/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/skills/update/route.js +1 -1
  128. package/.next/server/app/api/skills/update/route.js.nft.json +1 -1
  129. package/.next/server/app/api/skills/update/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/task-card-questions/[id]/answer/route.js +1 -1
  131. package/.next/server/app/api/task-card-questions/[id]/answer/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/task-card-questions/route.js +1 -1
  133. package/.next/server/app/api/task-card-questions/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/task-cards/[id]/route.js +1 -1
  135. package/.next/server/app/api/task-cards/[id]/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/task-cards/route.js +1 -1
  137. package/.next/server/app/api/task-cards/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/task-scheduler/status/route.js +1 -1
  139. package/.next/server/app/api/task-scheduler/status/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/tasks/[id]/board/route.js +1 -1
  141. package/.next/server/app/api/tasks/[id]/board/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/tasks/[id]/route.js +1 -1
  143. package/.next/server/app/api/tasks/[id]/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/tasks/reorder/route.js +1 -1
  145. package/.next/server/app/api/tasks/reorder/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/tasks/route.js +1 -1
  147. package/.next/server/app/api/tasks/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/terminal/[id]/events/route.js +3 -3
  149. package/.next/server/app/api/terminal/[id]/events/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/terminal/[id]/route.js +2 -2
  151. package/.next/server/app/api/terminal/[id]/route_client-reference-manifest.js +1 -1
  152. package/.next/server/app/api/terminal/route.js +2 -2
  153. package/.next/server/app/api/terminal/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/route.js +2 -2
  155. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  156. package/.next/server/app/favicon.ico/route.js +1 -1
  157. package/.next/server/app/index.html +1 -1
  158. package/.next/server/app/index.rsc +5 -5
  159. package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
  160. package/.next/server/app/index.segments/_full.segment.rsc +5 -5
  161. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  162. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  163. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/page.js +3 -3
  165. package/.next/server/app/page_client-reference-manifest.js +1 -1
  166. package/.next/server/app-paths-manifest.json +11 -11
  167. package/.next/server/chunks/2404.js +2 -2
  168. package/.next/server/chunks/2430.js +4 -4
  169. package/.next/server/chunks/3500.js +30 -0
  170. package/.next/server/chunks/4226.js +2 -2
  171. package/.next/server/chunks/542.js +11 -6
  172. package/.next/server/chunks/5794.js +2 -2
  173. package/.next/server/chunks/6429.js +1 -1
  174. package/.next/server/chunks/898.js +2 -2
  175. package/.next/server/instrumentation.js +1 -1
  176. package/.next/server/middleware-build-manifest.js +1 -1
  177. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  178. package/.next/server/middleware.js +3 -3
  179. package/.next/server/next-font-manifest.js +1 -1
  180. package/.next/server/next-font-manifest.json +1 -1
  181. package/.next/server/pages/404.html +1 -1
  182. package/.next/server/pages/500.html +1 -1
  183. package/.next/server/server-reference-manifest.json +1 -1
  184. package/.next/static/chunks/5315.dba7515ae6e139ae.js +4 -0
  185. package/.next/static/chunks/{7999.f4ab221861378788.js → 7999.879d5a20e8641752.js} +1 -1
  186. package/.next/static/chunks/app/_not-found/{page-af4fe23994cb3d66.js → page-8cea8d8de35f4da4.js} +1 -1
  187. package/.next/static/chunks/app/{layout-1d9a7a44fbc92d2e.js → layout-c4ca28ccc08bc975.js} +1 -1
  188. package/.next/static/chunks/app/{page-6d6779a81a0a7639.js → page-c9ead38525a85686.js} +3 -3
  189. package/.next/static/chunks/{main-app-b3e8a8d15d83e1f0.js → main-app-dd2385a1a5a588df.js} +1 -1
  190. package/.next/static/chunks/next/dist/client/components/builtin/global-error-be943dcb89867ab9.js +1 -0
  191. package/.next/static/chunks/{webpack-fa4f40085b1ec1ac.js → webpack-4bbf04891e910713.js} +1 -1
  192. package/.next/static/css/422c7dc0faa04d89.css +1 -0
  193. package/.next/trace +8 -9
  194. package/.next/trace-build +1 -1
  195. package/.next/types/app/api/agent/[id]/bash-output/route.ts +1 -1
  196. package/.next/types/app/api/agent/[id]/events/route.ts +1 -1
  197. package/.next/types/app/api/agent/[id]/route.ts +1 -1
  198. package/.next/types/app/api/agent/new/route.ts +1 -1
  199. package/.next/types/app/api/agent/running/route.ts +1 -1
  200. package/.next/types/app/api/app-update/route.ts +1 -1
  201. package/.next/types/app/api/auth/all-providers/route.ts +1 -1
  202. package/.next/types/app/api/auth/api-key/[provider]/route.ts +1 -1
  203. package/.next/types/app/api/auth/login/[provider]/route.ts +1 -1
  204. package/.next/types/app/api/auth/logout/[provider]/route.ts +1 -1
  205. package/.next/types/app/api/auth/providers/route.ts +1 -1
  206. package/.next/types/app/api/boards/[id]/canvas/route.ts +1 -1
  207. package/.next/types/app/api/boards/[id]/edges/[eid]/route.ts +1 -1
  208. package/.next/types/app/api/boards/[id]/edges/route.ts +1 -1
  209. package/.next/types/app/api/boards/[id]/nodes/[nid]/route.ts +1 -1
  210. package/.next/types/app/api/boards/[id]/nodes/route.ts +1 -1
  211. package/.next/types/app/api/boards/[id]/reconcile-task-edges/route.ts +1 -1
  212. package/.next/types/app/api/boards/[id]/route.ts +1 -1
  213. package/.next/types/app/api/boards/purge-orphans/route.ts +1 -1
  214. package/.next/types/app/api/boards/reorder/route.ts +1 -1
  215. package/.next/types/app/api/boards/route.ts +1 -1
  216. package/.next/types/app/api/cwd/browse/route.ts +1 -1
  217. package/.next/types/app/api/cwd/validate/route.ts +1 -1
  218. package/.next/types/app/api/default-cwd/route.ts +1 -1
  219. package/.next/types/app/api/file-index/route.ts +1 -1
  220. package/.next/types/app/api/files/[...path]/route.ts +1 -1
  221. package/.next/types/app/api/git/diff/route.ts +1 -1
  222. package/.next/types/app/api/git/status/route.ts +1 -1
  223. package/.next/types/app/api/home/route.ts +1 -1
  224. package/.next/types/app/api/mcp/route.ts +1 -1
  225. package/.next/types/app/api/models/route.ts +1 -1
  226. package/.next/types/app/api/models-config/catalog/route.ts +1 -1
  227. package/.next/types/app/api/models-config/discover/route.ts +1 -1
  228. package/.next/types/app/api/models-config/route.ts +1 -1
  229. package/.next/types/app/api/models-config/test/route.ts +1 -1
  230. package/.next/types/app/api/plugins/route.ts +1 -1
  231. package/.next/types/app/api/project-trust/route.ts +1 -1
  232. package/.next/types/app/api/quota/route.ts +1 -1
  233. package/.next/types/app/api/search/route.ts +1 -1
  234. package/.next/types/app/api/sessions/[id]/auto-name/route.ts +1 -1
  235. package/.next/types/app/api/sessions/[id]/context/route.ts +1 -1
  236. package/.next/types/app/api/sessions/[id]/entries/[entryId]/thinking/route.ts +1 -1
  237. package/.next/types/app/api/sessions/[id]/export/route.ts +1 -1
  238. package/.next/types/app/api/sessions/[id]/route.ts +1 -1
  239. package/.next/types/app/api/sessions/[id]/state/route.ts +1 -1
  240. package/.next/types/app/api/sessions/[id]/todos/route.ts +1 -1
  241. package/.next/types/app/api/sessions/route.ts +1 -1
  242. package/.next/types/app/api/skills/check/route.ts +1 -1
  243. package/.next/types/app/api/skills/install/route.ts +1 -1
  244. package/.next/types/app/api/skills/route.ts +1 -1
  245. package/.next/types/app/api/skills/search/route.ts +1 -1
  246. package/.next/types/app/api/skills/update/route.ts +1 -1
  247. package/.next/types/app/api/task-card-questions/[id]/answer/route.ts +1 -1
  248. package/.next/types/app/api/task-card-questions/route.ts +1 -1
  249. package/.next/types/app/api/task-cards/[id]/route.ts +1 -1
  250. package/.next/types/app/api/task-cards/route.ts +1 -1
  251. package/.next/types/app/api/task-scheduler/status/route.ts +1 -1
  252. package/.next/types/app/api/tasks/[id]/board/route.ts +1 -1
  253. package/.next/types/app/api/tasks/[id]/route.ts +1 -1
  254. package/.next/types/app/api/tasks/reorder/route.ts +1 -1
  255. package/.next/types/app/api/tasks/route.ts +1 -1
  256. package/.next/types/app/api/terminal/[id]/events/route.ts +1 -1
  257. package/.next/types/app/api/terminal/[id]/route.ts +1 -1
  258. package/.next/types/app/api/terminal/route.ts +1 -1
  259. package/.next/types/app/api/worktrees/route.ts +1 -1
  260. package/.next/types/app/layout.ts +1 -1
  261. package/.next/types/app/page.ts +1 -1
  262. package/bin/pi-web-sky.js +15 -20
  263. package/lib/agent-client.test.mjs +64 -0
  264. package/lib/agent-client.ts +53 -0
  265. package/lib/agent-event-connection.test.mjs +193 -0
  266. package/lib/agent-event-connection.ts +202 -0
  267. package/lib/agent-event-stream.test.mjs +155 -0
  268. package/lib/agent-event-stream.ts +132 -0
  269. package/lib/agent-event-wire.test.mjs +278 -0
  270. package/lib/agent-event-wire.ts +107 -0
  271. package/lib/agent-phase.ts +24 -0
  272. package/lib/allowed-roots.ts +31 -0
  273. package/lib/ansi.test.mjs +52 -0
  274. package/lib/ansi.ts +211 -0
  275. package/lib/api-types.ts +139 -0
  276. package/lib/app-update.test.mjs +27 -0
  277. package/lib/app-update.ts +28 -0
  278. package/lib/atomic-file.test.mjs +37 -0
  279. package/lib/atomic-file.ts +34 -0
  280. package/lib/audit-session.ts +235 -0
  281. package/lib/bash-output.test.mjs +63 -0
  282. package/lib/bash-output.ts +53 -0
  283. package/lib/bg-image.ts +129 -0
  284. package/lib/board-events.ts +83 -0
  285. package/lib/board-purge.test.mjs +66 -0
  286. package/lib/board-purge.ts +65 -0
  287. package/lib/board-search.test.mjs +110 -0
  288. package/lib/board-search.ts +63 -0
  289. package/lib/board-store.test.mjs +542 -0
  290. package/lib/board-store.ts +794 -0
  291. package/lib/board-types.ts +96 -0
  292. package/lib/board-utils.ts +17 -0
  293. package/lib/bounded-form-data.test.mjs +54 -0
  294. package/lib/bounded-form-data.ts +49 -0
  295. package/lib/browser-notifications.test.mjs +167 -0
  296. package/lib/browser-notifications.ts +104 -0
  297. package/lib/chat-lazy-load.test.mjs +140 -0
  298. package/lib/chat-lazy-load.ts +64 -0
  299. package/lib/clipboard-paths.test.mjs +140 -0
  300. package/lib/clipboard-paths.ts +192 -0
  301. package/lib/clipboard.ts +18 -0
  302. package/lib/compaction-summary.test.mjs +44 -0
  303. package/lib/compaction-summary.ts +34 -0
  304. package/lib/custom-ui-terminal.test.mjs +32 -0
  305. package/lib/custom-ui-terminal.ts +27 -0
  306. package/lib/deepseek-pricing.test.mjs +120 -0
  307. package/lib/deepseek-pricing.ts +71 -0
  308. package/lib/directory-browser.test.mjs +60 -0
  309. package/lib/directory-browser.ts +83 -0
  310. package/lib/draft-stash.test.mjs +91 -0
  311. package/lib/draft-stash.ts +102 -0
  312. package/lib/draft-store.ts +105 -0
  313. package/lib/dropdown-direction.ts +27 -0
  314. package/lib/extension-command.test.mjs +63 -0
  315. package/lib/extension-command.ts +81 -0
  316. package/lib/file-access.test.mjs +31 -0
  317. package/lib/file-access.ts +59 -0
  318. package/lib/file-dirent.test.mjs +56 -0
  319. package/lib/file-dirent.ts +17 -0
  320. package/lib/file-explorer-state.test.mjs +63 -0
  321. package/lib/file-explorer-state.ts +36 -0
  322. package/lib/file-fuzzy.test.mjs +29 -0
  323. package/lib/file-fuzzy.ts +189 -0
  324. package/lib/file-links.test.mjs +92 -0
  325. package/lib/file-links.ts +148 -0
  326. package/lib/file-paths.ts +43 -0
  327. package/lib/file-types.test.mjs +34 -0
  328. package/lib/file-types.ts +73 -0
  329. package/lib/file-upload.test.mjs +70 -0
  330. package/lib/file-upload.ts +59 -0
  331. package/lib/file-viewer-state.test.mjs +20 -0
  332. package/lib/file-viewer-state.ts +15 -0
  333. package/lib/frontmatter.test.mjs +55 -0
  334. package/lib/frontmatter.ts +87 -0
  335. package/lib/git-changes.test.mjs +52 -0
  336. package/lib/git-changes.ts +232 -0
  337. package/lib/git-status.ts +47 -0
  338. package/lib/git-types.ts +29 -0
  339. package/lib/http-dispatcher.test.mjs +83 -0
  340. package/lib/http-dispatcher.ts +86 -0
  341. package/lib/i18n/format.test.mjs +19 -0
  342. package/lib/i18n/format.ts +60 -0
  343. package/lib/i18n/messages/en.ts +662 -0
  344. package/lib/i18n/messages/zh-CN.ts +661 -0
  345. package/lib/i18n/registry.test.mjs +32 -0
  346. package/lib/i18n/registry.ts +43 -0
  347. package/lib/i18n/types.ts +15 -0
  348. package/lib/image-attachments.test.mjs +27 -0
  349. package/lib/image-attachments.ts +56 -0
  350. package/lib/initial-navigation.test.mjs +65 -0
  351. package/lib/initial-navigation.ts +15 -0
  352. package/lib/markdown.test.mjs +104 -0
  353. package/lib/markdown.ts +347 -0
  354. package/lib/message-display.test.mjs +130 -0
  355. package/lib/message-display.ts +47 -0
  356. package/lib/model-catalog.test.mjs +327 -0
  357. package/lib/model-catalog.ts +414 -0
  358. package/lib/model-discovery-auth.ts +58 -0
  359. package/lib/model-discovery.test.mjs +50 -0
  360. package/lib/model-discovery.ts +75 -0
  361. package/lib/model-id-preference.ts +46 -0
  362. package/lib/model-scope.test.mjs +193 -0
  363. package/lib/model-scope.ts +198 -0
  364. package/lib/models-cache.test.mjs +122 -0
  365. package/lib/models-cache.ts +90 -0
  366. package/lib/models-config-store.test.mjs +200 -0
  367. package/lib/models-config-store.ts +84 -0
  368. package/lib/next-config-esm.test.mjs +14 -0
  369. package/lib/next-config.test.mjs +13 -0
  370. package/lib/node-version.test.mjs +33 -0
  371. package/lib/normalize.test.mjs +54 -0
  372. package/lib/normalize.ts +63 -0
  373. package/lib/npx.ts +62 -0
  374. package/lib/panel-layout.test.mjs +56 -0
  375. package/lib/panel-layout.ts +52 -0
  376. package/lib/patch.test.mjs +110 -0
  377. package/lib/patch.ts +134 -0
  378. package/lib/path-security.ts +42 -0
  379. package/lib/paths.test.mjs +58 -0
  380. package/lib/paths.ts +71 -0
  381. package/lib/pi-types.ts +183 -0
  382. package/lib/pi-web-options.test.mjs +63 -0
  383. package/lib/process-lifecycle.test.mjs +83 -0
  384. package/lib/project-command-env.test.mjs +298 -0
  385. package/lib/project-command-env.ts +129 -0
  386. package/lib/project-groups.test.mjs +60 -0
  387. package/lib/project-groups.ts +53 -0
  388. package/lib/project-identity.test.mjs +37 -0
  389. package/lib/project-identity.ts +26 -0
  390. package/lib/project-tree.test.mjs +115 -0
  391. package/lib/project-tree.ts +176 -0
  392. package/lib/project-trust.test.mjs +117 -0
  393. package/lib/project-trust.ts +48 -0
  394. package/lib/prompt-recovery.test.mjs +33 -0
  395. package/lib/prompt-recovery.ts +47 -0
  396. package/lib/provider-api-key-route.test.mjs +12 -0
  397. package/lib/provider-credential-store.test.mjs +126 -0
  398. package/lib/provider-credential-store.ts +114 -0
  399. package/lib/provider-listing-runtime.ts +37 -0
  400. package/lib/provider-listing.test.mjs +114 -0
  401. package/lib/provider-listing.ts +118 -0
  402. package/lib/provider-services.ts +64 -0
  403. package/lib/request-security.test.mjs +256 -0
  404. package/lib/request-security.ts +136 -0
  405. package/lib/rpc-manager-shutdown.test.mjs +372 -0
  406. package/lib/rpc-manager-widgets.test.mjs +342 -0
  407. package/lib/rpc-manager.test.mjs +139 -0
  408. package/lib/rpc-manager.ts +1703 -0
  409. package/lib/rpc-session-info.test.mjs +93 -0
  410. package/lib/session-delete.test.mjs +53 -0
  411. package/lib/session-delete.ts +86 -0
  412. package/lib/session-file-references-core.ts +82 -0
  413. package/lib/session-file-references.test.mjs +91 -0
  414. package/lib/session-file-references.ts +29 -0
  415. package/lib/session-path.test.mjs +24 -0
  416. package/lib/session-path.ts +11 -0
  417. package/lib/session-reader.pagination.test.mjs +115 -0
  418. package/lib/session-reader.test.mjs +446 -0
  419. package/lib/session-reader.ts +548 -0
  420. package/lib/session-resolve.test.mjs +65 -0
  421. package/lib/session-row-context-menu.test.mjs +46 -0
  422. package/lib/session-row-context-menu.ts +29 -0
  423. package/lib/session-scanner.test.mjs +151 -0
  424. package/lib/session-scanner.ts +238 -0
  425. package/lib/session-search.test.mjs +173 -0
  426. package/lib/session-search.ts +284 -0
  427. package/lib/session-stats.ts +26 -0
  428. package/lib/session-timing.test.mjs +107 -0
  429. package/lib/session-timing.ts +45 -0
  430. package/lib/session-title.test.mjs +284 -0
  431. package/lib/session-title.ts +255 -0
  432. package/lib/skill-frontmatter.test.mjs +75 -0
  433. package/lib/skill-frontmatter.ts +40 -0
  434. package/lib/skill-lock.test.mjs +182 -0
  435. package/lib/skill-lock.ts +146 -0
  436. package/lib/skill-updates.test.mjs +182 -0
  437. package/lib/skill-updates.ts +263 -0
  438. package/lib/skills-service.ts +16 -0
  439. package/lib/slash-display.test.mjs +122 -0
  440. package/lib/slash-display.ts +30 -0
  441. package/lib/sqlite-db.test.mjs +140 -0
  442. package/lib/sqlite-db.ts +205 -0
  443. package/lib/startup-preferences.test.mjs +130 -0
  444. package/lib/startup-preferences.ts +65 -0
  445. package/lib/streaming-message.test.mjs +212 -0
  446. package/lib/streaming-message.ts +151 -0
  447. package/lib/subagent-widget.test.mjs +87 -0
  448. package/lib/subagent-widget.ts +109 -0
  449. package/lib/sync-room-server.mjs +137 -0
  450. package/lib/task-card-store.test.mjs +309 -0
  451. package/lib/task-card-store.ts +559 -0
  452. package/lib/task-scheduler.ts +498 -0
  453. package/lib/task-store.test.mjs +216 -0
  454. package/lib/task-store.ts +271 -0
  455. package/lib/terminal-input.test.mjs +46 -0
  456. package/lib/terminal-input.ts +63 -0
  457. package/lib/terminal-manager.test.mjs +71 -0
  458. package/lib/terminal-manager.ts +245 -0
  459. package/lib/tool-execution-progress.test.mjs +32 -0
  460. package/lib/tool-execution-progress.ts +29 -0
  461. package/lib/tool-names.ts +25 -0
  462. package/lib/tool-preset-preference.test.mjs +72 -0
  463. package/lib/tool-preset-preference.ts +41 -0
  464. package/lib/tool-presets.test.mjs +56 -0
  465. package/lib/tool-presets.ts +42 -0
  466. package/lib/turn-written-files.test.mjs +137 -0
  467. package/lib/turn-written-files.ts +60 -0
  468. package/lib/types.ts +349 -0
  469. package/lib/wallpaper-settings.ts +191 -0
  470. package/lib/web-auth.test.mjs +50 -0
  471. package/lib/web-auth.ts +45 -0
  472. package/lib/workspace-memory.test.mjs +129 -0
  473. package/lib/workspace-memory.ts +98 -0
  474. package/lib/worktree.test.mjs +50 -0
  475. package/lib/worktree.ts +250 -0
  476. package/package.json +3 -1
  477. package/server.mjs +34 -0
  478. package/.next/server/chunks/7513.js +0 -30
  479. package/.next/static/chunks/5315.03de3c6abd8b283e.js +0 -4
  480. package/.next/static/chunks/next/dist/client/components/builtin/global-error-00a4badaccb388e6.js +0 -1
  481. package/.next/static/css/cf25164cb7c04d95.css +0 -1
  482. /package/.next/static/{Ud6Y2ONkGuPVFbo1xZQvk → 7sDSz-wW-RWYnxh1l6-fD}/_buildManifest.js +0 -0
  483. /package/.next/static/{Ud6Y2ONkGuPVFbo1xZQvk → 7sDSz-wW-RWYnxh1l6-fD}/_ssgManifest.js +0 -0
@@ -0,0 +1,232 @@
1
+ import { execFile } from "child_process";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { promisify } from "util";
5
+ import { TEXT_PREVIEW_MAX_BYTES } from "./file-types";
6
+ import type {
7
+ GitFileDiffResponse,
8
+ GitFileStatus,
9
+ GitStatusResponse,
10
+ } from "./git-types";
11
+ import {
12
+ classifyGitStatus,
13
+ parseGitPorcelainV1,
14
+ type GitPorcelainEntry,
15
+ } from "./git-status";
16
+
17
+ const execFileAsync = promisify(execFile);
18
+ const GIT_TIMEOUT_MS = 10_000;
19
+ const GIT_STATUS_MAX_BUFFER = 8 * 1024 * 1024;
20
+
21
+ async function git(cwd: string, args: string[], maxBuffer = GIT_STATUS_MAX_BUFFER): Promise<string> {
22
+ const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
23
+ timeout: GIT_TIMEOUT_MS,
24
+ maxBuffer,
25
+ env: { ...process.env, LC_ALL: "C" },
26
+ });
27
+ return stdout;
28
+ }
29
+
30
+ async function findRepositoryRoot(cwd: string): Promise<string | null> {
31
+ try {
32
+ return (await git(cwd, ["rev-parse", "--show-toplevel"])).trim() || null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ function isWithinPath(parent: string, target: string): boolean {
39
+ const relative = path.relative(path.resolve(parent), path.resolve(target));
40
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
41
+ }
42
+
43
+ function toGitPath(filePath: string): string {
44
+ return filePath.split(path.sep).join("/");
45
+ }
46
+
47
+ async function readStatusEntries(repositoryRoot: string): Promise<GitPorcelainEntry[]> {
48
+ const output = await git(repositoryRoot, [
49
+ "status",
50
+ "--porcelain=v1",
51
+ "-z",
52
+ "--untracked-files=all",
53
+ ]);
54
+ return parseGitPorcelainV1(output);
55
+ }
56
+
57
+ async function readTrackedLineStats(
58
+ repositoryRoot: string,
59
+ cwd: string,
60
+ ): Promise<{ additions: number; deletions: number }> {
61
+ const relativeCwd = toGitPath(path.relative(repositoryRoot, cwd));
62
+ const pathspec = relativeCwd || ".";
63
+ try {
64
+ const output = await git(repositoryRoot, [
65
+ "diff",
66
+ "--no-color",
67
+ "--no-ext-diff",
68
+ "--numstat",
69
+ "HEAD",
70
+ "--",
71
+ pathspec,
72
+ ]);
73
+ let additions = 0;
74
+ let deletions = 0;
75
+ for (const line of output.split(/\r?\n/)) {
76
+ if (!line) continue;
77
+ const [added, deleted] = line.split("\t", 2);
78
+ const addedCount = Number(added);
79
+ const deletedCount = Number(deleted);
80
+ if (Number.isInteger(addedCount)) additions += addedCount;
81
+ if (Number.isInteger(deletedCount)) deletions += deletedCount;
82
+ }
83
+ return { additions, deletions };
84
+ } catch {
85
+ return { additions: 0, deletions: 0 };
86
+ }
87
+ }
88
+
89
+ function countUntrackedTextLines(filePath: string): number {
90
+ try {
91
+ const stat = fs.lstatSync(filePath);
92
+ if (!stat.isFile() || stat.size > TEXT_PREVIEW_MAX_BYTES) return 0;
93
+ const content = fs.readFileSync(filePath);
94
+ if (hasNullByte(content) || content.length === 0) return 0;
95
+ const text = content.toString("utf8");
96
+ return text.endsWith("\n") ? text.split("\n").length - 1 : text.split("\n").length;
97
+ } catch {
98
+ return 0;
99
+ }
100
+ }
101
+
102
+ export async function getGitStatus(cwd: string): Promise<GitStatusResponse> {
103
+ const repositoryRoot = await findRepositoryRoot(cwd);
104
+ if (!repositoryRoot) {
105
+ return {
106
+ isGitRepository: false,
107
+ repositoryRoot: null,
108
+ files: [],
109
+ additions: 0,
110
+ deletions: 0,
111
+ };
112
+ }
113
+
114
+ const [entries, trackedLineStats] = await Promise.all([
115
+ readStatusEntries(repositoryRoot),
116
+ readTrackedLineStats(repositoryRoot, cwd),
117
+ ]);
118
+ const files = entries.flatMap((entry): GitFileStatus[] => {
119
+ const filePath = path.resolve(repositoryRoot, entry.path);
120
+ if (!isWithinPath(cwd, filePath)) return [];
121
+ const classified = classifyGitStatus(entry);
122
+ return [{
123
+ filePath,
124
+ ...classified,
125
+ indexStatus: entry.indexStatus,
126
+ worktreeStatus: entry.worktreeStatus,
127
+ }];
128
+ });
129
+ const untrackedAdditions = files.reduce(
130
+ (total, file) => total + (file.status === "untracked" ? countUntrackedTextLines(file.filePath) : 0),
131
+ 0,
132
+ );
133
+
134
+ return {
135
+ isGitRepository: true,
136
+ repositoryRoot,
137
+ files,
138
+ additions: trackedLineStats.additions + untrackedAdditions,
139
+ deletions: trackedLineStats.deletions,
140
+ };
141
+ }
142
+
143
+ function hasNullByte(content: Buffer): boolean {
144
+ return content.includes(0);
145
+ }
146
+
147
+ function createAddedFilePatch(gitPath: string, content: string): string {
148
+ const hasTrailingNewline = content.endsWith("\n");
149
+ const lines = content.split("\n");
150
+ if (hasTrailingNewline) lines.pop();
151
+ const body = lines.map((line) => `+${line}`).join("\n");
152
+ const noNewlineMarker = !hasTrailingNewline && lines.length > 0
153
+ ? "\n\"
154
+ : "";
155
+ return [
156
+ `diff --git a/${gitPath} b/${gitPath}`,
157
+ "new file mode 100644",
158
+ "--- /dev/null",
159
+ `+++ b/${gitPath}`,
160
+ `@@ -0,0 +1,${lines.length} @@`,
161
+ `${body}${noNewlineMarker}`,
162
+ ].join("\n");
163
+ }
164
+
165
+ async function createTrackedFilePatch(
166
+ repositoryRoot: string,
167
+ relativePath: string,
168
+ originalPath?: string,
169
+ ): Promise<string | null> {
170
+ const paths = originalPath && originalPath !== relativePath
171
+ ? [originalPath, relativePath]
172
+ : [relativePath];
173
+ try {
174
+ return await git(repositoryRoot, [
175
+ "diff",
176
+ "--no-color",
177
+ "--no-ext-diff",
178
+ "--unified=3",
179
+ "HEAD",
180
+ "--",
181
+ ...paths,
182
+ ], TEXT_PREVIEW_MAX_BYTES * 4);
183
+ } catch {
184
+ return null;
185
+ }
186
+ }
187
+
188
+ export async function getGitFileDiff(cwd: string, filePath: string): Promise<GitFileDiffResponse> {
189
+ const repositoryRoot = await findRepositoryRoot(cwd);
190
+ if (!repositoryRoot || !isWithinPath(repositoryRoot, filePath)) return { supported: false };
191
+
192
+ const resolvedFilePath = path.resolve(filePath);
193
+ const relativePath = toGitPath(path.relative(repositoryRoot, resolvedFilePath));
194
+ const entries = await readStatusEntries(repositoryRoot);
195
+ const entry = entries.find((candidate) => candidate.path === relativePath);
196
+ if (!entry) return { supported: false };
197
+
198
+ const { status } = classifyGitStatus(entry);
199
+ if (status === "deleted") {
200
+ const patch = await createTrackedFilePatch(repositoryRoot, relativePath, entry.originalPath);
201
+ if (!patch?.includes("\n@@ ")) return { supported: false };
202
+ return { supported: true, status, patch };
203
+ }
204
+
205
+ let stat: fs.Stats;
206
+ try {
207
+ stat = fs.lstatSync(resolvedFilePath);
208
+ } catch {
209
+ return { supported: false };
210
+ }
211
+ if (!stat.isFile() || stat.size > TEXT_PREVIEW_MAX_BYTES) return { supported: false };
212
+
213
+ const currentBuffer = fs.readFileSync(resolvedFilePath);
214
+ if (hasNullByte(currentBuffer)) return { supported: false };
215
+ const newContent = currentBuffer.toString("utf8");
216
+
217
+ let patch: string;
218
+ if (status === "untracked") {
219
+ patch = createAddedFilePatch(relativePath, newContent);
220
+ } else {
221
+ const trackedPatch = await createTrackedFilePatch(repositoryRoot, relativePath, entry.originalPath);
222
+ if (trackedPatch === null) {
223
+ if (status !== "added") return { supported: false };
224
+ patch = createAddedFilePatch(relativePath, newContent);
225
+ } else {
226
+ patch = trackedPatch;
227
+ }
228
+ }
229
+
230
+ if (!patch.includes("\n@@ ")) return { supported: false };
231
+ return { supported: true, status, patch };
232
+ }
@@ -0,0 +1,47 @@
1
+ import type { GitFileStatus } from "./git-types";
2
+
3
+ export interface GitPorcelainEntry {
4
+ path: string;
5
+ originalPath?: string;
6
+ indexStatus: string;
7
+ worktreeStatus: string;
8
+ }
9
+
10
+ function usesRenamePath(indexStatus: string, worktreeStatus: string): boolean {
11
+ return indexStatus === "R" || indexStatus === "C" || worktreeStatus === "R" || worktreeStatus === "C";
12
+ }
13
+
14
+ export function parseGitPorcelainV1(output: string): GitPorcelainEntry[] {
15
+ const records = output.split("\0");
16
+ const entries: GitPorcelainEntry[] = [];
17
+
18
+ for (let i = 0; i < records.length; i++) {
19
+ const record = records[i];
20
+ if (!record || record.length < 4 || record[2] !== " ") continue;
21
+ const indexStatus = record[0];
22
+ const worktreeStatus = record[1];
23
+ const entry: GitPorcelainEntry = {
24
+ path: record.slice(3),
25
+ indexStatus,
26
+ worktreeStatus,
27
+ };
28
+ if (usesRenamePath(indexStatus, worktreeStatus)) {
29
+ entry.originalPath = records[++i] || undefined;
30
+ }
31
+ entries.push(entry);
32
+ }
33
+
34
+ return entries;
35
+ }
36
+
37
+ const CONFLICT_STATUSES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
38
+
39
+ export function classifyGitStatus(entry: GitPorcelainEntry): Pick<GitFileStatus, "status" | "code"> {
40
+ const pair = `${entry.indexStatus}${entry.worktreeStatus}`;
41
+ if (pair === "??") return { status: "untracked", code: "U" };
42
+ if (CONFLICT_STATUSES.has(pair) || pair.includes("U")) return { status: "conflict", code: "C" };
43
+ if (pair.includes("D")) return { status: "deleted", code: "D" };
44
+ if (pair.includes("R") || pair.includes("C")) return { status: "renamed", code: "R" };
45
+ if (pair.includes("A")) return { status: "added", code: "A" };
46
+ return { status: "modified", code: "M" };
47
+ }
@@ -0,0 +1,29 @@
1
+ export type GitFileStatusKind =
2
+ | "modified"
3
+ | "added"
4
+ | "deleted"
5
+ | "renamed"
6
+ | "untracked"
7
+ | "conflict";
8
+
9
+ export interface GitFileStatus {
10
+ filePath: string;
11
+ status: GitFileStatusKind;
12
+ code: "M" | "A" | "D" | "R" | "U" | "C";
13
+ indexStatus: string;
14
+ worktreeStatus: string;
15
+ }
16
+
17
+ export interface GitStatusResponse {
18
+ isGitRepository: boolean;
19
+ repositoryRoot: string | null;
20
+ files: GitFileStatus[];
21
+ additions: number;
22
+ deletions: number;
23
+ }
24
+
25
+ export interface GitFileDiffResponse {
26
+ supported: boolean;
27
+ status?: GitFileStatusKind;
28
+ patch?: string;
29
+ }
@@ -0,0 +1,83 @@
1
+ import assert from "node:assert/strict";
2
+ import { createServer } from "node:http";
3
+ import { once } from "node:events";
4
+ import test from "node:test";
5
+ import { createJiti } from "jiti";
6
+
7
+ const PROXY_ENV_KEYS = [
8
+ "HTTP_PROXY",
9
+ "HTTPS_PROXY",
10
+ "NO_PROXY",
11
+ "http_proxy",
12
+ "https_proxy",
13
+ "no_proxy",
14
+ "ALL_PROXY",
15
+ "all_proxy",
16
+ ];
17
+
18
+ test("configures HTTP_PROXY, HTTPS_PROXY, and NO_PROXY for global fetch", async (t) => {
19
+ const originalEnv = new Map(PROXY_ENV_KEYS.map((key) => [key, process.env[key]]));
20
+ for (const key of PROXY_ENV_KEYS) delete process.env[key];
21
+
22
+ const connectTargets = [];
23
+ const forwardedRequests = [];
24
+ const proxy = createServer((req, res) => {
25
+ forwardedRequests.push(`${req.method} ${req.url}`);
26
+ res.writeHead(204, { Connection: "close" });
27
+ res.end();
28
+ });
29
+ proxy.on("connect", (req, socket) => {
30
+ connectTargets.push(req.url);
31
+ socket.end("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n");
32
+ });
33
+ proxy.listen(0, "127.0.0.1");
34
+ await once(proxy, "listening");
35
+
36
+ t.after(async () => {
37
+ for (const [key, value] of originalEnv) {
38
+ if (value === undefined) delete process.env[key];
39
+ else process.env[key] = value;
40
+ }
41
+ await new Promise((resolve, reject) => {
42
+ proxy.close((error) => error ? reject(error) : resolve());
43
+ });
44
+ });
45
+
46
+ const address = proxy.address();
47
+ assert.ok(address && typeof address === "object");
48
+ const proxyUrl = `http://127.0.0.1:${address.port}`;
49
+ process.env.HTTP_PROXY = proxyUrl;
50
+ process.env.HTTPS_PROXY = proxyUrl;
51
+ process.env.NO_PROXY = "bypass.invalid";
52
+
53
+ const jiti = createJiti(import.meta.url);
54
+ const { configureHttpDispatcher } = await jiti.import("./http-dispatcher.ts");
55
+ const { getGlobalDispatcher } = await import("undici");
56
+
57
+ assert.throws(() => configureHttpDispatcher(-1), /Invalid HTTP idle timeout/);
58
+ configureHttpDispatcher(2_000);
59
+
60
+ const dispatcher = getGlobalDispatcher();
61
+ configureHttpDispatcher(5_000);
62
+ assert.equal(getGlobalDispatcher(), dispatcher, "configuration should be idempotent");
63
+
64
+ const httpResponse = await fetch("http://target.invalid/through-http-proxy", {
65
+ signal: AbortSignal.timeout(2_000),
66
+ });
67
+ assert.equal(httpResponse.status, 204);
68
+ assert.deepEqual(forwardedRequests, ["GET http://target.invalid/through-http-proxy"]);
69
+ assert.deepEqual(connectTargets, []);
70
+
71
+ await assert.rejects(fetch("https://target.invalid/through-https-proxy", {
72
+ signal: AbortSignal.timeout(2_000),
73
+ }));
74
+ assert.deepEqual(connectTargets, ["target.invalid:443"]);
75
+
76
+ const forwardedRequestCount = forwardedRequests.length;
77
+ const connectTargetCount = connectTargets.length;
78
+ await assert.rejects(fetch("http://bypass.invalid:9/no-proxy", {
79
+ signal: AbortSignal.timeout(2_000),
80
+ }));
81
+ assert.equal(forwardedRequests.length, forwardedRequestCount);
82
+ assert.equal(connectTargets.length, connectTargetCount);
83
+ });
@@ -0,0 +1,86 @@
1
+ import { EventEmitter } from "node:events";
2
+ import * as undici from "undici";
3
+
4
+ export const DEFAULT_HTTP_IDLE_TIMEOUT_MS = 300_000;
5
+
6
+ type DispatcherGlobal = typeof globalThis & {
7
+ __piWebHttpDispatcherConfigured?: boolean;
8
+ };
9
+
10
+ const dispatcherGlobal = globalThis as DispatcherGlobal;
11
+ const originalGlobalFetch = globalThis.fetch;
12
+ const ignoreUndiciDispatcherError = (): void => {};
13
+
14
+ function parseHttpIdleTimeoutMs(value: unknown): number | undefined {
15
+ if (typeof value === "string") {
16
+ const trimmed = value.trim();
17
+ if (trimmed.toLowerCase() === "disabled") return 0;
18
+ if (trimmed.length === 0) return undefined;
19
+ return parseHttpIdleTimeoutMs(Number(trimmed));
20
+ }
21
+
22
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
23
+ return undefined;
24
+ }
25
+ return Math.floor(value);
26
+ }
27
+
28
+ // Undici can emit an internal Client error while terminating a response body.
29
+ // The body stream still rejects; this prevents the EventEmitter error from
30
+ // terminating the Next.js process first.
31
+ function withUndiciErrorListener<T extends undici.Dispatcher>(dispatcher: T): T {
32
+ if (dispatcher instanceof EventEmitter) {
33
+ EventEmitter.prototype.on.call(dispatcher, "error", ignoreUndiciDispatcherError);
34
+ }
35
+ return dispatcher;
36
+ }
37
+
38
+ function createUndiciClient(origin: string | URL, options: object): undici.Dispatcher {
39
+ return withUndiciErrorListener(
40
+ new undici.Client(origin, options as undici.Client.Options),
41
+ );
42
+ }
43
+
44
+ function createUndiciOriginDispatcher(origin: string | URL, options: object): undici.Dispatcher {
45
+ const dispatcherOptions = options as undici.Pool.Options;
46
+ if (dispatcherOptions.connections === 1) {
47
+ return createUndiciClient(origin, dispatcherOptions);
48
+ }
49
+
50
+ return withUndiciErrorListener(
51
+ new undici.Pool(origin, {
52
+ ...dispatcherOptions,
53
+ factory: createUndiciClient,
54
+ }),
55
+ );
56
+ }
57
+
58
+ export function configureHttpDispatcher(
59
+ timeoutMs: number = DEFAULT_HTTP_IDLE_TIMEOUT_MS,
60
+ ): void {
61
+ if (dispatcherGlobal.__piWebHttpDispatcherConfigured) return;
62
+
63
+ const normalizedTimeoutMs = parseHttpIdleTimeoutMs(timeoutMs);
64
+ if (normalizedTimeoutMs === undefined) {
65
+ throw new Error(`Invalid HTTP idle timeout: ${String(timeoutMs)}`);
66
+ }
67
+
68
+ const dispatcher = withUndiciErrorListener(
69
+ new undici.EnvHttpProxyAgent({
70
+ allowH2: false,
71
+ bodyTimeout: normalizedTimeoutMs,
72
+ headersTimeout: normalizedTimeoutMs,
73
+ clientFactory: createUndiciClient,
74
+ factory: createUndiciOriginDispatcher,
75
+ }),
76
+ );
77
+ undici.setGlobalDispatcher(dispatcher);
78
+
79
+ // Keep fetch and the dispatcher on the same undici implementation. Preserve
80
+ // an intentional fetch override installed after this module was loaded.
81
+ if (globalThis.fetch === originalGlobalFetch) {
82
+ undici.install?.();
83
+ }
84
+
85
+ dispatcherGlobal.__piWebHttpDispatcherConfigured = true;
86
+ }
@@ -0,0 +1,19 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { formatRelativeTime, interpolateMessage, translateMessage } from "./format.ts";
5
+
6
+ test("interpolates string and numeric parameters", () => {
7
+ assert.equal(interpolateMessage("Hello, {name} ({count})", { name: "Pi", count: 2 }), "Hello, Pi (2)");
8
+ });
9
+
10
+ test("falls back to English and returns the key when both are missing", () => {
11
+ assert.equal(translateMessage("zh-CN", "common.ok", { en: { "common.ok": "OK" }, "zh-CN": {} }), "OK");
12
+ assert.equal(translateMessage("zh-CN", "missing.key", { en: {}, "zh-CN": {} }), "missing.key");
13
+ });
14
+
15
+ test("formats relative time using the selected locale", () => {
16
+ const now = new Date("2026-01-01T00:00:00.000Z");
17
+ assert.equal(formatRelativeTime(new Date("2026-01-01T00:05:00.000Z"), "en", now), "in 5 minutes");
18
+ assert.equal(formatRelativeTime(new Date("2025-12-31T23:00:00.000Z"), "zh-CN", now), "1小时前");
19
+ });
@@ -0,0 +1,60 @@
1
+ import type { Locale, TranslationParams } from "./types";
2
+
3
+ type MessagesByLocale = Record<string, Record<string, string>>;
4
+
5
+ /**
6
+ * 替换翻译消息中的简单插值占位符。
7
+ * @param message 原始翻译消息
8
+ * @param params 插值参数
9
+ * @returns 完成参数替换后的消息
10
+ */
11
+ export function interpolateMessage(message: string, params: TranslationParams = {}): string {
12
+ return message.replace(/\{([\w.-]+)\}/g, (token, name: string) => {
13
+ const value = params[name];
14
+ return value === undefined ? token : String(value);
15
+ });
16
+ }
17
+
18
+ /**
19
+ * 从当前语言和英语语言包中解析消息。
20
+ * @param locale 当前语言
21
+ * @param key 翻译 key
22
+ * @param messages 各语言的消息字典
23
+ * @param params 可选的插值参数
24
+ * @returns 翻译结果,缺失时返回 key
25
+ */
26
+ export function translateMessage(
27
+ locale: Locale,
28
+ key: string,
29
+ messages: MessagesByLocale,
30
+ params: TranslationParams = {},
31
+ ): string {
32
+ const message = messages[locale]?.[key] ?? messages.en?.[key];
33
+ if (message === undefined) {
34
+ if (process.env.NODE_ENV !== "production") console.warn(`[i18n] Missing translation: ${key}`);
35
+ return key;
36
+ }
37
+ return interpolateMessage(message, params);
38
+ }
39
+
40
+ /**
41
+ * 按当前语言格式化相对时间。
42
+ * @param date 要格式化的时间
43
+ * @param locale 当前语言
44
+ * @param now 用于测试或特殊场景的当前时间
45
+ * @returns locale-aware 的相对时间文本
46
+ */
47
+ export function formatRelativeTime(date: Date | string, locale: Locale, now = new Date()): string {
48
+ const target = date instanceof Date ? date : new Date(date);
49
+ const diffMs = target.getTime() - now.getTime();
50
+ const absMs = Math.abs(diffMs);
51
+ const [unit, divisor] = absMs < 60_000
52
+ ? ["second", 1_000]
53
+ : absMs < 3_600_000
54
+ ? ["minute", 60_000]
55
+ : absMs < 86_400_000
56
+ ? ["hour", 3_600_000]
57
+ : ["day", 86_400_000];
58
+ const value = Math.round(diffMs / divisor);
59
+ return new Intl.RelativeTimeFormat(locale, { numeric: "always" }).format(value, unit as Intl.RelativeTimeFormatUnit);
60
+ }