@jetcrab/pi-desk 1.1.20-dev → 1.1.22-dev

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 (202) 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/react-loadable-manifest.json +1 -2
  61. package/temp/build/pi-desk/release/.next/required-server-files.js +2 -2
  62. package/temp/build/pi-desk/release/.next/required-server-files.json +2 -2
  63. package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
  64. package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
  65. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  66. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  67. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
  68. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
  69. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
  70. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  71. package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
  72. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  73. package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +1 -1
  74. package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
  75. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
  76. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
  77. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
  78. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
  79. package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
  80. package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
  81. package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
  82. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  83. package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
  84. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
  85. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
  86. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
  87. package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
  88. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
  89. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -1
  90. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
  91. package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
  92. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
  93. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
  94. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
  95. package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
  96. package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
  97. package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
  98. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
  99. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
  100. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
  101. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
  102. package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
  103. package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
  104. package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
  105. package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
  106. package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
  107. package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
  108. package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
  109. package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
  110. package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
  111. package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
  112. package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
  113. package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +1 -1
  114. package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +1 -1
  115. package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
  116. package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +2 -2
  117. package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +1 -1
  118. package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +1 -1
  119. package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +1 -1
  120. package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -2
  121. package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +1 -1
  122. package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -2
  123. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
  124. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
  125. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
  126. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
  127. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
  128. package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
  129. package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
  130. package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
  131. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
  132. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
  133. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
  134. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
  135. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
  136. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
  137. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
  138. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
  139. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
  140. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/sort/route.js +1 -1
  141. package/temp/build/pi-desk/release/.next/server/app/favicon.ico/route.js +1 -1
  142. package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
  143. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  144. package/temp/build/pi-desk/release/.next/server/app/page.js +140 -140
  145. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -0
  147. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -0
  148. package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +3 -2
  149. package/temp/build/pi-desk/release/.next/server/chunks/10912.js +2 -2
  150. package/temp/build/pi-desk/release/.next/server/chunks/20460.js +15 -0
  151. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +8 -8
  152. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +3 -17
  153. package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
  154. package/temp/build/pi-desk/release/.next/server/chunks/48180.js +1 -1
  155. package/temp/build/pi-desk/release/.next/server/chunks/56830.js +235 -0
  156. package/temp/build/pi-desk/release/.next/server/chunks/78262.js +1 -1
  157. package/temp/build/pi-desk/release/.next/server/chunks/79686.js +62 -0
  158. package/temp/build/pi-desk/release/.next/server/chunks/80780.js +1 -0
  159. package/temp/build/pi-desk/release/.next/server/chunks/81363.js +1 -1
  160. package/temp/build/pi-desk/release/.next/server/chunks/82499.js +4 -4
  161. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +8 -8
  162. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  163. package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
  164. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  165. package/temp/build/pi-desk/release/.next/static/MV6_lBt7Mzmi1rBNI-3dx/_buildManifest.js +1 -0
  166. package/temp/build/pi-desk/release/.next/static/chunks/11937-1d0a9822f2c7995b.js +1 -0
  167. package/temp/build/pi-desk/release/.next/static/chunks/14825-5579aed34a631ad7.js +62 -0
  168. package/temp/build/pi-desk/release/.next/static/chunks/15302-9c807afc6c60de0c.js +1 -0
  169. package/temp/build/pi-desk/release/.next/static/chunks/19991.8aa2e4b6df15fad6.js +1 -0
  170. package/temp/build/pi-desk/release/.next/static/chunks/22092-6d1bfbefa825ec03.js +1 -0
  171. package/temp/build/pi-desk/release/.next/static/chunks/37313-d5970e2d0c270d22.js +238 -0
  172. package/temp/build/pi-desk/release/.next/static/chunks/57235-03ba1044f22b6c84.js +1 -0
  173. package/temp/build/pi-desk/release/.next/static/chunks/79267-7d21d7cdbd7cfca1.js +1 -0
  174. package/temp/build/pi-desk/release/.next/static/chunks/82371-f7ba4a07a5dbcf34.js +1 -0
  175. package/temp/build/pi-desk/release/.next/static/chunks/9038-03144ed96d613f75.js +15 -0
  176. package/temp/build/pi-desk/release/.next/static/chunks/app/{error-0296a3b20cd1bf00.js → error-216b14b5da089ed3.js} +1 -1
  177. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-20718f1503910291.js +1 -0
  178. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-0d3d4827802108e5.js +1 -0
  179. package/temp/build/pi-desk/release/.next/static/chunks/app/{not-found-2aab8b33ebbf3257.js → not-found-720cdf6a606b821a.js} +1 -1
  180. package/temp/build/pi-desk/release/.next/static/chunks/app/page-e8a5274c115f528d.js +158 -0
  181. package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-d70ee4e9409b970a.js +1 -0
  182. package/temp/build/pi-desk/release/.next/static/chunks/webpack-b60891a214e0925d.js +1 -0
  183. package/temp/build/pi-desk/release/.next/static/css/{250a6b93cc2222d1.css → 1e780a4936aa6ba3.css} +1 -1
  184. package/temp/build/pi-desk/release/.next/static/css/f646ca6c07245c4c.css +1 -0
  185. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  186. package/temp/build/pi-desk/release/.next/server/chunks/10881.js +0 -296
  187. package/temp/build/pi-desk/release/.next/static/RLKcK-RQF5FvEeDmS9uih/_buildManifest.js +0 -1
  188. package/temp/build/pi-desk/release/.next/static/chunks/27611-0890482fe76bd9d6.js +0 -238
  189. package/temp/build/pi-desk/release/.next/static/chunks/31087-d8e5765d541d5c26.js +0 -1
  190. package/temp/build/pi-desk/release/.next/static/chunks/3814-35cfab0ce3aa7c41.js +0 -1
  191. package/temp/build/pi-desk/release/.next/static/chunks/70669.07f3670bfb7d6dd2.js +0 -1
  192. package/temp/build/pi-desk/release/.next/static/chunks/74220-795a2e7185bd66cb.js +0 -1
  193. package/temp/build/pi-desk/release/.next/static/chunks/77592-5bef6acb8e7c4b96.js +0 -13
  194. package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +0 -1
  195. package/temp/build/pi-desk/release/.next/static/chunks/81881-f11dbe7dc0d04ed9.js +0 -1
  196. package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +0 -62
  197. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-a50c456648537afb.js +0 -3
  198. package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-84892b06243891f0.js +0 -1
  199. package/temp/build/pi-desk/release/.next/static/chunks/app/page-0789c43551b29dd2.js +0 -158
  200. package/temp/build/pi-desk/release/.next/static/chunks/webpack-d7248206b7fc7ba8.js +0 -1
  201. package/temp/build/pi-desk/release/.next/static/css/a4cf28fd76c70165.css +0 -1
  202. /package/temp/build/pi-desk/release/.next/static/{RLKcK-RQF5FvEeDmS9uih → MV6_lBt7Mzmi1rBNI-3dx}/_ssgManifest.js +0 -0
@@ -27,6 +27,13 @@ import {
27
27
  type BrowserPluginGlobalStateHost,
28
28
  type BrowserPluginHost,
29
29
  type BrowserPluginNotificationEventListener,
30
+ type BrowserSessionHeaderAction,
31
+ type BrowserSessionHeaderActionContext,
32
+ type BrowserSessionHeaderCustom,
33
+ type BrowserSessionHeaderItemDefinition,
34
+ type BrowserSessionHeaderItemTarget,
35
+ type BrowserWidgetDefinition,
36
+ type BrowserWidgetTarget,
30
37
  type BrowserSessionSidebarHeaderAction,
31
38
  type BrowserSessionSidebarTabContributionDefinition,
32
39
  type BrowserSessionSidebarTabTarget,
@@ -83,7 +90,7 @@ const BUILTIN_ICON_NAMES = new Set([
83
90
  'database',
84
91
  'globe'
85
92
  ])
86
- const CONTRIBUTION_KINDS = new Set<L4PluginContributionKind>([
93
+ const CONTRIBUTION_KINDS = new Set<BrowserContributionKind>([
87
94
  'application',
88
95
  'composer-panel',
89
96
  'settings-page',
@@ -141,7 +148,7 @@ export interface L4PluginHostTransport {
141
148
  }
142
149
 
143
150
  export type L4PluginNotificationSink = (pluginName: string, input: BrowserNotificationInput) => void
144
- export type L4PluginContributionKind = BrowserContributionKind
151
+ export type L4PluginContributionKind = BrowserContributionDescriptor['kind']
145
152
 
146
153
  export interface L4PluginHostWorkSessionInput {
147
154
  workId: string
@@ -198,7 +205,9 @@ interface L4PluginContributionRegistration {
198
205
  kind: L4PluginContributionKind
199
206
  contributionName: string
200
207
  descriptor: L4PluginBrowserContributionDescriptor
201
- load: () => Promise<BrowserContributionImplementation>
208
+ load?: () => Promise<BrowserContributionImplementation>
209
+ header?: BrowserSessionHeaderItemDefinition
210
+ headerActionScopes?: WeakMap<AbortSignal, BrowserSessionHeaderActionContext>
202
211
  resolve?: BrowserApplicationContributionDefinition['resolve']
203
212
  owner: L4PluginEntryOwner
204
213
  }
@@ -233,6 +242,8 @@ export type L4PluginContributionTarget =
233
242
  | BrowserSettingsPageTarget
234
243
  | BrowserMessageViewTarget
235
244
  | BrowserSessionSidebarTabTarget
245
+ | BrowserWidgetTarget
246
+ | BrowserSessionHeaderItemTarget
236
247
 
237
248
  export interface L4PluginContributionMountInput {
238
249
  pluginName: string
@@ -255,6 +266,20 @@ function pluginContributionKey(
255
266
  return `${pluginName}\u0000${contributionKey(kind, contributionName)}`
256
267
  }
257
268
 
269
+ function contributionLoadKey(
270
+ pluginName: string,
271
+ kind: L4PluginContributionKind,
272
+ contributionName: string,
273
+ presentation?: BrowserSessionHeaderItemTarget['presentation']
274
+ ): string {
275
+ const key = pluginContributionKey(pluginName, kind, contributionName)
276
+ if (kind !== 'session-header-item') return key
277
+ if (presentation !== 'inline' && presentation !== 'overflow') {
278
+ throw new Error('会话标题视图必须指定 inline 或 overflow')
279
+ }
280
+ return `${key}\u0000${presentation}`
281
+ }
282
+
258
283
  function defaultEntryImporter(url: string): Promise<L4PluginEntryNamespace> {
259
284
  return import(/* webpackIgnore: true */ url) as Promise<L4PluginEntryNamespace>
260
285
  }
@@ -369,6 +394,54 @@ function cloneIcon(value: unknown): BrowserContributionIcon | undefined {
369
394
  throw new Error('Contribution icon type 无效')
370
395
  }
371
396
 
397
+ function parseHeaderPresentation(
398
+ value: unknown,
399
+ presentation: BrowserSessionHeaderItemTarget['presentation']
400
+ ): BrowserSessionHeaderAction | BrowserSessionHeaderCustom {
401
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
402
+ throw new Error(`会话标题 ${presentation} 必须提供 action 或 custom 定义`)
403
+ }
404
+ const input = value as Record<string, unknown>
405
+ const allowed =
406
+ input.type === 'action'
407
+ ? ['type', 'icon', 'label', 'onSelect']
408
+ : presentation === 'inline'
409
+ ? ['type', 'load', 'minWidthRem']
410
+ : ['type', 'load']
411
+ for (const key of Object.keys(input)) {
412
+ if (!allowed.includes(key)) {
413
+ throw new Error(`会话标题 ${presentation} ${String(input.type)} 不支持字段 ${key}`)
414
+ }
415
+ }
416
+ if (input.type === 'action') {
417
+ if (typeof input.label !== 'string') throw new Error('会话标题 action label 必须是字符串')
418
+ const icon = cloneIcon(input.icon)
419
+ if (!icon) throw new Error('会话标题 action 必须提供 icon')
420
+ if (typeof input.onSelect !== 'function') throw new Error('会话标题 action 必须提供 onSelect()')
421
+ return {
422
+ type: 'action',
423
+ label: parseLabel(input.label, 'Session Header action label'),
424
+ icon,
425
+ onSelect: input.onSelect as BrowserSessionHeaderAction['onSelect']
426
+ }
427
+ }
428
+ if (input.type !== 'custom') throw new Error('会话标题表现 type 必须是 action 或 custom')
429
+ if (typeof input.load !== 'function') throw new Error('会话标题 custom 必须提供 load()')
430
+ if (
431
+ presentation === 'inline' &&
432
+ (typeof input.minWidthRem !== 'number' ||
433
+ !Number.isFinite(input.minWidthRem) ||
434
+ input.minWidthRem <= 0)
435
+ ) {
436
+ throw new Error('会话标题 inline custom minWidthRem 必须是正数')
437
+ }
438
+ return {
439
+ type: 'custom',
440
+ load: input.load as BrowserSessionHeaderCustom['load'],
441
+ ...(presentation === 'inline' ? { minWidthRem: input.minWidthRem } : {})
442
+ }
443
+ }
444
+
372
445
  function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
373
446
  return (
374
447
  typeof value === 'object' && value !== null && typeof Reflect.get(value, 'then') === 'function'
@@ -509,11 +582,12 @@ export class L4PluginHostRuntime {
509
582
  getContributionLoadState(
510
583
  pluginName: string,
511
584
  kind: L4PluginContributionKind,
512
- contributionName: string
585
+ contributionName: string,
586
+ presentation?: BrowserSessionHeaderItemTarget['presentation']
513
587
  ): BrowserContributionLoadState {
514
588
  return (
515
589
  this.contributionLoadStates.get(
516
- pluginContributionKey(pluginName, kind, contributionName)
590
+ contributionLoadKey(pluginName, kind, contributionName, presentation)
517
591
  ) ?? { status: 'unloaded' }
518
592
  )
519
593
  }
@@ -724,11 +798,13 @@ export class L4PluginHostRuntime {
724
798
  retryContribution(
725
799
  pluginName: string,
726
800
  kind: L4PluginContributionKind,
727
- contributionName: string
801
+ contributionName: string,
802
+ presentation?: BrowserSessionHeaderItemTarget['presentation']
728
803
  ): Promise<BrowserContributionImplementation> {
729
804
  const key = pluginContributionKey(pluginName, kind, contributionName)
730
- if (this.contributionLoadStates.get(key)?.status === 'failed') {
731
- this.contributionLoadStates.delete(key)
805
+ const loadKey = contributionLoadKey(pluginName, kind, contributionName, presentation)
806
+ if (this.contributionLoadStates.get(loadKey)?.status === 'failed') {
807
+ this.contributionLoadStates.delete(loadKey)
732
808
  this.publishRegistryChange()
733
809
  }
734
810
  const registration = this.registrations.get(key)
@@ -742,7 +818,100 @@ export class L4PluginHostRuntime {
742
818
  })
743
819
  )
744
820
  }
745
- return this.loadContribution(registration)
821
+ return this.loadContribution(registration, presentation)
822
+ }
823
+
824
+ async invokeSessionHeaderAction(input: {
825
+ pluginName: string
826
+ contributionName: string
827
+ presentation: BrowserSessionHeaderItemTarget['presentation']
828
+ source: PluginSource
829
+ signal: AbortSignal
830
+ }): Promise<void> {
831
+ const key = pluginContributionKey(
832
+ input.pluginName,
833
+ 'session-header-item',
834
+ input.contributionName
835
+ )
836
+ const registration = this.registrations.get(key)
837
+ const action = registration?.header?.[input.presentation]
838
+ if (!registration || action?.type !== 'action') {
839
+ throw new Error('会话标题 action 不存在')
840
+ }
841
+ input.signal.throwIfAborted()
842
+ if (!registration.owner.active) throw new Error('Browser Entry 已失效')
843
+ if (!this.workSessions.some((item) => sameSource(item.source, input.source))) {
844
+ throw new Error('工作会话来源已变化')
845
+ }
846
+ const scope = this.getHeaderActionScope(registration, input.source, input.signal)
847
+ try {
848
+ await action.onSelect({ ...scope })
849
+ scope.signal.throwIfAborted()
850
+ if (!registration.owner.active || this.registrations.get(key) !== registration) {
851
+ throw new Error('Browser Entry 已失效')
852
+ }
853
+ } catch (cause) {
854
+ const error = new BrowserPluginError({
855
+ pluginName: input.pluginName,
856
+ contributionName: input.contributionName,
857
+ phase: 'session-header-action',
858
+ message: cause instanceof Error ? cause.message : String(cause),
859
+ cause
860
+ })
861
+ if (!scope.signal.aborted) {
862
+ runPluginCallback(
863
+ () => {
864
+ throw error
865
+ },
866
+ `header-action:${input.contributionName}:${input.presentation}`,
867
+ input.pluginName
868
+ )
869
+ }
870
+ throw error
871
+ }
872
+ }
873
+
874
+ private getHeaderActionScope(
875
+ registration: L4PluginContributionRegistration,
876
+ sourceInput: PluginSource,
877
+ signal: AbortSignal
878
+ ): BrowserSessionHeaderActionContext {
879
+ const scopes = (registration.headerActionScopes ??= new WeakMap())
880
+ const cached = scopes.get(signal)
881
+ if (cached) {
882
+ if (!sameSource(cached.source, sourceInput)) throw new Error('会话标题展示来源不一致')
883
+ return cached
884
+ }
885
+ const owner = registration.owner
886
+ const source = freezeSnapshot(structuredClone(sourceInput))
887
+ const controller = new AbortController()
888
+ const actionOwner = {
889
+ ...owner,
890
+ subscriptionDisposers: new Set<() => void>(),
891
+ get active(): boolean {
892
+ return owner.active && !controller.signal.aborted
893
+ }
894
+ }
895
+ const dispose = (): void => {
896
+ controller.abort()
897
+ scopes.delete(signal)
898
+ this.workSessionListeners.delete(checkSource)
899
+ owner.subscriptionDisposers.delete(dispose)
900
+ signal.removeEventListener('abort', dispose)
901
+ for (const unsubscribe of actionOwner.subscriptionDisposers) {
902
+ runPluginCallback(unsubscribe, 'header-action-dispose', registration.pluginName)
903
+ }
904
+ actionOwner.subscriptionDisposers.clear()
905
+ }
906
+ const checkSource = (): void => {
907
+ if (!this.workSessions.some((item) => sameSource(item.source, source))) dispose()
908
+ }
909
+ const scope = { source, host: this.createBrowserHost(actionOwner), signal: controller.signal }
910
+ scopes.set(signal, scope)
911
+ this.workSessionListeners.add(checkSource)
912
+ owner.subscriptionDisposers.add(dispose)
913
+ signal.addEventListener('abort', dispose, { once: true })
914
+ return scope
746
915
  }
747
916
 
748
917
  async mountContribution(input: L4PluginContributionMountInput): Promise<PluginDisposer> {
@@ -756,11 +925,23 @@ export class L4PluginHostRuntime {
756
925
  message: '插件 Contribution 不存在'
757
926
  })
758
927
  }
928
+ if (registration.kind !== input.target.kind) {
929
+ throw new Error('Contribution kind 与 mount target 不一致')
930
+ }
759
931
  const controller = new AbortController()
760
932
  const cancelled = new Promise<void>((resolve) => {
761
933
  controller.signal.addEventListener('abort', () => resolve(), { once: true })
762
934
  })
763
935
  let target = input.target
936
+ if (target.kind === 'widget' || target.kind === 'session-header-item') {
937
+ target = { ...target, source: freezeSnapshot(structuredClone(target.source)) }
938
+ }
939
+ const source =
940
+ target.kind === 'widget' || target.kind === 'session-header-item' ? target.source : null
941
+ const presentation = target.kind === 'session-header-item' ? target.presentation : undefined
942
+ const checkSource = (): void => {
943
+ if (source && !this.workSessions.some((item) => sameSource(item.source, source))) cancel()
944
+ }
764
945
  if (target.kind === 'session-sidebar-tab' && target.setHeaderActions) {
765
946
  const publishActions = target.setHeaderActions
766
947
  let currentActions: readonly BrowserSessionSidebarHeaderAction[] = []
@@ -831,7 +1012,7 @@ export class L4PluginHostRuntime {
831
1012
  }
832
1013
  const mounting = Promise.resolve().then(async () => {
833
1014
  if (controller.signal.aborted) throw new Error('插件视图已关闭')
834
- const implementation = await this.loadContribution(registration)
1015
+ const implementation = await this.loadContribution(registration, presentation)
835
1016
  if (controller.signal.aborted || !registration.owner.active) {
836
1017
  throw new Error('插件视图已失效')
837
1018
  }
@@ -853,6 +1034,7 @@ export class L4PluginHostRuntime {
853
1034
  if (disposal) return disposal
854
1035
  registration.owner.mounts.delete(dispose)
855
1036
  input.signal?.removeEventListener('abort', cancel)
1037
+ this.workSessionListeners.delete(checkSource)
856
1038
  controller.abort()
857
1039
  // 先取消,再有界等待;迟到的 mount 仍由同一次清理释放。
858
1040
  disposal = disposePluginResource(
@@ -869,8 +1051,10 @@ export class L4PluginHostRuntime {
869
1051
  void dispose()
870
1052
  }
871
1053
  registration.owner.mounts.add(dispose)
1054
+ if (source) this.workSessionListeners.add(checkSource)
872
1055
  input.signal?.addEventListener('abort', cancel, { once: true })
873
1056
  if (input.signal?.aborted) cancel()
1057
+ checkSource()
874
1058
  try {
875
1059
  await Promise.race([mounting, cancelled])
876
1060
  if (controller.signal.aborted || !registration.owner.active) {
@@ -1096,6 +1280,19 @@ export class L4PluginHostRuntime {
1096
1280
  }
1097
1281
 
1098
1282
  private createBrowserFacade(pluginName: string, owner: L4PluginEntryOwner): BrowserPluginFacade {
1283
+ const register = (create: () => L4PluginContributionRegistration): void => {
1284
+ if (!owner.active || !owner.registrationOpen) {
1285
+ throw new Error('Browser Entry registration phase has ended')
1286
+ }
1287
+ const registration = create()
1288
+ const key = contributionKey(registration.kind, registration.contributionName)
1289
+ if (owner.registrations.has(key)) {
1290
+ throw new Error(
1291
+ `Browser Contribution 重复:${pluginName}/${registration.kind}/${registration.contributionName}`
1292
+ )
1293
+ }
1294
+ owner.registrations.set(key, registration)
1295
+ }
1099
1296
  return {
1100
1297
  name: pluginName,
1101
1298
  host: owner.host,
@@ -1119,26 +1316,22 @@ export class L4PluginHostRuntime {
1119
1316
  this.notifyPlugin(pluginName, structuredClone(input))
1120
1317
  },
1121
1318
  registerContribution: (kind, contributionName, definition) => {
1122
- if (!owner.active || !owner.registrationOpen) {
1123
- throw new Error('Browser Entry registration phase has ended')
1124
- }
1125
- if (!CONTRIBUTION_KINDS.has(kind)) {
1126
- throw new Error(`Browser Contribution kind 无效:${String(kind)}`)
1127
- }
1128
- const registration = this.createRegistration(
1129
- pluginName,
1130
- kind,
1131
- contributionName,
1132
- definition,
1133
- owner
1319
+ register(() => {
1320
+ if (!CONTRIBUTION_KINDS.has(kind)) {
1321
+ throw new Error(`Browser Contribution kind 无效:${String(kind)}`)
1322
+ }
1323
+ return this.createRegistration(pluginName, kind, contributionName, definition, owner)
1324
+ })
1325
+ },
1326
+ registerWidget: (contributionName, definition) => {
1327
+ register(() =>
1328
+ this.createWidgetRegistration(pluginName, contributionName, definition, owner)
1329
+ )
1330
+ },
1331
+ registerSessionHeaderItem: (contributionName, definition) => {
1332
+ register(() =>
1333
+ this.createHeaderRegistration(pluginName, contributionName, definition, owner)
1134
1334
  )
1135
- const key = contributionKey(kind, registration.contributionName)
1136
- if (owner.registrations.has(key)) {
1137
- throw new Error(
1138
- `Browser Contribution 重复:${pluginName}/${kind}/${registration.contributionName}`
1139
- )
1140
- }
1141
- owner.registrations.set(key, registration)
1142
1335
  },
1143
1336
  onPush: (listener) => {
1144
1337
  if (!owner.active) throw new Error('Browser Entry owner is no longer active')
@@ -1150,7 +1343,7 @@ export class L4PluginHostRuntime {
1150
1343
  }
1151
1344
  }
1152
1345
 
1153
- private createRegistration<K extends L4PluginContributionKind>(
1346
+ private createRegistration<K extends BrowserContributionKind>(
1154
1347
  pluginName: string,
1155
1348
  kind: K,
1156
1349
  contributionNameInput: string,
@@ -1258,6 +1451,74 @@ export class L4PluginHostRuntime {
1258
1451
  }
1259
1452
  }
1260
1453
 
1454
+ private createWidgetRegistration(
1455
+ pluginName: string,
1456
+ contributionNameInput: string,
1457
+ definition: BrowserWidgetDefinition,
1458
+ owner: L4PluginEntryOwner
1459
+ ): L4PluginContributionRegistration {
1460
+ const contributionName = parseContributionName(contributionNameInput)
1461
+ if (!definition || typeof definition !== 'object' || typeof definition.load !== 'function') {
1462
+ throw new Error('Browser Widget 必须提供 load()')
1463
+ }
1464
+ const placement = definition.placement ?? 'aboveEditor'
1465
+ if (placement !== 'aboveEditor' && placement !== 'belowEditor') {
1466
+ throw new Error('Browser Widget placement 必须是 aboveEditor 或 belowEditor')
1467
+ }
1468
+ return {
1469
+ pluginName,
1470
+ kind: 'widget',
1471
+ contributionName,
1472
+ descriptor: { kind: 'widget', contributionName, placement },
1473
+ load: definition.load,
1474
+ owner
1475
+ }
1476
+ }
1477
+
1478
+ private createHeaderRegistration(
1479
+ pluginName: string,
1480
+ contributionNameInput: string,
1481
+ definition: BrowserSessionHeaderItemDefinition,
1482
+ owner: L4PluginEntryOwner
1483
+ ): L4PluginContributionRegistration {
1484
+ const contributionName = parseContributionName(contributionNameInput)
1485
+ if (!definition || typeof definition !== 'object' || Array.isArray(definition)) {
1486
+ throw new Error('会话标题条目必须提供 inline 和 overflow')
1487
+ }
1488
+ for (const key of Object.keys(definition)) {
1489
+ if (key !== 'inline' && key !== 'overflow') {
1490
+ throw new Error(`会话标题条目不支持顶层字段 ${key}`)
1491
+ }
1492
+ }
1493
+ const inline = parseHeaderPresentation(
1494
+ definition.inline,
1495
+ 'inline'
1496
+ ) as BrowserSessionHeaderItemDefinition['inline']
1497
+ const overflow = parseHeaderPresentation(
1498
+ definition.overflow,
1499
+ 'overflow'
1500
+ ) as BrowserSessionHeaderItemDefinition['overflow']
1501
+ return {
1502
+ pluginName,
1503
+ kind: 'session-header-item',
1504
+ contributionName,
1505
+ descriptor: {
1506
+ kind: 'session-header-item',
1507
+ contributionName,
1508
+ inline:
1509
+ inline.type === 'action'
1510
+ ? { type: 'action', icon: inline.icon, label: inline.label }
1511
+ : { type: 'custom', minWidthRem: inline.minWidthRem },
1512
+ overflow:
1513
+ overflow.type === 'action'
1514
+ ? { type: 'action', icon: overflow.icon, label: overflow.label }
1515
+ : { type: 'custom' }
1516
+ },
1517
+ header: { inline, overflow },
1518
+ owner
1519
+ }
1520
+ }
1521
+
1261
1522
  private commitRegistrations(owner: L4PluginEntryOwner): void {
1262
1523
  for (const registration of owner.registrations.values()) {
1263
1524
  const key = pluginContributionKey(
@@ -1273,26 +1534,42 @@ export class L4PluginHostRuntime {
1273
1534
  }
1274
1535
 
1275
1536
  private loadContribution(
1276
- registration: L4PluginContributionRegistration
1537
+ registration: L4PluginContributionRegistration,
1538
+ presentation?: BrowserSessionHeaderItemTarget['presentation']
1277
1539
  ): Promise<BrowserContributionImplementation> {
1278
- const key = pluginContributionKey(
1540
+ const registrationKey = pluginContributionKey(
1279
1541
  registration.pluginName,
1280
1542
  registration.kind,
1281
1543
  registration.contributionName
1282
1544
  )
1545
+ const key = contributionLoadKey(
1546
+ registration.pluginName,
1547
+ registration.kind,
1548
+ registration.contributionName,
1549
+ presentation
1550
+ )
1551
+ const header = presentation ? registration.header?.[presentation] : undefined
1552
+ const load = header?.type === 'custom' ? header.load : registration.load
1553
+ if (!load) return Promise.reject(new Error('会话标题 action 不支持加载视图'))
1554
+ if (!registration.owner.active || this.registrations.get(registrationKey) !== registration) {
1555
+ return Promise.reject(new Error('Browser Contribution 已失效'))
1556
+ }
1283
1557
  const state = this.contributionLoadStates.get(key)
1284
1558
  if (state?.status === 'ready') return Promise.resolve(state.implementation)
1285
1559
  if (state?.status === 'loading') return state.promise
1286
1560
  if (state?.status === 'failed') return Promise.reject(state.error)
1287
1561
 
1288
1562
  const promise = Promise.resolve()
1289
- .then(registration.load)
1563
+ .then(load)
1290
1564
  .then(
1291
1565
  (implementation) => {
1292
1566
  if (!implementation || typeof implementation.mount !== 'function') {
1293
1567
  throw new Error('Lazy Contribution Module 必须导出 mount()')
1294
1568
  }
1295
- if (!registration.owner.active || this.registrations.get(key) !== registration) {
1569
+ if (
1570
+ !registration.owner.active ||
1571
+ this.registrations.get(registrationKey) !== registration
1572
+ ) {
1296
1573
  throw new Error('Browser Contribution 已失效')
1297
1574
  }
1298
1575
  this.contributionLoadStates.set(key, { status: 'ready', implementation })
@@ -1313,7 +1590,7 @@ export class L4PluginHostRuntime {
1313
1590
  phase: 'contribution-load',
1314
1591
  message: String(cause)
1315
1592
  })
1316
- if (registration.owner.active && this.registrations.get(key) === registration) {
1593
+ if (registration.owner.active && this.registrations.get(registrationKey) === registration) {
1317
1594
  this.contributionLoadStates.set(key, { status: 'failed', error })
1318
1595
  this.publishRegistryChange()
1319
1596
  }
@@ -1490,6 +1767,10 @@ export class L4PluginHostRuntime {
1490
1767
  if (this.registrations.get(key) !== registration) continue
1491
1768
  this.registrations.delete(key)
1492
1769
  this.contributionLoadStates.delete(key)
1770
+ if (registration.kind === 'session-header-item') {
1771
+ this.contributionLoadStates.delete(`${key}\u0000inline`)
1772
+ this.contributionLoadStates.delete(`${key}\u0000overflow`)
1773
+ }
1493
1774
  this.applicationVisibility.delete(key)
1494
1775
  }
1495
1776
 
@@ -0,0 +1,41 @@
1
+ function hasControlCharacters(value: string): boolean {
2
+ return Array.from(value).some((character) => {
3
+ const code = character.charCodeAt(0)
4
+ return code <= 0x1f || (code >= 0x7f && code <= 0x9f)
5
+ })
6
+ }
7
+
8
+ export function L2ResolveAuthReturnTo(value: unknown): string {
9
+ if (
10
+ typeof value !== 'string' ||
11
+ !value.startsWith('/') ||
12
+ value.startsWith('//') ||
13
+ value.includes('\\') ||
14
+ hasControlCharacters(value)
15
+ ) {
16
+ return '/'
17
+ }
18
+
19
+ let pathname: string
20
+ try {
21
+ pathname = decodeURIComponent(value.split(/[?#]/, 1)[0])
22
+ if (hasControlCharacters(decodeURIComponent(value))) return '/'
23
+ } catch {
24
+ return '/'
25
+ }
26
+ if (pathname.startsWith('//') || pathname.includes('\\')) return '/'
27
+
28
+ const segments: string[] = []
29
+ for (const segment of pathname.split('/')) {
30
+ if (segment === '..') segments.pop()
31
+ else if (segment !== '' && segment !== '.') segments.push(segment)
32
+ }
33
+ if (segments.join('/').toLowerCase() === 'login') return '/'
34
+
35
+ return value
36
+ }
37
+
38
+ export function L2BuildLoginHref(returnTo: unknown): string {
39
+ const target = L2ResolveAuthReturnTo(returnTo)
40
+ return target === '/' ? '/login' : `/login?returnTo=${encodeURIComponent(target)}`
41
+ }
@@ -222,7 +222,11 @@ export const L2ModelSettingsPreviewResponseSchema = z
222
222
  .object({
223
223
  projectDefaults: z.array(
224
224
  z
225
- .object({ cwd: L3PiCwdSchema, from: L2ModelSelectionSchema, to: L2ModelSelectionSchema })
225
+ .object({
226
+ cwd: L3PiCwdSchema,
227
+ from: L2ModelSelectionSchema,
228
+ to: L2ModelSelectionSchema.nullable()
229
+ })
226
230
  .strict()
227
231
  )
228
232
  })
@@ -87,6 +87,8 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
87
87
  contributionSettings: 'Settings page',
88
88
  contributionMessage: 'Message view',
89
89
  contributionSidebar: 'Session sidebar',
90
+ contributionWidget: 'Composer widget',
91
+ contributionSessionHeader: 'Session header item',
90
92
  tagTools: '{{count}} tools',
91
93
  tagFixed: '{{count}} fixed prompts',
92
94
  tagDynamic: '{{count}} dynamic hooks',
@@ -324,6 +326,8 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
324
326
  contributionSettings: '设置页',
325
327
  contributionMessage: '消息视图',
326
328
  contributionSidebar: '会话侧栏',
329
+ contributionWidget: '输入区挂件',
330
+ contributionSessionHeader: '会话标题项',
327
331
  tagTools: '工具 {{count}}',
328
332
  tagFixed: '固定提示 {{count}}',
329
333
  tagDynamic: '动态影响 {{count}}',
@@ -1,5 +1,19 @@
1
1
  export const L2_PROJECT_HISTORY_MESSAGES = {
2
2
  en: {
3
+ chooseProject: 'Open a project',
4
+ switchProject: 'Switch project',
5
+ directoryPath: 'Directory path',
6
+ directoryPlaceholder: 'Enter an absolute directory path',
7
+ serverDirectoryHint: 'Browse directories on the connected server.',
8
+ openProject: 'Open',
9
+ recentProjects: 'Project history',
10
+ browseDirectories: 'Browse directories',
11
+ loadingDirectories: 'Loading directories…',
12
+ parentDirectory: 'Parent directory',
13
+ directoryRoots: 'Directories',
14
+ openThisDirectory: 'Open this directory',
15
+ noSubdirectories: 'No subdirectories',
16
+ noRecentProjects: 'No project history. Enter a path or browse directories.',
3
17
  currentHead: 'Current HEAD',
4
18
  localBranch: 'Local branch',
5
19
  remoteBranch: 'Remote branch · local record',
@@ -76,6 +90,20 @@ export const L2_PROJECT_HISTORY_MESSAGES = {
76
90
  historyReadFailed: 'Could not read Git history'
77
91
  },
78
92
  'zh-CN': {
93
+ chooseProject: '打开项目',
94
+ switchProject: '切换项目',
95
+ directoryPath: '目录路径',
96
+ directoryPlaceholder: '输入完整目录路径',
97
+ serverDirectoryHint: '选择所连接服务上的目录。',
98
+ openProject: '打开',
99
+ recentProjects: '历史项目',
100
+ browseDirectories: '浏览目录',
101
+ loadingDirectories: '正在读取目录…',
102
+ parentDirectory: '上级目录',
103
+ directoryRoots: '目录',
104
+ openThisDirectory: '打开此目录',
105
+ noSubdirectories: '没有子目录',
106
+ noRecentProjects: '没有历史项目,可输入路径或浏览目录。',
79
107
  currentHead: '当前 HEAD',
80
108
  localBranch: '本地分支',
81
109
  remoteBranch: '远程分支 · 本地记录',
@@ -0,0 +1,5 @@
1
+ export const L2_PROJECT_PREVIEW_PATH = '/project-preview'
2
+
3
+ export function buildL2ProjectPreviewHref(cwd?: string | null): string {
4
+ return cwd ? `${L2_PROJECT_PREVIEW_PATH}?cwd=${encodeURIComponent(cwd)}` : L2_PROJECT_PREVIEW_PATH
5
+ }
@@ -110,7 +110,8 @@ export const L2_SETTINGS_MESSAGES = {
110
110
  removeAndSave: 'Remove and save',
111
111
  replaceProjectDefaultsTitle: 'Update project default models',
112
112
  replaceProjectDefaultsDescription:
113
- 'The following projects will use the new model settings. Confirm to save all changes together.',
113
+ 'The following project defaults will be updated or cleared. Confirm to save all changes together.',
114
+ clearProjectDefault: 'Clear project default',
114
115
  confirmAndSaveModels: 'Confirm and save',
115
116
  modelSaved: 'Model configuration saved.',
116
117
  modelSaveFailed: 'Could not save the model configuration',
@@ -405,8 +406,8 @@ export const L2_SETTINGS_MESSAGES = {
405
406
  '有 {{count}} 个常用组合引用了已删除模型,保存配置将同时删除这些组合。',
406
407
  removeAndSave: '删除并保存',
407
408
  replaceProjectDefaultsTitle: '同步项目默认模型',
408
- replaceProjectDefaultsDescription:
409
- '以下项目的默认模型将替换为本次设置的新模型,确认后一起保存。',
409
+ replaceProjectDefaultsDescription: '以下项目默认将更新或清除,确认后一起保存。',
410
+ clearProjectDefault: '清除项目默认',
410
411
  confirmAndSaveModels: '确认并保存',
411
412
  modelSaved: '模型配置已保存。',
412
413
  modelSaveFailed: '模型配置保存失败',
@@ -212,6 +212,7 @@ export const L2_WORKBENCH_LOCALE_MESSAGES = {
212
212
  sidebarReplaceSession: 'Replace work session',
213
213
  createSession: 'Create work session',
214
214
  projectPreview: 'Project preview',
215
+ projectPreviewWindow: 'Open in new window',
215
216
  refreshFiles: 'Refresh project files',
216
217
  pinnedBelow: 'Pinned below',
217
218
  settings: 'Settings',
@@ -658,6 +659,7 @@ export const L2_WORKBENCH_LOCALE_MESSAGES = {
658
659
  sidebarReplaceSession: '替换工作会话',
659
660
  createSession: '新建工作会话',
660
661
  projectPreview: '项目预览',
662
+ projectPreviewWindow: '在新窗口打开',
661
663
  refreshFiles: '刷新项目文件',
662
664
  pinnedBelow: '以下固定',
663
665
  settings: '设置',