@rexvane/piweb 0.3.21 → 0.3.23

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 (343) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +9 -6
  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 +18 -11
  8. package/.next/required-server-files.js +16 -1
  9. package/.next/required-server-files.json +16 -1
  10. package/.next/routes-manifest.json +20 -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 +2 -2
  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]/side-chat/route.js +5 -0
  34. package/.next/server/app/api/agent/[id]/side-chat/route.js.nft.json +1 -0
  35. package/.next/server/app/api/agent/[id]/side-chat/route_client-reference-manifest.js +1 -0
  36. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  37. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  38. package/.next/server/app/api/agent/new/route.js +1 -1
  39. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  40. package/.next/server/app/api/config/route.js +1 -1
  41. package/.next/server/app/api/config/route.js.nft.json +1 -1
  42. package/.next/server/app/api/files/route.js +1 -1
  43. package/.next/server/app/api/files/route.js.nft.json +1 -1
  44. package/.next/server/app/api/fs/route.js +1 -1
  45. package/.next/server/app/api/fs/route.js.nft.json +1 -1
  46. package/.next/server/app/api/git/route.js +4 -4
  47. package/.next/server/app/api/git/route.js.nft.json +1 -1
  48. package/.next/server/app/api/github/route.js +5 -0
  49. package/.next/server/app/api/github/route.js.nft.json +1 -0
  50. package/.next/server/app/api/github/route_client-reference-manifest.js +1 -0
  51. package/.next/server/app/api/growth/route.js +1 -1
  52. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  53. package/.next/server/app/api/health/route.js +1 -1
  54. package/.next/server/app/api/health/route.js.nft.json +1 -1
  55. package/.next/server/app/api/models/defaults/route.js +1 -0
  56. package/.next/server/app/api/models/defaults/route.js.nft.json +1 -0
  57. package/.next/server/app/api/models/defaults/route_client-reference-manifest.js +1 -0
  58. package/.next/server/app/api/models/route.js +1 -1
  59. package/.next/server/app/api/models/route.js.nft.json +1 -1
  60. package/.next/server/app/api/pi-settings/route.js +1 -1
  61. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  62. package/.next/server/app/api/plugins/route.js +1 -1
  63. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  64. package/.next/server/app/api/prompts/route.js +1 -1
  65. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  66. package/.next/server/app/api/remote/route.js +4 -4
  67. package/.next/server/app/api/remote/route.js.nft.json +1 -1
  68. package/.next/server/app/api/security/route.js +1 -1
  69. package/.next/server/app/api/security/route.js.nft.json +1 -1
  70. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  71. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  72. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  73. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  74. package/.next/server/app/api/sessions/import/route.js +2 -2
  75. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  76. package/.next/server/app/api/sessions/route.js +1 -1
  77. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  78. package/.next/server/app/api/skills/route.js +2 -2
  79. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  80. package/.next/server/app/api/update/route.js +2 -2
  81. package/.next/server/app/api/update/route.js.nft.json +1 -1
  82. package/.next/server/app/api/version/route.js +1 -1
  83. package/.next/server/app/api/version/route.js.nft.json +1 -1
  84. package/.next/server/app/api/web-auth/route.js +2 -2
  85. package/.next/server/app/api/web-auth/route.js.nft.json +1 -1
  86. package/.next/server/app/api/workspaces/route.js +1 -1
  87. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  88. package/.next/server/app/icon.svg/route.js +1 -1
  89. package/.next/server/app/icon.svg/route.js.nft.json +1 -1
  90. package/.next/server/app/icon.svg.meta +1 -1
  91. package/.next/server/app/index.html +1 -1
  92. package/.next/server/app/index.meta +8 -0
  93. package/.next/server/app/index.rsc +20 -13
  94. package/.next/server/app/index.segments/__PAGE__.segment.rsc +26 -19
  95. package/.next/server/app/index.segments/_full.segment.rsc +20 -13
  96. package/.next/server/app/index.segments/_tree.segment.rsc +4 -2
  97. package/.next/server/app/login/page.js +1 -1
  98. package/.next/server/app/login/page.js.nft.json +1 -1
  99. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/login.html +1 -1
  101. package/.next/server/app/login.meta +8 -0
  102. package/.next/server/app/login.rsc +23 -17
  103. package/.next/server/app/login.segments/_full.segment.rsc +23 -17
  104. package/.next/server/app/login.segments/_tree.segment.rsc +4 -2
  105. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +30 -24
  106. package/.next/server/app/page.js +1 -1
  107. package/.next/server/app/page.js.nft.json +1 -1
  108. package/.next/server/app/page_client-reference-manifest.js +1 -1
  109. package/.next/server/app-paths-manifest.json +9 -6
  110. package/.next/server/chunks/1813.js +23 -0
  111. package/.next/server/chunks/2430.js +377 -0
  112. package/.next/server/chunks/2970.js +305 -0
  113. package/.next/server/chunks/3421.js +1 -0
  114. package/.next/server/chunks/3924.js +6 -0
  115. package/.next/server/chunks/4807.js +1 -0
  116. package/.next/server/chunks/538.js +1 -1
  117. package/.next/server/chunks/5393.js +1 -0
  118. package/.next/server/chunks/5802.js +2 -0
  119. package/.next/server/chunks/6229.js +1 -0
  120. package/.next/server/chunks/7496.js +1 -0
  121. package/.next/server/chunks/7662.js +2 -0
  122. package/.next/server/chunks/8686.js +1 -0
  123. package/.next/server/chunks/{593.js → 974.js} +1 -1
  124. package/.next/server/chunks/{920.js → 9920.js} +2 -2
  125. package/.next/server/functions-config-manifest.json +2 -1
  126. package/.next/server/instrumentation.js +1 -1
  127. package/.next/server/instrumentation.js.nft.json +1 -1
  128. package/.next/server/middleware-build-manifest.js +1 -1
  129. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  130. package/.next/server/middleware.js +185 -23
  131. package/.next/server/middleware.js.nft.json +1 -1
  132. package/.next/server/pages/404.html +1 -1
  133. package/.next/server/pages/500.html +1 -1
  134. package/.next/server/preview-props.json +5 -0
  135. package/.next/server/server-reference-manifest.json +1 -1
  136. package/.next/server/webpack-runtime.js +1 -1
  137. package/.next/static/chunks/2716.44fdf1b21454b961.js +1 -0
  138. package/.next/static/chunks/316-698d10eb465bd8ef.js +1 -0
  139. package/.next/static/chunks/3794-0978865cda773008.js +52 -0
  140. package/.next/static/chunks/4673.e261707dce391be7.js +1 -0
  141. package/.next/static/chunks/4686-0cd87513f9ea3538.js +1 -0
  142. package/.next/static/chunks/4778.eddd3a5813f85824.js +1 -0
  143. package/.next/static/chunks/4bd1b696-e199e788e58acf79.js +1 -0
  144. package/.next/static/chunks/{5173.91ddb803785009f8.js → 5173.dfd1cbb795b9a21c.js} +1 -1
  145. package/.next/static/chunks/618.949981da99be7182.js +4 -0
  146. package/.next/static/chunks/7411.5f79671208209b13.js +1 -0
  147. package/.next/static/chunks/app/_global-error/page-cbaaee0d7aacb85a.js +1 -0
  148. package/.next/static/chunks/app/api/agent/[id]/events/route-cbaaee0d7aacb85a.js +1 -0
  149. package/.next/static/chunks/app/api/agent/[id]/route-cbaaee0d7aacb85a.js +1 -0
  150. package/.next/static/chunks/app/api/agent/[id]/side-chat/route-cbaaee0d7aacb85a.js +1 -0
  151. package/.next/static/chunks/app/api/agent/[id]/tree/route-cbaaee0d7aacb85a.js +1 -0
  152. package/.next/static/chunks/app/api/agent/new/route-cbaaee0d7aacb85a.js +1 -0
  153. package/.next/static/chunks/app/api/config/route-cbaaee0d7aacb85a.js +1 -0
  154. package/.next/static/chunks/app/api/files/route-cbaaee0d7aacb85a.js +1 -0
  155. package/.next/static/chunks/app/api/fs/route-cbaaee0d7aacb85a.js +1 -0
  156. package/.next/static/chunks/app/api/git/route-cbaaee0d7aacb85a.js +1 -0
  157. package/.next/static/chunks/app/api/github/route-cbaaee0d7aacb85a.js +1 -0
  158. package/.next/static/chunks/app/api/growth/route-cbaaee0d7aacb85a.js +1 -0
  159. package/.next/static/chunks/app/api/health/route-cbaaee0d7aacb85a.js +1 -0
  160. package/.next/static/chunks/app/api/models/defaults/route-cbaaee0d7aacb85a.js +1 -0
  161. package/.next/static/chunks/app/api/models/route-cbaaee0d7aacb85a.js +1 -0
  162. package/.next/static/chunks/app/api/pi-settings/route-cbaaee0d7aacb85a.js +1 -0
  163. package/.next/static/chunks/app/api/plugins/route-cbaaee0d7aacb85a.js +1 -0
  164. package/.next/static/chunks/app/api/prompts/route-cbaaee0d7aacb85a.js +1 -0
  165. package/.next/static/chunks/app/api/remote/route-cbaaee0d7aacb85a.js +1 -0
  166. package/.next/static/chunks/app/api/security/route-cbaaee0d7aacb85a.js +1 -0
  167. package/.next/static/chunks/app/api/sessions/[id]/export/route-cbaaee0d7aacb85a.js +1 -0
  168. package/.next/static/chunks/app/api/sessions/[id]/route-cbaaee0d7aacb85a.js +1 -0
  169. package/.next/static/chunks/app/api/sessions/import/route-cbaaee0d7aacb85a.js +1 -0
  170. package/.next/static/chunks/app/api/sessions/route-cbaaee0d7aacb85a.js +1 -0
  171. package/.next/static/chunks/app/api/skills/route-cbaaee0d7aacb85a.js +1 -0
  172. package/.next/static/chunks/app/api/update/route-cbaaee0d7aacb85a.js +1 -0
  173. package/.next/static/chunks/app/api/version/route-cbaaee0d7aacb85a.js +1 -0
  174. package/.next/static/chunks/app/api/web-auth/route-cbaaee0d7aacb85a.js +1 -0
  175. package/.next/static/chunks/app/api/workspaces/route-cbaaee0d7aacb85a.js +1 -0
  176. package/.next/static/chunks/app/page-dc3cb41d91c7432e.js +1 -0
  177. package/.next/static/chunks/framework-47b723d27f4a4963.js +1 -0
  178. package/.next/static/chunks/main-5403f0764579e5fe.js +5 -0
  179. package/.next/static/chunks/next/dist/client/components/builtin/app-error-cbaaee0d7aacb85a.js +1 -0
  180. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cbaaee0d7aacb85a.js +1 -0
  181. package/.next/static/chunks/next/dist/client/components/builtin/not-found-cbaaee0d7aacb85a.js +1 -0
  182. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cbaaee0d7aacb85a.js +1 -0
  183. package/.next/static/chunks/webpack-2ac3d76b81895f5b.js +1 -0
  184. package/.next/static/css/9fc559c2c4ad1d0f.css +1 -0
  185. package/.next/static/css/c6e2227c36ec2fd6.css +1 -0
  186. package/.next/static/css/{2959bd6b626b7ffe.css → f15a853af8b5127f.css} +1 -1
  187. package/.next/static/hYPL1-dmhKFjLS3f5yoqN/_buildManifest.js +1 -0
  188. package/.next/types/app/api/agent/[id]/events/route.ts +3 -1
  189. package/.next/types/app/api/agent/[id]/route.ts +3 -1
  190. package/.next/types/app/api/agent/[id]/side-chat/route.ts +355 -0
  191. package/.next/types/app/api/agent/[id]/tree/route.ts +3 -1
  192. package/.next/types/app/api/agent/new/route.ts +3 -1
  193. package/.next/types/app/api/config/route.ts +3 -1
  194. package/.next/types/app/api/files/route.ts +3 -1
  195. package/.next/types/app/api/fs/route.ts +3 -1
  196. package/.next/types/app/api/git/route.ts +3 -1
  197. package/.next/types/app/api/github/route.ts +355 -0
  198. package/.next/types/app/api/growth/route.ts +3 -1
  199. package/.next/types/app/api/health/route.ts +3 -1
  200. package/.next/types/app/api/models/defaults/route.ts +355 -0
  201. package/.next/types/app/api/models/route.ts +3 -1
  202. package/.next/types/app/api/pi-settings/route.ts +3 -1
  203. package/.next/types/app/api/plugins/route.ts +3 -1
  204. package/.next/types/app/api/prompts/route.ts +3 -1
  205. package/.next/types/app/api/remote/route.ts +3 -1
  206. package/.next/types/app/api/security/route.ts +3 -1
  207. package/.next/types/app/api/sessions/[id]/export/route.ts +3 -1
  208. package/.next/types/app/api/sessions/[id]/route.ts +3 -1
  209. package/.next/types/app/api/sessions/import/route.ts +3 -1
  210. package/.next/types/app/api/sessions/route.ts +3 -1
  211. package/.next/types/app/api/skills/route.ts +3 -1
  212. package/.next/types/app/api/update/route.ts +3 -1
  213. package/.next/types/app/api/version/route.ts +3 -1
  214. package/.next/types/app/api/web-auth/route.ts +3 -1
  215. package/.next/types/app/api/workspaces/route.ts +3 -1
  216. package/.next/types/app/layout.ts +4 -1
  217. package/.next/types/app/login/page.ts +4 -1
  218. package/.next/types/app/page.ts +4 -1
  219. package/.next/types/cache-life.d.ts +11 -43
  220. package/.next/types/routes.d.ts +4 -1
  221. package/.next/types/validator.ts +86 -2
  222. package/next.config.ts +2 -0
  223. package/package.json +8 -2
  224. package/scripts/release.mjs +3 -7
  225. package/scripts/runtime/check-installed.mjs +66 -0
  226. package/scripts/runtime/pi-packages.mjs +51 -0
  227. package/scripts/verify-package.mjs +29 -7
  228. package/src/app/api/agent/[id]/side-chat/route.ts +77 -0
  229. package/src/app/api/files/route.ts +5 -2
  230. package/src/app/api/github/route.ts +26 -0
  231. package/src/app/api/models/defaults/route.ts +18 -0
  232. package/src/app/api/models/route.ts +24 -1
  233. package/src/app/globals.css +1748 -0
  234. package/src/components/AppShell.tsx +145 -43
  235. package/src/components/ChatInput.tsx +264 -79
  236. package/src/components/ChatWindow.tsx +13 -23
  237. package/src/components/ContextMeter.tsx +213 -144
  238. package/src/components/DrawerPanel.tsx +1 -1
  239. package/src/components/FileViewer.tsx +89 -8
  240. package/src/components/GitPanel.tsx +110 -40
  241. package/src/components/GrowthTree.tsx +30 -5
  242. package/src/components/ModelSelector.tsx +21 -5
  243. package/src/components/ProjectPanel.tsx +55 -30
  244. package/src/components/PromptPanel.tsx +2 -4
  245. package/src/components/ProviderSetupModal.module.css +20 -0
  246. package/src/components/ProviderSetupModal.tsx +134 -29
  247. package/src/components/SettingsPanel.tsx +252 -27
  248. package/src/components/TrajectoryView.tsx +4 -6
  249. package/src/components/chat/reading-anchor.ts +81 -0
  250. package/src/components/composer/ComposerImages.tsx +326 -0
  251. package/src/components/side-chat/SideChatPanel.module.css +131 -0
  252. package/src/components/side-chat/SideChatPanel.tsx +189 -0
  253. package/src/components/side-chat/icons.tsx +8 -0
  254. package/src/components/side-chat/request-history.ts +29 -0
  255. package/src/components/side-chat/store.ts +248 -0
  256. package/src/hooks/useFileViewer.ts +6 -4
  257. package/src/hooks/useGrowth.ts +3 -0
  258. package/src/hooks/usePiWeb.ts +12 -2
  259. package/src/hooks/useWorkspaceFiles.ts +93 -0
  260. package/src/i18n.tsx +150 -2
  261. package/src/lib/agent-manager.ts +135 -104
  262. package/src/lib/files-service.ts +166 -3
  263. package/src/lib/format.ts +56 -0
  264. package/src/lib/git-service.ts +45 -13
  265. package/src/lib/github-identity.ts +245 -0
  266. package/src/lib/growth-service.ts +6 -2
  267. package/src/lib/hero-model-memory.ts +28 -0
  268. package/src/lib/model-defaults.ts +105 -0
  269. package/src/lib/model-draft.ts +257 -9
  270. package/src/lib/model-endpoint.ts +23 -0
  271. package/src/lib/model-files.ts +33 -0
  272. package/src/lib/models-service.ts +240 -78
  273. package/src/lib/path-security.ts +24 -32
  274. package/src/lib/pi.ts +27 -2
  275. package/src/lib/process-format.ts +3 -2
  276. package/src/lib/session/metrics.ts +31 -0
  277. package/src/lib/session-reader.ts +1 -1
  278. package/src/lib/side-chat/context.ts +123 -0
  279. package/src/lib/side-chat/service.ts +191 -0
  280. package/src/lib/side-chat/types.ts +61 -0
  281. package/src/lib/thinking-level.ts +39 -0
  282. package/src/lib/trajectory.ts +17 -3
  283. package/src/lib/types.ts +12 -1
  284. package/src/lib/update-service.ts +4 -6
  285. package/src/lib/workspace-index.ts +270 -0
  286. package/.next/server/chunks/229.js +0 -1
  287. package/.next/server/chunks/393.js +0 -1
  288. package/.next/server/chunks/421.js +0 -1
  289. package/.next/server/chunks/430.js +0 -193
  290. package/.next/server/chunks/496.js +0 -1
  291. package/.next/server/chunks/662.js +0 -2
  292. package/.next/server/chunks/686.js +0 -1
  293. package/.next/server/chunks/741.js +0 -165
  294. package/.next/server/chunks/802.js +0 -2
  295. package/.next/server/chunks/807.js +0 -1
  296. package/.next/server/chunks/813.js +0 -1
  297. package/.next/server/chunks/924.js +0 -6
  298. package/.next/static/chunks/2716.7ad073038e94a802.js +0 -1
  299. package/.next/static/chunks/316-5ea443ee6b512aa9.js +0 -1
  300. package/.next/static/chunks/3794-3679f1c05160977c.js +0 -52
  301. package/.next/static/chunks/4673.8a5a9e6855350e34.js +0 -1
  302. package/.next/static/chunks/4686-882531aef1d2e250.js +0 -1
  303. package/.next/static/chunks/4778.305dc6abb9889627.js +0 -1
  304. package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +0 -1
  305. package/.next/static/chunks/618.ee5d5bf1c647e483.js +0 -4
  306. package/.next/static/chunks/839.bfd2db36483faa3e.js +0 -1
  307. package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +0 -1
  308. package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +0 -1
  309. package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +0 -1
  310. package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +0 -1
  311. package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +0 -1
  312. package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +0 -1
  313. package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +0 -1
  314. package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +0 -1
  315. package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +0 -1
  316. package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +0 -1
  317. package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +0 -1
  318. package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +0 -1
  319. package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +0 -1
  320. package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +0 -1
  321. package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +0 -1
  322. package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +0 -1
  323. package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +0 -1
  324. package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +0 -1
  325. package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +0 -1
  326. package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +0 -1
  327. package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +0 -1
  328. package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +0 -1
  329. package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +0 -1
  330. package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +0 -1
  331. package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +0 -1
  332. package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +0 -1
  333. package/.next/static/chunks/app/page-c04af72e549695bd.js +0 -1
  334. package/.next/static/chunks/framework-c4e651fe89edfa91.js +0 -1
  335. package/.next/static/chunks/main-6159b22a8cb0b7c5.js +0 -5
  336. package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +0 -1
  337. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +0 -1
  338. package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +0 -1
  339. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +0 -1
  340. package/.next/static/chunks/webpack-9b4bb574e5817654.js +0 -1
  341. package/.next/static/css/ea27b9e30f76303f.css +0 -1
  342. package/.next/static/sNX5GQkOWuK5YEID1hT-C/_buildManifest.js +0 -1
  343. /package/.next/static/{sNX5GQkOWuK5YEID1hT-C → hYPL1-dmhKFjLS3f5yoqN}/_ssgManifest.js +0 -0
package/src/i18n.tsx CHANGED
@@ -38,13 +38,42 @@ const zh = {
38
38
  capacityFields: "容量",
39
39
  contextWindowLabel: "上下文窗口",
40
40
  maxTokensLabel: "最大输出",
41
- capacityHint: "留空用默认 · 支持 256K / 2M 写法",
41
+ capacityHint: "留空按 pi 缺省(128K / 16K)· 支持 256K / 2M 写法",
42
+ // 模型能力(视觉)与配置体检
43
+ visionLabel: "视觉",
44
+ visionHint: "该模型能否接收图片。未声明时 pi 按纯文本处理:你贴的截图会被换成一行占位文字(提示语还会说成「模型不支持图像」)",
45
+ visionUnset: "未声明",
46
+ visionInputText: "文本",
47
+ visionInputTextHint: "pi 总是接受文本输入;贴图能不能发由「图片」决定",
48
+ visionInputImage: "图片",
49
+ visionInputImageHint: "勾上表示该模型能收图(写进 models.json 的 input);不勾则按纯文本处理",
50
+ visionOn: "有视觉",
51
+ visionOff: "纯文本",
52
+ imagePreview: "点击放大预览",
53
+ imageViewer: "图片预览",
54
+ imageClearAll: "清空图片",
55
+ imagePrev: "上一张",
56
+ imageNext: "下一张",
57
+ chipExpandMore: "展开 {n}",
58
+ chipCollapse: "收起",
59
+ modelAuditTitle: "{n} 个模型没写能力字段",
60
+ modelAuditDesc: "没写的字段,pi 会按上下文 {ctx}、输出上限 {out}、纯文本处理。端点没报的值不会替你猜:照模型的真实规格填上(支持 256K / 1M 写法),保存时只补空着的格子,已写的值不改。",
61
+ modelAuditColModel: "模型",
62
+ modelAuditColContext: "上下文",
63
+ modelAuditColOutput: "输出上限",
64
+ modelAuditColVision: "视觉",
65
+ modelAuditVisionQuick: "视觉一起设:",
66
+ modelAuditVisionOn: "都有视觉",
67
+ modelAuditVisionOff: "都是纯文本",
68
+ modelAuditSave: "保存 {n} 个模型",
69
+ modelAuditSaveIdle: "填好后保存",
42
70
  capacityInvalid: "第 {n} 行:容量需为数字(可用 K/M)",
43
71
  discoverEmpty: "远端没有返回可识别的模型",
44
72
  candidateTitle: "发现 {count} 个模型,勾选要添加的",
45
73
  candidateAll: "全选",
46
74
  candidatePicked: "已选 {n} 个",
47
75
  candidateAdopt: "添加 {n} 个",
76
+ candidateInCatalog: "已在目录中",
48
77
  runningTool: "Running {name}…",
49
78
  thinkingLive: "Thinking",
50
79
  stepRunning: "Running",
@@ -127,6 +156,10 @@ const zh = {
127
156
  gitNoChanges: "工作区干净,没有变更",
128
157
  gitStatusUnknown: "Git 状态读取失败,无法确认是否有变更",
129
158
  gitRecentCommits: "最近提交",
159
+ gitStagingArea: "暂存区",
160
+ gitWorkingTree: "工作区",
161
+ gitGithubAccount: "GitHub 账号",
162
+ gitGithubUnknown: "未识别到 GitHub 账号",
130
163
  toolNoOutput: "无输出",
131
164
  toolFailed: "失败",
132
165
  thinkingClamped: "当前模型不支持「{requested}」,已改为「{level}」",
@@ -285,6 +318,11 @@ const zh = {
285
318
  extensionStatus: "扩展状态",
286
319
  contextUsed: "上下文已用",
287
320
  contextUnknown: "上下文用量未知",
321
+ contextEstimated: "上下文估算",
322
+ contextMeasured: "上下文用量",
323
+ contextBackground: "Main Conversation",
324
+ contextApiInput: "最近请求输入(API)",
325
+ contextChars: "字符",
288
326
  contextSystem: "系统提示词",
289
327
  contextMessages: "对话消息",
290
328
  contextTools: "工具",
@@ -326,6 +364,20 @@ const zh = {
326
364
  contextColdHint: "会话还没启动,System Prompt 和工具定义要启动后才算得出",
327
365
  contextCountNow: "现在统计",
328
366
  contextCounting: "统计中…",
367
+ contextWindowEdit: "修改",
368
+ contextWindowBuiltin: "pi 内置目录:{value}",
369
+ contextWindowUseBuiltin: "填入",
370
+ contextWindowScope: "改的是这个模型的全局配置(models.json),pi 终端和其它会话一起生效",
371
+ contextWindowOverBuiltin: "比内置目录大:超过模型实际上限时,请求会被上游拒绝(400)",
372
+ contextWindowNoBuiltin: "填模型实际支持的上限;填大了,请求会被上游拒绝(400)",
373
+ contextWindowInvalid: "填正整数,可带 K / M(如 200K)",
374
+ // @ 文件引用(输入 @ 弹候选)
375
+ mentionFiles: "@ 引用文件 / 文件夹",
376
+ mentionHint: "↑↓ 选择 · Enter 引用 · Esc 关闭",
377
+ mentionNoMatch: "没有匹配的文件",
378
+ mentionTruncated: "候选已截断",
379
+ mentionDiskSource: "目录遍历",
380
+ mentionDiskSourceHint: "当前工作区不是 git 仓库(或没装 git):候选来自目录遍历,会跳过 node_modules、构建产物等目录;列为 git 仓库后候选会自动按 .gitignore 过滤",
329
381
  // 统计条
330
382
  turns: "轮次",
331
383
  toolCalls: "工具调用",
@@ -416,6 +468,21 @@ const zh = {
416
468
  drawerPanel: "右侧抽屉",
417
469
  drawerSections: "抽屉入口",
418
470
  drawerPrompts: "提示词",
471
+ sideChat: "旁聊",
472
+ sideChatModel: "旁聊模型",
473
+ sideChatFollowMain: "跟随主会话",
474
+ sideChatNew: "新旁聊",
475
+ sideChatHistory: "旁聊记录",
476
+ sideChatMessages: "旁聊消息",
477
+ sideChatQuestion: "旁聊提问",
478
+ sideChatPlaceholder: "读到哪里有疑问,就在这里问…",
479
+ sideChatEmpty: "边读边问,主会话停在原处",
480
+ sideChatHint: "参考主会话,问答独立。记录仅保存在此浏览器。",
481
+ sideChatPickSession: "打开一个主会话后,就可以旁聊",
482
+ sideChatAnswering: "正在回答…",
483
+ sideChatContextTruncated: "背景较长,本次仅参考了部分主会话或旁聊记录。",
484
+ sideChatLengthLimited: "已达到本次输出上限,可以继续追问。",
485
+ sideChatStorageFailed: "浏览器未能保存旁聊记录,请先保留需要的内容。",
419
486
  setPlugins: "插件",
420
487
  setRemote: "远程连接",
421
488
  remoteBuiltin: "内置插件",
@@ -507,10 +574,14 @@ const zh = {
507
574
  allowPrivateDiscovery: "允许本机/私网",
508
575
  allowPrivateHint: "模型地址指向本机或私有网络(如 Ollama、LM Studio、one-api 本地网关)时勾选,本次探测放行;发消息不受此限制",
509
576
  discoveringModels: "获取中...",
577
+ autoDiscovering: "正在自动识别模型…",
578
+ autoDiscovered: "已自动识别 {n} 个模型(端点报了容量与视觉的已一并填好)",
579
+ autoDiscoverFailed: "自动识别失败,可点「获取可用模型」重试",
510
580
  usingDefaultModels: "正在使用 Pi SDK 默认模型 · {count} 个",
511
581
  customModelsRequired: "至少添加一个可发送的模型 ID",
512
582
  modelCatalogEmptyBuiltin: "未添加覆盖模型;模型选择器会继续实时使用 Pi SDK 的官方模型目录。",
513
583
  modelCatalogEmptyCustom: "模型选择器中将不显示任何模型;请至少添加一个模型 ID。",
584
+ catalogOverrideHint: "与内置目录同 ID 的行只覆盖填写的字段,推理、价格、思考档位等沿用内置值。",
514
585
  addModel: "添加模型",
515
586
  modelId: "模型 ID",
516
587
  createProvider: "创建提供方",
@@ -668,6 +739,9 @@ const zh = {
668
739
  viewerNotInSnapshot: "此文件不在快照里(超过 1MB 或被排除),显示磁盘上的当前内容",
669
740
  viewerLoadFailed: "加载失败",
670
741
  viewerEmpty: "从左侧树里选择一个文件",
742
+ viewerOpenFolder: "打开文件夹预览",
743
+ viewerDirHint: "文件夹:共 {n} 项(已按 .gitignore 过滤)",
744
+ viewerDirMore: "加载更多",
671
745
  // 流式输出
672
746
  streamStyleTitle: "流式输出样式",
673
747
  streamStyleDesc: "回答逐字出现的方式;改完立刻生效,正在流出的回答也一并换",
@@ -723,13 +797,42 @@ const en: Dict = {
723
797
  capacityFields: "Capacities",
724
798
  contextWindowLabel: "Context window",
725
799
  maxTokensLabel: "Max output",
726
- capacityHint: "blank = default · 256K / 2M accepted",
800
+ capacityHint: "blank = pi default (128K / 16K) · 256K / 2M accepted",
801
+ // model capability (vision) and config checkup
802
+ visionLabel: "Vision",
803
+ visionHint: "Whether this model accepts images. Left unset, pi treats it as text-only and replaces pasted screenshots with a placeholder line.",
804
+ visionUnset: "Not declared",
805
+ visionInputText: "Text",
806
+ visionInputTextHint: "pi always accepts text; whether images can be sent is decided by the Image box",
807
+ visionInputImage: "Image",
808
+ visionInputImageHint: "Checked means this model accepts images (written into models.json input); unchecked is treated as text-only",
809
+ visionOn: "Vision",
810
+ visionOff: "Text only",
811
+ imagePreview: "Click to preview",
812
+ imageViewer: "Image preview",
813
+ imageClearAll: "Clear images",
814
+ imagePrev: "Previous image",
815
+ imageNext: "Next image",
816
+ chipExpandMore: "Show {n} more",
817
+ chipCollapse: "Collapse",
818
+ modelAuditTitle: "{n} models don't declare their capabilities",
819
+ modelAuditDesc: "For fields left out, pi assumes a {ctx} context window, {out} max output and text-only input. Values the endpoint didn't report are never guessed: fill in the model's real limits (256K / 1M accepted). Saving only fills empty cells and never changes values already set.",
820
+ modelAuditColModel: "Model",
821
+ modelAuditColContext: "Context",
822
+ modelAuditColOutput: "Max output",
823
+ modelAuditColVision: "Vision",
824
+ modelAuditVisionQuick: "Set vision for all:",
825
+ modelAuditVisionOn: "All vision",
826
+ modelAuditVisionOff: "All text only",
827
+ modelAuditSave: "Save {n} models",
828
+ modelAuditSaveIdle: "Fill in, then save",
727
829
  capacityInvalid: "Row {n}: capacity must be a number (K/M ok)",
728
830
  discoverEmpty: "No recognizable models returned",
729
831
  candidateTitle: "Found {count} models — pick ones to add",
730
832
  candidateAll: "Select all",
731
833
  candidatePicked: "{n} selected",
732
834
  candidateAdopt: "Add {n}",
835
+ candidateInCatalog: "Already in catalog",
733
836
  runningTool: "Running {name}…",
734
837
  thinkingLive: "Thinking",
735
838
  stepRunning: "Running",
@@ -812,6 +915,10 @@ const en: Dict = {
812
915
  gitNoChanges: "Working tree clean",
813
916
  gitStatusUnknown: "Git status unavailable; changes could not be determined",
814
917
  gitRecentCommits: "Recent commits",
918
+ gitStagingArea: "Staging area",
919
+ gitWorkingTree: "Working tree",
920
+ gitGithubAccount: "GitHub account",
921
+ gitGithubUnknown: "No GitHub account detected",
815
922
  toolNoOutput: "no output",
816
923
  toolFailed: "failed",
817
924
  thinkingClamped: "This model does not support \"{requested}\"; using \"{level}\" instead",
@@ -968,6 +1075,11 @@ const en: Dict = {
968
1075
  extensionStatus: "Extension status",
969
1076
  contextUsed: "context used",
970
1077
  contextUnknown: "Context usage unknown",
1078
+ contextEstimated: "estimated context usage",
1079
+ contextMeasured: "context usage",
1080
+ contextBackground: "Main Conversation",
1081
+ contextApiInput: "Last request input (API)",
1082
+ contextChars: "chars",
971
1083
  contextSystem: "System prompt",
972
1084
  contextMessages: "Messages",
973
1085
  contextTools: "Tools",
@@ -1009,6 +1121,20 @@ const en: Dict = {
1009
1121
  contextColdHint: "Session not started yet — System Prompt and tool definitions are counted once it starts",
1010
1122
  contextCountNow: "Count now",
1011
1123
  contextCounting: "Counting…",
1124
+ contextWindowEdit: "Edit",
1125
+ contextWindowBuiltin: "pi built-in catalog: {value}",
1126
+ contextWindowUseBuiltin: "Use",
1127
+ contextWindowScope: "Changes this model's global config (models.json); the pi terminal and other sessions use it too",
1128
+ contextWindowOverBuiltin: "Larger than the built-in catalog: requests past the model's real limit are rejected upstream (400)",
1129
+ contextWindowNoBuiltin: "Enter the model's real limit; a larger value gets requests rejected upstream (400)",
1130
+ contextWindowInvalid: "Enter a positive integer; K / M allowed (e.g. 200K)",
1131
+ // @ file references (type @ to open the picker)
1132
+ mentionFiles: "@ reference files / folders",
1133
+ mentionHint: "↑↓ select · Enter reference · Esc close",
1134
+ mentionNoMatch: "No matching files",
1135
+ mentionTruncated: "list truncated",
1136
+ mentionDiskSource: "directory scan",
1137
+ mentionDiskSourceHint: "This workspace is not a git repository (or git is missing): candidates come from a directory scan that skips node_modules and build output; in a git repository they follow .gitignore automatically",
1012
1138
  turns: "turns",
1013
1139
  toolCalls: "tool calls",
1014
1140
  cost: "cost",
@@ -1094,6 +1220,21 @@ const en: Dict = {
1094
1220
  drawerPanel: "Drawer",
1095
1221
  drawerSections: "Drawer sections",
1096
1222
  drawerPrompts: "Prompts",
1223
+ sideChat: "Side chat",
1224
+ sideChatModel: "Side-chat model",
1225
+ sideChatFollowMain: "Follow main chat",
1226
+ sideChatNew: "New side chat",
1227
+ sideChatHistory: "Side-chat history",
1228
+ sideChatMessages: "Side-chat messages",
1229
+ sideChatQuestion: "Side-chat question",
1230
+ sideChatPlaceholder: "Ask about anything you're reading…",
1231
+ sideChatEmpty: "Ask as you read. Keep your place.",
1232
+ sideChatHint: "Uses the main chat as background. Separate answers, saved only in this browser.",
1233
+ sideChatPickSession: "Open a main conversation to start a side chat",
1234
+ sideChatAnswering: "Answering…",
1235
+ sideChatContextTruncated: "The background is long; this answer uses part of the main or side-chat history.",
1236
+ sideChatLengthLimited: "This answer reached its output limit. You can ask it to continue.",
1237
+ sideChatStorageFailed: "Your browser could not save the side chat. Keep a copy of anything you need.",
1097
1238
  setPlugins: "Plugins",
1098
1239
  setRemote: "Remote access",
1099
1240
  remoteBuiltin: "Built-in plugin",
@@ -1185,10 +1326,14 @@ const en: Dict = {
1185
1326
  allowPrivateDiscovery: "Allow local/private",
1186
1327
  allowPrivateHint: "Check this when the model address points to loopback or a private network (Ollama, LM Studio, one-api gateways) to allow this probe; chat requests are not affected",
1187
1328
  discoveringModels: "Fetching...",
1329
+ autoDiscovering: "Detecting models…",
1330
+ autoDiscovered: "Detected {n} models (capacity and vision filled in where the endpoint reports them)",
1331
+ autoDiscoverFailed: "Automatic detection failed — use the button to retry",
1188
1332
  usingDefaultModels: "Using Pi SDK default models · {count}",
1189
1333
  customModelsRequired: "Add at least one model ID that can be sent",
1190
1334
  modelCatalogEmptyBuiltin: "No model overrides. The model selector will continue using Pi SDK's live official catalog.",
1191
1335
  modelCatalogEmptyCustom: "No models will appear in the selector. Add at least one model ID.",
1336
+ catalogOverrideHint: "Rows matching a built-in model ID only override the fields you fill in; reasoning, pricing and thinking levels stay built-in.",
1192
1337
  addModel: "Add model",
1193
1338
  modelId: "Model ID",
1194
1339
  createProvider: "Create provider",
@@ -1345,6 +1490,9 @@ const en: Dict = {
1345
1490
  viewerNotInSnapshot: "Not in the snapshot (over 1MB or excluded); showing the current content from disk",
1346
1491
  viewerLoadFailed: "Failed to load",
1347
1492
  viewerEmpty: "Pick a file from the tree on the left",
1493
+ viewerOpenFolder: "Preview folder",
1494
+ viewerDirHint: "Folder: {n} items (ignored files hidden)",
1495
+ viewerDirMore: "Load more",
1348
1496
  // Streaming text
1349
1497
  streamStyleTitle: "Streaming text",
1350
1498
  streamStyleDesc: "How the answer appears while it arrives; applies to the stream already running",
@@ -25,17 +25,18 @@ import {
25
25
  SessionManager,
26
26
  createAgentSession,
27
27
  getModelRuntime,
28
+ refreshModelRuntime,
28
29
  openSessionManager,
29
30
  } from "./pi";
30
31
  import { createExtensionUiBridge, type ExtensionUiBridge } from "./extension-ui";
31
32
  import { createGrowthTracker, GROWTH_TRACKER_VERSION, type GrowthTracker } from "./growth-tracker";
32
33
  import { userTurnsFromEntries } from "./growth-turns";
34
+ import { projectedContextUsage } from "./session/metrics";
33
35
  import { TrajLedger, buildTrajectoryFromEntries, toTrajTokens } from "./trajectory";
34
36
  import { startArchivedSessionRetention } from "./session-retention";
35
37
  import { sanitizeToolOutput } from "./text-sanitize";
36
38
  import { BoundaryError } from "./path-security";
37
- import { estimateTokensOf } from "./process-format";
38
- import { getPiSettings } from "./pi-settings";
39
+ import { newSessionThinkingLevel } from "./thinking-level";
39
40
  import { getGrowthInitConsent, setGrowthInitConsent } from "./workspace-store";
40
41
  import { clampThinkingLevel, getSupportedThinkingLevels } from "@earendil-works/pi-ai";
41
42
  import type {
@@ -67,6 +68,8 @@ interface Managed {
67
68
  viewerGraceUntil?: number;
68
69
  /** 本轮已排队推送上下文分项(publishContextBreakdown 合并同一轮的多次触发) */
69
70
  breakdownQueued?: boolean;
71
+ /** 首次运行后 pi 才会把系统分段持久化,届时用它把分类从差分换成投影口径,只换一次 */
72
+ breakdownProjected?: boolean;
70
73
  buffer: { seq: number; json: string }[];
71
74
  seq: number;
72
75
  /**
@@ -349,36 +352,58 @@ function reapplyDesiredLevel(m: Managed, session: AgentSession): void {
349
352
  if (target !== session.thinkingLevel) session.setThinkingLevel(target as never);
350
353
  }
351
354
 
355
+ /**
356
+ * 新会话页选定模型后的思考档位:与界面显示的生效值同一规则(每模型覆盖 → 设置默认 → medium,再按模型能力钳制)。
357
+ * SDK 切换模型时会沿用会话原来的档位,新会话不能沿用:那个档位是 SDK 按它自己的初始模型定的。
358
+ */
359
+ function applyNewSessionThinking(m: Managed, session: AgentSession): void {
360
+ const model = session.model;
361
+ if (!model) return;
362
+ const settings = getSettingsManager(m.cwd);
363
+ const level = newSessionThinkingLevel({
364
+ supported: session.getAvailableThinkingLevels() as unknown as string[],
365
+ modelOverride: settings.getModelThinkingLevel(model.provider, model.id),
366
+ defaultLevel: settings.getDefaultThinkingLevel(),
367
+ });
368
+ if (level !== session.thinkingLevel) session.setThinkingLevel(level as never);
369
+ }
370
+
352
371
  function publishTraj(m: Managed): void {
353
372
  for (const entry of m.ledger.drainDirty()) {
354
373
  publish(m, { type: "traj", entry, ts: Date.now() });
355
374
  }
356
375
  }
357
376
 
358
- function timingStats(m: Managed): Pick<WebStats, "llmMs" | "toolMs" | "ttftMs" | "ttftSteps" | "decodeMs" | "decodeTokens"> {
377
+ function timingStats(m: Managed): Pick<WebStats, "llmMs" | "toolMs" | "ttftMs" | "ttftSteps" | "decodeMs" | "decodeTokens" | "llmTimedSteps" | "toolTimedCalls" | "decodeSteps"> {
359
378
  let llmMs = 0;
360
379
  let toolMs = 0;
361
380
  let ttftMs = 0;
362
381
  let ttftSteps = 0;
363
382
  let decodeMs = 0;
364
383
  let decodeTokens = 0;
384
+ let llmTimedSteps = 0;
385
+ let toolTimedCalls = 0;
386
+ let decodeSteps = 0;
365
387
  for (const entry of m.ledger.entries) {
366
388
  if (entry.kind === "tool") {
389
+ if (entry.timing?.durationMs !== undefined) toolTimedCalls++;
367
390
  toolMs += Math.max(0, entry.timing?.durationMs ?? 0);
368
391
  continue;
369
392
  }
370
393
  if (entry.kind !== "message") continue;
394
+ if (entry.timing?.durationMs !== undefined) llmTimedSteps++;
371
395
  llmMs += Math.max(0, entry.timing?.durationMs ?? 0);
372
396
  if (entry.timing?.ttftMs !== undefined) {
373
397
  ttftMs += Math.max(0, entry.timing.ttftMs);
374
398
  ttftSteps += 1;
375
399
  }
376
- if (entry.timing?.decodeMs !== undefined && entry.tokens?.output !== undefined) {
377
- decodeMs += Math.max(0, entry.timing.decodeMs);
400
+ if ((entry.timing?.decodeMs ?? 0) > 0 && entry.tokens?.output !== undefined) {
401
+ decodeSteps++;
402
+ decodeMs += Math.max(0, entry.timing?.decodeMs ?? 0);
378
403
  decodeTokens += Math.max(0, entry.tokens.output);
379
404
  }
380
405
  }
381
- return { llmMs, toolMs, ttftMs, ttftSteps, decodeMs, decodeTokens };
406
+ return { llmMs, toolMs, ttftMs, ttftSteps, decodeMs, decodeTokens, llmTimedSteps, toolTimedCalls, decodeSteps };
382
407
  }
383
408
 
384
409
  function sessionStats(m: Managed): WebStats | null {
@@ -415,7 +440,7 @@ function coldSessionStats(m: Managed): WebStats {
415
440
  cost += Number(usage.cost?.total ?? 0);
416
441
  };
417
442
  for (const entry of m.sm.getEntries() as any[]) {
418
- if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) addUsage(entry.usage);
443
+ if ((entry.type === "usage" || entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) addUsage(entry.usage);
419
444
  if (entry.type !== "message" || !entry.message) continue;
420
445
  totalMessages += 1;
421
446
  const message = entry.message;
@@ -497,7 +522,14 @@ function translate(m: Managed, evt: AgentSessionEvent): void {
497
522
  // the browser receives the durable entry ID without reordering later tool events.
498
523
  deferPublish(m, () => {
499
524
  const msg = projectMessage(m, raw);
500
- if (msg.role === "assistant") msg.endedAt = now;
525
+ if (msg.role === "assistant") {
526
+ msg.endedAt = now;
527
+ // 首次回复结束后 pi 已把系统分段写进会话文件,此时分类能读到投影(比 systemPrompt 差分精确)
528
+ if (!m.breakdownProjected) {
529
+ m.breakdownProjected = true;
530
+ publishContextBreakdown(m);
531
+ }
532
+ }
501
533
  const usage = usageEvent(m);
502
534
  return usage
503
535
  ? [{ type: "message", message: msg, phase: "end", ts: now }, usage]
@@ -597,8 +629,8 @@ function translate(m: Managed, evt: AgentSessionEvent): void {
597
629
  else publish(m, { type: "model", thinkingLevel: (evt as any).level, ts: now });
598
630
  break;
599
631
  case "auto_retry_start":
600
- // 重试是流程内通知:发一条结构化 retry 事件(界面聚合成「重试 n/max」一行),
601
- // 不再走 error 通道,免得每次尝试都多出一条报错。
632
+ // 重试是流程内通知:发一条结构化 retry 事件(界面聚合成「重试 n/max」一行),
633
+ // 不再走 error 通道,免得每次尝试都多出一条报错。
602
634
  publish(m, {
603
635
  type: "retry",
604
636
  attempt: Number((evt as any).attempt) || 0,
@@ -607,6 +639,13 @@ function translate(m: Managed, evt: AgentSessionEvent): void {
607
639
  ts: now,
608
640
  });
609
641
  break;
642
+ case "auto_retry_end":
643
+ // 重试收尾:SDK 成功/最终失败/被中止都会发这条,而重试成功后的这轮对话
644
+ // agent_settled 之前界面上还留着「重试 1/5 terminated」那一行(用户看到的
645
+ // 就是「已经恢复了还显示错误」)。这里显式发一条清除事件,重试区间一结束
646
+ // 就收掉这行;真失败时紧随其后的 assistant error 消息会照常出错误块。
647
+ publish(m, { type: "retry_end", attempt: Number((evt as any).attempt) || 0, ts: now });
648
+ break;
610
649
  default:
611
650
  break;
612
651
  }
@@ -742,13 +781,9 @@ const SKILLS_SECTION_START = "The following skills provide specialized instructi
742
781
  const SKILLS_SECTION_END = "</available_skills>";
743
782
 
744
783
  /**
745
- * 上下文占用的服务端估算分项。systemPrompt 是整段拼接(正文 + memory + skills + 工具列表),
746
- * 所以 Memory / Skills 从资源加载器与 systemPrompt 的资源段差分得出:
747
- * SDK 的拼接顺序固定为 正文 → context files → skills → 工具列表,按内容定位切块。
748
- *
749
- * 冷会话(只浏览历史、不冷启动 agent)拿不到组装后的 system prompt 和工具定义,这三项为 0、标 cold;
750
- * Memory / Skills / Compacted / Buffer 不依赖 agent,照常从发现缓存、会话文件和设置里算,
751
- * 不能因为没启动就整块归零(那样弹窗只剩消息几类)。
784
+ * 当前上下文的字符分类,优先读 SDK 投影中的系统分段、工具 schema 与摘要。
785
+ * 旧客户端仍可读取字符/4 的估算字段;新客户端仅将字符数作为分类数据。
786
+ * 没有持久化系统消息且尚未启动的会话,资源来自发现缓存并标记 cold。
752
787
  */
753
788
  async function buildContextBreakdown(m: Managed, resources: ContextResource[]): Promise<ContextBreakdown> {
754
789
  const session = m.session;
@@ -758,64 +793,56 @@ async function buildContextBreakdown(m: Managed, resources: ContextResource[]):
758
793
  };
759
794
  // Memory:AGENTS.md 等注入文件(contextResources 的 project 部分)
760
795
  const memoryText = resources.filter((r) => r.source === "project").map((r) => r.content).join("\n\n");
761
- breakdown.memory = estimateTokensOf(memoryText);
762
- if (session) {
763
- try {
764
- const prompt = session.systemPrompt ?? "";
765
- // Skills:按 SDK 的 formatSkillsForPrompt 段落定位。该段落没有 Markdown 标题(以
766
- // "The following skills provide specialized instructions…" 开头、</available_skills> 结尾),
767
- // 以前按 "## Skills" 找永远找不到,技能段一直是 0。
768
- try {
769
- if (session.resourceLoader.getSkills().skills.length && prompt) {
770
- const start = prompt.indexOf(SKILLS_SECTION_START);
771
- const end = prompt.indexOf(SKILLS_SECTION_END);
772
- if (start >= 0 && end > start) breakdown.skills = estimateTokensOf(prompt.slice(start, end + SKILLS_SECTION_END.length));
773
- }
774
- } catch {
775
- /* loader 不可用则跳过 */
796
+ // 字符分项只读取当前投影中的内容。历史压缩摘要、未激活工具不混入当前统计。
797
+ const characters = { systemPrompt: 0, systemTools: 0, customTools: 0, memory: 0, skills: 0, compacted: 0 };
798
+ try {
799
+ const messages = m.sm.buildSessionProjection().messages as any[];
800
+ const system = messages.findLast((message) => message.role === "system");
801
+ if (system) {
802
+ const contentChars = typeof system.content === "string" ? system.content.length : textOf(system.content).length;
803
+ characters.memory = system.sections?.project_context?.length ?? 0;
804
+ characters.skills = system.sections?.skills?.length ?? 0;
805
+ characters.systemPrompt = contentChars + Object.entries(system.sections ?? {})
806
+ .filter(([key]) => key !== "project_context" && key !== "skills")
807
+ .reduce((sum, [, value]) => sum + (typeof value === "string" ? value.length : 0), 0);
808
+ const builtinNames = new Set(session
809
+ ? session.getAllTools().filter((tool: any) => tool.sourceInfo?.source === "builtin").map((tool) => tool.name)
810
+ : ["read", "bash", "edit", "write", "grep", "find", "ls"]);
811
+ for (const tool of system.toolsAdded ?? []) {
812
+ characters[builtinNames.has(tool.name) ? "systemTools" : "customTools"] += JSON.stringify(tool).length;
776
813
  }
777
- // System Prompt 正文 = 全文减去资源段与 skills 段的近似差分,下限 0
778
- breakdown.systemPrompt = Math.max(0, estimateTokensOf(prompt) - breakdown.memory - breakdown.skills);
779
- // 工具:builtin 与扩展注册的分开统计(schema + description + guidelines 序列化估算)
780
- for (const tool of session.getAllTools()) {
781
- const size = estimateTokensOf(JSON.stringify({
782
- name: tool.name,
783
- description: tool.description,
784
- parameters: tool.parameters,
785
- promptGuidelines: tool.promptGuidelines,
786
- }));
787
- if ((tool as { sourceInfo?: { source?: string } }).sourceInfo?.source === "builtin") breakdown.systemTools += size;
788
- else breakdown.customTools += size;
814
+ delete breakdown.cold;
815
+ } else if (session) {
816
+ const prompt = session.systemPrompt ?? "";
817
+ characters.memory = memoryText.length;
818
+ const start = prompt.indexOf(SKILLS_SECTION_START);
819
+ const end = prompt.indexOf(SKILLS_SECTION_END);
820
+ characters.skills = start >= 0 && end > start ? prompt.slice(start, end + SKILLS_SECTION_END.length).length : 0;
821
+ characters.systemPrompt = Math.max(0, prompt.length - characters.memory - characters.skills);
822
+ const active = new Set(session.getActiveToolNames());
823
+ for (const tool of session.getAllTools().filter((tool) => active.has(tool.name))) {
824
+ const size = JSON.stringify({ name: tool.name, description: tool.description, parameters: tool.parameters }).length;
825
+ characters[(tool as any).sourceInfo?.source === "builtin" ? "systemTools" : "customTools"] += size;
789
826
  }
790
- } catch {
791
- /* systemPrompt / getAllTools 不可用则保持 0 */
792
- }
793
- } else {
794
- // 冷会话:技能段用发现缓存里的清单按 SDK 同一个格式化函数估算(启动后改用实际 prompt 里的那段)
795
- try {
827
+ } else {
828
+ // 初次启动前只能读到待注入的资源,界面保留 cold 提示。
829
+ characters.memory = memoryText.length;
796
830
  const loader = peekResourceLoader(m.cwd) ?? getResourceLoader(m.cwd);
797
- breakdown.skills = estimateTokensOf(formatSkillsForPrompt(loader.getSkills().skills).trim());
798
- } catch {
799
- /* 发现缓存还没好则跳过 */
831
+ characters.skills = formatSkillsForPrompt(loader.getSkills().skills).trim().length;
800
832
  }
801
- }
802
- // 最新一次压缩的摘要(会话文件里就有,冷热都能算)
803
- try {
804
- const entries = m.sm.getEntries() as unknown as { type?: string; summary?: string }[];
805
- for (let i = entries.length - 1; i >= 0; i -= 1) {
806
- const entry = entries[i];
807
- if (entry?.type === "compaction" && typeof entry.summary === "string") {
808
- breakdown.compacted = estimateTokensOf(entry.summary);
809
- break;
810
- }
833
+ for (const message of messages) {
834
+ if (message.role === "compactionSummary" || message.role === "branchSummary") characters.compacted += message.summary?.length ?? 0;
811
835
  }
836
+ breakdown.characters = characters;
837
+ // 保留旧客户端的估算字段,当前客户端直接展示实际字符数。
838
+ for (const key of Object.keys(characters) as Array<keyof typeof characters>) breakdown[key] = Math.ceil(characters[key] / 4);
812
839
  } catch {
813
- /* entries 不可用则跳过 */
840
+ /* 投影不可用时不给字符分项,不能从旧 token 估算反推字符数。 */
814
841
  }
815
- // auto-compact 预留 = compaction.reserveTokens(启用时)
842
+ // auto-compact 预留读本工作区、当前模型实际使用的设置。
816
843
  try {
817
- const settings = await getPiSettings();
818
- if (settings.compaction.enabled) breakdown.autoCompactBuffer = settings.compaction.reserveTokens;
844
+ const compaction = getSettingsManager(m.cwd).getCompactionSettings(session?.model);
845
+ if (compaction.enabled) breakdown.autoCompactBuffer = compaction.reserveTokens;
819
846
  } catch {
820
847
  /* 设置读取失败则保持 0 */
821
848
  }
@@ -1208,11 +1235,7 @@ async function reloadManagedResources(m: Managed): Promise<boolean> {
1208
1235
  m.resourceReloadPending = false;
1209
1236
  try {
1210
1237
  await m.session.reload();
1211
- // SDK 的 reload 不会换当前模型对象:models.json 改了 baseUrl / contextWindow 后,
1212
- // 会话手里还是旧对象。按 provider/id 从(已就地刷新的)共享 runtime 取一次新的。
1213
- const current = m.session.model;
1214
- const fresh = current && m.session.modelRuntime.getModel(current.provider, current.id);
1215
- if (fresh && fresh !== current) m.session.agent.state.model = fresh;
1238
+ swapCurrentModelObject(m.session);
1216
1239
  return true;
1217
1240
  } catch (err) {
1218
1241
  publish(m, { type: "error", message: `重载扩展失败:${String((err as Error)?.message ?? err)}`, ts: Date.now() });
@@ -1222,6 +1245,32 @@ async function reloadManagedResources(m: Managed): Promise<boolean> {
1222
1245
  }
1223
1246
  }
1224
1247
 
1248
+ /**
1249
+ * SDK 的 reload 不会换当前模型对象:models.json 改了 baseUrl / contextWindow 后,会话手里还是旧对象。
1250
+ * 按 provider/id 从(已就地刷新的)共享 runtime 取一次新的。
1251
+ */
1252
+ function swapCurrentModelObject(session: AgentSession): void {
1253
+ const current = session.model;
1254
+ const fresh = current && session.modelRuntime.getModel(current.provider, current.id);
1255
+ if (fresh && fresh !== current) session.agent.state.model = fresh;
1256
+ }
1257
+
1258
+ /**
1259
+ * 共享目录刷新后,给正在用这个模型的会话换上新模型对象(上下文窗口等字段跟着变),再推一次用量让圆环当场更新。
1260
+ * 运行中的会话也换:SDK 在扩展注册 provider 时同样会中途替换 agent.state.model,已发出的请求不受影响。
1261
+ */
1262
+ export function refreshSessionModels(provider: string, id: string): number {
1263
+ let n = 0;
1264
+ for (const m of sessions.values()) {
1265
+ const current = m.session?.model;
1266
+ if (!m.session || current?.provider !== provider || current.id !== id) continue;
1267
+ swapCurrentModelObject(m.session);
1268
+ publishUsage(m);
1269
+ n += 1;
1270
+ }
1271
+ return n;
1272
+ }
1273
+
1225
1274
  export async function reloadSessionsForCwd(cwd?: string): Promise<number> {
1226
1275
  const key = (p: string) => (process.platform === "win32" ? p.replace(/\\/g, "/").toLowerCase() : p.replace(/\\/g, "/"));
1227
1276
  let n = 0;
@@ -1325,42 +1374,13 @@ export async function buildSnapshot(m: Managed): Promise<WebSnapshot> {
1325
1374
  const usage = session.getContextUsage() ?? null;
1326
1375
  contextUsage = usage ? { tokens: usage.tokens, contextWindow: usage.contextWindow, percent: usage.percent } : null;
1327
1376
  } else if (model) {
1328
- // 冷会话没有 SDK 实例:用最后一轮 assistant 用量(input+cache+output ≈ 该轮后的
1329
- // 上下文规模)除以模型窗口估算,避免上下文计量永远显示 0% / —
1377
+ // 冷会话也使用当前分支投影,包含最后一次 API 用量之后的用户消息和工具输出。
1330
1378
  try {
1331
1379
  const rt = await getModelRuntime();
1332
1380
  const modelMeta = (rt.getModels(model.provider) as any[]).find((x) => String(x.id) === model.id);
1333
- const window = Number(modelMeta?.contextWindow ?? 0);
1334
- let lastTurnTokens = 0;
1335
- // 与 SDK getContextUsage 同一条规则:最近一次压缩之后还没有新的助手回复时,上下文大小未知(不能拿压缩前的用量充数)
1336
- const entries = m.sm.getEntries() as Array<{ type?: string; message?: { role?: string; stopReason?: string } }>;
1337
- let compactedSinceLastAssistant = false;
1338
- for (let i = entries.length - 1; i >= 0; i--) {
1339
- const e = entries[i];
1340
- if (e.type === "compaction" || e.type === "branch_summary") {
1341
- compactedSinceLastAssistant = true;
1342
- break;
1343
- }
1344
- if (e.type === "message" && e.message?.role === "assistant" && e.message.stopReason !== "error" && e.message.stopReason !== "aborted") break;
1345
- }
1346
- for (let i = messages.length - 1; i >= 0 && !compactedSinceLastAssistant; i--) {
1347
- const msg = messages[i];
1348
- if (msg.role === "assistant" && msg.usage) {
1349
- lastTurnTokens =
1350
- Number(msg.usage.input ?? 0) +
1351
- Number(msg.usage.cacheRead ?? 0) +
1352
- Number(msg.usage.cacheWrite ?? 0) +
1353
- Number(msg.usage.output ?? 0);
1354
- break;
1355
- }
1356
- }
1357
- if (window > 0 && lastTurnTokens > 0) {
1358
- contextUsage = { tokens: lastTurnTokens, contextWindow: window, percent: (lastTurnTokens / window) * 100 };
1359
- } else if (window > 0) {
1360
- contextUsage = { tokens: null, contextWindow: window, percent: null };
1361
- }
1381
+ contextUsage = projectedContextUsage(m.sm.buildSessionProjection(), m.sm.getBranch(), Number(modelMeta?.contextWindow ?? 0));
1362
1382
  } catch {
1363
- /* 模型目录不可用则维持 null(UI 显示 0%) */
1383
+ /* 模型或投影不可用时用量未知。 */
1364
1384
  }
1365
1385
  }
1366
1386
 
@@ -1600,6 +1620,8 @@ export async function execute(m: Managed, cmd: AgentCommand): Promise<CommandRes
1600
1620
  if (!model) return { ok: false, error: `unknown model ${cmd.provider}/${cmd.modelId}` };
1601
1621
  await session.setModel(model, { persist: false });
1602
1622
  reapplyDesiredLevel(m, session);
1623
+ // 新会话页选的模型(fresh):思考档位按新会话的规则定;之后显式选的档位仍会覆盖
1624
+ if (cmd.fresh === true && !m.desiredThinkingLevel) applyNewSessionThinking(m, session);
1603
1625
  publishModelState(m, session);
1604
1626
  return { ok: true, data: { thinkingLevel: session.thinkingLevel, thinkingLevels: session.getAvailableThinkingLevels() } };
1605
1627
  }
@@ -1712,8 +1734,17 @@ export async function execute(m: Managed, cmd: AgentCommand): Promise<CommandRes
1712
1734
  }
1713
1735
  case "reload": {
1714
1736
  if (isManagedBusy(m)) return { ok: false, error: "session is busy" };
1737
+ // /reload:先就地刷新共享的模型目录(models.json / auth.json 可能是手改的);其它会话手里的
1738
+ // 模型对象跟着换新,忙着的等本轮结束再换;本会话重载资源后再换一次
1739
+ await refreshModelRuntime();
1740
+ for (const other of sessions.values()) {
1741
+ if (other === m || !other.session) continue;
1742
+ if (isManagedBusy(other)) other.resourceReloadPending = true;
1743
+ else swapCurrentModelObject(other.session);
1744
+ }
1715
1745
  const session = await ensureSession(m);
1716
1746
  await session.reload();
1747
+ swapCurrentModelObject(session);
1717
1748
  return { ok: true };
1718
1749
  }
1719
1750
  case "fork": {