@rexvane/piweb 0.3.21 → 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 (293) 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 +10 -10
  8. package/.next/required-server-files.js +16 -1
  9. package/.next/required-server-files.json +16 -1
  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 +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]/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 -1
  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 -2
  113. package/.next/server/chunks/686.js +1 -1
  114. package/.next/server/chunks/813.js +23 -1
  115. package/.next/server/chunks/920.js +2 -2
  116. package/.next/server/chunks/924.js +1 -1
  117. package/.next/server/chunks/970.js +305 -0
  118. package/.next/server/middleware-build-manifest.js +1 -1
  119. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  120. package/.next/server/middleware.js +185 -23
  121. package/.next/server/middleware.js.nft.json +1 -1
  122. package/.next/server/pages/404.html +1 -1
  123. package/.next/server/pages/500.html +1 -1
  124. package/.next/server/preview-props.json +5 -0
  125. package/.next/server/server-reference-manifest.json +1 -1
  126. package/.next/static/WMazYvD-gbeLsCUfUxvvM/_buildManifest.js +1 -0
  127. package/.next/static/chunks/316-698d10eb465bd8ef.js +1 -0
  128. package/.next/static/chunks/3794-0978865cda773008.js +52 -0
  129. package/.next/static/chunks/4673.4cf66cbdd8364c57.js +1 -0
  130. package/.next/static/chunks/4686-09fcd0028079006c.js +1 -0
  131. package/.next/static/chunks/4778.eddd3a5813f85824.js +1 -0
  132. package/.next/static/chunks/4bd1b696-e199e788e58acf79.js +1 -0
  133. package/.next/static/chunks/{5173.91ddb803785009f8.js → 5173.dfd1cbb795b9a21c.js} +1 -1
  134. package/.next/static/chunks/618.949981da99be7182.js +4 -0
  135. package/.next/static/chunks/7411.67366dc5032a141d.js +1 -0
  136. package/.next/static/chunks/app/_global-error/page-f977af7f858990a2.js +1 -0
  137. package/.next/static/chunks/app/api/agent/[id]/events/route-f977af7f858990a2.js +1 -0
  138. package/.next/static/chunks/app/api/agent/[id]/route-f977af7f858990a2.js +1 -0
  139. package/.next/static/chunks/app/api/agent/[id]/tree/route-f977af7f858990a2.js +1 -0
  140. package/.next/static/chunks/app/api/agent/new/route-f977af7f858990a2.js +1 -0
  141. package/.next/static/chunks/app/api/config/route-f977af7f858990a2.js +1 -0
  142. package/.next/static/chunks/app/api/files/route-f977af7f858990a2.js +1 -0
  143. package/.next/static/chunks/app/api/fs/route-f977af7f858990a2.js +1 -0
  144. package/.next/static/chunks/app/api/git/route-f977af7f858990a2.js +1 -0
  145. package/.next/static/chunks/app/api/github/route-f977af7f858990a2.js +1 -0
  146. package/.next/static/chunks/app/api/growth/route-f977af7f858990a2.js +1 -0
  147. package/.next/static/chunks/app/api/health/route-f977af7f858990a2.js +1 -0
  148. package/.next/static/chunks/app/api/models/defaults/route-f977af7f858990a2.js +1 -0
  149. package/.next/static/chunks/app/api/models/route-f977af7f858990a2.js +1 -0
  150. package/.next/static/chunks/app/api/pi-settings/route-f977af7f858990a2.js +1 -0
  151. package/.next/static/chunks/app/api/plugins/route-f977af7f858990a2.js +1 -0
  152. package/.next/static/chunks/app/api/prompts/route-f977af7f858990a2.js +1 -0
  153. package/.next/static/chunks/app/api/remote/route-f977af7f858990a2.js +1 -0
  154. package/.next/static/chunks/app/api/security/route-f977af7f858990a2.js +1 -0
  155. package/.next/static/chunks/app/api/sessions/[id]/export/route-f977af7f858990a2.js +1 -0
  156. package/.next/static/chunks/app/api/sessions/[id]/route-f977af7f858990a2.js +1 -0
  157. package/.next/static/chunks/app/api/sessions/import/route-f977af7f858990a2.js +1 -0
  158. package/.next/static/chunks/app/api/sessions/route-f977af7f858990a2.js +1 -0
  159. package/.next/static/chunks/app/api/skills/route-f977af7f858990a2.js +1 -0
  160. package/.next/static/chunks/app/api/update/route-f977af7f858990a2.js +1 -0
  161. package/.next/static/chunks/app/api/version/route-f977af7f858990a2.js +1 -0
  162. package/.next/static/chunks/app/api/web-auth/route-f977af7f858990a2.js +1 -0
  163. package/.next/static/chunks/app/api/workspaces/route-f977af7f858990a2.js +1 -0
  164. package/.next/static/chunks/app/page-fc4ed2ff29b6235e.js +1 -0
  165. package/.next/static/chunks/framework-47b723d27f4a4963.js +1 -0
  166. package/.next/static/chunks/main-5403f0764579e5fe.js +5 -0
  167. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f977af7f858990a2.js +1 -0
  168. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f977af7f858990a2.js +1 -0
  169. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f977af7f858990a2.js +1 -0
  170. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f977af7f858990a2.js +1 -0
  171. package/.next/static/chunks/webpack-22ebffc20adb44ae.js +1 -0
  172. package/.next/static/css/6671d0d1589fdc0e.css +1 -0
  173. package/.next/static/css/{2959bd6b626b7ffe.css → f15a853af8b5127f.css} +1 -1
  174. package/.next/types/app/api/agent/[id]/events/route.ts +3 -1
  175. package/.next/types/app/api/agent/[id]/route.ts +3 -1
  176. package/.next/types/app/api/agent/[id]/tree/route.ts +3 -1
  177. package/.next/types/app/api/agent/new/route.ts +3 -1
  178. package/.next/types/app/api/config/route.ts +3 -1
  179. package/.next/types/app/api/files/route.ts +3 -1
  180. package/.next/types/app/api/fs/route.ts +3 -1
  181. package/.next/types/app/api/git/route.ts +3 -1
  182. package/.next/types/app/api/github/route.ts +355 -0
  183. package/.next/types/app/api/growth/route.ts +3 -1
  184. package/.next/types/app/api/health/route.ts +3 -1
  185. package/.next/types/app/api/models/defaults/route.ts +355 -0
  186. package/.next/types/app/api/models/route.ts +3 -1
  187. package/.next/types/app/api/pi-settings/route.ts +3 -1
  188. package/.next/types/app/api/plugins/route.ts +3 -1
  189. package/.next/types/app/api/prompts/route.ts +3 -1
  190. package/.next/types/app/api/remote/route.ts +3 -1
  191. package/.next/types/app/api/security/route.ts +3 -1
  192. package/.next/types/app/api/sessions/[id]/export/route.ts +3 -1
  193. package/.next/types/app/api/sessions/[id]/route.ts +3 -1
  194. package/.next/types/app/api/sessions/import/route.ts +3 -1
  195. package/.next/types/app/api/sessions/route.ts +3 -1
  196. package/.next/types/app/api/skills/route.ts +3 -1
  197. package/.next/types/app/api/update/route.ts +3 -1
  198. package/.next/types/app/api/version/route.ts +3 -1
  199. package/.next/types/app/api/web-auth/route.ts +3 -1
  200. package/.next/types/app/api/workspaces/route.ts +3 -1
  201. package/.next/types/app/layout.ts +4 -1
  202. package/.next/types/app/login/page.ts +4 -1
  203. package/.next/types/app/page.ts +4 -1
  204. package/.next/types/cache-life.d.ts +11 -43
  205. package/.next/types/routes.d.ts +3 -1
  206. package/.next/types/validator.ts +76 -2
  207. package/package.json +2 -2
  208. package/src/app/api/files/route.ts +5 -2
  209. package/src/app/api/github/route.ts +26 -0
  210. package/src/app/api/models/defaults/route.ts +18 -0
  211. package/src/app/api/models/route.ts +24 -1
  212. package/src/app/globals.css +186 -0
  213. package/src/components/AppShell.tsx +82 -30
  214. package/src/components/ChatInput.tsx +244 -63
  215. package/src/components/ChatWindow.tsx +18 -27
  216. package/src/components/ContextMeter.tsx +193 -143
  217. package/src/components/GitPanel.tsx +110 -40
  218. package/src/components/ProjectPanel.tsx +55 -30
  219. package/src/components/PromptPanel.tsx +2 -4
  220. package/src/components/ProviderSetupModal.module.css +20 -0
  221. package/src/components/ProviderSetupModal.tsx +134 -29
  222. package/src/components/SettingsPanel.tsx +243 -27
  223. package/src/components/TrajectoryView.tsx +4 -6
  224. package/src/components/composer/ComposerImages.tsx +306 -0
  225. package/src/hooks/useGrowth.ts +3 -0
  226. package/src/hooks/usePiWeb.ts +12 -2
  227. package/src/hooks/useWorkspaceFiles.ts +93 -0
  228. package/src/i18n.tsx +108 -2
  229. package/src/lib/agent-manager.ts +135 -104
  230. package/src/lib/files-service.ts +100 -1
  231. package/src/lib/format.ts +56 -0
  232. package/src/lib/git-service.ts +45 -13
  233. package/src/lib/github-identity.ts +245 -0
  234. package/src/lib/growth-service.ts +6 -2
  235. package/src/lib/hero-model-memory.ts +28 -0
  236. package/src/lib/model-defaults.ts +105 -0
  237. package/src/lib/model-draft.ts +257 -9
  238. package/src/lib/model-endpoint.ts +23 -0
  239. package/src/lib/model-files.ts +33 -0
  240. package/src/lib/models-service.ts +240 -78
  241. package/src/lib/pi.ts +27 -2
  242. package/src/lib/process-format.ts +3 -2
  243. package/src/lib/session/metrics.ts +31 -0
  244. package/src/lib/thinking-level.ts +39 -0
  245. package/src/lib/trajectory.ts +17 -3
  246. package/src/lib/types.ts +12 -1
  247. package/src/lib/workspace-index.ts +270 -0
  248. package/.next/server/chunks/741.js +0 -165
  249. package/.next/static/chunks/316-5ea443ee6b512aa9.js +0 -1
  250. package/.next/static/chunks/3794-3679f1c05160977c.js +0 -52
  251. package/.next/static/chunks/4673.8a5a9e6855350e34.js +0 -1
  252. package/.next/static/chunks/4686-882531aef1d2e250.js +0 -1
  253. package/.next/static/chunks/4778.305dc6abb9889627.js +0 -1
  254. package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +0 -1
  255. package/.next/static/chunks/618.ee5d5bf1c647e483.js +0 -4
  256. package/.next/static/chunks/839.bfd2db36483faa3e.js +0 -1
  257. package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +0 -1
  258. package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +0 -1
  259. package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +0 -1
  260. package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +0 -1
  261. package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +0 -1
  262. package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +0 -1
  263. package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +0 -1
  264. package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +0 -1
  265. package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +0 -1
  266. package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +0 -1
  267. package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +0 -1
  268. package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +0 -1
  269. package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +0 -1
  270. package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +0 -1
  271. package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +0 -1
  272. package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +0 -1
  273. package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +0 -1
  274. package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +0 -1
  275. package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +0 -1
  276. package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +0 -1
  277. package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +0 -1
  278. package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +0 -1
  279. package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +0 -1
  280. package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +0 -1
  281. package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +0 -1
  282. package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +0 -1
  283. package/.next/static/chunks/app/page-c04af72e549695bd.js +0 -1
  284. package/.next/static/chunks/framework-c4e651fe89edfa91.js +0 -1
  285. package/.next/static/chunks/main-6159b22a8cb0b7c5.js +0 -5
  286. package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +0 -1
  287. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +0 -1
  288. package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +0 -1
  289. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +0 -1
  290. package/.next/static/chunks/webpack-9b4bb574e5817654.js +0 -1
  291. package/.next/static/css/ea27b9e30f76303f.css +0 -1
  292. package/.next/static/sNX5GQkOWuK5YEID1hT-C/_buildManifest.js +0 -1
  293. /package/.next/static/{sNX5GQkOWuK5YEID1hT-C → WMazYvD-gbeLsCUfUxvvM}/_ssgManifest.js +0 -0
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 思考档位的纯函数,客户端与服务端共用(不引入 pi-ai)。规则与 pi-ai 的 clampThinkingLevel、
3
+ * pi 的 createAgentSession 一致;tests/thinking-level.test.ts 里逐项和 pi-ai 对照。
4
+ */
5
+
6
+ /** pi-ai 的 EXTENDED_THINKING_LEVELS:从低到高 */
7
+ export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
8
+
9
+ /** pi 的 DEFAULT_THINKING_LEVEL:设置里没有默认档位、模型也没有覆盖时用它 */
10
+ export const DEFAULT_THINKING_LEVEL = "medium";
11
+
12
+ /**
13
+ * 把档位钳到模型支持的范围:支持就原样返回;否则先向更高的档位找,再向更低的档位找,都没有就取第一个支持的。
14
+ */
15
+ export function clampThinkingLevelTo(supported: readonly string[], level: string): string {
16
+ if (supported.includes(level)) return level;
17
+ const index = THINKING_LEVELS.indexOf(level);
18
+ if (index === -1) return supported[0] ?? "off";
19
+ for (let i = index; i < THINKING_LEVELS.length; i += 1) {
20
+ if (supported.includes(THINKING_LEVELS[i])) return THINKING_LEVELS[i];
21
+ }
22
+ for (let i = index - 1; i >= 0; i -= 1) {
23
+ if (supported.includes(THINKING_LEVELS[i])) return THINKING_LEVELS[i];
24
+ }
25
+ return supported[0] ?? "off";
26
+ }
27
+
28
+ /**
29
+ * 新会话实际会用的档位(SDK createAgentSession 的规则):用户显式选过就用它;否则依次取该模型的每模型覆盖、
30
+ * 设置里的默认档位、DEFAULT_THINKING_LEVEL;最后按模型能力钳制。
31
+ */
32
+ export function newSessionThinkingLevel(input: {
33
+ supported: readonly string[];
34
+ requested?: string;
35
+ modelOverride?: string;
36
+ defaultLevel?: string;
37
+ }): string {
38
+ return clampThinkingLevelTo(input.supported, input.requested || input.modelOverride || input.defaultLevel || DEFAULT_THINKING_LEVEL);
39
+ }
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * 轨迹账本:对齐 dsh ui-trajectory 的 cell 模型(system/user/message/tool/compacted)。
3
3
  * 活跃会话由 pi 事件实时驱动;冷会话从 JSONL 条目离线重建。
4
- * pi 无耗时字段——TTFT/decode 由服务端自测,仅活跃会话有值。
4
+ * pi 持久化 LLM / 工具 durationMs;TTFT/decode 由服务端自测,仅活跃会话有值。
5
5
  */
6
6
  import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
7
7
  import type { TrajEntry, TrajTokens } from "./types";
8
8
  import { sanitizeToolOutput } from "./text-sanitize";
9
9
 
10
+ /** 只恢复 SDK 已记录的耗时,不从相邻消息时间戳推算。 */
11
+ function storedDuration(value: unknown): number | undefined {
12
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
13
+ }
14
+
10
15
  export function toTrajTokens(u: any): TrajTokens | undefined {
11
16
  if (!u || typeof u !== "object") return undefined;
12
17
  const t: TrajTokens = {};
@@ -88,6 +93,8 @@ export class TrajLedger {
88
93
  e.encodingLoss = result.encodingLoss || e.encodingLoss;
89
94
  e.isError = m.isError === true;
90
95
  e.tokens = toTrajTokens(m.usage) ?? e.tokens;
96
+ const durationMs = storedDuration(m.durationMs);
97
+ if (durationMs !== undefined) e.timing = { ...e.timing, durationMs };
91
98
  this.touch(e);
92
99
  }
93
100
  }
@@ -96,7 +103,7 @@ export class TrajLedger {
96
103
  case "message_update": {
97
104
  const e: any = evt.assistantMessageEvent;
98
105
  if (!e) break;
99
- if ((e.type === "text_delta" || e.type === "thinking_delta") && this.streamingStart) {
106
+ if ((e.type === "text_delta" || e.type === "thinking_delta" || e.type === "toolcall_delta") && e.delta?.length > 0 && this.streamingStart) {
100
107
  if (this.streamingStart.firstToken === undefined) {
101
108
  this.streamingStart.firstToken = now;
102
109
  const last = this.entries[this.entries.length - 1];
@@ -127,6 +134,8 @@ export class TrajLedger {
127
134
  durationMs: now - this.streamingStart.t0,
128
135
  };
129
136
  }
137
+ const durationMs = storedDuration(m.durationMs);
138
+ if (durationMs !== undefined) e.timing = { ...e.timing, durationMs };
130
139
  e.tokens = toTrajTokens(m.usage);
131
140
  e.thinking = textOf(
132
141
  (m.content ?? []).filter((c: any) => c.type === "thinking").map((c: any) => c.thinking),
@@ -194,7 +203,8 @@ export class TrajLedger {
194
203
  e.encodingLoss = result.encodingLoss || e.encodingLoss;
195
204
  e.isError = evt.isError === true;
196
205
  const startedAt = this.toolStart.get(evt.toolCallId);
197
- if (startedAt !== undefined) e.timing = { ...(e.timing ?? {}), durationMs: now - startedAt };
206
+ const durationMs = storedDuration((evt as any).durationMs) ?? (startedAt !== undefined ? now - startedAt : undefined);
207
+ if (durationMs !== undefined) e.timing = { ...(e.timing ?? {}), durationMs };
198
208
  this.touch(e);
199
209
  }
200
210
  this.toolStart.delete(evt.toolCallId);
@@ -236,6 +246,7 @@ export function buildTrajectoryFromEntries(entries: any[]): TrajEntry[] {
236
246
  } else if (m.role === "assistant") {
237
247
  const thinking = textOf((m.content ?? []).filter((c: any) => c.type === "thinking"));
238
248
  const text = textOf((m.content ?? []).filter((c: any) => c.type === "text"));
249
+ const durationMs = storedDuration(m.durationMs);
239
250
  out.push({
240
251
  seq: seq++,
241
252
  kind: "message",
@@ -244,6 +255,7 @@ export function buildTrajectoryFromEntries(entries: any[]): TrajEntry[] {
244
255
  detail: text,
245
256
  thinking: thinking || undefined,
246
257
  tokens: toTrajTokens(m.usage),
258
+ ...(durationMs === undefined ? {} : { timing: { durationMs } }),
247
259
  isError: m.stopReason === "error" || undefined,
248
260
  });
249
261
  for (const c of m.content ?? []) {
@@ -269,6 +281,8 @@ export function buildTrajectoryFromEntries(entries: any[]): TrajEntry[] {
269
281
  target.encodingLoss = result.encodingLoss || target.encodingLoss;
270
282
  target.isError = m.isError === true || undefined;
271
283
  target.tokens = toTrajTokens(m.usage) ?? target.tokens;
284
+ const durationMs = storedDuration(m.durationMs);
285
+ if (durationMs !== undefined) target.timing = { durationMs };
272
286
  }
273
287
  }
274
288
  } else if (raw.type === "compaction") {
package/src/lib/types.ts CHANGED
@@ -145,8 +145,10 @@ export type WebEvent =
145
145
  }
146
146
  | { type: "extension_ui_resolved"; id: string; ts: number }
147
147
  | { type: "error"; message: string; ts: number }
148
- /** 自动重试:流程内通知,界面聚合成一条「重试 n/max」,不作为错误条目累积 */
148
+ /** 自动重试:流程内通知,界面聚合成一条「重试 n/max」,不作为错误条目累积 */
149
149
  | { type: "retry"; attempt: number; maxAttempts: number; message: string; ts: number }
150
+ /** 重试区间结束(SDK auto_retry_end):收掉「重试 n/max」那一行;真失败由后续 error 消息表达 */
151
+ | { type: "retry_end"; attempt: number; ts: number }
150
152
  /** 项目生长:提交了一个 commit(pi 的一轮 / 用户两轮之间的修改 / 工作区基线) */
151
153
  | { type: "growth"; round: GrowthRound; ts: number }
152
154
  | { type: "growth_error"; message: string | null; ts: number }
@@ -166,6 +168,11 @@ export interface WebStats {
166
168
  ttftSteps: number;
167
169
  decodeMs: number;
168
170
  decodeTokens: number;
171
+ /** 有持久化耗时的消息 / 工具数量;旧历史缺数据时显示覆盖范围。 */
172
+ llmTimedSteps?: number;
173
+ toolTimedCalls?: number;
174
+ /** 生成速度实际测到的回复数,不代表整个历史。 */
175
+ decodeSteps?: number;
169
176
  }
170
177
 
171
178
  // ---------- 项目生长(每轮一个 git commit) ----------
@@ -236,6 +243,8 @@ export interface ContextResource {
236
243
  * 这里只放服务端才能拿到的部分。
237
244
  */
238
245
  export interface ContextBreakdown {
246
+ /** 当前投影中实际文本的字符数;不将估算 token 按比例分摊。 */
247
+ characters?: { systemPrompt: number; systemTools: number; customTools: number; memory: number; skills: number; compacted: number };
239
248
  /** SDK 内建 system prompt 正文(不含 memory/skills/tools 段)的估算 token */
240
249
  systemPrompt: number;
241
250
  /** 内建工具(SDK 自带 bash/read/edit 等)定义的估算 token */
@@ -338,6 +347,8 @@ export interface AgentCommand {
338
347
  instructions?: string;
339
348
  provider?: string;
340
349
  modelId?: string;
350
+ /** setModel:新会话页选的模型,思考档位按新会话的规则定(不沿用会话原来的档位) */
351
+ fresh?: boolean;
341
352
  level?: string;
342
353
  preset?: ToolPreset;
343
354
  entryId?: string;
@@ -0,0 +1,270 @@
1
+ /**
2
+ * @ 引用的候选与匹配(纯逻辑,便于单测):照搬 pi 终端的 `@` 补全
3
+ * (pi-tui `CombinedAutocompleteProvider.getFuzzyFileSuggestions` + `extractAtPrefix`),
4
+ * 同一个 `@que` 在终端和网页里给出同一批、同一顺序的候选。
5
+ *
6
+ * pi 的做法:`@` 后的文字按最后一个 `/` 拆成「目录 + 查询」(目录存在才算),
7
+ * 在该目录下用 fd 过滤(查询当正则、smart case,默认只匹配文件名,带 `/` 时匹配整条路径),
8
+ * 再用 scoreEntry 打分(文件名全等 100 / 前缀 80 / 包含 50 / 路径包含 30,目录 +10),
9
+ * 按分数、层级、路径长度、字典序排,取前 20。
10
+ *
11
+ * 有意的差别(tests/mention-parity.test.ts 里逐条对照,差别也写在那里):
12
+ * - 候选来自服务端给的工作区清单(git 可见文件 + 推导出的文件夹),不现场跑 fd;
13
+ * pi 每次 fd 只取前 100 条再打分,这里对整份清单打分——结果是「fd 不截断时 pi 会给的」,而且稳定。
14
+ * - 不支持 `~/`、绝对路径和跳出工作区的 `../`:引用只针对当前工作区。
15
+ * - fd 的 `--full-path` 匹配绝对路径,这里匹配相对路径(只差在工作区自身路径恰好命中的极端情况)。
16
+ */
17
+
18
+ export interface PathCandidate {
19
+ /** 工作区根的相对路径("/" 分隔),目录不带尾斜杠 */
20
+ path: string;
21
+ dir: boolean;
22
+ }
23
+
24
+ /** 显示用:目录带尾斜杠 */
25
+ export function candidateText(candidate: PathCandidate): string {
26
+ return candidate.dir ? `${candidate.path}/` : candidate.path;
27
+ }
28
+
29
+ /** 文件路径 → 全部父目录(去重、不含 ".") */
30
+ export function dirsOfPaths(files: string[]): string[] {
31
+ const dirs = new Set<string>();
32
+ for (const file of files) {
33
+ const parts = file.split("/");
34
+ for (let i = 1; i < parts.length; i += 1) dirs.add(parts.slice(0, i).join("/"));
35
+ }
36
+ return [...dirs];
37
+ }
38
+
39
+ export function buildCandidates(files: string[], dirs: string[]): PathCandidate[] {
40
+ const out: PathCandidate[] = [];
41
+ for (const dir of dirs) out.push({ path: dir, dir: true });
42
+ for (const file of files) out.push({ path: file, dir: false });
43
+ return out;
44
+ }
45
+
46
+ // ---------- 匹配与排序(pi-tui autocomplete.js) ----------
47
+
48
+ /** pi 一次最多列 20 条 */
49
+ export const MENTION_LIMIT = 20;
50
+
51
+ export interface MentionSuggestion {
52
+ candidate: PathCandidate;
53
+ /** 文件名,目录带尾斜杠(pi 的 label) */
54
+ label: string;
55
+ /** 路径(pi 的 description):按用户打的目录前缀显示,如 `./src/lib` */
56
+ description: string;
57
+ }
58
+
59
+ function toDisplayPath(value: string): string {
60
+ return value.replace(/\\/g, "/");
61
+ }
62
+
63
+ function baseName(path: string): string {
64
+ const trimmed = path.replace(/\/+$/, "");
65
+ return trimmed.slice(trimmed.lastIndexOf("/") + 1);
66
+ }
67
+
68
+ function escapeRegex(value: string): string {
69
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
70
+ }
71
+
72
+ /** pi 的 buildFdPathQuery:带 `/` 的查询按段转义、段间允许 `/` 或 `\` */
73
+ function buildFdPathQuery(query: string): string {
74
+ const normalized = toDisplayPath(query);
75
+ if (!normalized.includes("/")) return normalized;
76
+ const trimmed = normalized.replace(/^\/+|\/+$/g, "");
77
+ if (!trimmed) return normalized;
78
+ const segments = trimmed.split("/").filter(Boolean).map(escapeRegex);
79
+ if (!segments.length) return normalized;
80
+ const separator = "[\\\\/]";
81
+ return segments.join(separator) + (normalized.endsWith("/") ? separator : "");
82
+ }
83
+
84
+ /**
85
+ * fd 的过滤:模式当正则,smart case(模式里有大写字母才区分大小写,转义序列如 `\W` 不算)。
86
+ * fd 用的是 Rust 正则,`c++` 这种叠加量词它能编(等于 `(c+)+`),JS 会报错:
87
+ * 编不过就退回字面包含——之后 scoreEntry 本来就要求查询按字面出现,所以 `c++` 的结果和 pi 一样;
88
+ * Rust 也编不过的(未闭合的括号等)pi 没有候选,这里按字面给,算有意放宽。
89
+ */
90
+ function fdFilter(pattern: string): (target: string) => boolean {
91
+ const caseSensitive = /\p{Lu}/u.test(pattern.replace(/\\./g, ""));
92
+ try {
93
+ const regex = new RegExp(pattern, caseSensitive ? "" : "i");
94
+ return (target) => regex.test(target);
95
+ } catch {
96
+ const literal = caseSensitive ? pattern : pattern.toLowerCase();
97
+ return (target) => (caseSensitive ? target : target.toLowerCase()).includes(literal);
98
+ }
99
+ }
100
+
101
+ /** pi 的 scoreEntry:文件名全等 100 / 前缀 80 / 包含 50 / 整条路径包含 30,目录命中再 +10 */
102
+ export function scoreEntry(filePath: string, query: string, isDirectory: boolean): number {
103
+ const lowerFileName = baseName(filePath).toLowerCase();
104
+ const lowerQuery = query.toLowerCase();
105
+ let score = 0;
106
+ if (lowerFileName === lowerQuery) score = 100;
107
+ else if (lowerFileName.startsWith(lowerQuery)) score = 80;
108
+ else if (lowerFileName.includes(lowerQuery)) score = 50;
109
+ else if (filePath.toLowerCase().includes(lowerQuery)) score = 30;
110
+ if (isDirectory && score > 0) score += 10;
111
+ return score;
112
+ }
113
+
114
+ /**
115
+ * pi 的 resolveScopedFuzzyQuery:最后一个 `/` 之前是目录(必须存在),之后是查询。
116
+ * 返回 base = 工作区相对路径("" 为根),displayBase = 用户打的原样前缀。
117
+ */
118
+ function resolveScopedQuery(rawQuery: string, dirs: ReadonlySet<string>): { base: string; query: string; displayBase: string } | null {
119
+ const normalized = toDisplayPath(rawQuery);
120
+ const slash = normalized.lastIndexOf("/");
121
+ if (slash === -1) return null;
122
+ const displayBase = normalized.slice(0, slash + 1);
123
+ if (displayBase.startsWith("/") || displayBase.startsWith("~/")) return null;
124
+ const segments: string[] = [];
125
+ for (const segment of displayBase.split("/")) {
126
+ if (!segment || segment === ".") continue;
127
+ if (segment === "..") {
128
+ if (!segments.length) return null;
129
+ segments.pop();
130
+ } else segments.push(segment);
131
+ }
132
+ const base = segments.join("/");
133
+ if (base && !dirs.has(base)) return null;
134
+ return { base, query: normalized.slice(slash + 1), displayBase };
135
+ }
136
+
137
+ interface Entry {
138
+ candidate: PathCandidate;
139
+ /** 相对 fd 搜索目录的路径,目录带尾斜杠(和 fd 的输出一致,长度和字典序都按它算) */
140
+ path: string;
141
+ score: number;
142
+ depth: number;
143
+ }
144
+
145
+ /**
146
+ * 候选排序与截断,和 pi 一致:空查询全部 1 分,按层级 → 路径长度 → 字典序(先看当前目录、名字短的在前)。
147
+ * 不在模块里缓存目录集合:调用方用 useMemo 包住整次计算。
148
+ */
149
+ export function suggestPaths(candidates: readonly PathCandidate[], rawQuery: string, limit = MENTION_LIMIT): MentionSuggestion[] {
150
+ const dirs = new Set<string>();
151
+ for (const candidate of candidates) if (candidate.dir) dirs.add(candidate.path);
152
+ const scoped = resolveScopedQuery(rawQuery, dirs);
153
+ const query = scoped ? scoped.query : rawQuery;
154
+ const prefix = scoped?.base ? `${scoped.base}/` : "";
155
+
156
+ let filter: ((relative: string) => boolean) | null = null;
157
+ if (query) {
158
+ // 默认只拿文件名去匹配;查询带 `/` 时(目录不存在、没走上面的限定目录)匹配整条路径
159
+ const fullPath = toDisplayPath(query).includes("/");
160
+ const matches = fdFilter(buildFdPathQuery(query));
161
+ filter = fullPath ? matches : (relative) => matches(baseName(relative));
162
+ }
163
+
164
+ const entries: Entry[] = [];
165
+ for (const candidate of candidates) {
166
+ if (prefix && !candidate.path.startsWith(prefix)) continue;
167
+ const relative = candidate.path.slice(prefix.length);
168
+ if (!relative || (filter && !filter(relative))) continue;
169
+ const path = candidate.dir ? `${relative}/` : relative;
170
+ const score = query ? scoreEntry(path, query, candidate.dir) : 1;
171
+ if (score <= 0) continue;
172
+ entries.push({ candidate, path, score, depth: relative.split("/").filter(Boolean).length });
173
+ }
174
+ entries.sort((a, b) => b.score - a.score || a.depth - b.depth || a.path.length - b.path.length || a.path.localeCompare(b.path));
175
+
176
+ return entries.slice(0, limit).map(({ candidate, path }) => {
177
+ const withoutSlash = candidate.dir ? path.slice(0, -1) : path;
178
+ return {
179
+ candidate,
180
+ label: baseName(withoutSlash) + (candidate.dir ? "/" : ""),
181
+ description: scoped ? `${scoped.displayBase}${withoutSlash}` : withoutSlash,
182
+ };
183
+ });
184
+ }
185
+
186
+ // ---------- @ token(pi-tui extractAtPrefix) ----------
187
+
188
+ /** pi 的 PATH_DELIMITERS */
189
+ const PATH_DELIMITERS = new Set([" ", "\t", '"', "'", "="]);
190
+ /** pi-tui utils.js 的 cjkBreakRegex / cjkPunctuationRegex / autocompleteSeparatorRegex(原样抄来,测试里核对 source) */
191
+ export const CJK_BREAK_REGEX = /[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}\p{Script_Extensions=Bopomofo}]/u;
192
+ const CJK_PUNCTUATION_REGEX = new RegExp(`(?:(?=\\p{Punctuation})${CJK_BREAK_REGEX.source}|[,.:;!?()[]{}“”‘’…—])`, "u");
193
+ export const AUTOCOMPLETE_SEPARATOR_REGEX = new RegExp(`(?:\\s|${CJK_PUNCTUATION_REGEX.source})`, "u");
194
+ const TOKEN_START_REGEX = new RegExp(`(?:^|${AUTOCOMPLETE_SEPARATOR_REGEX.source})$`, "u");
195
+ /** 正文里路径前常见的左括号类:`(@src` 也能触发;token 里有对应的右括号就保留(`app/[slug]`) */
196
+ const PATH_WRAPPERS: Record<string, string> = { "(": ")", "[": "]", "{": "}", "<": ">", "`": "`" };
197
+
198
+ function isDelimiter(character: string): boolean {
199
+ return PATH_DELIMITERS.has(character) || AUTOCOMPLETE_SEPARATOR_REGEX.test(character);
200
+ }
201
+
202
+ /** 最后一个分隔符的位置(按码点走,返回它最后一个 UTF-16 单元的下标),没有则 -1 */
203
+ function findLastDelimiter(text: string): number {
204
+ let last = -1;
205
+ let index = 0;
206
+ for (const character of text) {
207
+ index += character.length;
208
+ if (isDelimiter(character)) last = index - 1;
209
+ }
210
+ return last;
211
+ }
212
+
213
+ function stripLeadingWrappers(token: string): string {
214
+ let result = token;
215
+ while (result.length > 0) {
216
+ const closer = PATH_WRAPPERS[result[0]];
217
+ if (!closer || result.includes(closer, 1)) break;
218
+ result = result.slice(1);
219
+ }
220
+ return result;
221
+ }
222
+
223
+ function findUnclosedQuoteStart(text: string): number | null {
224
+ let inQuotes = false;
225
+ let quoteStart = -1;
226
+ for (let i = 0; i < text.length; i += 1) {
227
+ if (text[i] === '"') {
228
+ inQuotes = !inQuotes;
229
+ if (inQuotes) quoteStart = i;
230
+ }
231
+ }
232
+ return inQuotes ? quoteStart : null;
233
+ }
234
+
235
+ function isTokenStart(text: string, index: number): boolean {
236
+ let start = index;
237
+ while (start > 0 && PATH_WRAPPERS[text[start - 1]]) start -= 1;
238
+ return PATH_DELIMITERS.has(text[start - 1] ?? "") || TOKEN_START_REGEX.test(text.slice(0, start));
239
+ }
240
+
241
+ /**
242
+ * 光标前的 `@` 引用 token,规则同 pi:
243
+ * 从最后一个分隔符(空白、`"` `'` `=`、中日韩标点)之后取一段,去掉开头的左括号类,以 `@` 开头才算
244
+ * (`foo@bar` 这种邮箱不触发);另支持 `@"带空格 的路径`。返回 null 表示当前不该弹候选。
245
+ * pi 只看光标所在的那一行;光标在 token 中间时(`@lib|bar`)删除范围盖住整个 token,免得留下半截。
246
+ */
247
+ export function atToken(text: string, caret: number): { query: string; start: number; end: number; tokenEnd: number } | null {
248
+ const end = Math.max(0, Math.min(caret, text.length));
249
+ const lineStart = text.lastIndexOf("\n", end - 1) + 1;
250
+ const line = text.slice(lineStart, end);
251
+
252
+ const quoteStart = findUnclosedQuoteStart(line);
253
+ if (quoteStart !== null && quoteStart > 0 && line[quoteStart - 1] === "@" && isTokenStart(line, quoteStart - 1)) {
254
+ const close = text.indexOf('"', end);
255
+ const lineEnd = text.indexOf("\n", end);
256
+ const closed = close !== -1 && (lineEnd === -1 || close < lineEnd);
257
+ return { query: line.slice(quoteStart + 1), start: lineStart + quoteStart - 1, end, tokenEnd: closed ? close + 1 : end };
258
+ }
259
+
260
+ const tokenStart = findLastDelimiter(line) + 1;
261
+ const raw = line.slice(tokenStart);
262
+ const token = stripLeadingWrappers(raw);
263
+ if (!token.startsWith("@")) return null;
264
+ let tokenEnd = end;
265
+ for (const character of text.slice(end)) {
266
+ if (isDelimiter(character)) break;
267
+ tokenEnd += character.length;
268
+ }
269
+ return { query: token.slice(1), start: lineStart + tokenStart + raw.length - token.length, end, tokenEnd };
270
+ }