@rexvane/piweb 0.3.24 → 0.3.26

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 (285) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +11 -11
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +5 -5
  5. package/.next/react-loadable-manifest.json +13 -20
  6. package/.next/required-server-files.js +2 -1
  7. package/.next/required-server-files.json +2 -1
  8. package/.next/routes-manifest.json +6 -8
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  28. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  31. package/.next/server/app/api/agent/resources/route.js +1 -0
  32. package/.next/server/app/api/agent/resources/route.js.nft.json +1 -0
  33. package/.next/server/app/api/agent/resources/route_client-reference-manifest.js +1 -0
  34. package/.next/server/app/api/config/route.js +1 -1
  35. package/.next/server/app/api/config/route.js.nft.json +1 -1
  36. package/.next/server/app/api/files/route.js +1 -1
  37. package/.next/server/app/api/files/route.js.nft.json +1 -1
  38. package/.next/server/app/api/fs/route.js +1 -1
  39. package/.next/server/app/api/fs/route.js.nft.json +1 -1
  40. package/.next/server/app/api/git/route.js +4 -4
  41. package/.next/server/app/api/git/route.js.nft.json +1 -1
  42. package/.next/server/app/api/github/route.js +4 -4
  43. package/.next/server/app/api/github/route.js.nft.json +1 -1
  44. package/.next/server/app/api/growth/route.js +1 -1
  45. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  46. package/.next/server/app/api/health/route.js +1 -1
  47. package/.next/server/app/api/models/defaults/route.js +1 -1
  48. package/.next/server/app/api/models/defaults/route.js.nft.json +1 -1
  49. package/.next/server/app/api/models/route.js +1 -1
  50. package/.next/server/app/api/models/route.js.nft.json +1 -1
  51. package/.next/server/app/api/pi-settings/route.js +1 -1
  52. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  53. package/.next/server/app/api/plugins/route.js +1 -1
  54. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  55. package/.next/server/app/api/prompts/route.js +1 -1
  56. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  57. package/.next/server/app/api/remote/route.js +4 -3
  58. package/.next/server/app/api/security/route.js +1 -1
  59. package/.next/server/app/api/security/route.js.nft.json +1 -1
  60. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  61. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  62. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  63. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/sessions/import/route.js +2 -2
  65. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  66. package/.next/server/app/api/sessions/route.js +1 -1
  67. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  68. package/.next/server/app/api/skills/route.js +2 -2
  69. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  70. package/.next/server/app/api/update/route.js +2 -2
  71. package/.next/server/app/api/update/route.js.nft.json +1 -1
  72. package/.next/server/app/api/version/route.js +1 -1
  73. package/.next/server/app/api/version/route.js.nft.json +1 -1
  74. package/.next/server/app/api/web-auth/route.js +2 -2
  75. package/.next/server/app/api/workspaces/route.js +1 -1
  76. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  77. package/.next/server/app/icon.svg/route.js +1 -1
  78. package/.next/server/app/index.html +1 -1
  79. package/.next/server/app/index.rsc +6 -7
  80. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -7
  81. package/.next/server/app/index.segments/_full.segment.rsc +6 -7
  82. package/.next/server/app/index.segments/_tree.segment.rsc +2 -3
  83. package/.next/server/app/login/page.js +1 -1
  84. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/login.html +1 -1
  86. package/.next/server/app/login.rsc +3 -3
  87. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  88. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  89. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
  90. package/.next/server/app/page.js +1 -1
  91. package/.next/server/app/page.js.nft.json +1 -1
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app-paths-manifest.json +11 -11
  94. package/.next/server/chunks/3924.js +3 -3
  95. package/.next/server/chunks/538.js +1 -1
  96. package/.next/server/chunks/620.js +1 -0
  97. package/.next/server/chunks/6229.js +1 -1
  98. package/.next/server/chunks/7496.js +1 -1
  99. package/.next/server/chunks/7662.js +2 -2
  100. package/.next/server/chunks/8686.js +1 -1
  101. package/.next/server/functions-config-manifest.json +1 -2
  102. package/.next/server/instrumentation.js.nft.json +1 -1
  103. package/.next/server/middleware-build-manifest.js +1 -1
  104. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  105. package/.next/server/pages/404.html +1 -1
  106. package/.next/server/pages/500.html +1 -1
  107. package/.next/server/preview-props.json +3 -3
  108. package/.next/server/server-reference-manifest.json +1 -1
  109. package/.next/static/QJCtqgDKzGS2BdKABd9ch/_buildManifest.js +1 -0
  110. package/.next/static/chunks/2577.86c8538fbf05a8b2.js +1 -0
  111. package/.next/static/chunks/2716.fa561519675a3ff1.js +1 -0
  112. package/.next/static/chunks/2906.8a0e09bb47605cf1.js +1 -0
  113. package/.next/static/chunks/4673.ea36e55005c0f82f.js +1 -0
  114. package/.next/static/chunks/4686-4dc565817a160351.js +1 -0
  115. package/.next/static/chunks/4778.0d2c5d75b791d352.js +1 -0
  116. package/.next/static/chunks/{618.949981da99be7182.js → 618.9a22656b863f242b.js} +2 -2
  117. package/.next/static/chunks/app/_global-error/page-f2d93969c0bcf406.js +1 -0
  118. package/.next/static/chunks/app/api/agent/[id]/events/route-f2d93969c0bcf406.js +1 -0
  119. package/.next/static/chunks/app/api/agent/[id]/route-f2d93969c0bcf406.js +1 -0
  120. package/.next/static/chunks/app/api/agent/[id]/tree/route-f2d93969c0bcf406.js +1 -0
  121. package/.next/static/chunks/app/api/agent/new/route-f2d93969c0bcf406.js +1 -0
  122. package/.next/static/chunks/app/api/agent/resources/route-f2d93969c0bcf406.js +1 -0
  123. package/.next/static/chunks/app/api/config/route-f2d93969c0bcf406.js +1 -0
  124. package/.next/static/chunks/app/api/files/route-f2d93969c0bcf406.js +1 -0
  125. package/.next/static/chunks/app/api/fs/route-f2d93969c0bcf406.js +1 -0
  126. package/.next/static/chunks/app/api/git/route-f2d93969c0bcf406.js +1 -0
  127. package/.next/static/chunks/app/api/github/route-f2d93969c0bcf406.js +1 -0
  128. package/.next/static/chunks/app/api/growth/route-f2d93969c0bcf406.js +1 -0
  129. package/.next/static/chunks/app/api/health/route-f2d93969c0bcf406.js +1 -0
  130. package/.next/static/chunks/app/api/models/defaults/route-f2d93969c0bcf406.js +1 -0
  131. package/.next/static/chunks/app/api/models/route-f2d93969c0bcf406.js +1 -0
  132. package/.next/static/chunks/app/api/pi-settings/route-f2d93969c0bcf406.js +1 -0
  133. package/.next/static/chunks/app/api/plugins/route-f2d93969c0bcf406.js +1 -0
  134. package/.next/static/chunks/app/api/prompts/route-f2d93969c0bcf406.js +1 -0
  135. package/.next/static/chunks/app/api/remote/route-f2d93969c0bcf406.js +1 -0
  136. package/.next/static/chunks/app/api/security/route-f2d93969c0bcf406.js +1 -0
  137. package/.next/static/chunks/app/api/sessions/[id]/export/route-f2d93969c0bcf406.js +1 -0
  138. package/.next/static/chunks/app/api/sessions/[id]/route-f2d93969c0bcf406.js +1 -0
  139. package/.next/static/chunks/app/api/sessions/import/route-f2d93969c0bcf406.js +1 -0
  140. package/.next/static/chunks/app/api/sessions/route-f2d93969c0bcf406.js +1 -0
  141. package/.next/static/chunks/app/api/skills/route-f2d93969c0bcf406.js +1 -0
  142. package/.next/static/chunks/app/api/update/route-f2d93969c0bcf406.js +1 -0
  143. package/.next/static/chunks/app/api/version/route-f2d93969c0bcf406.js +1 -0
  144. package/.next/static/chunks/app/api/web-auth/route-f2d93969c0bcf406.js +1 -0
  145. package/.next/static/chunks/app/api/workspaces/route-f2d93969c0bcf406.js +1 -0
  146. package/.next/static/chunks/app/page-71f2d3b3ed864b3d.js +1 -0
  147. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f2d93969c0bcf406.js +1 -0
  148. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f2d93969c0bcf406.js +1 -0
  149. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f2d93969c0bcf406.js +1 -0
  150. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f2d93969c0bcf406.js +1 -0
  151. package/.next/static/chunks/webpack-ffc09b5fcc8aaa6d.js +1 -0
  152. package/.next/static/css/014eb1f21414e615.css +1 -0
  153. package/.next/static/css/56b129a13637fcfd.css +1 -0
  154. package/.next/static/css/7e3caf797e4cb19a.css +1 -0
  155. package/.next/types/app/api/agent/{[id]/side-chat → resources}/route.ts +3 -3
  156. package/.next/types/routes.d.ts +2 -2
  157. package/.next/types/validator.ts +9 -9
  158. package/README.md +1 -1
  159. package/next.config.ts +1 -1
  160. package/package.json +9 -9
  161. package/scripts/apply-global.mjs +87 -0
  162. package/scripts/launcher.mjs +21 -0
  163. package/scripts/testing/prepare-sdk-tools.mjs +19 -0
  164. package/src/app/api/agent/new/route.ts +2 -1
  165. package/src/app/api/agent/resources/route.ts +18 -0
  166. package/src/app/api/models/route.ts +22 -1
  167. package/src/app/api/plugins/route.ts +3 -2
  168. package/src/app/api/prompts/route.ts +3 -2
  169. package/src/app/api/remote/route.ts +22 -4
  170. package/src/app/api/sessions/[id]/export/route.ts +3 -1
  171. package/src/app/api/skills/route.ts +3 -2
  172. package/src/app/globals.css +1002 -1765
  173. package/src/components/AppShell.tsx +128 -93
  174. package/src/components/ChatInput.tsx +177 -98
  175. package/src/components/ChatWindow.tsx +189 -56
  176. package/src/components/ContextMeter.tsx +33 -9
  177. package/src/components/CustomSelect.module.css +142 -0
  178. package/src/components/CustomSelect.tsx +157 -0
  179. package/src/components/DrawerPanel.tsx +1 -1
  180. package/src/components/ExtensionUI.tsx +9 -3
  181. package/src/components/FileViewer.tsx +24 -21
  182. package/src/components/GitPanel.tsx +35 -13
  183. package/src/components/GrowthTree.tsx +6 -2
  184. package/src/components/ImageViewer.tsx +72 -0
  185. package/src/components/MarkdownBlock.tsx +67 -96
  186. package/src/components/ModelSelector.tsx +36 -39
  187. package/src/components/PiMark.tsx +76 -41
  188. package/src/components/PreviewImage.tsx +33 -0
  189. package/src/components/ProjectPanel.tsx +8 -5
  190. package/src/components/PromptPanel.tsx +85 -20
  191. package/src/components/ProviderSetupModal.module.css +12 -11
  192. package/src/components/ProviderSetupModal.tsx +107 -36
  193. package/src/components/RemoteSection.tsx +53 -2
  194. package/src/components/SessionImportSection.tsx +30 -15
  195. package/src/components/SessionSidebar.tsx +72 -62
  196. package/src/components/SettingsPanel.tsx +49 -151
  197. package/src/components/TrajectoryAnalyzer.module.css +26 -11
  198. package/src/components/TrajectoryView.tsx +11 -3
  199. package/src/components/chat/markdown-actions.tsx +23 -0
  200. package/src/components/composer/ComposerFiles.tsx +224 -0
  201. package/src/components/composer/ComposerImages.tsx +59 -127
  202. package/src/hooks/usePiWeb.ts +47 -2
  203. package/src/i18n.tsx +62 -80
  204. package/src/lib/agent-manager.ts +365 -33
  205. package/src/lib/chat/file-path.ts +54 -0
  206. package/src/lib/chat/skill-expansion.ts +51 -0
  207. package/src/lib/extension-ui.ts +25 -1
  208. package/src/lib/font.ts +37 -39
  209. package/src/lib/git-service.ts +4 -2
  210. package/src/lib/growth-turns.ts +3 -1
  211. package/src/lib/model-draft.ts +69 -2
  212. package/src/lib/models-service.ts +53 -3
  213. package/src/lib/pi.ts +2 -2
  214. package/src/lib/remote/assets.json +1 -1
  215. package/src/lib/remote/supervisor.ts +48 -5
  216. package/src/lib/security-service.ts +2 -1
  217. package/src/lib/session-reader.ts +25 -13
  218. package/src/lib/session-retention.ts +11 -8
  219. package/src/lib/skills-service.ts +2 -1
  220. package/src/lib/slash-command-token.ts +14 -0
  221. package/src/lib/trajectory.ts +111 -15
  222. package/src/lib/types.ts +28 -3
  223. package/src/lib/update-service.ts +29 -9
  224. package/src/lib/workspace-store.ts +36 -1
  225. package/.next/server/app/api/agent/[id]/side-chat/route.js +0 -5
  226. package/.next/server/app/api/agent/[id]/side-chat/route.js.nft.json +0 -1
  227. package/.next/server/app/api/agent/[id]/side-chat/route_client-reference-manifest.js +0 -1
  228. package/.next/server/chunks/3421.js +0 -1
  229. package/.next/static/chunks/2716.44fdf1b21454b961.js +0 -1
  230. package/.next/static/chunks/4673.e261707dce391be7.js +0 -1
  231. package/.next/static/chunks/4686-0cd87513f9ea3538.js +0 -1
  232. package/.next/static/chunks/4778.eddd3a5813f85824.js +0 -1
  233. package/.next/static/chunks/7411.5f79671208209b13.js +0 -1
  234. package/.next/static/chunks/8552.25c39ef4c995eba0.js +0 -1
  235. package/.next/static/chunks/app/_global-error/page-cbaaee0d7aacb85a.js +0 -1
  236. package/.next/static/chunks/app/api/agent/[id]/events/route-cbaaee0d7aacb85a.js +0 -1
  237. package/.next/static/chunks/app/api/agent/[id]/route-cbaaee0d7aacb85a.js +0 -1
  238. package/.next/static/chunks/app/api/agent/[id]/side-chat/route-cbaaee0d7aacb85a.js +0 -1
  239. package/.next/static/chunks/app/api/agent/[id]/tree/route-cbaaee0d7aacb85a.js +0 -1
  240. package/.next/static/chunks/app/api/agent/new/route-cbaaee0d7aacb85a.js +0 -1
  241. package/.next/static/chunks/app/api/config/route-cbaaee0d7aacb85a.js +0 -1
  242. package/.next/static/chunks/app/api/files/route-cbaaee0d7aacb85a.js +0 -1
  243. package/.next/static/chunks/app/api/fs/route-cbaaee0d7aacb85a.js +0 -1
  244. package/.next/static/chunks/app/api/git/route-cbaaee0d7aacb85a.js +0 -1
  245. package/.next/static/chunks/app/api/github/route-cbaaee0d7aacb85a.js +0 -1
  246. package/.next/static/chunks/app/api/growth/route-cbaaee0d7aacb85a.js +0 -1
  247. package/.next/static/chunks/app/api/health/route-cbaaee0d7aacb85a.js +0 -1
  248. package/.next/static/chunks/app/api/models/defaults/route-cbaaee0d7aacb85a.js +0 -1
  249. package/.next/static/chunks/app/api/models/route-cbaaee0d7aacb85a.js +0 -1
  250. package/.next/static/chunks/app/api/pi-settings/route-cbaaee0d7aacb85a.js +0 -1
  251. package/.next/static/chunks/app/api/plugins/route-cbaaee0d7aacb85a.js +0 -1
  252. package/.next/static/chunks/app/api/prompts/route-cbaaee0d7aacb85a.js +0 -1
  253. package/.next/static/chunks/app/api/remote/route-cbaaee0d7aacb85a.js +0 -1
  254. package/.next/static/chunks/app/api/security/route-cbaaee0d7aacb85a.js +0 -1
  255. package/.next/static/chunks/app/api/sessions/[id]/export/route-cbaaee0d7aacb85a.js +0 -1
  256. package/.next/static/chunks/app/api/sessions/[id]/route-cbaaee0d7aacb85a.js +0 -1
  257. package/.next/static/chunks/app/api/sessions/import/route-cbaaee0d7aacb85a.js +0 -1
  258. package/.next/static/chunks/app/api/sessions/route-cbaaee0d7aacb85a.js +0 -1
  259. package/.next/static/chunks/app/api/skills/route-cbaaee0d7aacb85a.js +0 -1
  260. package/.next/static/chunks/app/api/update/route-cbaaee0d7aacb85a.js +0 -1
  261. package/.next/static/chunks/app/api/version/route-cbaaee0d7aacb85a.js +0 -1
  262. package/.next/static/chunks/app/api/web-auth/route-cbaaee0d7aacb85a.js +0 -1
  263. package/.next/static/chunks/app/api/workspaces/route-cbaaee0d7aacb85a.js +0 -1
  264. package/.next/static/chunks/app/page-dc3cb41d91c7432e.js +0 -1
  265. package/.next/static/chunks/next/dist/client/components/builtin/app-error-cbaaee0d7aacb85a.js +0 -1
  266. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cbaaee0d7aacb85a.js +0 -1
  267. package/.next/static/chunks/next/dist/client/components/builtin/not-found-cbaaee0d7aacb85a.js +0 -1
  268. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cbaaee0d7aacb85a.js +0 -1
  269. package/.next/static/chunks/webpack-2ac3d76b81895f5b.js +0 -1
  270. package/.next/static/css/08815507594e8b8d.css +0 -1
  271. package/.next/static/css/9fc559c2c4ad1d0f.css +0 -1
  272. package/.next/static/css/c6e2227c36ec2fd6.css +0 -1
  273. package/.next/static/css/f15a853af8b5127f.css +0 -1
  274. package/.next/static/lvKCJmjFj8LuqSojRVLRF/_buildManifest.js +0 -1
  275. package/src/app/api/agent/[id]/side-chat/route.ts +0 -77
  276. package/src/components/side-chat/SideChatPanel.module.css +0 -131
  277. package/src/components/side-chat/SideChatPanel.tsx +0 -189
  278. package/src/components/side-chat/icons.tsx +0 -8
  279. package/src/components/side-chat/request-history.ts +0 -29
  280. package/src/components/side-chat/store.ts +0 -248
  281. package/src/lib/chat/stream-settings.ts +0 -192
  282. package/src/lib/side-chat/context.ts +0 -123
  283. package/src/lib/side-chat/service.ts +0 -191
  284. package/src/lib/side-chat/types.ts +0 -61
  285. /package/.next/static/{lvKCJmjFj8LuqSojRVLRF → QJCtqgDKzGS2BdKABd9ch}/_ssgManifest.js +0 -0
@@ -13,8 +13,6 @@ import { UpdateNotice } from "@/components/update/UpdateNotice";
13
13
  import dynamic from "next/dynamic";
14
14
  import { SessionSidebar } from "@/components/SessionSidebar";
15
15
  import type { DrawerSection } from "@/components/DrawerPanel";
16
- import { SideChatPanel } from "@/components/side-chat/SideChatPanel";
17
- import { SideChatIcon } from "@/components/side-chat/icons";
18
16
  import { preserveChatReadingPosition } from "@/components/chat/reading-anchor";
19
17
  import { DirectoryPicker } from "@/components/DirectoryPicker";
20
18
 
@@ -47,7 +45,11 @@ import type { ModelChoice } from "@/components/ModelSelector";
47
45
  import type { NewSessionDefaults } from "@/lib/model-defaults";
48
46
  import { readRememberedHeroModel, writeRememberedHeroModel } from "@/lib/hero-model-memory";
49
47
  import { newSessionThinkingLevel } from "@/lib/thinking-level";
50
- import type { ImageAttachment, TrajEntry } from "@/lib/types";
48
+ import { promptTitleText } from "@/lib/chat/skill-expansion";
49
+ import type { ImageAttachment, TrajEntry, WebSnapshot } from "@/lib/types";
50
+
51
+ /** 补全需要的资源清单;新会话页与冷会话都能从服务端单独发现,不必等快照 */
52
+ type WorkspaceCommands = Pick<WebSnapshot, "skills" | "promptTemplates" | "extensionCommands">;
51
53
 
52
54
  // dsh ui-layout columns.ts 几何常量
53
55
  const SIDEBAR_MIN = 264;
@@ -160,20 +162,14 @@ export function AppShell() {
160
162
  const [settingsOpen, setSettingsOpen] = useState(false);
161
163
  const [selected, setSelected] = useState<TrajEntry | null>(null);
162
164
  /** 抽屉当前入口(由这里持有:轨迹详情关掉后要回到同一个入口) */
163
- /** 抽屉入口保存在浏览器;新用户先看到旁聊。 */
165
+ /** 抽屉入口保存在浏览器 */
164
166
  const [drawerSection, setDrawerSection] = useState<DrawerSection>("project");
165
167
  const chooseDrawerSection = useCallback((section: DrawerSection) => {
166
168
  setDrawerSection(section);
167
169
  localStorage.setItem("piweb.drawerSection", section);
168
- setSideChatOpen(false);
169
170
  }, []);
170
171
  /** 右侧抽屉(最右一列):入口列表 + 内容 */
171
172
  const [drawerOpen, setDrawerOpen] = useState(false);
172
- /**
173
- * 右侧旁聊面板:和抽屉共用最右一列,所以两者互斥(开一个就关另一个)。
174
- * 旁聊不再作为抽屉的一个入口 —— 它是独立面板,入口是会话头部那个图标按钮。
175
- */
176
- const [sideChatOpen, setSideChatOpen] = useState(false);
177
173
  /** 打开抽屉时「落到最新轮」只跳过这一次(点 badge 指定轮次时用) */
178
174
  const skipLatestOnce = useRef(false);
179
175
  /** useGrowth 的"落到最新轮":它在 AppShell 里、不随抽屉卸载,所以必须显式调用 */
@@ -182,6 +178,9 @@ export function AppShell() {
182
178
  const [heroModel, setHeroModel] = useState<{ provider: string; id: string } | null>(null);
183
179
  /** 当前工作区的新会话默认值(pi 的初始模型规则 + 设置里的思考档位),切换工作区时重新取 */
184
180
  const [heroDefaults, setHeroDefaults] = useState<NewSessionDefaults | null>(null);
181
+ /** 按工作区缓存的命令清单:切会话时先用着,再后台刷新 */
182
+ const [workspaceResources, setWorkspaceResources] = useState<Record<string, WorkspaceCommands>>({});
183
+ const [resourcesVersion, setResourcesVersion] = useState(0);
185
184
  const [heroThinking, setHeroThinking] = useState("");
186
185
  const [isNarrow, setIsNarrow] = useState(false);
187
186
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
@@ -193,15 +192,25 @@ export function AppShell() {
193
192
  const section = localStorage.getItem("piweb.drawerSection");
194
193
  if (["project", "prompts", "git", "import"].includes(section ?? "")) setDrawerSection(section as DrawerSection);
195
194
  }, []);
196
- /** 新会话时加载用户之前的选择,或使用系统建议(第一个认证的) */
195
+ /** 新会话时加载用户之前的选择;没有记住的,等 /api/models/defaults 回来用 pi 设置里的建议模型兜底 */
197
196
  useEffect(() => {
198
197
  if (!heroCwd) {
199
198
  setHeroModel(null);
200
199
  return;
201
200
  }
202
- // 只认这个工作区自己记住的模型;没有就不预选,输入框等用户在新会话页选
201
+ // 优先这个工作区自己记住的模型;没有就先留空,交给下面的建议模型兜底
203
202
  setHeroModel(readRememberedHeroModel(localStorage, heroCwd));
204
203
  }, [heroCwd]);
204
+ /**
205
+ * 兜底:这个工作区没记住过选择时,用 pi 设置里的建议模型(defaults.model,服务端按
206
+ * enabledModels + 认证状态挑出来的)。否则点「新会话」永远是「当前模型不可用,请先选择模型」,
207
+ * 明明有可用模型却要先手动选一次。用户显式选过(记住的)就不覆盖。
208
+ */
209
+ useEffect(() => {
210
+ const suggested = heroDefaults?.model;
211
+ if (!heroCwd || !suggested) return;
212
+ setHeroModel((current) => current ?? suggested);
213
+ }, [heroCwd, heroDefaults]);
205
214
  useEffect(() => {
206
215
  setHeroDefaults(null);
207
216
  if (!heroCwd) return;
@@ -216,14 +225,36 @@ export function AppShell() {
216
225
  stale = true;
217
226
  };
218
227
  }, [heroCwd]);
228
+ /** 补全数据归属的工作区:有会话看会话的 cwd,新会话看 Hero 选中的工作区 */
229
+ const resourceCwd = currentId
230
+ ? sessions.find((session) => samePath(session.path, currentPath ?? ""))?.cwd ??
231
+ (state.snapshot?.sessionPath && samePath(state.snapshot.sessionPath, currentPath ?? "") ? state.snapshot.cwd : "")
232
+ : heroCwd;
233
+ // 补全不能等历史快照或 session_start/MCP:按工作区单独发现并缓存,切会话/改设置时后台刷新。
234
+ useEffect(() => {
235
+ if (!resourceCwd) return;
236
+ let stale = false;
237
+ fetch(`/api/agent/resources?cwd=${encodeURIComponent(resourceCwd)}`, { cache: "no-store" })
238
+ .then((r) => r.json() as Promise<{ success?: boolean; data?: WorkspaceCommands; error?: string }>)
239
+ .then((json) => {
240
+ if (stale) return;
241
+ if (!json.success || !json.data) throw new Error(json.error || "Failed to load workspace resources");
242
+ const data = json.data;
243
+ setWorkspaceResources((previous) => ({ ...previous, [resourceCwd]: data }));
244
+ })
245
+ .catch((error) => {
246
+ if (!stale) setError(error instanceof Error ? error.message : String(error));
247
+ });
248
+ return () => {
249
+ stale = true;
250
+ };
251
+ }, [currentId, resourceCwd, settingsOpen, resourcesVersion, setError]);
219
252
  /** 右侧抽屉开关(项目栏降级成抽屉入口后,它是"最右一列是否打开"唯一的开关) */
220
253
  const toggleDrawer = useCallback((next?: boolean) => {
221
254
  preserveChatReadingPosition();
222
255
  setDrawerOpen((cur) => {
223
256
  const v = next ?? !cur;
224
257
  localStorage.setItem("piweb.drawerOpen", v ? "1" : "0");
225
- // 最右一列只有一个:开抽屉就收起旁聊
226
- if (v) setSideChatOpen(false);
227
258
  return v;
228
259
  });
229
260
  }, []);
@@ -248,7 +279,8 @@ export function AppShell() {
248
279
  };
249
280
  document.addEventListener("keydown", h);
250
281
  return () => document.removeEventListener("keydown", h);
251
- }, [drawerOpen, drawerSection, toggleDrawer, openDrawerSection]);
282
+ // selected 必须在依赖里:漏了它,打开轨迹详情后闭包里仍是旧的 null,第一下被当成「收起」,要按两次
283
+ }, [drawerOpen, drawerSection, selected, toggleDrawer, openDrawerSection]);
252
284
 
253
285
  useEffect(() => {
254
286
  const query = window.matchMedia("(max-width: 840px)");
@@ -391,12 +423,12 @@ export function AppShell() {
391
423
  [sidebarWidth, rightWidth],
392
424
  );
393
425
 
394
- const toggleSidebar = () => {
426
+ const toggleSidebar = useCallback(() => {
395
427
  preserveChatReadingPosition();
396
428
  const next = !sidebarCollapsed;
397
429
  setSidebarCollapsed(next);
398
430
  localStorage.setItem("piweb.sidebarCollapsed", next ? "1" : "0");
399
- };
431
+ }, [sidebarCollapsed]);
400
432
 
401
433
  // ---------- 会话操作 ----------
402
434
  const snapshot = state.snapshot;
@@ -412,6 +444,13 @@ export function AppShell() {
412
444
  setSelected(entry);
413
445
  }, []);
414
446
 
447
+ // 轨迹 context 条目的全文回查:快照只在轨迹里带资源头部(全文在 contextResources 一处)
448
+ const contextResourcesRef = useRef(snapshot?.contextResources ?? []);
449
+ useEffect(() => {
450
+ contextResourcesRef.current = snapshot?.contextResources ?? [];
451
+ }, [snapshot?.contextResources]);
452
+ const contextLookup = useCallback((path: string) => contextResourcesRef.current.find((r) => r.path === path)?.content, []);
453
+
415
454
  // 上下文分段的数据源:系统提示词按注入资源字符数(很便宜);
416
455
  // 消息字符统计交给 ContextMeter 弹窗打开时再算,避免每次 token 增量全量扫描
417
456
  const contextSystemChars = useMemo(
@@ -426,20 +465,19 @@ export function AppShell() {
426
465
  return () => clearTimeout(timer);
427
466
  }, [state.error, clearError]);
428
467
 
429
- const doRename = async (path: string, newName: string) => {
468
+ const doRename = useCallback(async (path: string, newName: string) => {
430
469
  if (!newName.trim()) return;
431
- const result = path === currentPath
470
+ // 冷会话:临时走命令路由(会按需打开,不启动 agent 的 rename 走 appendSessionInfo)
471
+ const result = currentPath && samePath(path, currentPath)
432
472
  ? await sendCommand({ cmd: "rename", text: newName.trim() })
433
473
  : await sendCommand({ cmd: "rename", text: newName.trim() }, encodeURIComponent(b64url(path)));
434
474
  if (!result?.success) {
435
475
  throw new Error(result?.error || "failed to rename session");
436
476
  }
477
+ // 侧栏就地改名即可。不要 resync:它重连的是「当前」会话的事件流,与被改名的冷会话无关,
478
+ // 正在流式输出时还会把眼前的对话整页重载一遍
437
479
  patchSessionName(path, newName.trim());
438
- if (path !== currentPath) {
439
- // 冷会话:临时走命令路由(会按需打开,不启动 agent 的 rename 走 appendSessionInfo)
440
- resync();
441
- }
442
- };
480
+ }, [currentPath, sendCommand, patchSessionName]);
443
481
 
444
482
  const doForkSession = useCallback(
445
483
  async (path: string, _cwd: string) => {
@@ -494,15 +532,42 @@ export function AppShell() {
494
532
  const doUnarchiveSession = useCallback(
495
533
  async (path: string) => {
496
534
  try {
535
+ // 归档列表由 unarchiveSession 按服务端返回更新;不 resync 当前会话(同 doRename)
497
536
  await unarchiveSession(path);
498
- resync();
499
537
  } catch {
500
538
  // usePiWeb exposes the request failure in the shared error banner.
501
539
  }
502
540
  },
503
- [unarchiveSession, resync],
541
+ [unarchiveSession],
504
542
  );
505
543
 
544
+ // ---------- 侧栏回调:全部 useCallback,配合 SessionSidebar 的 memo ——
545
+ // 流式期间 state.messages 每 16ms 换引用,AppShell 整树重渲染时侧栏不再跟着空转
546
+ const openSessionAndCloseMobileNav = useCallback((path: string) => {
547
+ openSession(path);
548
+ setMobileSidebarOpen(false);
549
+ }, [openSession]);
550
+ const closeMobileNav = useCallback(() => setMobileSidebarOpen(false), []);
551
+ const startNewSessionInCurrentWorkspace = useCallback(() => {
552
+ // 对齐 dsh startSession:新会话默认落在当前会话的工作区(无则取最近工作区)
553
+ const cur = sessions.find((s) => s.path === currentPath)?.cwd;
554
+ setHeroCwd(cur || heroCwd || knownCwds[0] || "");
555
+ closeSession();
556
+ }, [sessions, currentPath, heroCwd, knownCwds, closeSession]);
557
+ const startNewSessionInWorkspace = useCallback((cwd: string) => {
558
+ // dsh 惰性新建:只预选工作区进草稿态,发送第一条消息才真正创建会话
559
+ setHeroCwd(cwd);
560
+ closeSession();
561
+ }, [closeSession]);
562
+ const exportSessionFile = useCallback((path: string) => {
563
+ window.open(`/api/sessions/${encodeURIComponent(b64url(path))}/export?format=jsonl`, "_blank");
564
+ }, []);
565
+ const openSettingsFromSidebar = useCallback(() => {
566
+ setSettingsOpen(true);
567
+ setMobileSidebarOpen(false);
568
+ }, []);
569
+ const addWorkspaceFromSidebar = useCallback(() => void addWorkspaceByPicker(), [addWorkspaceByPicker]);
570
+
506
571
  const doRenameWorkspace = useCallback(
507
572
  async (cwd: string, newName: string) => {
508
573
  await renameWorkspace(cwd, newName);
@@ -512,13 +577,13 @@ export function AppShell() {
512
577
 
513
578
  const doDeleteWorkspace = useCallback(
514
579
  async (cwd: string) => {
580
+ // 工作区列表由 removeWorkspace 按服务端返回更新;不 resync 当前会话(同 doRename)
515
581
  await removeWorkspace(cwd);
516
- if (heroCwd === cwd) {
582
+ if (heroCwd && samePath(heroCwd, cwd)) {
517
583
  setHeroCwd("");
518
584
  }
519
- resync();
520
585
  },
521
- [removeWorkspace, heroCwd, resync],
586
+ [removeWorkspace, heroCwd],
522
587
  );
523
588
 
524
589
  // Hero 发送:先完整应用新会话预设,再发第一条消息。
@@ -562,6 +627,9 @@ export function AppShell() {
562
627
  [sendCommand, openSession, resync],
563
628
  );
564
629
 
630
+ const commandResources = currentId && snapshot?.sessionPath && samePath(snapshot.sessionPath, currentPath ?? "")
631
+ ? snapshot
632
+ : workspaceResources[resourceCwd];
565
633
  // 斜杠命令:Web 内置 + pi 的技能 / 提示模板 / 扩展命令(后三类原样交给 SDK 展开或执行)
566
634
  const slashCommands = useMemo(() => {
567
635
  const list: { name: string; desc: string; kind: "builtin" | "skill" | "template" | "extension"; argumentHint?: string }[] = [
@@ -571,12 +639,12 @@ export function AppShell() {
571
639
  { name: "new", desc: t.cmdNew, kind: "builtin" },
572
640
  { name: "fork", desc: t.cmdFork, kind: "builtin" },
573
641
  { name: "reload", desc: t.cmdReload, kind: "builtin" },
574
- ...(snapshot?.promptTemplates ?? []).map((p) => ({ name: p.name, desc: p.description, kind: "template" as const, argumentHint: p.argumentHint })),
575
- ...(snapshot?.extensionCommands ?? []).map((c) => ({ name: c.name, desc: c.description || c.source, kind: "extension" as const })),
576
- ...(snapshot?.skills ?? []).map((s) => ({ name: `skill:${s.name}`, desc: s.description, kind: "skill" as const })),
642
+ ...(commandResources?.promptTemplates ?? []).map((p) => ({ name: p.name, desc: p.description, kind: "template" as const, argumentHint: p.argumentHint })),
643
+ ...(commandResources?.extensionCommands ?? []).map((c) => ({ name: c.name, desc: c.description || c.source, kind: "extension" as const })),
644
+ ...(commandResources?.skills ?? []).map((s) => ({ name: `skill:${s.name}`, desc: s.description, kind: "skill" as const })),
577
645
  ];
578
646
  return list.sort((a, b) => a.name.localeCompare(b.name));
579
- }, [snapshot, t]);
647
+ }, [commandResources, t]);
580
648
 
581
649
  const runSlashCommand = useCallback(
582
650
  (name: string, args: string) => {
@@ -588,10 +656,16 @@ export function AppShell() {
588
656
  void sendCommand({ cmd: "fork" }).then((r) => {
589
657
  if (r.success && r.data?.sessionPath) openSession(r.data.sessionPath);
590
658
  });
591
- } else if (name === "reload") void sendCommand({ cmd: "reload" }).then(() => refreshModels());
592
- else setError(t.cmdUnknown.replace("{name}", name));
659
+ } else if (name === "reload") {
660
+ // 新会话页没有会话可重载:重新发现命令并刷模型目录即可
661
+ if (currentId) void sendCommand({ cmd: "reload" }).then(() => refreshModels());
662
+ else {
663
+ setResourcesVersion((version) => version + 1);
664
+ void refreshModels();
665
+ }
666
+ } else setError(t.cmdUnknown.replace("{name}", name));
593
667
  },
594
- [sendCommand, currentId, closeSession, openSession, setError, t],
668
+ [sendCommand, currentId, closeSession, openSession, refreshModels, setError, t],
595
669
  );
596
670
 
597
671
  const projectTrust = snapshot?.projectTrust;
@@ -612,11 +686,13 @@ export function AppShell() {
612
686
 
613
687
  // 当前会话名 / 工作区标题
614
688
  const currentSession = sessions.find((s) => s.path === currentPath);
615
- const firstUserMsgText = (
689
+ const firstUserText = (
616
690
  state.messages
617
691
  .find((m) => m.role === "user")
618
692
  ?.content?.find((c: any) => c.type === "text" && c.text?.trim()) as any
619
693
  )?.text?.trim();
694
+ // 技能展开的消息不能原样当标题(会露出 <skill name=…> 和 SKILL.md 正文)
695
+ const firstUserMsgText = firstUserText ? promptTitleText(firstUserText) : firstUserText;
620
696
  const title =
621
697
  snapshot?.name ||
622
698
  currentSession?.name ||
@@ -696,6 +772,8 @@ export function AppShell() {
696
772
  connected: state.connected,
697
773
  });
698
774
  const openFileNode = useCallback((node: TreeNode) => viewer.open(node.path, { from: node.from, lazy: node.lazy, kind: node.kind }), [viewer]);
775
+ // 正文行内代码里的路径走页内查看器;工具行上那个按钮仍然是本机编辑器(openInEditor)
776
+ const openPathInViewer = useCallback((filePath: string) => viewer.open(filePath), [viewer.open]);
699
777
  // 打开面板时落在最新轮:useGrowth 活在 AppShell 里、不随抽屉卸载,state 不会自己回到最新,
700
778
  // 所以每次从「关」变「开」(或换了工作区)都显式选一次最新轮。
701
779
  // 新轮次出现时不再自动跳——用户打开面板后停在哪一轮就是哪一轮。
@@ -749,7 +827,7 @@ export function AppShell() {
749
827
  // 抽屉里的一个入口,不再单独占一列,所以是 3 列而不是 4 列。
750
828
  // JSX 里各列的渲染顺序必须与这里严格一致,否则网格会把内容摆错列。
751
829
  // 最右一列只有一条宽度(rightWidth):详情栏与抽屉合并后不再各占一列。
752
- const rightColumnVisible = Boolean(selected) || drawerOpen || sideChatOpen;
830
+ const rightColumnVisible = Boolean(selected) || drawerOpen;
753
831
  const gridCols = isNarrow ? "minmax(0,1fr)" : `${sidebarCollapsed ? SIDEBAR_COLLAPSED : sidebarWidth}px minmax(0,1fr) ${
754
832
  rightColumnVisible ? `${rightWidth}px` : "0px"}`;
755
833
 
@@ -769,17 +847,7 @@ export function AppShell() {
769
847
  } : { background: "var(--dsw-sidebar-fill)", borderLeft: "0.5px solid var(--dsw-border-l2)" }}
770
848
  >
771
849
  {selected ? (
772
- <TrajInspector entry={selected} onClose={() => setSelected(null)} />
773
- ) : sideChatOpen ? (
774
- <SideChatPanel
775
- sessionId={currentId}
776
- ready={Boolean(snapshot)}
777
- mainModel={snapshot?.model}
778
- models={modelChoices}
779
- providerNames={providerNames}
780
- authByProvider={authByProvider}
781
- onClose={() => setSideChatOpen(false)}
782
- />
850
+ <TrajInspector entry={selected} onClose={() => setSelected(null)} contextLookup={contextLookup} />
783
851
  ) : (
784
852
  <DrawerPanel
785
853
  cwd={panelCwd}
@@ -845,35 +913,20 @@ export function AppShell() {
845
913
  setGroupBy={setGroupBy}
846
914
  setOrderBy={setOrderBy}
847
915
  collapsed={isNarrow ? false : sidebarCollapsed}
848
- onToggleCollapse={isNarrow ? () => setMobileSidebarOpen(false) : toggleSidebar}
849
- onOpen={(path) => {
850
- openSession(path);
851
- setMobileSidebarOpen(false);
852
- }}
853
- onNew={() => {
854
- // 对齐 dsh startSession:新会话默认落在当前会话的工作区(无则取最近工作区)
855
- const cur = sessions.find((s) => s.path === currentPath)?.cwd;
856
- setHeroCwd(cur || heroCwd || knownCwds[0] || "");
857
- closeSession();
858
- }}
859
- onNewInWorkspace={(cwd) => {
860
- // dsh 惰性新建:只预选工作区进草稿态,发送第一条消息才真正创建会话
861
- setHeroCwd(cwd);
862
- closeSession();
863
- }}
916
+ onToggleCollapse={isNarrow ? closeMobileNav : toggleSidebar}
917
+ onOpen={openSessionAndCloseMobileNav}
918
+ onNew={startNewSessionInCurrentWorkspace}
919
+ onNewInWorkspace={startNewSessionInWorkspace}
864
920
  onRename={doRename}
865
921
  onFork={doForkSession}
866
922
  onArchive={doArchiveSession}
867
923
  onUnarchive={doUnarchiveSession}
868
- onExport={(path) => window.open(`/api/sessions/${encodeURIComponent(b64url(path))}/export?format=jsonl`, "_blank")}
924
+ onExport={exportSessionFile}
869
925
  getWorkspaceName={getWorkspaceName}
870
926
  onRenameWorkspace={doRenameWorkspace}
871
927
  onDeleteWorkspace={doDeleteWorkspace}
872
- onOpenSettings={() => {
873
- setSettingsOpen(true);
874
- setMobileSidebarOpen(false);
875
- }}
876
- onAddWorkspace={() => void addWorkspaceByPicker()}
928
+ onOpenSettings={openSettingsFromSidebar}
929
+ onAddWorkspace={addWorkspaceFromSidebar}
877
930
  draftCwd={!currentId ? heroCwd || null : null}
878
931
  />
879
932
  </div>
@@ -939,27 +992,6 @@ export function AppShell() {
939
992
  <span className="min-w-0 flex-1 truncate" style={{ fontSize: 14.5, fontWeight: 600 }}>
940
993
  {title}
941
994
  </span>
942
- {/* 旁聊直达按钮;旁聊与其他入口共用右侧抽屉。 */}
943
- <button
944
- type="button"
945
- className="icon-btn"
946
- style={{ width: 26, height: 26, color: sideChatOpen ? "var(--dsw-accent)" : "var(--dsw-label-secondary)" }}
947
- aria-label={t.sideChat}
948
- title={t.sideChat}
949
- aria-pressed={sideChatOpen}
950
- data-testid="side-chat-toggle"
951
- onClick={() => {
952
- preserveChatReadingPosition();
953
- setSelected(null);
954
- setSideChatOpen((open) => {
955
- // 最右一列只有一个:开旁聊就收起抽屉
956
- if (!open) toggleDrawer(false);
957
- return !open;
958
- });
959
- }}
960
- >
961
- <SideChatIcon size={16} />
962
- </button>
963
995
  {/* 右侧抽屉的入口与开关 */}
964
996
  <button
965
997
  type="button"
@@ -1031,6 +1063,7 @@ export function AppShell() {
1031
1063
  cwd={panelCwd}
1032
1064
  onOpenTrajectory={openToolTrajectory}
1033
1065
  onOpenFile={openInEditor}
1066
+ onOpenPath={openPathInViewer}
1034
1067
  onFork={handleFork}
1035
1068
  onEditMessage={doEditMessage}
1036
1069
  roundBadges={roundBadges}
@@ -1115,7 +1148,7 @@ export function AppShell() {
1115
1148
  {/* 窄屏最右一列:贴右边滑出(轨迹详情与抽屉共用,遮罩关闭回到对话) */}
1116
1149
  {isNarrow && rightColumnVisible && (
1117
1150
  <>
1118
- <button className="fixed inset-0 z-[85]" style={{ background: "var(--dsw-mask)" }} onClick={() => (selected ? setSelected(null) : sideChatOpen ? setSideChatOpen(false) : toggleDrawer(false))} aria-label={t.close} />
1151
+ <button className="fixed inset-0 z-[85]" style={{ background: "var(--dsw-mask)" }} onClick={() => (selected ? setSelected(null) : toggleDrawer(false))} aria-label={t.close} />
1119
1152
  {rightColumn}
1120
1153
  </>
1121
1154
  )}
@@ -1345,6 +1378,8 @@ function Hero({
1345
1378
  pendingUploadCount={pendingUploadCount}
1346
1379
  pendingSend={pendingSend}
1347
1380
  onSendPendingChange={onSendPendingChange}
1381
+ // Hero 的附件占位,避免 @ 引用和图片盖住上方工作区选择器
1382
+ attachmentLayout="flow"
1348
1383
  // 新会话也能 @ 引用:候选来自上面选中的工作区
1349
1384
  cwd={cwd}
1350
1385
  commands={commands}