@jetcrab/pi-desk 1.1.18-dev → 1.1.21

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 (250) hide show
  1. package/docs/plugins.md +82 -2
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/src/app/page.tsx +6 -1
  5. package/src/app/project-preview/page.tsx +10 -0
  6. package/src/client/l1_entry/pages/l1-home-page.tsx +30 -0
  7. package/src/client/l1_entry/pages/l1-project-preview-page.tsx +33 -0
  8. package/src/client/l1_entry/providers/l1-app-providers.tsx +18 -15
  9. package/src/client/l1_entry/providers/l1-app-runtime-provider.tsx +2 -2
  10. package/src/client/l2_biz/auth/l2-login-biz.ts +3 -0
  11. package/src/client/l2_biz/auth/l2-login.tsx +2 -3
  12. package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +6 -1
  13. package/src/client/l2_biz/plugin-host/l2-plugin-contribution-host.tsx +71 -10
  14. package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +9 -0
  15. package/src/client/l2_biz/plugin-host/l2-plugin-session-header-layout.ts +18 -0
  16. package/src/client/l2_biz/plugin-host/l2-plugin-session-header.tsx +247 -0
  17. package/src/client/l2_biz/plugin-host/l2-plugin-widgets.tsx +50 -0
  18. package/src/client/l2_biz/plugin-host/views/l2-plugin-session-header-action.tsx +112 -0
  19. package/src/client/l2_biz/project-preview/hooks/l2-use-project-picker.ts +76 -0
  20. package/src/client/l2_biz/project-preview/hooks/l2-use-project-preview.ts +214 -0
  21. package/src/client/l2_biz/project-preview/l2-project-preview-biz.ts +49 -4
  22. package/src/client/l2_biz/project-preview/l2-project-preview-dialog.tsx +42 -450
  23. package/src/client/l2_biz/project-preview/l2-project-preview-page.tsx +61 -0
  24. package/src/client/l2_biz/project-preview/l2-project-preview.tsx +342 -0
  25. package/src/client/l2_biz/project-preview/runtime/l2-project-history-runtime.ts +9 -3
  26. package/src/client/l2_biz/project-preview/views/l2-project-picker.tsx +168 -0
  27. package/src/client/l2_biz/settings/hooks/l2-use-model-settings.ts +34 -31
  28. package/src/client/l2_biz/settings/views/l2-model-replacement-preview.tsx +7 -1
  29. package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-runtime.ts +1 -1
  30. package/src/client/l2_biz/workbench/file-workspace/l2-project-preview-biz.ts +4 -3
  31. package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-reload.ts +42 -0
  32. package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +28 -21
  33. package/src/client/l2_biz/workbench/l2-work-session-columns.module.css +32 -0
  34. package/src/client/l2_biz/workbench/l2-work-session-columns.tsx +55 -16
  35. package/src/client/l2_biz/workbench/l2-work-session-sidebar.tsx +38 -9
  36. package/src/client/l2_biz/workbench/l2-workbench-chat-composer.tsx +12 -1
  37. package/src/client/l2_biz/workbench/l2-workbench-chat.ts +45 -0
  38. package/src/client/l2_biz/workbench/l2-workbench-initialization-notice.tsx +8 -3
  39. package/src/client/l2_biz/workbench/l2-workbench-plugin-slots.ts +13 -0
  40. package/src/client/l2_biz/workbench/l2-workbench.tsx +19 -2
  41. package/src/client/l3_modules/conversation/l3-conversation-markdown.tsx +10 -1
  42. package/src/client/l3_modules/conversation/l3-conversation-viewport.tsx +12 -6
  43. package/src/client/l3_modules/project-files/l3-markdown-preview.module.css +1 -1
  44. package/src/client/l3_modules/project-files/l3-markdown-preview.tsx +10 -1
  45. package/src/client/l3_modules/project-files/l3-project-document-git.ts +4 -3
  46. package/src/client/l3_modules/project-files/l3-project-document-view.tsx +6 -1
  47. package/src/client/l3_modules/project-files/l3-project-file-browser.tsx +171 -229
  48. package/src/client/l3_modules/project-files/l3-project-file-runtime.ts +133 -70
  49. package/src/client/l3_modules/project-files/l3-project-reader.ts +26 -37
  50. package/src/client/l3_modules/project-files/views/l3-project-search-controls.tsx +201 -0
  51. package/src/client/l3_modules/project-files/views/l3-project-search-results.tsx +333 -0
  52. package/src/client/l4_foundation/lib/l4-client-id.tsx +11 -0
  53. package/src/client/l4_foundation/plugin-host/l4-plugin-host-runtime.ts +316 -35
  54. package/src/client/l4_foundation/ui/code/l4-code-preview.tsx +47 -3
  55. package/src/client/l4_foundation/ui/markdown/l4-markdown-remark.ts +89 -0
  56. package/src/client/l4_foundation/ui/markdown/l4-markdown.module.css +58 -0
  57. package/src/client/l4_foundation/ui/markdown/l4-markdown.tsx +108 -0
  58. package/src/common/l2_biz/auth/l2-auth-return-to.ts +41 -0
  59. package/src/common/l2_biz/model-settings/l2-model-settings-contract.ts +5 -1
  60. package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +4 -0
  61. package/src/common/l2_biz/project-preview/l2-project-history-messages.ts +28 -0
  62. package/src/common/l2_biz/project-preview/l2-project-preview-navigation.ts +5 -0
  63. package/src/common/l2_biz/settings/l2-settings-messages.ts +4 -3
  64. package/src/common/l2_biz/work-session/l2-work-session-file-contract.ts +4 -3
  65. package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +8 -0
  66. package/src/common/l3_modules/project-files/l3-project-files-contract.ts +2 -3
  67. package/src/common/l3_modules/project-files/l3-project-files-locale-messages.ts +56 -10
  68. package/src/common/l4_foundation/file/l4-project-file-contract.ts +51 -1
  69. package/src/common/l4_foundation/locale/l4-host-error-messages.ts +11 -0
  70. package/src/common/l4_foundation/locale/l4-locale-messages.ts +16 -0
  71. package/src/instrumentation.ts +0 -2
  72. package/src/server/l1_entry/api/l1-project-read-routes.ts +6 -4
  73. package/src/server/l1_entry/api/l1-project-route.ts +9 -8
  74. package/src/server/l1_entry/api/l1-work-session-file-error.ts +23 -0
  75. package/src/server/l1_entry/node/l1-server.ts +8 -4
  76. package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +0 -3
  77. package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +30 -58
  78. package/src/server/l2_biz/work-session/l2-work-session-manage.ts +0 -4
  79. package/src/server/l4_foundation/file/l4-project-file-index.ts +276 -0
  80. package/src/server/l4_foundation/file/l4-project-search.ts +399 -0
  81. package/src/server/l4_foundation/file/l4-ripgrep-search.ts +192 -0
  82. package/src/server/l4_foundation/file/l4-work-session-project-file.ts +8 -332
  83. package/src/server/l4_foundation/model-settings/l4-model-settings-store.ts +29 -20
  84. package/src/server/l4_foundation/model-settings/l4-model-settings-types.ts +1 -1
  85. package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +22 -5
  86. package/src/server/l4_foundation/pi/l4-pi-package-maintenance.ts +0 -6
  87. package/src/server/l4_foundation/pi/l4-pi-plugin-capability-probe.ts +1 -3
  88. package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +133 -13
  89. package/src/server/l4_foundation/pi/l4-pi-plugin-probe.ts +1 -3
  90. package/src/server/l4_foundation/pi/l4-pi-runner-process.ts +2 -2
  91. package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +9 -0
  92. package/src/server/l4_foundation/process/l4-process-tree.js +23 -4
  93. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  94. package/temp/build/pi-desk/release/.next/app-path-routes-manifest.json +3 -2
  95. package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
  96. package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
  97. package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -2
  98. package/temp/build/pi-desk/release/.next/required-server-files.js +4 -3
  99. package/temp/build/pi-desk/release/.next/required-server-files.json +4 -3
  100. package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
  101. package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
  102. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  103. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  104. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
  105. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
  106. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
  107. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  108. package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
  109. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  110. package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +1 -1
  111. package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
  112. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
  113. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
  114. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
  115. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
  116. package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
  117. package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
  118. package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
  119. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  120. package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
  121. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
  122. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
  123. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
  124. package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
  125. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
  126. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -1
  127. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
  128. package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
  129. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
  130. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
  131. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
  132. package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
  133. package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
  134. package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
  135. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
  136. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
  137. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
  138. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
  139. package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
  140. package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
  141. package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
  142. package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
  143. package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
  144. package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
  145. package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
  146. package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
  147. package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
  148. package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
  149. package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
  150. package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +2 -1
  151. package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +2 -1
  152. package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
  153. package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +2 -1
  154. package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +2 -1
  155. package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +2 -1
  156. package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +2 -1
  157. package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -1
  158. package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +2 -1
  159. package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -1
  160. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
  161. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
  162. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
  163. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
  164. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
  165. package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
  166. package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
  167. package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
  168. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
  169. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
  170. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
  171. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
  172. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
  173. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
  174. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
  175. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
  176. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
  177. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/sort/route.js +1 -1
  178. package/temp/build/pi-desk/release/.next/server/app/favicon.ico/route.js +1 -1
  179. package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
  180. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  181. package/temp/build/pi-desk/release/.next/server/app/page.js +72 -72
  182. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  183. package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -0
  184. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -0
  185. package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +3 -2
  186. package/temp/build/pi-desk/release/.next/server/chunks/10912.js +2 -2
  187. package/temp/build/pi-desk/release/.next/server/chunks/20460.js +15 -0
  188. package/temp/build/pi-desk/release/.next/server/chunks/23054.js +1 -1
  189. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +10 -10
  190. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +3 -17
  191. package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
  192. package/temp/build/pi-desk/release/.next/server/chunks/48180.js +1 -1
  193. package/temp/build/pi-desk/release/.next/server/chunks/50520.js +2 -4
  194. package/temp/build/pi-desk/release/.next/server/chunks/56830.js +235 -0
  195. package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
  196. package/temp/build/pi-desk/release/.next/server/chunks/70780.js +1 -0
  197. package/temp/build/pi-desk/release/.next/server/chunks/74528.js +1 -1
  198. package/temp/build/pi-desk/release/.next/server/chunks/78262.js +1 -1
  199. package/temp/build/pi-desk/release/.next/server/chunks/79686.js +62 -0
  200. package/temp/build/pi-desk/release/.next/server/chunks/80780.js +1 -0
  201. package/temp/build/pi-desk/release/.next/server/chunks/81363.js +1 -1
  202. package/temp/build/pi-desk/release/.next/server/chunks/82499.js +4 -4
  203. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +8 -8
  204. package/temp/build/pi-desk/release/.next/server/instrumentation.js +1 -1
  205. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  206. package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
  207. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  208. package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
  209. package/temp/build/pi-desk/release/.next/static/8Yd33M0zPi4tbRn4xCaRY/_buildManifest.js +1 -0
  210. package/temp/build/pi-desk/release/.next/static/chunks/11937-1d0a9822f2c7995b.js +1 -0
  211. package/temp/build/pi-desk/release/.next/static/chunks/14825-5579aed34a631ad7.js +62 -0
  212. package/temp/build/pi-desk/release/.next/static/chunks/15302-9c807afc6c60de0c.js +1 -0
  213. package/temp/build/pi-desk/release/.next/static/chunks/19991.8aa2e4b6df15fad6.js +1 -0
  214. package/temp/build/pi-desk/release/.next/static/chunks/22092-6d1bfbefa825ec03.js +1 -0
  215. package/temp/build/pi-desk/release/.next/static/chunks/37313-d5970e2d0c270d22.js +238 -0
  216. package/temp/build/pi-desk/release/.next/static/chunks/{49516-cc4dcd84d64ab4fe.js → 51474-8dc714cb76b2e60a.js} +2 -2
  217. package/temp/build/pi-desk/release/.next/static/chunks/57235-03ba1044f22b6c84.js +1 -0
  218. package/temp/build/pi-desk/release/.next/static/chunks/79267-7d21d7cdbd7cfca1.js +1 -0
  219. package/temp/build/pi-desk/release/.next/static/chunks/82371-f7ba4a07a5dbcf34.js +1 -0
  220. package/temp/build/pi-desk/release/.next/static/chunks/9038-03144ed96d613f75.js +15 -0
  221. package/temp/build/pi-desk/release/.next/static/chunks/app/{error-0296a3b20cd1bf00.js → error-216b14b5da089ed3.js} +1 -1
  222. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-20718f1503910291.js +1 -0
  223. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-0d3d4827802108e5.js +1 -0
  224. package/temp/build/pi-desk/release/.next/static/chunks/app/{not-found-2aab8b33ebbf3257.js → not-found-720cdf6a606b821a.js} +1 -1
  225. package/temp/build/pi-desk/release/.next/static/chunks/app/page-6ebc1c877f6025dd.js +158 -0
  226. package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-d70ee4e9409b970a.js +1 -0
  227. package/temp/build/pi-desk/release/.next/static/chunks/webpack-b60891a214e0925d.js +1 -0
  228. package/temp/build/pi-desk/release/.next/static/css/1e780a4936aa6ba3.css +1 -0
  229. package/temp/build/pi-desk/release/.next/static/css/6dbae2922f88ca31.css +1 -0
  230. package/temp/build/pi-desk/release/.next/static/css/f646ca6c07245c4c.css +1 -0
  231. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  232. package/temp/build/pi-desk/release/.next/server/chunks/33658.js +0 -296
  233. package/temp/build/pi-desk/release/.next/server/chunks/8584.js +0 -2
  234. package/temp/build/pi-desk/release/.next/static/chunks/31087-ba426d09d6f68bfd.js +0 -1
  235. package/temp/build/pi-desk/release/.next/static/chunks/3814-35cfab0ce3aa7c41.js +0 -1
  236. package/temp/build/pi-desk/release/.next/static/chunks/39654-b5fedd762ea0312e.js +0 -238
  237. package/temp/build/pi-desk/release/.next/static/chunks/70669.07f3670bfb7d6dd2.js +0 -1
  238. package/temp/build/pi-desk/release/.next/static/chunks/74220-795a2e7185bd66cb.js +0 -1
  239. package/temp/build/pi-desk/release/.next/static/chunks/75130-d2ca74894aff8085.js +0 -13
  240. package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +0 -1
  241. package/temp/build/pi-desk/release/.next/static/chunks/81881-f11dbe7dc0d04ed9.js +0 -1
  242. package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +0 -62
  243. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-c0e1c5cef8620cf8.js +0 -3
  244. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-84892b06243891f0.js +0 -1
  245. package/temp/build/pi-desk/release/.next/static/chunks/app/page-a984139e99586041.js +0 -158
  246. package/temp/build/pi-desk/release/.next/static/chunks/webpack-40a782fc8a3535b9.js +0 -1
  247. package/temp/build/pi-desk/release/.next/static/css/6427d02b6edf406f.css +0 -1
  248. package/temp/build/pi-desk/release/.next/static/css/79e06f05df9edff0.css +0 -1
  249. package/temp/build/pi-desk/release/.next/static/n2LrXQgq8UkbH_AzhR3fw/_buildManifest.js +0 -1
  250. /package/temp/build/pi-desk/release/.next/static/{n2LrXQgq8UkbH_AzhR3fw → 8Yd33M0zPi4tbRn4xCaRY}/_ssgManifest.js +0 -0
@@ -0,0 +1,42 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useMemo, useSyncExternalStore } from 'react'
4
+ import { useTranslation } from 'react-i18next'
5
+ import type { L2WorkSessionListItem } from '@common/l2_biz/work-session/l2-work-session-contract'
6
+ import { useL4AppToast } from '@client/l4_foundation/ui/l4-app-toast'
7
+ import type { L2WorkbenchChatRuntime } from '../l2-workbench-chat'
8
+
9
+ export function useL2WorkbenchChatReload(
10
+ runtime: L2WorkbenchChatRuntime,
11
+ workSessions: readonly L2WorkSessionListItem[]
12
+ ): {
13
+ syncingWorkIds: readonly string[]
14
+ reload: (workSession: L2WorkSessionListItem) => void
15
+ } {
16
+ const { t } = useTranslation('workbench')
17
+ const toast = useL4AppToast()
18
+ const getSyncing = useCallback(
19
+ (): string =>
20
+ JSON.stringify(
21
+ workSessions
22
+ .filter(
23
+ ({ workId, sessionId, branchId }) =>
24
+ runtime.getSourceState({ workId, sessionId, branchId }).syncStatus === 'syncing'
25
+ )
26
+ .map((session) => session.workId)
27
+ ),
28
+ [runtime, workSessions]
29
+ )
30
+ const syncing = useSyncExternalStore(runtime.subscribe, getSyncing, getSyncing)
31
+ const syncingWorkIds = useMemo((): string[] => JSON.parse(syncing), [syncing])
32
+ const reload = useCallback(
33
+ (session: L2WorkSessionListItem): void => {
34
+ const { workId, sessionId, branchId } = session
35
+ void runtime.reloadMessages({ workId, sessionId, branchId }).catch((cause: unknown) => {
36
+ toast.error(cause instanceof Error ? cause.message : t('reloadChatDataFailed'))
37
+ })
38
+ },
39
+ [runtime, t, toast]
40
+ )
41
+ return { syncingWorkIds, reload }
42
+ }
@@ -7,7 +7,8 @@ import {
7
7
  clearL3ConversationTailSpacer,
8
8
  findL3ConversationMessage,
9
9
  logL3ConversationScroll,
10
- positionL3ConversationMessage
10
+ positionL3ConversationMessage,
11
+ refreshL3ConversationTailSpacer
11
12
  } from '@client/l3_modules/conversation/l3-conversation-viewport'
12
13
  import type { L2WorkbenchChatViewportSnapshot } from '../l2-workbench-chat'
13
14
 
@@ -252,25 +253,18 @@ export function L2WorkbenchChatViewportManager({
252
253
  const enterRunningAtBottom =
253
254
  entryPosition?.follow ?? (initializing && running && snapshot === null)
254
255
  const entryTarget = target
255
- const positionEntry = (): void => {
256
- stopScroll()
257
- if (entryTarget) {
258
- positionL3ConversationMessage(scrollElement, entryTarget, targetOffset, containsTail)
259
- } else {
260
- clearL3ConversationTailSpacer(scrollElement)
261
- }
262
- if (enterRunningAtBottom || !entryTarget) {
263
- // 首屏懒布局展开后也在绘制前置底,不交给后续缓动收敛。
264
- scrollElement.scrollTop = Math.max(
265
- 0,
266
- scrollElement.scrollHeight - scrollElement.clientHeight
267
- )
268
- }
269
- markProgrammaticScroll(state, scrollElement)
270
- // 同步确立跟随,立即切走也不能丢失这个状态。
271
- if (followAfterPosition || !entryTarget) void scrollToBottom({ animation: 'instant' })
256
+ stopScroll()
257
+ if (entryTarget) {
258
+ positionL3ConversationMessage(scrollElement, entryTarget, targetOffset, containsTail)
259
+ } else {
260
+ clearL3ConversationTailSpacer(scrollElement)
261
+ }
262
+ if (enterRunningAtBottom || !entryTarget) {
263
+ scrollElement.scrollTop = Math.max(0, state.calculatedTargetScrollTop)
272
264
  }
273
- positionEntry()
265
+ markProgrammaticScroll(state, scrollElement)
266
+ // 同步确立跟随,立即切走也不能丢失这个状态。
267
+ if (followAfterPosition || !entryTarget) void scrollToBottom({ animation: 'instant' })
274
268
 
275
269
  logL3ConversationScroll('viewport:initialize:positioned', scrollElement, {
276
270
  initializing,
@@ -288,7 +282,18 @@ export function L2WorkbenchChatViewportManager({
288
282
  anchor: anchor ? { messageId: anchor, offset: targetOffset } : null,
289
283
  follow: followAfterPosition || target === null
290
284
  }
291
- const observer = new ResizeObserver(positionEntry)
285
+ const observer = new ResizeObserver(() => {
286
+ // 懒布局与拖宽只校正既定目标,不再重走提问定位、停止和启动跟随。
287
+ if (followAfterPosition || !entryTarget) {
288
+ refreshL3ConversationTailSpacer(scrollElement)
289
+ scrollElement.scrollTop = Math.max(0, state.calculatedTargetScrollTop)
290
+ } else {
291
+ // 内容收缩时库可能自动恢复跟随;首屏阅读锚点仍优先。
292
+ if (state.isAtBottom) stopScroll()
293
+ positionL3ConversationMessage(scrollElement, entryTarget, targetOffset, containsTail)
294
+ }
295
+ markProgrammaticScroll(state, scrollElement)
296
+ })
292
297
  observer.observe(scrollElement)
293
298
  if (scrollElement.firstElementChild) observer.observe(scrollElement.firstElementChild)
294
299
  const interactionElement = scrollElement.parentElement!
@@ -356,13 +361,15 @@ export function L2WorkbenchChatViewportManager({
356
361
  isAtBottom: state.isAtBottom,
357
362
  escapedFromLock: state.escapedFromLock
358
363
  })
364
+ // 新视图会在旧视图清理前读取快照,恢复跟随时就释放旧阅读位置。
365
+ onClearSnapshot()
359
366
  void scrollToBottom({ animation: 'instant' })
360
367
  }
361
368
  }
362
369
 
363
370
  scrollElement.addEventListener('scroll', resumeFollowAtBottom, { passive: true })
364
371
  return () => scrollElement.removeEventListener('scroll', resumeFollowAtBottom)
365
- }, [latestUserAnchor, scrollRef, scrollToBottom, state, visible])
372
+ }, [latestUserAnchor, onClearSnapshot, scrollRef, scrollToBottom, state, visible])
366
373
 
367
374
  useLayoutEffect(() => {
368
375
  if (!running || !visible) return
@@ -1,3 +1,35 @@
1
+ .chatContainer {
2
+ container-name: work-session-chat;
3
+ container-type: inline-size;
4
+ }
5
+
6
+ .headerActionIcon {
7
+ display: none;
8
+ }
9
+
10
+ @container work-session-chat (width < 28rem) {
11
+ .sessionHeader {
12
+ gap: 0.5rem;
13
+ }
14
+
15
+ .headerAction {
16
+ width: 1.75rem;
17
+ height: 1.75rem;
18
+ min-height: 1.75rem;
19
+ gap: 0;
20
+ border-radius: 0.375rem;
21
+ padding: 0;
22
+ }
23
+
24
+ .headerActionLabel {
25
+ display: none;
26
+ }
27
+
28
+ .headerActionIcon {
29
+ display: block;
30
+ }
31
+ }
32
+
1
33
  .collapsedText {
2
34
  overflow: hidden;
3
35
  text-overflow: ellipsis;
@@ -54,8 +54,10 @@ import { useL2WorkSessionColumnCapacity } from './hooks/l2-work-session-column-c
54
54
  import type { L2FileWorkspaceRuntime } from './file-workspace/l2-file-workspace-runtime'
55
55
  import { L2WorkSessionFileLayout } from './file-workspace/l2-work-session-file-layout'
56
56
  import { Button, buttonVariants } from '@client/l4_foundation/ui/shadcn/button'
57
+ import type { L2WorkbenchPluginSessionSlots } from './l2-workbench-plugin-slots'
57
58
 
58
- interface L2WorkSessionColumnsProps extends L2WorkbenchComposerSlots {
59
+ interface L2WorkSessionColumnsProps
60
+ extends L2WorkbenchComposerSlots, L2WorkbenchPluginSessionSlots {
59
61
  onOpenSettings?: (target?: 'model-presets') => void
60
62
  workSessions: readonly L2WorkSessionListItem[]
61
63
  primaryWorkId: string | null
@@ -126,7 +128,7 @@ function SidebarToggle({
126
128
  )
127
129
  }
128
130
 
129
- interface WorkSessionColumnProps extends L2WorkbenchComposerSlots {
131
+ interface WorkSessionColumnProps extends L2WorkbenchComposerSlots, L2WorkbenchPluginSessionSlots {
130
132
  onOpenSettings?: (target?: 'model-presets') => void
131
133
  workSession: L2WorkSessionListItem
132
134
  fixed: boolean
@@ -181,6 +183,8 @@ const WorkSessionColumn = memo(function WorkSessionColumn({
181
183
  renderTaskSummary,
182
184
  renderTaskMenu,
183
185
  renderPluginComposerPanel,
186
+ renderPluginWidgets,
187
+ renderPluginSessionHeader,
184
188
  gitRuntime,
185
189
  invokePluginMethod,
186
190
  listPiCommands,
@@ -214,6 +218,34 @@ const WorkSessionColumn = memo(function WorkSessionColumn({
214
218
  useSyncExternalStore(fileRuntime.subscribe, fileRuntime.getRevision, fileRuntime.getRevision)
215
219
  const fileWorkspace = fileRuntime.getWorkspace(workSession.workId)
216
220
  const fileCount = (fileWorkspace?.tabs.length ?? 0) + (fileWorkspace?.activeDiff ? 1 : 0)
221
+ const source = useMemo<L2ChatSource>(
222
+ () => ({
223
+ workId: workSession.workId,
224
+ sessionId: workSession.sessionId,
225
+ branchId: workSession.branchId
226
+ }),
227
+ [workSession.workId, workSession.sessionId, workSession.branchId]
228
+ )
229
+ const title = (
230
+ <div className="min-w-0">
231
+ <p
232
+ className={cn(
233
+ 'truncate text-sm transition-colors duration-150 motion-reduce:transition-none',
234
+ focused ? 'font-semibold text-foreground' : 'font-medium text-muted-foreground'
235
+ )}
236
+ title={workSession.projectName}
237
+ >
238
+ {workSession.projectName}
239
+ </p>
240
+ <p
241
+ className="truncate text-xs text-muted-foreground"
242
+ data-testid="session-header-title"
243
+ title={workSession.sessionTitle ?? t('newSession')}
244
+ >
245
+ {workSession.sessionTitle ?? t('newSession')}
246
+ </p>
247
+ </div>
248
+ )
217
249
 
218
250
  useEffect(() => {
219
251
  const column = columnRef.current
@@ -259,23 +291,18 @@ const WorkSessionColumn = memo(function WorkSessionColumn({
259
291
  onOpenSidebar={() => onOpenFileSidebar(workSession.workId)}
260
292
  >
261
293
  {(chatVisible) => (
262
- <div className="flex h-full min-h-0 min-w-0 flex-1 flex-col">
263
- <header className="flex min-h-14 items-center gap-3 px-3">
294
+ <div className={cn(styles.chatContainer, 'flex h-full min-h-0 min-w-0 flex-1 flex-col')}>
295
+ <header
296
+ className={cn(styles.sessionHeader, 'flex min-h-14 shrink-0 items-center gap-3 px-3')}
297
+ data-testid="session-header"
298
+ >
264
299
  {fixed && !mobile ? null : (
265
300
  <SidebarToggle sidebarOpen={sidebarOpen} onToggle={onToggleSidebar} />
266
301
  )}
267
302
  <div className="min-w-0 flex-1">
268
- <p
269
- className={cn(
270
- 'truncate text-sm transition-colors duration-150 motion-reduce:transition-none',
271
- focused ? 'font-semibold text-foreground' : 'font-medium text-muted-foreground'
272
- )}
273
- >
274
- {workSession.projectName}
275
- </p>
276
- <p className="truncate text-xs text-muted-foreground">
277
- {workSession.sessionTitle ?? t('newSession')}
278
- </p>
303
+ {chatVisible && contentActive
304
+ ? renderPluginSessionHeader({ source, title })
305
+ : title}
279
306
  </div>
280
307
  <div className="-mr-1 flex shrink-0 items-center">
281
308
  {initializationIssue.error ? (
@@ -292,13 +319,17 @@ const WorkSessionColumn = memo(function WorkSessionColumn({
292
319
  {fileCount > 0 ? (
293
320
  <Button
294
321
  variant="ghost"
322
+ className={styles.headerAction}
295
323
  data-testid="session-files-entry"
296
324
  aria-label={t('openSessionFiles')}
325
+ title={t('sessionFiles', { count: fileCount })}
297
326
  aria-pressed={fileWorkspace?.windowOpen ?? false}
298
327
  onClick={() => fileRuntime.showWindow(workSession.workId)}
299
328
  >
300
329
  <FilesIcon className="size-4" />
301
- {t('sessionFiles', { count: fileCount })}
330
+ <span className={styles.headerActionLabel}>
331
+ {t('sessionFiles', { count: fileCount })}
332
+ </span>
302
333
  </Button>
303
334
  ) : null}
304
335
  {onUnpinWorkSession ? (
@@ -338,6 +369,8 @@ const WorkSessionColumn = memo(function WorkSessionColumn({
338
369
  renderTaskSummary={renderTaskSummary}
339
370
  renderTaskMenu={renderTaskMenu}
340
371
  renderPluginComposerPanel={renderPluginComposerPanel}
372
+ renderPluginWidgets={renderPluginWidgets}
373
+ widgetsVisible={chatVisible && contentActive}
341
374
  gitRuntime={gitRuntime}
342
375
  active={composerActive}
343
376
  mobile={mobile}
@@ -473,6 +506,8 @@ export function L2WorkSessionColumns({
473
506
  renderTaskSummary,
474
507
  renderTaskMenu,
475
508
  renderPluginComposerPanel,
509
+ renderPluginWidgets,
510
+ renderPluginSessionHeader,
476
511
  gitRuntime,
477
512
  invokePluginMethod,
478
513
  listPiCommands,
@@ -660,6 +695,8 @@ export function L2WorkSessionColumns({
660
695
  renderTaskSummary={renderTaskSummary}
661
696
  renderTaskMenu={renderTaskMenu}
662
697
  renderPluginComposerPanel={renderPluginComposerPanel}
698
+ renderPluginWidgets={renderPluginWidgets}
699
+ renderPluginSessionHeader={renderPluginSessionHeader}
663
700
  gitRuntime={gitRuntime}
664
701
  invokePluginMethod={invokePluginMethod}
665
702
  listPiCommands={listPiCommands}
@@ -758,6 +795,8 @@ export function L2WorkSessionColumns({
758
795
  renderTaskSummary={renderTaskSummary}
759
796
  renderTaskMenu={renderTaskMenu}
760
797
  renderPluginComposerPanel={renderPluginComposerPanel}
798
+ renderPluginWidgets={renderPluginWidgets}
799
+ renderPluginSessionHeader={renderPluginSessionHeader}
761
800
  gitRuntime={gitRuntime}
762
801
  invokePluginMethod={invokePluginMethod}
763
802
  listPiCommands={listPiCommands}
@@ -21,6 +21,7 @@ import {
21
21
  EllipsisIcon,
22
22
  FolderTreeIcon,
23
23
  FolderSearchIcon,
24
+ ExternalLinkIcon,
24
25
  LoaderCircleIcon,
25
26
  MessageSquareTextIcon,
26
27
  PinIcon,
@@ -131,6 +132,7 @@ interface L2WorkSessionSidebarProps {
131
132
  onModeChange: (mode: L2WorkSessionSidebarMode) => void
132
133
  onRefreshFiles: () => void
133
134
  onPreviewProject: () => void
135
+ onPreviewProjectWindow: () => void
134
136
  onOpenTerminal: () => void
135
137
  onCreate: () => void
136
138
  onReplace: () => void
@@ -140,6 +142,8 @@ interface L2WorkSessionSidebarProps {
140
142
  onTogglePin: (workId: string) => void
141
143
  onDelete: (workId: string) => void
142
144
  onRename: (workSession: L2WorkSessionListItem) => void
145
+ syncingWorkIds: readonly string[]
146
+ onReloadChat: (workSession: L2WorkSessionListItem) => void
143
147
  onArrangementChange: (arrangement: L2WorkSessionArrangement, movedWorkId: string) => void
144
148
  }
145
149
 
@@ -343,7 +347,9 @@ const SortableWorkSessionRow = memo(function SortableWorkSessionRow({
343
347
  onSelect,
344
348
  onTogglePin,
345
349
  onDelete,
346
- onRename
350
+ onRename,
351
+ syncing,
352
+ onReloadChat
347
353
  }: {
348
354
  workSession: L2WorkSessionListItem
349
355
  mobile: boolean
@@ -357,6 +363,8 @@ const SortableWorkSessionRow = memo(function SortableWorkSessionRow({
357
363
  onTogglePin: (workId: string) => void
358
364
  onDelete: (workId: string) => void
359
365
  onRename: (workSession: L2WorkSessionListItem) => void
366
+ syncing: boolean
367
+ onReloadChat: (workSession: L2WorkSessionListItem) => void
360
368
  }): React.JSX.Element {
361
369
  const suppressTouchClick = useRef(false)
362
370
  const lastPointer = useRef('')
@@ -606,6 +614,14 @@ const SortableWorkSessionRow = memo(function SortableWorkSessionRow({
606
614
  <PencilIcon />
607
615
  {t('renameSessionTitle')}
608
616
  </ContextMenuItem>
617
+ <ContextMenuItem
618
+ data-testid={`work-session-reload-chat-${workSession.workId}`}
619
+ disabled={actionsDisabled || deleting || syncing}
620
+ onClick={() => onReloadChat(workSession)}
621
+ >
622
+ {syncing ? <LoaderCircleIcon className="animate-spin" /> : <RefreshCwIcon />}
623
+ {t('reloadChatData')}
624
+ </ContextMenuItem>
609
625
  <ContextMenuItem
610
626
  data-testid={`work-session-copy-project-path-${workSession.workId}`}
611
627
  onClick={copyProjectPath}
@@ -666,6 +682,7 @@ function L2WorkSessionSidebarComponent({
666
682
  onModeChange,
667
683
  onRefreshFiles,
668
684
  onPreviewProject,
685
+ onPreviewProjectWindow,
669
686
  onOpenTerminal,
670
687
  onCreate,
671
688
  onReplace,
@@ -675,6 +692,8 @@ function L2WorkSessionSidebarComponent({
675
692
  onTogglePin,
676
693
  onDelete,
677
694
  onRename,
695
+ syncingWorkIds,
696
+ onReloadChat,
678
697
  onArrangementChange
679
698
  }: L2WorkSessionSidebarProps): React.JSX.Element {
680
699
  const { t } = useTranslation('workbench')
@@ -753,14 +772,6 @@ function L2WorkSessionSidebarComponent({
753
772
  ]
754
773
  : mode === 'files'
755
774
  ? [
756
- {
757
- key: 'new-terminal',
758
- label: t('newTerminal'),
759
- disabled,
760
- icon: <TerminalIcon />,
761
- testId: 'project-files-terminal-open',
762
- onSelect: onOpenTerminal
763
- },
764
775
  {
765
776
  key: 'preview-project',
766
777
  label: t('projectPreview'),
@@ -769,6 +780,22 @@ function L2WorkSessionSidebarComponent({
769
780
  testId: 'project-preview-open',
770
781
  onSelect: onPreviewProject
771
782
  },
783
+ {
784
+ key: 'preview-project-window',
785
+ label: t('projectPreviewWindow'),
786
+ disabled: !canBrowseFiles,
787
+ icon: <ExternalLinkIcon />,
788
+ testId: 'project-preview-window-open',
789
+ onSelect: onPreviewProjectWindow
790
+ },
791
+ {
792
+ key: 'new-terminal',
793
+ label: t('newTerminal'),
794
+ disabled,
795
+ icon: <TerminalIcon />,
796
+ testId: 'project-files-terminal-open',
797
+ onSelect: onOpenTerminal
798
+ },
772
799
  {
773
800
  key: 'refresh-files',
774
801
  label: t('refreshFiles'),
@@ -888,6 +915,8 @@ function L2WorkSessionSidebarComponent({
888
915
  onTogglePin={onTogglePin}
889
916
  onDelete={onDelete}
890
917
  onRename={onRename}
918
+ syncing={syncingWorkIds.includes(workSession.workId)}
919
+ onReloadChat={onReloadChat}
891
920
  />
892
921
  )
893
922
  })
@@ -89,6 +89,7 @@ import type { L3CapabilityModeNames } from '@common/l3_modules/capability-modes/
89
89
  import { L2WorkbenchCapabilityModePicker } from './l2-workbench-capability-mode-picker'
90
90
  import { useL2NativeUi } from './native-ui/l2-native-ui'
91
91
  import { L2NativeUiExtras, L2NativeUiQuestions } from './native-ui/l2-native-ui-view'
92
+ import type { L2WorkbenchPluginWidgetSlots } from './l2-workbench-plugin-slots'
92
93
 
93
94
  interface L2WorkbenchTaskSlotInput {
94
95
  source: L2ChatSource
@@ -107,13 +108,15 @@ export interface L2WorkbenchComposerSlots {
107
108
  }) => ReactNode
108
109
  }
109
110
 
110
- interface L2WorkbenchChatComposerProps extends L2WorkbenchComposerSlots {
111
+ interface L2WorkbenchChatComposerProps
112
+ extends L2WorkbenchComposerSlots, L2WorkbenchPluginWidgetSlots {
111
113
  onOpenSettings?: (target?: 'model-presets') => void
112
114
  workSession: L2WorkSessionListItem
113
115
  chatRuntime: L2WorkbenchChatRuntime
114
116
  inputRuntime: L2WorkbenchChatInputRuntime
115
117
  gitRuntime: L2GitWorkspaceRuntime
116
118
  active: boolean
119
+ widgetsVisible: boolean
117
120
  mobile: boolean
118
121
  invokePluginMethod: L2WorkbenchBiz['invokePluginMethod']
119
122
  listPiCommands: L2WorkbenchBiz['listPiCommands']
@@ -161,8 +164,10 @@ function L2WorkbenchChatComposerComponent({
161
164
  renderTaskSummary,
162
165
  renderTaskMenu,
163
166
  renderPluginComposerPanel,
167
+ renderPluginWidgets,
164
168
  gitRuntime,
165
169
  active,
170
+ widgetsVisible,
166
171
  mobile,
167
172
  invokePluginMethod,
168
173
  listPiCommands,
@@ -633,6 +638,9 @@ function L2WorkbenchChatComposerComponent({
633
638
  <p className="mb-1 px-1 text-xs text-muted-foreground">{t('nativeUiComposer')}</p>
634
639
  ) : null}
635
640
 
641
+ {widgetsVisible && !basicChat
642
+ ? renderPluginWidgets({ source, placement: 'aboveEditor' })
643
+ : null}
636
644
  <form
637
645
  aria-label={
638
646
  workSessionRunning
@@ -1021,6 +1029,9 @@ function L2WorkbenchChatComposerComponent({
1021
1029
  </PromptInputFooter>
1022
1030
  </InputGroup>
1023
1031
  </form>
1032
+ {widgetsVisible && !basicChat
1033
+ ? renderPluginWidgets({ source, placement: 'belowEditor' })
1034
+ : null}
1024
1035
  <L2NativeUiExtras snapshot={nativeUi.state.snapshot} placement="belowEditor" />
1025
1036
 
1026
1037
  {activePluginPanel &&
@@ -73,6 +73,7 @@ export interface L2WorkbenchChatRuntime {
73
73
  ) => Promise<L2ChatMessageDetail>
74
74
  compact: (source: L2ChatSource) => Promise<void>
75
75
  reload: (source: L2ChatSource, mode?: L2ChatMode) => Promise<void>
76
+ reloadMessages: (source: L2ChatSource) => Promise<void>
76
77
  setPresentation: (source: L2ChatSource, mode: L2ChatMode) => Promise<void>
77
78
  disconnected: () => void
78
79
  dispose: () => void
@@ -611,6 +612,50 @@ export function createL2WorkbenchChatRuntime(
611
612
  )
612
613
  },
613
614
 
615
+ async reloadMessages(sourceInput): Promise<void> {
616
+ const source = L2ChatSourceSchema.parse(sourceInput)
617
+ const key = sourceKey(source)
618
+ const workSession = currentWorkSessions.get(source.workId)
619
+ if (!workSession || !sameSource(sourceFor(workSession), source)) {
620
+ throw new Error(l2WorkbenchText('reloadChatSourceChanged'))
621
+ }
622
+ const previous = statesBySource.get(key)
623
+ if (
624
+ !workSessionsReady ||
625
+ disposed ||
626
+ pendingSyncs.has(key) ||
627
+ previous?.syncStatus === 'syncing'
628
+ ) {
629
+ throw new Error(l2WorkbenchText('sourceSyncing'))
630
+ }
631
+ const epoch = connectionEpoch
632
+ const queue = eventQueues.get(key)
633
+ const previousSyncEpoch = queue?.syncEpoch ?? null
634
+ if (queue) queue.syncEpoch = null
635
+ subscriptionPool.set(source.workId, source)
636
+ setSourceState(source, { ...(previous ?? EMPTY_CHAT_SOURCE_STATE), syncStatus: 'syncing' })
637
+ try {
638
+ // 重拉期间继续接收旧基线上的实时消息,失败时无需丢弃缓存或补偿重放。
639
+ await appSocket.request(L2ChatSocketContracts.subscribe, {
640
+ subscriptions: [{ source, cursor: null }]
641
+ })
642
+ } catch (error) {
643
+ if (!disposed && epoch === connectionEpoch && isCurrentAndSubscribed(source)) {
644
+ if (queue && eventQueues.get(key) === queue) queue.syncEpoch = previousSyncEpoch
645
+ const current = statesBySource.get(key)
646
+ if (current)
647
+ setSourceState(source, { ...current, syncStatus: previous?.syncStatus ?? 'loading' })
648
+ }
649
+ console.error('[Pi Desk][WorkbenchChat] 重新加载聊天数据失败,保留已有缓存', {
650
+ workId: source.workId,
651
+ sessionId: source.sessionId,
652
+ branchId: source.branchId,
653
+ message: error instanceof Error ? error.message : String(error)
654
+ })
655
+ throw error
656
+ }
657
+ },
658
+
614
659
  async setPresentation(sourceInput, mode): Promise<void> {
615
660
  const source = L2ChatSourceSchema.parse(sourceInput)
616
661
  const key = sourceKey(source)
@@ -1,7 +1,9 @@
1
1
  'use client'
2
2
 
3
- import { CheckIcon, CopyIcon, LoaderCircleIcon, RefreshCwIcon } from 'lucide-react'
3
+ import { CheckIcon, CircleAlertIcon, CopyIcon, LoaderCircleIcon, RefreshCwIcon } from 'lucide-react'
4
4
  import { useTranslation } from 'react-i18next'
5
+ import { cn } from '@client/l4_foundation/lib/l4-utils'
6
+ import styles from './l2-work-session-columns.module.css'
5
7
  import { Button } from '@client/l4_foundation/ui/shadcn/button'
6
8
  import {
7
9
  Popover,
@@ -36,11 +38,14 @@ export function L2WorkbenchInitializationNotice({
36
38
  <Button
37
39
  variant="ghost"
38
40
  size="sm"
39
- className="text-xs text-destructive hover:text-destructive"
41
+ aria-label={t('initializationDegraded')}
42
+ title={t('initializationDegraded')}
43
+ className={cn(styles.headerAction, 'text-xs text-destructive hover:text-destructive')}
40
44
  />
41
45
  }
42
46
  >
43
- {t('initializationDegraded')}
47
+ <CircleAlertIcon className={cn(styles.headerActionIcon, 'size-4')} aria-hidden="true" />
48
+ <span className={styles.headerActionLabel}>{t('initializationDegraded')}</span>
44
49
  </PopoverTrigger>
45
50
  <PopoverContent
46
51
  align="end"
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react'
2
+ import type { PluginSource } from '@jetcrab/pi-desk-sdk/browser'
3
+
4
+ export interface L2WorkbenchPluginWidgetSlots {
5
+ renderPluginWidgets: (input: {
6
+ source: PluginSource
7
+ placement: 'aboveEditor' | 'belowEditor'
8
+ }) => ReactNode
9
+ }
10
+
11
+ export interface L2WorkbenchPluginSessionSlots extends L2WorkbenchPluginWidgetSlots {
12
+ renderPluginSessionHeader: (input: { source: PluginSource; title: ReactNode }) => ReactNode
13
+ }
@@ -66,6 +66,7 @@ import { L4_APP_VERSION } from '@client/l4_foundation/l4-app-info'
66
66
  import { L2WorkbenchSidebarLayout } from './l2-workbench-sidebar-layout'
67
67
  import type { L4PluginBrowserContributionDescriptor } from '@client/l4_foundation/plugin-host/l4-plugin-host-runtime'
68
68
  import type { L2WorkbenchComposerSlots } from './l2-workbench-chat-composer'
69
+ import type { L2WorkbenchPluginSessionSlots } from './l2-workbench-plugin-slots'
69
70
  import { createL2WorkbenchBiz } from './l2-workbench-biz'
70
71
  import { createL2WorkbenchVersionRefresh } from './l2-workbench-version-refresh'
71
72
  import { L2FileWorkspaceRuntime } from './file-workspace/l2-file-workspace-runtime'
@@ -112,6 +113,7 @@ import {
112
113
  } from './l2-work-session-sidebar-order'
113
114
  import { useL2WorkbenchBranchPicker } from './hooks/l2-workbench-branch-picker'
114
115
  import { useL2WorkSessionRename } from './hooks/l2-work-session-rename'
116
+ import { useL2WorkbenchChatReload } from './hooks/l2-workbench-chat-reload'
115
117
  import { L2WorkSessionRenameDialog } from './views/l2-work-session-rename-dialog'
116
118
  import { useL2WorkbenchPluginHealth } from './hooks/l2-workbench-plugin-health'
117
119
  import { useL2WorkbenchSessionHistory } from './hooks/l2-workbench-session-history'
@@ -171,10 +173,11 @@ function WorkbenchDisplaySlot<Input>({
171
173
  return render(input)
172
174
  }
173
175
 
174
- interface L2WorkbenchProps extends L2WorkbenchComposerSlots {
176
+ interface L2WorkbenchProps extends L2WorkbenchComposerSlots, L2WorkbenchPluginSessionSlots {
175
177
  onTerminalReady: () => Promise<void>
176
178
  onTerminalDisconnected: () => void
177
179
  onOpenTerminal: (cwd?: string) => Promise<void>
180
+ onOpenProjectPreviewWindow: (cwd: string) => void
178
181
  renderTerminalWorkspace: (input: { mobile: boolean; children: ReactNode }) => ReactNode
179
182
  listPluginManagement: () => Promise<L2PluginManagementSnapshot>
180
183
  subscribePluginManagementChanges: (listener: () => void) => () => void
@@ -220,6 +223,7 @@ export function L2Workbench({
220
223
  onTerminalReady,
221
224
  onTerminalDisconnected,
222
225
  onOpenTerminal,
226
+ onOpenProjectPreviewWindow,
223
227
  renderTerminalWorkspace,
224
228
  listPluginManagement,
225
229
  subscribePluginManagementChanges,
@@ -233,7 +237,9 @@ export function L2Workbench({
233
237
  renderSettings,
234
238
  renderTaskSummary,
235
239
  renderTaskMenu,
236
- renderPluginComposerPanel
240
+ renderPluginComposerPanel,
241
+ renderPluginWidgets,
242
+ renderPluginSessionHeader
237
243
  }: L2WorkbenchProps): React.JSX.Element {
238
244
  const { t } = useTranslation('workbench')
239
245
  const { clientId, appSocket } = useL4AppSocket()
@@ -370,6 +376,7 @@ export function L2Workbench({
370
376
  createL2WorkbenchChatRuntime(appSocket, (input) => workbenchBiz.getChatMessageDetail(input)),
371
377
  [appSocket, workbenchBiz]
372
378
  )
379
+ const chatReload = useL2WorkbenchChatReload(chatRuntime, workSessions)
373
380
  const chatInputRuntime = useMemo(
374
381
  () => createL2WorkbenchChatInputRuntime(workbenchBiz, chatRuntime),
375
382
  [chatRuntime, workbenchBiz]
@@ -1915,6 +1922,12 @@ export function L2Workbench({
1915
1922
  fileRuntime.collapseExpandedWindow()
1916
1923
  pluginHost.previewProject(activeWorkId ? fileRuntime.getDirectory(activeWorkId) : activeCwd)
1917
1924
  }}
1925
+ onPreviewProjectWindow={() => {
1926
+ if (!activeCwd) return
1927
+ onOpenProjectPreviewWindow(
1928
+ activeWorkId ? fileRuntime.getDirectory(activeWorkId) : activeCwd
1929
+ )
1930
+ }}
1918
1931
  onOpenTerminal={() => {
1919
1932
  closeMobileDrawer()
1920
1933
  fileRuntime.collapseExpandedWindow()
@@ -1928,6 +1941,8 @@ export function L2Workbench({
1928
1941
  onTogglePin={togglePinnedWorkSession}
1929
1942
  onDelete={requestDeleteWorkSession}
1930
1943
  onRename={renameSession.show}
1944
+ syncingWorkIds={chatReload.syncingWorkIds}
1945
+ onReloadChat={chatReload.reload}
1931
1946
  onArrangementChange={requestArrangementUpdate}
1932
1947
  />
1933
1948
  )
@@ -1969,6 +1984,8 @@ export function L2Workbench({
1969
1984
  renderTaskSummary={renderTaskSummary}
1970
1985
  renderTaskMenu={renderTaskMenu}
1971
1986
  renderPluginComposerPanel={renderPluginComposerPanel}
1987
+ renderPluginWidgets={renderPluginWidgets}
1988
+ renderPluginSessionHeader={renderPluginSessionHeader}
1972
1989
  gitRuntime={gitRuntime}
1973
1990
  invokePluginMethod={workbenchBiz.invokePluginMethod}
1974
1991
  listPiCommands={workbenchBiz.listPiCommands}