@rexvane/piweb 0.3.20 → 0.3.22

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 (317) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +6 -4
  3. package/.next/build-manifest.json +5 -5
  4. package/.next/next-minimal-server.js.nft.json +1 -1
  5. package/.next/next-server.js.nft.json +1 -1
  6. package/.next/prerender-manifest.json +7 -7
  7. package/.next/react-loadable-manifest.json +12 -12
  8. package/.next/required-server-files.js +18 -2
  9. package/.next/required-server-files.json +18 -2
  10. package/.next/routes-manifest.json +12 -0
  11. package/.next/server/app/_global-error/page.js +2 -2
  12. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  13. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_global-error.html +1 -1
  15. package/.next/server/app/_global-error.meta +8 -0
  16. package/.next/server/app/_global-error.rsc +15 -11
  17. package/.next/server/app/_global-error.segments/_full.segment.rsc +15 -11
  18. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +20 -16
  19. package/.next/server/app/_global-error.segments/_tree.segment.rsc +3 -1
  20. package/.next/server/app/_not-found/page.js +1 -1
  21. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/_not-found.html +1 -1
  24. package/.next/server/app/_not-found.meta +8 -0
  25. package/.next/server/app/_not-found.rsc +17 -13
  26. package/.next/server/app/_not-found.segments/_full.segment.rsc +17 -13
  27. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +24 -20
  28. package/.next/server/app/_not-found.segments/_tree.segment.rsc +4 -2
  29. package/.next/server/app/api/agent/[id]/events/route.js +3 -3
  30. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  31. package/.next/server/app/api/agent/[id]/route.js +1 -1
  32. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  33. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  34. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  35. package/.next/server/app/api/agent/new/route.js +1 -1
  36. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  37. package/.next/server/app/api/config/route.js +1 -1
  38. package/.next/server/app/api/config/route.js.nft.json +1 -1
  39. package/.next/server/app/api/files/route.js +1 -1
  40. package/.next/server/app/api/files/route.js.nft.json +1 -1
  41. package/.next/server/app/api/fs/route.js +1 -1
  42. package/.next/server/app/api/fs/route.js.nft.json +1 -1
  43. package/.next/server/app/api/git/route.js +4 -4
  44. package/.next/server/app/api/git/route.js.nft.json +1 -1
  45. package/.next/server/app/api/github/route.js +5 -0
  46. package/.next/server/app/api/github/route.js.nft.json +1 -0
  47. package/.next/server/app/api/github/route_client-reference-manifest.js +1 -0
  48. package/.next/server/app/api/growth/route.js +1 -1
  49. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  50. package/.next/server/app/api/health/route.js +1 -1
  51. package/.next/server/app/api/health/route.js.nft.json +1 -1
  52. package/.next/server/app/api/models/defaults/route.js +1 -0
  53. package/.next/server/app/api/models/defaults/route.js.nft.json +1 -0
  54. package/.next/server/app/api/models/defaults/route_client-reference-manifest.js +1 -0
  55. package/.next/server/app/api/models/route.js +1 -1
  56. package/.next/server/app/api/models/route.js.nft.json +1 -1
  57. package/.next/server/app/api/pi-settings/route.js +1 -1
  58. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  59. package/.next/server/app/api/plugins/route.js +1 -1
  60. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  61. package/.next/server/app/api/prompts/route.js +1 -1
  62. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  63. package/.next/server/app/api/remote/route.js +3 -3
  64. package/.next/server/app/api/remote/route.js.nft.json +1 -1
  65. package/.next/server/app/api/security/route.js +1 -1
  66. package/.next/server/app/api/security/route.js.nft.json +1 -1
  67. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  68. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  69. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  70. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  71. package/.next/server/app/api/sessions/import/route.js +2 -2
  72. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  73. package/.next/server/app/api/sessions/route.js +1 -1
  74. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  75. package/.next/server/app/api/skills/route.js +2 -2
  76. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  77. package/.next/server/app/api/update/route.js +2 -2
  78. package/.next/server/app/api/update/route.js.nft.json +1 -1
  79. package/.next/server/app/api/version/route.js +1 -1
  80. package/.next/server/app/api/version/route.js.nft.json +1 -1
  81. package/.next/server/app/api/web-auth/route.js +1 -1
  82. package/.next/server/app/api/web-auth/route.js.nft.json +1 -1
  83. package/.next/server/app/api/workspaces/route.js +1 -1
  84. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  85. package/.next/server/app/icon.svg/route.js +1 -1
  86. package/.next/server/app/icon.svg/route.js.nft.json +1 -1
  87. package/.next/server/app/icon.svg.meta +1 -1
  88. package/.next/server/app/index.html +1 -1
  89. package/.next/server/app/index.meta +8 -0
  90. package/.next/server/app/index.rsc +19 -13
  91. package/.next/server/app/index.segments/__PAGE__.segment.rsc +25 -19
  92. package/.next/server/app/index.segments/_full.segment.rsc +19 -13
  93. package/.next/server/app/index.segments/_tree.segment.rsc +3 -2
  94. package/.next/server/app/login/page.js +1 -1
  95. package/.next/server/app/login/page.js.nft.json +1 -1
  96. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  97. package/.next/server/app/login.html +1 -1
  98. package/.next/server/app/login.meta +8 -0
  99. package/.next/server/app/login.rsc +23 -17
  100. package/.next/server/app/login.segments/_full.segment.rsc +23 -17
  101. package/.next/server/app/login.segments/_tree.segment.rsc +4 -2
  102. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +30 -24
  103. package/.next/server/app/page.js +1 -1
  104. package/.next/server/app/page.js.nft.json +1 -1
  105. package/.next/server/app/page_client-reference-manifest.js +1 -1
  106. package/.next/server/app-paths-manifest.json +6 -4
  107. package/.next/server/chunks/229.js +1 -1
  108. package/.next/server/chunks/421.js +1 -0
  109. package/.next/server/chunks/430.js +215 -31
  110. package/.next/server/chunks/496.js +1 -1
  111. package/.next/server/chunks/538.js +1 -1
  112. package/.next/server/chunks/662.js +2 -0
  113. package/.next/server/chunks/686.js +1 -1
  114. package/.next/server/chunks/807.js +1 -1
  115. package/.next/server/chunks/813.js +23 -1
  116. package/.next/server/chunks/920.js +2 -2
  117. package/.next/server/chunks/924.js +1 -1
  118. package/.next/server/chunks/970.js +305 -0
  119. package/.next/server/instrumentation.js +1 -1
  120. package/.next/server/instrumentation.js.nft.json +1 -1
  121. package/.next/server/middleware-build-manifest.js +1 -1
  122. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  123. package/.next/server/middleware.js +185 -23
  124. package/.next/server/middleware.js.nft.json +1 -1
  125. package/.next/server/pages/404.html +1 -1
  126. package/.next/server/pages/500.html +1 -1
  127. package/.next/server/preview-props.json +5 -0
  128. package/.next/server/server-reference-manifest.json +1 -1
  129. package/.next/static/WMazYvD-gbeLsCUfUxvvM/_buildManifest.js +1 -0
  130. package/.next/static/chunks/316-698d10eb465bd8ef.js +1 -0
  131. package/.next/static/chunks/3794-0978865cda773008.js +52 -0
  132. package/.next/static/chunks/4673.4cf66cbdd8364c57.js +1 -0
  133. package/.next/static/chunks/4686-09fcd0028079006c.js +1 -0
  134. package/.next/static/chunks/4778.eddd3a5813f85824.js +1 -0
  135. package/.next/static/chunks/4bd1b696-e199e788e58acf79.js +1 -0
  136. package/.next/static/chunks/{5173.91ddb803785009f8.js → 5173.dfd1cbb795b9a21c.js} +1 -1
  137. package/.next/static/chunks/618.949981da99be7182.js +4 -0
  138. package/.next/static/chunks/7411.67366dc5032a141d.js +1 -0
  139. package/.next/static/chunks/8552.25c39ef4c995eba0.js +1 -0
  140. package/.next/static/chunks/app/_global-error/page-f977af7f858990a2.js +1 -0
  141. package/.next/static/chunks/app/api/agent/[id]/events/route-f977af7f858990a2.js +1 -0
  142. package/.next/static/chunks/app/api/agent/[id]/route-f977af7f858990a2.js +1 -0
  143. package/.next/static/chunks/app/api/agent/[id]/tree/route-f977af7f858990a2.js +1 -0
  144. package/.next/static/chunks/app/api/agent/new/route-f977af7f858990a2.js +1 -0
  145. package/.next/static/chunks/app/api/config/route-f977af7f858990a2.js +1 -0
  146. package/.next/static/chunks/app/api/files/route-f977af7f858990a2.js +1 -0
  147. package/.next/static/chunks/app/api/fs/route-f977af7f858990a2.js +1 -0
  148. package/.next/static/chunks/app/api/git/route-f977af7f858990a2.js +1 -0
  149. package/.next/static/chunks/app/api/github/route-f977af7f858990a2.js +1 -0
  150. package/.next/static/chunks/app/api/growth/route-f977af7f858990a2.js +1 -0
  151. package/.next/static/chunks/app/api/health/route-f977af7f858990a2.js +1 -0
  152. package/.next/static/chunks/app/api/models/defaults/route-f977af7f858990a2.js +1 -0
  153. package/.next/static/chunks/app/api/models/route-f977af7f858990a2.js +1 -0
  154. package/.next/static/chunks/app/api/pi-settings/route-f977af7f858990a2.js +1 -0
  155. package/.next/static/chunks/app/api/plugins/route-f977af7f858990a2.js +1 -0
  156. package/.next/static/chunks/app/api/prompts/route-f977af7f858990a2.js +1 -0
  157. package/.next/static/chunks/app/api/remote/route-f977af7f858990a2.js +1 -0
  158. package/.next/static/chunks/app/api/security/route-f977af7f858990a2.js +1 -0
  159. package/.next/static/chunks/app/api/sessions/[id]/export/route-f977af7f858990a2.js +1 -0
  160. package/.next/static/chunks/app/api/sessions/[id]/route-f977af7f858990a2.js +1 -0
  161. package/.next/static/chunks/app/api/sessions/import/route-f977af7f858990a2.js +1 -0
  162. package/.next/static/chunks/app/api/sessions/route-f977af7f858990a2.js +1 -0
  163. package/.next/static/chunks/app/api/skills/route-f977af7f858990a2.js +1 -0
  164. package/.next/static/chunks/app/api/update/route-f977af7f858990a2.js +1 -0
  165. package/.next/static/chunks/app/api/version/route-f977af7f858990a2.js +1 -0
  166. package/.next/static/chunks/app/api/web-auth/route-f977af7f858990a2.js +1 -0
  167. package/.next/static/chunks/app/api/workspaces/route-f977af7f858990a2.js +1 -0
  168. package/.next/static/chunks/app/page-fc4ed2ff29b6235e.js +1 -0
  169. package/.next/static/chunks/framework-47b723d27f4a4963.js +1 -0
  170. package/.next/static/chunks/main-5403f0764579e5fe.js +5 -0
  171. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f977af7f858990a2.js +1 -0
  172. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f977af7f858990a2.js +1 -0
  173. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f977af7f858990a2.js +1 -0
  174. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f977af7f858990a2.js +1 -0
  175. package/.next/static/chunks/webpack-22ebffc20adb44ae.js +1 -0
  176. package/.next/static/css/6671d0d1589fdc0e.css +1 -0
  177. package/.next/static/css/{2959bd6b626b7ffe.css → f15a853af8b5127f.css} +1 -1
  178. package/.next/types/app/api/agent/[id]/events/route.ts +3 -1
  179. package/.next/types/app/api/agent/[id]/route.ts +3 -1
  180. package/.next/types/app/api/agent/[id]/tree/route.ts +3 -1
  181. package/.next/types/app/api/agent/new/route.ts +3 -1
  182. package/.next/types/app/api/config/route.ts +3 -1
  183. package/.next/types/app/api/files/route.ts +3 -1
  184. package/.next/types/app/api/fs/route.ts +3 -1
  185. package/.next/types/app/api/git/route.ts +3 -1
  186. package/.next/types/app/api/github/route.ts +355 -0
  187. package/.next/types/app/api/growth/route.ts +3 -1
  188. package/.next/types/app/api/health/route.ts +3 -1
  189. package/.next/types/app/api/models/defaults/route.ts +355 -0
  190. package/.next/types/app/api/models/route.ts +3 -1
  191. package/.next/types/app/api/pi-settings/route.ts +3 -1
  192. package/.next/types/app/api/plugins/route.ts +3 -1
  193. package/.next/types/app/api/prompts/route.ts +3 -1
  194. package/.next/types/app/api/remote/route.ts +3 -1
  195. package/.next/types/app/api/security/route.ts +3 -1
  196. package/.next/types/app/api/sessions/[id]/export/route.ts +3 -1
  197. package/.next/types/app/api/sessions/[id]/route.ts +3 -1
  198. package/.next/types/app/api/sessions/import/route.ts +3 -1
  199. package/.next/types/app/api/sessions/route.ts +3 -1
  200. package/.next/types/app/api/skills/route.ts +3 -1
  201. package/.next/types/app/api/update/route.ts +3 -1
  202. package/.next/types/app/api/version/route.ts +3 -1
  203. package/.next/types/app/api/web-auth/route.ts +3 -1
  204. package/.next/types/app/api/workspaces/route.ts +3 -1
  205. package/.next/types/app/layout.ts +4 -1
  206. package/.next/types/app/login/page.ts +4 -1
  207. package/.next/types/app/page.ts +4 -1
  208. package/.next/types/cache-life.d.ts +11 -43
  209. package/.next/types/routes.d.ts +3 -1
  210. package/.next/types/validator.ts +76 -2
  211. package/README.md +14 -5
  212. package/next.config.ts +4 -2
  213. package/package.json +2 -2
  214. package/scripts/release.mjs +1 -1
  215. package/src/app/api/agent/[id]/events/route.ts +42 -13
  216. package/src/app/api/files/route.ts +5 -2
  217. package/src/app/api/github/route.ts +26 -0
  218. package/src/app/api/models/defaults/route.ts +18 -0
  219. package/src/app/api/models/route.ts +24 -1
  220. package/src/app/api/update/route.ts +18 -2
  221. package/src/app/globals.css +275 -0
  222. package/src/components/AppShell.tsx +89 -33
  223. package/src/components/ChatInput.tsx +245 -63
  224. package/src/components/ChatWindow.tsx +123 -108
  225. package/src/components/ContextMeter.tsx +204 -123
  226. package/src/components/DrawerPanel.tsx +19 -26
  227. package/src/components/ExtensionUI.tsx +8 -2
  228. package/src/components/GitPanel.tsx +110 -40
  229. package/src/components/MarkdownBlock.tsx +333 -37
  230. package/src/components/ProjectPanel.tsx +55 -30
  231. package/src/components/PromptPanel.tsx +2 -4
  232. package/src/components/ProviderSetupModal.module.css +20 -0
  233. package/src/components/ProviderSetupModal.tsx +134 -29
  234. package/src/components/SettingsPanel.tsx +365 -80
  235. package/src/components/TrajectoryView.tsx +4 -6
  236. package/src/components/chat/chat-scroll.ts +18 -0
  237. package/src/components/composer/ComposerImages.tsx +306 -0
  238. package/src/components/update/CopyCommand.tsx +34 -0
  239. package/src/components/update/UpdateNotice.tsx +130 -0
  240. package/src/components/update/update-store.ts +155 -0
  241. package/src/hooks/useGrowth.ts +3 -0
  242. package/src/hooks/usePiWeb.ts +54 -27
  243. package/src/hooks/useWorkspaceFiles.ts +93 -0
  244. package/src/i18n.tsx +182 -6
  245. package/src/instrumentation-node.ts +3 -0
  246. package/src/lib/agent-manager.ts +193 -119
  247. package/src/lib/chat/event-stream.ts +113 -0
  248. package/src/lib/chat/stream-settings.ts +192 -0
  249. package/src/lib/files-service.ts +100 -1
  250. package/src/lib/format.ts +56 -0
  251. package/src/lib/git-service.ts +45 -13
  252. package/src/lib/github-identity.ts +245 -0
  253. package/src/lib/growth-service.ts +6 -2
  254. package/src/lib/hero-model-memory.ts +28 -0
  255. package/src/lib/model-defaults.ts +105 -0
  256. package/src/lib/model-draft.ts +257 -9
  257. package/src/lib/model-endpoint.ts +23 -0
  258. package/src/lib/model-files.ts +33 -0
  259. package/src/lib/models-service.ts +240 -78
  260. package/src/lib/pi.ts +27 -2
  261. package/src/lib/process-format.ts +3 -2
  262. package/src/lib/session/metrics.ts +31 -0
  263. package/src/lib/thinking-level.ts +39 -0
  264. package/src/lib/trajectory.ts +17 -3
  265. package/src/lib/types.ts +19 -1
  266. package/src/lib/update/startup-check.ts +117 -0
  267. package/src/lib/update-service.ts +23 -3
  268. package/src/lib/workspace-index.ts +270 -0
  269. package/.next/server/chunks/131.js +0 -1
  270. package/.next/server/chunks/243.js +0 -1
  271. package/.next/server/chunks/741.js +0 -165
  272. package/.next/static/_Y3Lq0lI5RJ1tFSOnDi0L/_buildManifest.js +0 -1
  273. package/.next/static/chunks/316-5ea443ee6b512aa9.js +0 -1
  274. package/.next/static/chunks/3794-3679f1c05160977c.js +0 -52
  275. package/.next/static/chunks/4673.8a5a9e6855350e34.js +0 -1
  276. package/.next/static/chunks/4686-b1e9fa876d0ea671.js +0 -1
  277. package/.next/static/chunks/4778.e4c9ed093a413bc4.js +0 -1
  278. package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +0 -1
  279. package/.next/static/chunks/618.ee5d5bf1c647e483.js +0 -4
  280. package/.next/static/chunks/839.d3fe337a9117c325.js +0 -1
  281. package/.next/static/chunks/8552.6bfe3e101250b20f.js +0 -1
  282. package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +0 -1
  283. package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +0 -1
  284. package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +0 -1
  285. package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +0 -1
  286. package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +0 -1
  287. package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +0 -1
  288. package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +0 -1
  289. package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +0 -1
  290. package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +0 -1
  291. package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +0 -1
  292. package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +0 -1
  293. package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +0 -1
  294. package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +0 -1
  295. package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +0 -1
  296. package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +0 -1
  297. package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +0 -1
  298. package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +0 -1
  299. package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +0 -1
  300. package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +0 -1
  301. package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +0 -1
  302. package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +0 -1
  303. package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +0 -1
  304. package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +0 -1
  305. package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +0 -1
  306. package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +0 -1
  307. package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +0 -1
  308. package/.next/static/chunks/app/page-2e425ef83fba0782.js +0 -1
  309. package/.next/static/chunks/framework-c4e651fe89edfa91.js +0 -1
  310. package/.next/static/chunks/main-6159b22a8cb0b7c5.js +0 -5
  311. package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +0 -1
  312. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +0 -1
  313. package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +0 -1
  314. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +0 -1
  315. package/.next/static/chunks/webpack-3614a8337843456b.js +0 -1
  316. package/.next/static/css/7391c42e1ae617f9.css +0 -1
  317. /package/.next/static/{_Y3Lq0lI5RJ1tFSOnDi0L → WMazYvD-gbeLsCUfUxvvM}/_ssgManifest.js +0 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * GitHub 身份:Git 面板头部显示「头像 + 账号」。
3
+ *
4
+ * 只读,不碰任何写操作,也不把 token 交给浏览器。三档降级:
5
+ *
6
+ * 1. `gh api user` —— 本机 gh 已登录时拿到的是**当前登录账号**(最准);
7
+ * 2. 仓库 `origin` remote 的 owner —— 没装 gh / 没登录也能用,头像是免登录的
8
+ * `github.com/<owner>.png`;但拿到的可能是「仓库归属者」而不是登录账号;
9
+ * 3. `git config user.name` —— 纯本地身份,没有头像,只作文字兜底。
10
+ *
11
+ * 两条网络路径实测:`gh api user` 约 1.1s、头像约 2.2s,所以结果缓存 12 小时
12
+ * (内存 + 落盘,PiWeb 重启后仍然即时)。头像取到后转成 data URL 随身份一起返回,
13
+ * 前端不用再发第二次请求,断网也能显示。
14
+ *
15
+ * 任何一步失败都只降级,不抛错:Git 面板不该因为 GitHub 不通而少东西。
16
+ */
17
+ import { execFile } from "node:child_process";
18
+ import fs from "node:fs/promises";
19
+ import path from "node:path";
20
+ import { promisify } from "node:util";
21
+ import { resolveGitBin } from "./growth-service";
22
+ import { getAgentDir } from "./pi";
23
+
24
+ const exec = promisify(execFile);
25
+
26
+ export interface GithubIdentity {
27
+ login: string;
28
+ /** gh 里的显示名(如「心安」),没有就省略 */
29
+ name?: string;
30
+ /** data URL;取不到头像时省略,UI 退回首字母 */
31
+ avatar?: string;
32
+ /** 账号主页,点击可跳转 */
33
+ url?: string;
34
+ /** `owner/repo`(来自 origin remote),没配 remote 就没有 */
35
+ repo?: string;
36
+ }
37
+
38
+ const TTL_MS = 12 * 60 * 60 * 1_000;
39
+ const GH_TIMEOUT_MS = 8_000;
40
+ const AVATAR_TIMEOUT_MS = 8_000;
41
+ const AVATAR_SIZE = 64;
42
+ const AVATAR_MAX_BYTES = 256 * 1_024;
43
+ const CACHE_FILENAME = "piweb-github-identity.json";
44
+ // 身份字段一变就提版本:旧的缓存条目直接作废重取,避免返回已经删掉的字段
45
+ const CACHE_VERSION = 2;
46
+
47
+ interface CacheFile {
48
+ version: number;
49
+ entries: Record<string, { at: number; value: GithubIdentity | null }>;
50
+ }
51
+
52
+ const memory = new Map<string, { at: number; value: GithubIdentity | null }>();
53
+ const inflight = new Map<string, Promise<GithubIdentity | null>>();
54
+ let diskLoaded = false;
55
+ let diskWrite: Promise<void> | null = null;
56
+
57
+ function cachePath(): string {
58
+ return path.join(getAgentDir(), CACHE_FILENAME);
59
+ }
60
+
61
+ /** 离线模式(SDK 也认这个变量):不发任何网络请求,只走本地能算出来的那一档 */
62
+ function offline(): boolean {
63
+ const value = process.env.PI_OFFLINE;
64
+ if (!value || value === "0") return false;
65
+ return value.toLowerCase() !== "false";
66
+ }
67
+
68
+ async function loadDisk(): Promise<void> {
69
+ if (diskLoaded) return;
70
+ diskLoaded = true;
71
+ try {
72
+ const raw = JSON.parse(await fs.readFile(cachePath(), "utf8")) as CacheFile;
73
+ if (raw?.version !== CACHE_VERSION || !raw.entries || typeof raw.entries !== "object") return;
74
+ const now = Date.now();
75
+ for (const [key, entry] of Object.entries(raw.entries)) {
76
+ if (!entry || typeof entry.at !== "number" || now - entry.at >= TTL_MS) continue;
77
+ if (!memory.has(key)) memory.set(key, entry);
78
+ }
79
+ } catch {
80
+ /* 没有缓存 / 读不了都当第一次 */
81
+ }
82
+ }
83
+
84
+ function persist(): void {
85
+ if (diskWrite) return;
86
+ diskWrite = (async () => {
87
+ try {
88
+ const entries: CacheFile["entries"] = {};
89
+ for (const [key, entry] of memory) entries[key] = entry;
90
+ await fs.writeFile(cachePath(), JSON.stringify({ version: CACHE_VERSION, entries }), "utf8");
91
+ } catch {
92
+ /* 写不了就只在内存里缓存 */
93
+ } finally {
94
+ diskWrite = null;
95
+ }
96
+ })();
97
+ }
98
+
99
+ /** 解析 gh 可执行文件(PATH 找不到时退回常见安装位置);结果缓存 */
100
+ let ghBin: string | null | undefined;
101
+ async function resolveGhBin(): Promise<string | null> {
102
+ if (ghBin !== undefined) return ghBin;
103
+ const candidates = process.platform === "win32"
104
+ ? ["gh", path.join(process.env.ProgramFiles ?? "C:\\Program Files", "GitHub CLI", "gh.exe"), path.join(process.env.LOCALAPPDATA ?? "", "Programs", "GitHub CLI", "gh.exe")]
105
+ : ["gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"];
106
+ for (const candidate of candidates) {
107
+ if (!candidate) continue;
108
+ const ok = await exec(candidate, ["--version"], { timeout: 5_000, windowsHide: true }).then(() => true, () => false);
109
+ if (ok) {
110
+ ghBin = candidate;
111
+ return ghBin;
112
+ }
113
+ }
114
+ ghBin = null;
115
+ return ghBin;
116
+ }
117
+
118
+ async function git(cwd: string, args: string[]): Promise<string> {
119
+ const out = await exec(await resolveGitBin(), args, {
120
+ cwd,
121
+ timeout: 15_000,
122
+ maxBuffer: 1024 * 1_024,
123
+ windowsHide: true,
124
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" },
125
+ });
126
+ return out.stdout;
127
+ }
128
+
129
+ /** 从 remote URL 里取 owner 与仓库名:支持 https / ssh / git:// 三种写法 */
130
+ export function parseGithubRemote(remote: string | undefined | null): { owner: string; repo?: string } | null {
131
+ if (!remote) return null;
132
+ const trimmed = remote.trim().replace(/\.git$/, "");
133
+ const m = trimmed.match(/github\.com[:/]+([^/]+)(?:\/([^/]+))?/i);
134
+ if (!m?.[1]) return null;
135
+ return { owner: m[1], repo: m[2] ? `${m[1]}/${m[2]}` : undefined };
136
+ }
137
+
138
+ async function ghAccount(cwd: string): Promise<{ login: string; name?: string; url?: string } | null> {
139
+ if (offline()) return null;
140
+ const bin = await resolveGhBin();
141
+ if (!bin) return null;
142
+ try {
143
+ const { stdout } = await exec(bin, ["api", "user"], {
144
+ cwd,
145
+ timeout: GH_TIMEOUT_MS,
146
+ maxBuffer: 1024 * 1_024,
147
+ windowsHide: true,
148
+ env: { ...process.env, GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1" },
149
+ });
150
+ const parsed = JSON.parse(stdout) as { login?: unknown; name?: unknown; html_url?: unknown };
151
+ if (typeof parsed.login !== "string" || !parsed.login) return null;
152
+ return {
153
+ login: parsed.login,
154
+ name: typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : undefined,
155
+ url: typeof parsed.html_url === "string" ? parsed.html_url : undefined,
156
+ };
157
+ } catch {
158
+ // 没网 / 没登录 / token 失效:交给下一档
159
+ return null;
160
+ }
161
+ }
162
+
163
+ async function avatarDataUrl(owner: string): Promise<string | undefined> {
164
+ if (offline()) return undefined;
165
+ try {
166
+ const res = await fetch(`https://github.com/${encodeURIComponent(owner)}.png?size=${AVATAR_SIZE}`, {
167
+ signal: AbortSignal.timeout(AVATAR_TIMEOUT_MS),
168
+ headers: { Accept: "image/*" },
169
+ });
170
+ if (!res.ok) return undefined;
171
+ const type = res.headers.get("content-type") ?? "image/png";
172
+ if (!type.startsWith("image/")) return undefined;
173
+ const buffer = Buffer.from(await res.arrayBuffer());
174
+ if (!buffer.length || buffer.length > AVATAR_MAX_BYTES) return undefined;
175
+ return `data:${type};base64,${buffer.toString("base64")}`;
176
+ } catch {
177
+ return undefined;
178
+ }
179
+ }
180
+
181
+ async function compute(root: string): Promise<GithubIdentity | null> {
182
+ let remote: { owner: string; repo?: string } | null = null;
183
+ try {
184
+ remote = parseGithubRemote(await git(root, ["remote", "get-url", "origin"]));
185
+ } catch {
186
+ /* 没有 origin 就往下走 */
187
+ }
188
+ const account = await ghAccount(root);
189
+ if (account) {
190
+ return {
191
+ login: account.login,
192
+ name: account.name,
193
+ url: account.url ?? `https://github.com/${account.login}`,
194
+ avatar: await avatarDataUrl(account.login),
195
+ repo: remote?.repo,
196
+ };
197
+ }
198
+ if (remote) {
199
+ return { login: remote.owner, url: `https://github.com/${remote.owner}`, avatar: await avatarDataUrl(remote.owner), repo: remote.repo };
200
+ }
201
+ try {
202
+ const name = (await git(root, ["config", "user.name"])).trim();
203
+ if (name) return { login: name };
204
+ } catch {
205
+ /* 什么都没配 */
206
+ }
207
+ return null;
208
+ }
209
+
210
+ function start(root: string): Promise<GithubIdentity | null> {
211
+ const existing = inflight.get(root);
212
+ if (existing) return existing;
213
+ const task = compute(root)
214
+ .then((value) => {
215
+ memory.set(root, { at: Date.now(), value });
216
+ persist();
217
+ return value;
218
+ })
219
+ .catch(() => null)
220
+ .finally(() => inflight.delete(root)) as Promise<GithubIdentity | null>;
221
+ inflight.set(root, task);
222
+ return task;
223
+ }
224
+
225
+ /**
226
+ * 取仓库的 GitHub 身份。命中缓存立刻返回;否则**后台**刷新并返回已过期的旧值(或 null),
227
+ * 不会让调用方等网络。要拿首次结果用 `awaitGithubIdentity`。
228
+ */
229
+ export function githubIdentity(root: string | null): GithubIdentity | null {
230
+ if (!root) return null;
231
+ void loadDisk();
232
+ const hit = memory.get(root);
233
+ if (hit && Date.now() - hit.at < TTL_MS) return hit.value;
234
+ void start(root);
235
+ return hit?.value ?? null;
236
+ }
237
+
238
+ /** 等首次解析完成(面板刚打开、缓存还冷时用;调用方自己控制超时) */
239
+ export async function awaitGithubIdentity(root: string | null): Promise<GithubIdentity | null> {
240
+ if (!root) return null;
241
+ await loadDisk();
242
+ const hit = memory.get(root);
243
+ if (hit && Date.now() - hit.at < TTL_MS) return hit.value;
244
+ return start(root);
245
+ }
@@ -433,8 +433,12 @@ export function parseNumstat(raw: string): Map<string, { add: number; del: numbe
433
433
  for (let i = 0; i < tokens.length; i += 1) {
434
434
  const t = tokens[i];
435
435
  if (!t) continue;
436
- const [a, d, rest] = t.split("\t");
437
- if (a === undefined || d === undefined) continue;
436
+ const first = t.indexOf("\t");
437
+ const second = t.indexOf("\t", first + 1);
438
+ if (first < 0 || second < 0) continue;
439
+ const a = t.slice(0, first);
440
+ const d = t.slice(first + 1, second);
441
+ const rest = t.slice(second + 1);
438
442
  const binary = a === "-" || d === "-";
439
443
  const entry = { add: binary ? 0 : Number(a) || 0, del: binary ? 0 : Number(d) || 0, binary };
440
444
  if (rest === undefined || rest === "") {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 新会话页记住的模型:按工作区存在这个浏览器的 localStorage 里(key 为 piweb.heroModel.<工作区>)。
3
+ * 只认工作区自己记住的那一条;没有就是没选过,不回落到别的工作区,也不替用户预选。
4
+ */
5
+
6
+ export interface HeroModelChoice {
7
+ provider: string;
8
+ id: string;
9
+ }
10
+
11
+ const KEY_PREFIX = "piweb.heroModel.";
12
+
13
+ /** 读某个工作区记住的模型;没有、或内容损坏,都当作没选过 */
14
+ export function readRememberedHeroModel(storage: Pick<Storage, "getItem">, cwd: string): HeroModelChoice | null {
15
+ const raw = storage.getItem(`${KEY_PREFIX}${cwd}`);
16
+ if (!raw) return null;
17
+ try {
18
+ const value = JSON.parse(raw) as Partial<HeroModelChoice> | null;
19
+ return value && typeof value.provider === "string" && typeof value.id === "string" ? { provider: value.provider, id: value.id } : null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ /** 记住某个工作区选的模型,只写这个工作区名下 */
26
+ export function writeRememberedHeroModel(storage: Pick<Storage, "setItem">, cwd: string, choice: HeroModelChoice): void {
27
+ storage.setItem(`${KEY_PREFIX}${cwd}`, JSON.stringify({ provider: choice.provider, id: choice.id }));
28
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * 新会话页的默认模型与思考档位:照 pi CLI 选初始模型的规则(SDK 的 findInitialModel 没有导出,这里照着做)。
3
+ * 顺序:enabledModels 第一个 → 设置里的默认模型(且有凭证)→ 已知 provider 的默认模型表 → 第一个可用模型。
4
+ * CLI 参数与会话恢复不适用于 Web 的新会话。
5
+ */
6
+ import type { Api, Model } from "@earendil-works/pi-ai";
7
+ import { resolveModelScopeWithDiagnostics } from "@earendil-works/pi-coding-agent";
8
+ import type { ModelScopeRuntime } from "./models/model-scope";
9
+ import { getModelRuntime, getSettingsManager } from "./pi";
10
+
11
+ /**
12
+ * pi 的 defaultModelPerProvider(SDK 未导出,照抄)。键的顺序就是 pi 的查找顺序。
13
+ * 测试会和已安装 SDK 的源码对表,SDK 升级后表对不上会直接报出来。
14
+ */
15
+ export const DEFAULT_MODEL_PER_PROVIDER: Readonly<Record<string, string>> = {
16
+ "amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
17
+ "ant-ling": "Ring-2.6-1T",
18
+ anthropic: "claude-opus-4-8",
19
+ openai: "gpt-5.5",
20
+ azure: "gpt-5.4",
21
+ "openai-codex": "gpt-6.1-sol",
22
+ radius: "balanced",
23
+ nvidia: "nvidia/nemotron-3-ultra-550b-a55b",
24
+ deepseek: "deepseek-v4-pro",
25
+ google: "gemini-3.1-pro-preview",
26
+ "google-vertex": "gemini-3.1-pro-preview",
27
+ "github-copilot": "gpt-5.4",
28
+ openrouter: "moonshotai/kimi-k2.6",
29
+ "vercel-ai-gateway": "zai/glm-5.1",
30
+ xai: "grok-4.7",
31
+ groq: "openai/gpt-oss-120b",
32
+ cerebras: "gpt-oss-120b",
33
+ zai: "glm-5.3",
34
+ "zai-coding-cn": "glm-5.3",
35
+ mistral: "devstral-medium-latest",
36
+ minimax: "MiniMax-M2.7",
37
+ "minimax-cn": "MiniMax-M2.7",
38
+ moonshotai: "kimi-k2.6",
39
+ "moonshotai-cn": "kimi-k2.6",
40
+ huggingface: "moonshotai/Kimi-K2.6",
41
+ fireworks: "accounts/fireworks/models/kimi-k3",
42
+ together: "moonshotai/Kimi-K3",
43
+ baseten: "zai-org/GLM-5.2",
44
+ opencode: "kimi-k2.6",
45
+ "opencode-go": "kimi-k3",
46
+ "kimi-coding": "kimi-for-coding",
47
+ meta: "muse-spark-1.3",
48
+ "cloudflare-workers-ai": "@cf/moonshotai/kimi-k2.6",
49
+ "cloudflare-ai-gateway": "workers-ai/@cf/moonshotai/kimi-k2.6",
50
+ "qwen-token-plan": "qwen3.7-max",
51
+ "qwen-token-plan-cn": "qwen3.7-max",
52
+ "qwen-token-plan-individual": "qwen3.8-max",
53
+ xiaomi: "mimo-v2.5-pro",
54
+ "xiaomi-token-plan-cn": "mimo-v2.5-pro",
55
+ "xiaomi-token-plan-ams": "mimo-v2.5-pro",
56
+ "xiaomi-token-plan-sgp": "mimo-v2.5-pro",
57
+ };
58
+
59
+ export interface InitialModelInput {
60
+ /** settings 的 enabledModels(模式串);空数组表示不限定范围 */
61
+ patterns: string[];
62
+ defaultProvider?: string;
63
+ defaultModelId?: string;
64
+ }
65
+
66
+ /** pi 新会话的初始模型(findInitialModel 的第 2–4 步) */
67
+ export async function pickInitialModel(runtime: ModelScopeRuntime, input: InitialModelInput): Promise<Model<Api> | undefined> {
68
+ if (input.patterns.length > 0) {
69
+ const { scopedModels } = await resolveModelScopeWithDiagnostics(input.patterns, runtime);
70
+ if (scopedModels.length > 0) return scopedModels[0].model;
71
+ }
72
+ if (input.defaultProvider && input.defaultModelId) {
73
+ const configured = runtime.getModel(input.defaultProvider, input.defaultModelId);
74
+ if (configured && runtime.hasConfiguredAuth(configured.provider)) return configured;
75
+ }
76
+ const available = runtime.getAvailableSnapshot();
77
+ for (const [provider, modelId] of Object.entries(DEFAULT_MODEL_PER_PROVIDER)) {
78
+ const match = available.find((model) => model.provider === provider && model.id === modelId);
79
+ if (match) return match;
80
+ }
81
+ return available[0];
82
+ }
83
+
84
+ export interface NewSessionDefaults {
85
+ model?: { provider: string; id: string };
86
+ /** settings.json 的 defaultThinkingLevel */
87
+ defaultThinkingLevel?: string;
88
+ /** settings.json 的 modelThinkingLevels,键为 provider/modelId */
89
+ modelThinkingLevels: Record<string, string>;
90
+ }
91
+
92
+ /** 新会话页要的:建议的初始模型,以及它会用到的思考档位设置(按工作区读 pi 的设置) */
93
+ export async function newSessionDefaults(cwd: string): Promise<NewSessionDefaults> {
94
+ const settings = getSettingsManager(cwd);
95
+ const model = await pickInitialModel(await getModelRuntime(), {
96
+ patterns: settings.getEnabledModels() ?? [],
97
+ defaultProvider: settings.getDefaultProvider(),
98
+ defaultModelId: settings.getDefaultModel(),
99
+ });
100
+ return {
101
+ model: model ? { provider: model.provider, id: model.id } : undefined,
102
+ defaultThinkingLevel: settings.getDefaultThinkingLevel(),
103
+ modelThinkingLevels: settings.getAllModelThinkingLevels(),
104
+ };
105
+ }