@jetcrab/pi-desk 1.1.20-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 (204) hide show
  1. package/docs/plugins.md +81 -1
  2. package/package.json +3 -3
  3. package/src/app/page.tsx +6 -1
  4. package/src/app/project-preview/page.tsx +10 -0
  5. package/src/client/l1_entry/pages/l1-home-page.tsx +30 -0
  6. package/src/client/l1_entry/pages/l1-project-preview-page.tsx +33 -0
  7. package/src/client/l1_entry/providers/l1-app-providers.tsx +18 -15
  8. package/src/client/l1_entry/providers/l1-app-runtime-provider.tsx +2 -2
  9. package/src/client/l2_biz/auth/l2-login-biz.ts +3 -0
  10. package/src/client/l2_biz/auth/l2-login.tsx +2 -3
  11. package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +6 -1
  12. package/src/client/l2_biz/plugin-host/l2-plugin-contribution-host.tsx +71 -10
  13. package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +9 -0
  14. package/src/client/l2_biz/plugin-host/l2-plugin-session-header-layout.ts +18 -0
  15. package/src/client/l2_biz/plugin-host/l2-plugin-session-header.tsx +247 -0
  16. package/src/client/l2_biz/plugin-host/l2-plugin-widgets.tsx +50 -0
  17. package/src/client/l2_biz/plugin-host/views/l2-plugin-session-header-action.tsx +112 -0
  18. package/src/client/l2_biz/project-preview/hooks/l2-use-project-picker.ts +76 -0
  19. package/src/client/l2_biz/project-preview/hooks/l2-use-project-preview.ts +214 -0
  20. package/src/client/l2_biz/project-preview/l2-project-preview-biz.ts +49 -4
  21. package/src/client/l2_biz/project-preview/l2-project-preview-dialog.tsx +42 -450
  22. package/src/client/l2_biz/project-preview/l2-project-preview-page.tsx +61 -0
  23. package/src/client/l2_biz/project-preview/l2-project-preview.tsx +342 -0
  24. package/src/client/l2_biz/project-preview/runtime/l2-project-history-runtime.ts +9 -3
  25. package/src/client/l2_biz/project-preview/views/l2-project-picker.tsx +168 -0
  26. package/src/client/l2_biz/settings/hooks/l2-use-model-settings.ts +34 -31
  27. package/src/client/l2_biz/settings/views/l2-model-replacement-preview.tsx +7 -1
  28. package/src/client/l2_biz/workbench/file-workspace/l2-project-preview-biz.ts +4 -3
  29. package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +28 -21
  30. package/src/client/l2_biz/workbench/l2-work-session-columns.module.css +32 -0
  31. package/src/client/l2_biz/workbench/l2-work-session-columns.tsx +55 -16
  32. package/src/client/l2_biz/workbench/l2-work-session-sidebar.tsx +19 -8
  33. package/src/client/l2_biz/workbench/l2-workbench-chat-composer.tsx +12 -1
  34. package/src/client/l2_biz/workbench/l2-workbench-initialization-notice.tsx +8 -3
  35. package/src/client/l2_biz/workbench/l2-workbench-plugin-slots.ts +13 -0
  36. package/src/client/l2_biz/workbench/l2-workbench.tsx +15 -2
  37. package/src/client/l3_modules/conversation/l3-conversation-viewport.tsx +12 -6
  38. package/src/client/l3_modules/project-files/l3-markdown-preview.module.css +1 -1
  39. package/src/client/l3_modules/project-files/l3-project-document-git.ts +4 -3
  40. package/src/client/l3_modules/project-files/l3-project-document-view.tsx +4 -1
  41. package/src/client/l3_modules/project-files/l3-project-reader.ts +23 -35
  42. package/src/client/l4_foundation/lib/l4-client-id.tsx +11 -0
  43. package/src/client/l4_foundation/plugin-host/l4-plugin-host-runtime.ts +316 -35
  44. package/src/common/l2_biz/auth/l2-auth-return-to.ts +41 -0
  45. package/src/common/l2_biz/model-settings/l2-model-settings-contract.ts +5 -1
  46. package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +4 -0
  47. package/src/common/l2_biz/project-preview/l2-project-history-messages.ts +28 -0
  48. package/src/common/l2_biz/project-preview/l2-project-preview-navigation.ts +5 -0
  49. package/src/common/l2_biz/settings/l2-settings-messages.ts +4 -3
  50. package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +2 -0
  51. package/src/server/l1_entry/node/l1-server.ts +8 -4
  52. package/src/server/l4_foundation/model-settings/l4-model-settings-store.ts +29 -20
  53. package/src/server/l4_foundation/model-settings/l4-model-settings-types.ts +1 -1
  54. package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +22 -5
  55. package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +133 -13
  56. package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +9 -0
  57. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  58. package/temp/build/pi-desk/release/.next/app-path-routes-manifest.json +3 -2
  59. package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
  60. package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
  61. package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -2
  62. package/temp/build/pi-desk/release/.next/required-server-files.js +3 -3
  63. package/temp/build/pi-desk/release/.next/required-server-files.json +3 -3
  64. package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
  65. package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
  66. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  67. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  68. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
  69. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
  70. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
  71. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  72. package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
  73. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  74. package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +1 -1
  75. package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
  76. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
  77. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
  78. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
  79. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
  80. package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
  81. package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
  82. package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
  83. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  84. package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
  85. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
  86. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
  87. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
  88. package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
  89. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
  90. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -1
  91. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
  92. package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
  93. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
  94. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
  95. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
  96. package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
  97. package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
  98. package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
  99. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
  100. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
  101. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
  102. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
  103. package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
  104. package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
  105. package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
  106. package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
  107. package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
  108. package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
  109. package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
  110. package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
  111. package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
  112. package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
  113. package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
  114. package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +1 -1
  115. package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +1 -1
  116. package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
  117. package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +2 -2
  118. package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +1 -1
  119. package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +1 -1
  120. package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +1 -1
  121. package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -2
  122. package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +1 -1
  123. package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -2
  124. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
  125. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
  126. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
  127. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
  128. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
  129. package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
  130. package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
  131. package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
  132. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
  133. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
  134. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
  135. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
  136. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
  137. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
  138. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
  139. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
  140. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
  141. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/sort/route.js +1 -1
  142. package/temp/build/pi-desk/release/.next/server/app/favicon.ico/route.js +1 -1
  143. package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
  144. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  145. package/temp/build/pi-desk/release/.next/server/app/page.js +140 -140
  146. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  147. package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -0
  148. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -0
  149. package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +3 -2
  150. package/temp/build/pi-desk/release/.next/server/chunks/10912.js +2 -2
  151. package/temp/build/pi-desk/release/.next/server/chunks/20460.js +15 -0
  152. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +8 -8
  153. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +3 -17
  154. package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
  155. package/temp/build/pi-desk/release/.next/server/chunks/48180.js +1 -1
  156. package/temp/build/pi-desk/release/.next/server/chunks/56830.js +235 -0
  157. package/temp/build/pi-desk/release/.next/server/chunks/78262.js +1 -1
  158. package/temp/build/pi-desk/release/.next/server/chunks/79686.js +62 -0
  159. package/temp/build/pi-desk/release/.next/server/chunks/80780.js +1 -0
  160. package/temp/build/pi-desk/release/.next/server/chunks/81363.js +1 -1
  161. package/temp/build/pi-desk/release/.next/server/chunks/82499.js +4 -4
  162. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +8 -8
  163. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  164. package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
  165. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  166. package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
  167. package/temp/build/pi-desk/release/.next/static/8Yd33M0zPi4tbRn4xCaRY/_buildManifest.js +1 -0
  168. package/temp/build/pi-desk/release/.next/static/chunks/11937-1d0a9822f2c7995b.js +1 -0
  169. package/temp/build/pi-desk/release/.next/static/chunks/14825-5579aed34a631ad7.js +62 -0
  170. package/temp/build/pi-desk/release/.next/static/chunks/15302-9c807afc6c60de0c.js +1 -0
  171. package/temp/build/pi-desk/release/.next/static/chunks/19991.8aa2e4b6df15fad6.js +1 -0
  172. package/temp/build/pi-desk/release/.next/static/chunks/22092-6d1bfbefa825ec03.js +1 -0
  173. package/temp/build/pi-desk/release/.next/static/chunks/37313-d5970e2d0c270d22.js +238 -0
  174. package/temp/build/pi-desk/release/.next/static/chunks/57235-03ba1044f22b6c84.js +1 -0
  175. package/temp/build/pi-desk/release/.next/static/chunks/79267-7d21d7cdbd7cfca1.js +1 -0
  176. package/temp/build/pi-desk/release/.next/static/chunks/82371-f7ba4a07a5dbcf34.js +1 -0
  177. package/temp/build/pi-desk/release/.next/static/chunks/9038-03144ed96d613f75.js +15 -0
  178. package/temp/build/pi-desk/release/.next/static/chunks/app/{error-0296a3b20cd1bf00.js → error-216b14b5da089ed3.js} +1 -1
  179. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-20718f1503910291.js +1 -0
  180. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-0d3d4827802108e5.js +1 -0
  181. package/temp/build/pi-desk/release/.next/static/chunks/app/{not-found-2aab8b33ebbf3257.js → not-found-720cdf6a606b821a.js} +1 -1
  182. package/temp/build/pi-desk/release/.next/static/chunks/app/page-6ebc1c877f6025dd.js +158 -0
  183. package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-d70ee4e9409b970a.js +1 -0
  184. package/temp/build/pi-desk/release/.next/static/chunks/webpack-b60891a214e0925d.js +1 -0
  185. package/temp/build/pi-desk/release/.next/static/css/{250a6b93cc2222d1.css → 1e780a4936aa6ba3.css} +1 -1
  186. package/temp/build/pi-desk/release/.next/static/css/f646ca6c07245c4c.css +1 -0
  187. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  188. package/temp/build/pi-desk/release/.next/server/chunks/10881.js +0 -296
  189. package/temp/build/pi-desk/release/.next/static/RLKcK-RQF5FvEeDmS9uih/_buildManifest.js +0 -1
  190. package/temp/build/pi-desk/release/.next/static/chunks/27611-0890482fe76bd9d6.js +0 -238
  191. package/temp/build/pi-desk/release/.next/static/chunks/31087-d8e5765d541d5c26.js +0 -1
  192. package/temp/build/pi-desk/release/.next/static/chunks/3814-35cfab0ce3aa7c41.js +0 -1
  193. package/temp/build/pi-desk/release/.next/static/chunks/70669.07f3670bfb7d6dd2.js +0 -1
  194. package/temp/build/pi-desk/release/.next/static/chunks/74220-795a2e7185bd66cb.js +0 -1
  195. package/temp/build/pi-desk/release/.next/static/chunks/77592-5bef6acb8e7c4b96.js +0 -13
  196. package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +0 -1
  197. package/temp/build/pi-desk/release/.next/static/chunks/81881-f11dbe7dc0d04ed9.js +0 -1
  198. package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +0 -62
  199. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-a50c456648537afb.js +0 -3
  200. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-84892b06243891f0.js +0 -1
  201. package/temp/build/pi-desk/release/.next/static/chunks/app/page-0789c43551b29dd2.js +0 -158
  202. package/temp/build/pi-desk/release/.next/static/chunks/webpack-d7248206b7fc7ba8.js +0 -1
  203. package/temp/build/pi-desk/release/.next/static/css/a4cf28fd76c70165.css +0 -1
  204. /package/temp/build/pi-desk/release/.next/static/{RLKcK-RQF5FvEeDmS9uih → 8Yd33M0zPi4tbRn4xCaRY}/_ssgManifest.js +0 -0
package/docs/plugins.md CHANGED
@@ -50,6 +50,8 @@ Node Entry 默认导出普通 `PiDeskPluginDefinition` 对象即可,`definePiD
50
50
 
51
51
  `registerBrowserEntry()` 最多一次,路径位于包内,文件名为 `entry.js`。无需自有资源时 `setup()` 不必返回清理;创建 Timer、Watcher、连接或子进程时返回 disposer,释放旧实例资源。Host 自动撤销注册;旧异步清理不得写入新实例。
52
52
 
53
+ 宿主启动时,单个 Node 插件的入口导入、`setup()` 和 Browser 资源检查共用 15 秒上限。超时不代表插件代码已经停止;宿主撤销旧实例,等首轮插件加载、旧加载及其清理结束后,延迟 10 秒自动重试一次。明确加载错误和再次超时不自动重试;手动重载、禁用、卸载或宿主关闭会取消待执行的恢复。加载失败可在插件管理行点击“重新加载”,只重载本机代码。
54
+
53
55
  会话能力在真实原生扩展中绑定,不用 Node Global 冒充 Session:
54
56
 
55
57
  ```ts
@@ -81,7 +83,7 @@ plugin.addPreviewDirectory(directory, { default: true })
81
83
 
82
84
  ## Browser Entry 与懒界面
83
85
 
84
- Entry 默认导出普通同步函数即可;在函数返回前完成全部 `registerContribution(kind, name, definition)`。kind 支持 `application`、`composer-panel`、`settings-page`、`session-sidebar-tab`、`message-view`。Entry 只放元数据、loader、轻量页面状态和事件订阅;返回 disposer 清理自有订阅与资源。
86
+ Entry 默认导出普通同步函数即可;在函数返回前完成全部注册。`registerContribution(kind, name, definition)` 支持 `application`、`composer-panel`、`settings-page`、`session-sidebar-tab`、`message-view`;输入区挂件与会话标题项分别使用 `registerWidget()`、`registerSessionHeaderItem()`,不能通过 `registerContribution` 注册。Entry 只放元数据、loader、轻量页面状态和事件订阅;返回 disposer 清理自有订阅与资源。
85
87
 
86
88
  Entry 不静态引入 React、ReactDOM、UI、CSS 或大型实现,也不等网络或动态 import 后再注册。UI 放在 `load()` 的动态模块,导出 `{ mount({ container, target, host, signal }) }`;Lazy Module 顶层不启动副作用。
87
89
 
@@ -107,6 +109,84 @@ Application 使用 `chrome: 'host'` 让宿主负责 Dialog、标题、关闭、
107
109
 
108
110
  界面继承宿主明暗主题与字号,布局使用 `rem`、合理内边距和自身滚动;窄屏与触控可用,不以裁切或只靠 hover 隐藏必要操作。按钮、表单、加载/错误/重试优先复用 `/react/base`;需要 Markdown 时才加载 `/react/markdown`。
109
111
 
112
+ ### 输入框上下方挂件
113
+
114
+ 在 Browser Entry 注册,不调用原生 `ctx.ui.setWidget()`:
115
+
116
+ ```js
117
+ plugin.registerWidget('session-info', {
118
+ placement: 'aboveEditor',
119
+ load: () => import('./session-info.js').then((module) => module.default)
120
+ })
121
+ ```
122
+
123
+ 同目录 `session-info.js` 是最小 Lazy View:
124
+
125
+ ```js
126
+ export default {
127
+ mount({ container, target }) {
128
+ const element = document.createElement('p')
129
+ element.textContent = `当前会话:${target.source.sessionId}`
130
+ element.style.cssText = 'margin:0;overflow-wrap:anywhere'
131
+ container.append(element)
132
+ return () => element.remove()
133
+ }
134
+ }
135
+ ```
136
+
137
+ `placement` 只允许 `aboveEditor / belowEditor`,省略时是上方。Target 为 `{ kind: 'widget', source }`。挂件在对应聊天区可见时挂载,自然高度;没有内容时不预留空白。多个挂件按插件名和注册名稳定排列,不进入标题栏更多浮层。关闭或隐藏聊天区、Source 变化和插件重载会 Abort/dispose;重新挂载时按现有 Method/Push 建立数据基线。
138
+
139
+ ### 锚定浮层
140
+
141
+ Lazy View 可从 `@jetcrab/pi-desk-sdk/react/base` 使用受控 `PluginPopover`。`trigger` 接收 React Element,其他内容作为 children;宿主组件负责 Portal、锚定、视口约束和焦点,打开不会抢走输入焦点。悬停、短暂提示和业务状态由插件控制,不自行定位到宿主 DOM。
142
+
143
+ ```tsx
144
+ const [open, setOpen] = useState(false)
145
+
146
+ <PluginPopover
147
+ open={open}
148
+ onOpenChange={setOpen}
149
+ side="top"
150
+ trigger={<PluginButton>查看详情</PluginButton>}
151
+ >
152
+ <div style={{ padding: '0.75rem' }}>当前内容</div>
153
+ </PluginPopover>
154
+ ```
155
+
156
+ `side` 默认 `bottom`,`align` 默认 `start`,`className` 用于自身浮层的布局。浮层维持指定方向并限制可用高度;长内容自行使用 `PluginScroll`。调用方关闭或卸载时清理自己的计时器、Observer 和订阅。此组件需要支持该导出的宿主 Host Runtime,不能只更新插件依赖后向旧宿主请求。
157
+
158
+ ### 会话标题右侧条目
159
+
160
+ `inline` 和 `overflow` 必须分别声明,可以独立选择普通按钮或自定义视图:
161
+
162
+ ```js
163
+ const showSession = {
164
+ type: 'action',
165
+ icon: { type: 'builtin', name: 'file' },
166
+ label: '会话信息',
167
+ onSelect({ source, host }) {
168
+ host.notify({ level: 'info', title: '当前会话', description: source.sessionId })
169
+ }
170
+ }
171
+
172
+ plugin.registerSessionHeaderItem('session-info', {
173
+ inline: {
174
+ type: 'custom',
175
+ minWidthRem: 6,
176
+ load: () => import('./session-info.js').then((module) => module.default)
177
+ },
178
+ overflow: showSession
179
+ })
180
+ ```
181
+
182
+ - `action` 只接受固定 `icon`、`label`、`onSelect({ source, host, signal })`,不提供动态图标、文字或状态订阅。宿主显示图标加文字,异步操作期间防止重复点击;失败记录局部错误,失效后的回调不得继续使用旧 Host。信号属于该展示生命周期,不会回滚服务端已经产生的副作用。
183
+ - `custom` 只提供 `load()`,宿主不包装点击回调。inline custom 必须额外声明正数 `minWidthRem`,单位为 `rem`;宿主按此预留可用宽度,不通过重复挂载业务视图测量。自定义内容应适应获得的容器,不能靠超宽 DOM 挤占标题。overflow custom 不声明宽度。
184
+ - 自定义 Target 为 `{ kind: 'session-header-item', source, presentation: 'inline' | 'overflow' }`。两种表现可以使用不同模块,也可以复用同一个 `{ mount }`;loader 分别按表现缓存和重试。可复用上面的 `session-info.js`,或使用公共 React/UI 构建交互视图。
185
+ - 标题项按插件名和注册名稳定排列;宽度不足时从末尾收进更多,宿主保留标题及必要操作。普通按钮选择后关闭更多;自定义区域内点击不会被转发,也不会自动关闭更多。Escape 或外部点击关闭浮层,键盘与触控均可操作。
186
+ - 收纳会卸载 inline 自定义视图;只有更多打开时才挂载 overflow 自定义视图,关闭后释放。动态状态与运行/停止等复杂交互由自定义视图自行实现;业务状态归插件运行态,不归 mount,也不能只存于会被卸载的组件局部状态。卸载只清理视图资源,不停止项目进程。
187
+
188
+ 两种注册均限定 Browser Entry 同步阶段,名称规则与其他贡献相同。它们需要支持对应方法的宿主和 SDK;旧宿主缺少能力时禁用这项入口,不使用 `registerContribution` 别名或访问宿主 DOM 代替。现有 `composer-panel` 仍是人工打开的会话弹窗,不是上下方挂件。
189
+
110
190
  ### 侧栏顶部操作
111
191
 
112
192
  `session-sidebar-tab` 的 Lazy `mount({ target, host })` 内可为当前 Tab 设置顶部右侧按钮:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetcrab/pi-desk",
3
- "version": "1.1.20-dev",
3
+ "version": "1.1.21",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -70,7 +70,7 @@
70
70
  "viewerjs": "^1.11.8",
71
71
  "ws": "^8.21.3",
72
72
  "zod": "^4.4.3",
73
- "@jetcrab/pi-desk-sdk": "^1.1.7-dev"
73
+ "@jetcrab/pi-desk-sdk": "^1.1.8"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@earendil-works/pi-agent-core": "1.2.0",
@@ -112,7 +112,7 @@
112
112
  "build:plugin-host-runtime": "pnpm --filter @jetcrab/pi-desk-sdk build",
113
113
  "start": "node bin/pi-desk.js --no-open",
114
114
  "test:package": "node --test tests/l4-pi-desk-package.test.mjs",
115
- "test:pidesk": "node --import tsx --test tests/l4-pidesk-command.test.ts tests/l2-plugin-commands.test.ts tests/l4-pidesk-session-reload.test.ts tests/l4-pidesk-runtime.test.ts tests/l4-pidesk-runtime-bundle.test.mjs tests/l4-pidesk-docs.test.mjs tests/l4-pidesk-installed-runners.test.mjs tests/l4-pidesk-package-maintenance.test.ts tests/l4-pidesk-release-gate.test.mjs tests/l4-pi-desk-process-tree.test.mjs tests/l4-pi-runner-process.test.ts tests/l4-pi-global-runtime.test.mjs tests/l4-pi-desk-supervisor-cli.test.mjs",
115
+ "test:pidesk": "node --import tsx --test --test-concurrency=1 tests/l4-pidesk-command.test.ts tests/l2-plugin-commands.test.ts tests/l4-pidesk-session-reload.test.ts tests/l4-pidesk-runtime.test.ts tests/l4-pidesk-runtime-bundle.test.mjs tests/l4-pidesk-docs.test.mjs tests/l4-pidesk-installed-runners.test.mjs tests/l4-pidesk-package-maintenance.test.ts tests/l4-pidesk-release-gate.test.mjs tests/l4-pi-desk-process-tree.test.mjs tests/l4-pi-runner-process.test.ts tests/l4-pi-global-runtime.test.mjs tests/l4-pi-desk-supervisor-cli.test.mjs",
116
116
  "test:app-websocket": "pnpm build && node --import tsx --test tests/l2-chat-state.test.ts tests/l2-workbench-chat-repository.test.ts tests/l2-workbench-chat.test.ts tests/l2-workbench-layout.test.ts tests/l2-work-session-column-layout.test.ts tests/l2-work-session-update.test.ts tests/l1-app-websocket.test.mjs",
117
117
  "lint": "eslint .",
118
118
  "lint:production": "eslint . --ignore-pattern \"apps/desktop/**\" --ignore-pattern \"**/tests/**\" --ignore-pattern \"**/__tests__/**\" --ignore-pattern \"**/*.test.*\" --ignore-pattern \"**/*.spec.*\"",
package/src/app/page.tsx CHANGED
@@ -1,5 +1,10 @@
1
1
  import { L1HomePage } from '@client/l1_entry/pages/l1-home-page'
2
+ import { L1AppRuntimeProvider } from '@client/l1_entry/providers/l1-app-runtime-provider'
2
3
 
3
4
  export default function Home(): React.JSX.Element {
4
- return <L1HomePage />
5
+ return (
6
+ <L1AppRuntimeProvider>
7
+ <L1HomePage />
8
+ </L1AppRuntimeProvider>
9
+ )
5
10
  }
@@ -0,0 +1,10 @@
1
+ import { Suspense } from 'react'
2
+ import { L1ProjectPreviewPage } from '@client/l1_entry/pages/l1-project-preview-page'
3
+
4
+ export default function ProjectPreview(): React.JSX.Element {
5
+ return (
6
+ <Suspense>
7
+ <L1ProjectPreviewPage />
8
+ </Suspense>
9
+ )
10
+ }
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { useCallback, useEffect, useMemo, type ComponentProps } from 'react'
4
+ import { buildL2ProjectPreviewHref } from '@common/l2_biz/project-preview/l2-project-preview-navigation'
4
5
  import { L2TerminalBiz } from '@client/l2_biz/terminal/l2-terminal-biz'
5
6
  import { L2TerminalWorkspace } from '@client/l2_biz/terminal/l2-terminal-workspace'
6
7
  import { useL4PluginHost } from '@client/l4_foundation/plugin-host/l4-plugin-host-context'
@@ -12,6 +13,8 @@ import { L2PluginManagement } from '@client/l2_biz/plugin-host/l2-plugin-managem
12
13
  import { createL2PluginManagementBiz } from '@client/l2_biz/plugin-host/l2-plugin-management-biz'
13
14
  import { L2PluginSessionSidebarTab } from '@client/l2_biz/plugin-host/l2-plugin-session-sidebar-tab'
14
15
  import { L2PluginSettingsPage } from '@client/l2_biz/plugin-host/l2-plugin-settings-page'
16
+ import { L2PluginWidgets } from '@client/l2_biz/plugin-host/l2-plugin-widgets'
17
+ import { L2PluginSessionHeader } from '@client/l2_biz/plugin-host/l2-plugin-session-header'
15
18
  import { L2Settings } from '@client/l2_biz/settings/l2-settings'
16
19
  import { L2PiSettings } from '@client/l2_biz/pi-settings/l2-pi-settings'
17
20
  import { createL2TaskCenterBiz } from '@client/l2_biz/task-center/l2-task-center-biz'
@@ -61,6 +64,28 @@ function renderPluginComposerPanel(
61
64
  )
62
65
  }
63
66
 
67
+ function renderPluginWidgets(
68
+ input: Parameters<WorkbenchProps['renderPluginWidgets']>[0]
69
+ ): React.JSX.Element {
70
+ return (
71
+ <L2PluginWidgets
72
+ key={`${input.source.workId}:${input.source.sessionId}:${input.source.branchId}:${input.placement}`}
73
+ {...input}
74
+ />
75
+ )
76
+ }
77
+
78
+ function renderPluginSessionHeader(
79
+ input: Parameters<WorkbenchProps['renderPluginSessionHeader']>[0]
80
+ ): React.JSX.Element {
81
+ return (
82
+ <L2PluginSessionHeader
83
+ key={`${input.source.workId}:${input.source.sessionId}:${input.source.branchId}`}
84
+ {...input}
85
+ />
86
+ )
87
+ }
88
+
64
89
  function renderPluginSettingsPage(
65
90
  input: Parameters<SettingsProps['renderPluginSettingsPage']>[0]
66
91
  ): React.JSX.Element {
@@ -158,6 +183,9 @@ export function L1HomePage(): React.JSX.Element {
158
183
  onTerminalReady={terminalBiz.connected}
159
184
  onTerminalDisconnected={terminalBiz.disconnected}
160
185
  onOpenTerminal={terminalBiz.open}
186
+ onOpenProjectPreviewWindow={(cwd) => {
187
+ window.open(buildL2ProjectPreviewHref(cwd), '_blank', 'noopener,noreferrer')
188
+ }}
161
189
  renderTerminalWorkspace={renderTerminalWorkspace}
162
190
  listPluginManagement={pluginManagementBiz.list}
163
191
  subscribePluginManagementChanges={pluginManagementBiz.subscribeChanges}
@@ -169,6 +197,8 @@ export function L1HomePage(): React.JSX.Element {
169
197
  renderPluginApplication={renderPluginApplication}
170
198
  renderPluginSessionSidebarTab={renderPluginSessionSidebarTab}
171
199
  renderPluginComposerPanel={renderPluginComposerPanel}
200
+ renderPluginWidgets={renderPluginWidgets}
201
+ renderPluginSessionHeader={renderPluginSessionHeader}
172
202
  renderSettings={renderSettings}
173
203
  renderTaskSummary={renderTaskSummary}
174
204
  renderTaskMenu={renderTaskMenu}
@@ -0,0 +1,33 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useRef } from 'react'
4
+ import { useRouter, useSearchParams } from 'next/navigation'
5
+ import { L2BuildLoginHref } from '@common/l2_biz/auth/l2-auth-return-to'
6
+ import { buildL2ProjectPreviewHref } from '@common/l2_biz/project-preview/l2-project-preview-navigation'
7
+ import { useL4ClientId } from '@client/l4_foundation/lib/l4-client-id'
8
+ import { L2ProjectPreviewPage } from '@client/l2_biz/project-preview/l2-project-preview-page'
9
+
10
+ export function L1ProjectPreviewPage(): React.JSX.Element {
11
+ const clientId = useL4ClientId()
12
+ const params = useSearchParams()
13
+ const router = useRouter()
14
+ const cwd = params.get('cwd')?.trim() || null
15
+ const redirecting = useRef(false)
16
+ const onUnauthorized = useCallback((): void => {
17
+ if (redirecting.current) return
18
+ redirecting.current = true
19
+ window.location.assign(L2BuildLoginHref(window.location.pathname + window.location.search))
20
+ }, [])
21
+ useEffect(() => {
22
+ const name = cwd?.replaceAll('\\', '/').split('/').filter(Boolean).at(-1)
23
+ document.title = name ? `${name} · Pi Desk` : 'Pi Desk'
24
+ }, [cwd])
25
+ return (
26
+ <L2ProjectPreviewPage
27
+ clientId={clientId}
28
+ cwd={cwd}
29
+ onUnauthorized={onUnauthorized}
30
+ onSelect={(directory) => router.push(buildL2ProjectPreviewHref(directory))}
31
+ />
32
+ )
33
+ }
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import type { ReactNode } from 'react'
3
+ import { useState, type ReactNode } from 'react'
4
4
  import type { HostSettingsSnapshot } from '@jetcrab/pi-desk-sdk/settings'
5
5
  import { L2_AUTH_MESSAGES } from '@common/l2_biz/auth/l2-auth-messages'
6
6
  import { L2_CAPABILITY_MODE_MESSAGES } from '@common/l2_biz/capability-modes/l2-capability-mode-messages'
@@ -22,7 +22,8 @@ import { L4RegionProvider } from '@client/l4_foundation/locale/l4-region-provide
22
22
  import { L4RegionNotice } from '@client/l4_foundation/locale/l4-region-notice'
23
23
  import { L4AppToastProvider } from '@client/l4_foundation/ui/l4-app-toast'
24
24
  import { TooltipProvider } from '@client/l4_foundation/ui/shadcn/tooltip'
25
- import { L1AppRuntimeProvider } from './l1-app-runtime-provider'
25
+ import { createL4BrowserUuid } from '@client/l4_foundation/lib/l4-browser-uuid'
26
+ import { L4ClientIdContext } from '@client/l4_foundation/lib/l4-client-id'
26
27
 
27
28
  const resources = {
28
29
  en: {
@@ -66,20 +67,22 @@ export function L1AppProviders({
66
67
  children,
67
68
  initialSettings
68
69
  }: L1AppProvidersProps): React.JSX.Element {
70
+ const [clientId] = useState(createL4BrowserUuid)
71
+
69
72
  return (
70
- <L4ThemeProvider>
71
- <L4DisplaySizeProvider>
72
- <L4CodePreferencesProvider>
73
- <L4RegionProvider resources={resources} initialSettings={initialSettings}>
74
- <L4AppToastProvider>
75
- <L4RegionNotice />
76
- <L1AppRuntimeProvider>
73
+ <L4ClientIdContext.Provider value={clientId}>
74
+ <L4ThemeProvider>
75
+ <L4DisplaySizeProvider>
76
+ <L4CodePreferencesProvider>
77
+ <L4RegionProvider resources={resources} initialSettings={initialSettings}>
78
+ <L4AppToastProvider>
79
+ <L4RegionNotice />
77
80
  <TooltipProvider>{children}</TooltipProvider>
78
- </L1AppRuntimeProvider>
79
- </L4AppToastProvider>
80
- </L4RegionProvider>
81
- </L4CodePreferencesProvider>
82
- </L4DisplaySizeProvider>
83
- </L4ThemeProvider>
81
+ </L4AppToastProvider>
82
+ </L4RegionProvider>
83
+ </L4CodePreferencesProvider>
84
+ </L4DisplaySizeProvider>
85
+ </L4ThemeProvider>
86
+ </L4ClientIdContext.Provider>
84
87
  )
85
88
  }
@@ -3,7 +3,7 @@
3
3
  import { useEffect, useRef, useState, type PropsWithChildren } from 'react'
4
4
  import { createL2PluginHostRuntime } from '@client/l2_biz/plugin-host/l2-plugin-host-runtime'
5
5
  import { L2ProjectPreviewDialog } from '@client/l2_biz/project-preview/l2-project-preview-dialog'
6
- import { createL4BrowserUuid } from '@client/l4_foundation/lib/l4-browser-uuid'
6
+ import { useL4ClientId } from '@client/l4_foundation/lib/l4-client-id'
7
7
  import { L4PluginHostContext } from '@client/l4_foundation/plugin-host/l4-plugin-host-context'
8
8
  import type { L4PluginHostRuntime } from '@client/l4_foundation/plugin-host/l4-plugin-host-runtime'
9
9
  import { useL4AppToast } from '@client/l4_foundation/ui/l4-app-toast'
@@ -22,8 +22,8 @@ interface L1AppRuntimeOwner {
22
22
 
23
23
  export function L1AppRuntimeProvider({ children }: PropsWithChildren): React.JSX.Element {
24
24
  const toast = useL4AppToast()
25
+ const clientId = useL4ClientId()
25
26
  const [runtime] = useState<L1AppRuntimeOwner>(() => {
26
- const clientId = createL4BrowserUuid()
27
27
  const owner = createL4AppSocketClient(clientId)
28
28
  return {
29
29
  clientId,
@@ -1,4 +1,5 @@
1
1
  import type { L4ApiResponse } from '@common/l4_foundation/http/l4-api-response-contract'
2
+ import { L2ResolveAuthReturnTo } from '@common/l2_biz/auth/l2-auth-return-to'
2
3
  import { l4LocalizedErrorMessage } from '@client/l4_foundation/locale/l4-localized-error'
3
4
  import {
4
5
  L2AuthLoginRequestSchema,
@@ -44,4 +45,6 @@ export async function loginL2Auth(clientId: string, input: L2AuthLoginRequest):
44
45
  )
45
46
  }
46
47
  L2AuthLoginResponseSchema.parse(envelope.data)
48
+ const returnTo = new URLSearchParams(window.location.search).get('returnTo')
49
+ window.location.replace(L2ResolveAuthReturnTo(returnTo))
47
50
  }
@@ -3,7 +3,7 @@
3
3
  import { ArrowRightIcon, LanguagesIcon, LoaderCircleIcon } from 'lucide-react'
4
4
  import { useEffect, useRef, useState, type FormEvent } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
- import { useL4AppSocket } from '@client/l4_foundation/realtime/app-socket/l4-app-socket'
6
+ import { useL4ClientId } from '@client/l4_foundation/lib/l4-client-id'
7
7
  import { useL4Region } from '@client/l4_foundation/locale/l4-region-provider'
8
8
  import { setL4RegionDisplayLocale } from '@client/l4_foundation/locale/l4-region-store'
9
9
  import { Button } from '@client/l4_foundation/ui/shadcn/button'
@@ -23,7 +23,7 @@ import styles from './l2-login.module.css'
23
23
  export function L2Login(): React.JSX.Element {
24
24
  const { t } = useTranslation('auth')
25
25
  const region = useL4Region()
26
- const { clientId } = useL4AppSocket()
26
+ const clientId = useL4ClientId()
27
27
  const [password, setPassword] = useState('')
28
28
  const [error, setError] = useState<string | null>(null)
29
29
  const [submitting, setSubmitting] = useState(false)
@@ -43,7 +43,6 @@ export function L2Login(): React.JSX.Element {
43
43
  setSubmitting(true)
44
44
  try {
45
45
  await loginL2Auth(clientId, { password })
46
- window.location.replace('/')
47
46
  } catch (cause) {
48
47
  submittingRef.current = false
49
48
  setError(cause instanceof Error ? cause.message : t('loginFailed'))
@@ -72,7 +72,9 @@ const CONTRIBUTION_LABELS: Readonly<Record<L4PluginBrowserContributionDescriptor
72
72
  'composer-panel': 'contributionComposer',
73
73
  'settings-page': 'contributionSettings',
74
74
  'message-view': 'contributionMessage',
75
- 'session-sidebar-tab': 'contributionSidebar'
75
+ 'session-sidebar-tab': 'contributionSidebar',
76
+ widget: 'contributionWidget',
77
+ 'session-header-item': 'contributionSessionHeader'
76
78
  }
77
79
 
78
80
  export type L2PluginCapabilityTagTone = 'neutral' | 'prompt' | 'dynamic' | 'override'
@@ -567,6 +569,9 @@ function PromptResources({
567
569
  function contributionDescription(descriptor: L4PluginBrowserContributionDescriptor): string {
568
570
  if (descriptor.kind === 'message-view') return descriptor.viewKey
569
571
  if (descriptor.kind === 'application') return descriptor.title
572
+ if (descriptor.kind === 'widget' || descriptor.kind === 'session-header-item') {
573
+ return descriptor.contributionName
574
+ }
570
575
  return descriptor.label
571
576
  }
572
577
 
@@ -8,7 +8,9 @@ import type {
8
8
  BrowserComposerPanelTarget,
9
9
  BrowserMessageViewTarget,
10
10
  BrowserSessionSidebarTabTarget,
11
+ BrowserSessionHeaderItemTarget,
11
12
  BrowserSettingsPageTarget,
13
+ BrowserWidgetTarget,
12
14
  PluginDisposer
13
15
  } from '@jetcrab/pi-desk-sdk/browser'
14
16
  import {
@@ -16,11 +18,17 @@ import {
16
18
  useL4PluginRegistry
17
19
  } from '@client/l4_foundation/plugin-host/l4-plugin-host-context'
18
20
  import { Button } from '@client/l4_foundation/ui/shadcn/button'
21
+ import {
22
+ Popover,
23
+ PopoverContent,
24
+ PopoverTitle,
25
+ PopoverTrigger
26
+ } from '@client/l4_foundation/ui/shadcn/popover'
19
27
  import { L4ErrorDetails } from '@client/l4_foundation/ui/l4-error-details'
20
28
  import { cn } from '@client/l4_foundation/lib/l4-utils'
21
29
  import type { L4PluginContributionKind } from '@client/l4_foundation/plugin-host/l4-plugin-host-runtime'
22
30
 
23
- type L2PluginContributionLayout = 'fill' | 'intrinsic'
31
+ type L2PluginContributionLayout = 'fill' | 'intrinsic' | 'inline'
24
32
 
25
33
  interface L2PluginContributionHostProps {
26
34
  pluginName: string
@@ -32,6 +40,8 @@ interface L2PluginContributionHostProps {
32
40
  | BrowserSettingsPageTarget
33
41
  | BrowserMessageViewTarget
34
42
  | BrowserSessionSidebarTabTarget
43
+ | BrowserWidgetTarget
44
+ | BrowserSessionHeaderItemTarget
35
45
  targetKey?: string
36
46
  className?: string
37
47
  layout?: L2PluginContributionLayout
@@ -57,6 +67,7 @@ export function L2PluginContributionHost({
57
67
  const containerRef = useRef<HTMLDivElement | null>(null)
58
68
  const targetRef = useRef(target)
59
69
  const mountIdentity = targetKey ?? target
70
+ const presentation = target.kind === 'session-header-item' ? target.presentation : undefined
60
71
  const [attempt, setAttempt] = useState(0)
61
72
  const [state, setState] = useState<MountState>({ status: 'loading' })
62
73
 
@@ -86,7 +97,9 @@ export function L2PluginContributionHost({
86
97
  mountContainer.className =
87
98
  layout === 'intrinsic'
88
99
  ? 'inline-block min-h-0 min-w-0 align-top'
89
- : 'h-full min-h-0 w-full min-w-0'
100
+ : layout === 'inline'
101
+ ? 'min-w-0 w-full'
102
+ : 'h-full min-h-0 w-full min-w-0'
90
103
  hostContainer.append(mountContainer)
91
104
  return runtime.mountContribution({
92
105
  pluginName,
@@ -163,13 +176,18 @@ export function L2PluginContributionHost({
163
176
  })
164
177
  return
165
178
  }
166
- const loadState = runtime.getContributionLoadState(pluginName, kind, contributionName)
179
+ const loadState = runtime.getContributionLoadState(
180
+ pluginName,
181
+ kind,
182
+ contributionName,
183
+ presentation
184
+ )
167
185
  if (loadState.status !== 'failed') {
168
186
  setAttempt((current) => current + 1)
169
187
  return
170
188
  }
171
189
  setState({ status: 'loading' })
172
- void runtime.retryContribution(pluginName, kind, contributionName).then(
190
+ void runtime.retryContribution(pluginName, kind, contributionName, presentation).then(
173
191
  () => {
174
192
  if (runtime.getEntryState(pluginName) === entryState) setAttempt((current) => current + 1)
175
193
  },
@@ -199,7 +217,9 @@ export function L2PluginContributionHost({
199
217
  'max-h-[calc(100dvh-7rem)] max-w-[calc(100dvw-3rem)] overflow-auto max-sm:max-h-[calc(100dvh-5rem)] max-sm:max-w-[calc(100dvw-2rem)]',
200
218
  intrinsicFallbackClass
201
219
  )
202
- : 'h-full w-full',
220
+ : layout === 'inline'
221
+ ? 'w-full'
222
+ : 'h-full w-full',
203
223
  className
204
224
  )}
205
225
  >
@@ -211,17 +231,58 @@ export function L2PluginContributionHost({
211
231
  className={cn(
212
232
  layout === 'intrinsic'
213
233
  ? 'inline-block min-h-0 min-w-0 align-top'
214
- : 'h-full min-h-0 w-full min-w-0',
234
+ : layout === 'inline'
235
+ ? 'w-full min-w-0'
236
+ : 'h-full min-h-0 w-full min-w-0',
215
237
  state.status === 'error' && 'hidden'
216
238
  )}
217
239
  />
218
240
  {state.status === 'loading' ? (
219
- <div className="absolute inset-0 flex items-center justify-center bg-popover p-6 text-sm text-muted-foreground">
220
- <LoaderCircleIcon className="mr-2 size-4 animate-spin" />
221
- {t('viewLoading')}
241
+ <div
242
+ role="status"
243
+ className={cn(
244
+ 'flex items-center text-sm text-muted-foreground',
245
+ layout === 'inline'
246
+ ? 'min-h-7 gap-1 py-1'
247
+ : 'absolute inset-0 justify-center bg-popover p-6'
248
+ )}
249
+ >
250
+ <LoaderCircleIcon className="mr-2 size-4 shrink-0 animate-spin" />
251
+ <span className="min-w-0 truncate">{t('viewLoading')}</span>
222
252
  </div>
223
253
  ) : null}
224
- {state.status === 'error' ? (
254
+ {state.status === 'error' && layout === 'inline' ? (
255
+ <div role="alert" className="min-w-0">
256
+ <Popover>
257
+ <PopoverTrigger
258
+ render={
259
+ <Button
260
+ type="button"
261
+ variant="ghost"
262
+ size="icon-sm"
263
+ aria-label={t('viewFailed')}
264
+ title={t('viewFailed')}
265
+ className="text-destructive"
266
+ />
267
+ }
268
+ >
269
+ <AlertTriangleIcon className="size-4" />
270
+ </PopoverTrigger>
271
+ <PopoverContent align="start" finalFocus>
272
+ <PopoverTitle>{t('viewFailed')}</PopoverTitle>
273
+ <L4ErrorDetails error={state.error}>
274
+ <p className="text-xs text-muted-foreground [overflow-wrap:anywhere]">
275
+ {pluginName} · {kind} · {contributionName}
276
+ </p>
277
+ </L4ErrorDetails>
278
+ <Button type="button" variant="outline" size="sm" onClick={retry}>
279
+ <RefreshCwIcon />
280
+ {t('retry')}
281
+ </Button>
282
+ </PopoverContent>
283
+ </Popover>
284
+ </div>
285
+ ) : state.status === 'error' ? (
225
286
  <div
226
287
  role="alert"
227
288
  className={cn(
@@ -57,6 +57,15 @@ export function L2PluginManagementControls({
57
57
  >
58
58
  {t('retry')}
59
59
  </Button>
60
+ ) : plugin.status === 'failed' ? (
61
+ <Button
62
+ size="sm"
63
+ variant="outline"
64
+ disabled={disabled || pending}
65
+ onClick={() => onAction('reload')}
66
+ >
67
+ {t('reloadOne')}
68
+ </Button>
60
69
  ) : browserError && plugin.pluginName ? (
61
70
  <Button
62
71
  size="sm"
@@ -0,0 +1,18 @@
1
+ export function calculateL2PluginSessionHeaderInlineCount(
2
+ widths: readonly number[],
3
+ availableWidth: number,
4
+ moreWidth: number,
5
+ gap: number
6
+ ): number {
7
+ const total = widths.reduce((sum, width) => sum + width, 0) + Math.max(0, widths.length - 1) * gap
8
+ if (total <= availableWidth) return widths.length
9
+
10
+ let used = moreWidth
11
+ let count = 0
12
+ for (const width of widths) {
13
+ if (used + gap + width > availableWidth) break
14
+ used += gap + width
15
+ count += 1
16
+ }
17
+ return count
18
+ }