@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
@@ -1,6 +1,7 @@
1
1
  import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
2
2
  import type { Duplex } from 'node:stream'
3
3
  import { URL } from 'node:url'
4
+ import { L2BuildLoginHref, L2ResolveAuthReturnTo } from '@common/l2_biz/auth/l2-auth-return-to'
4
5
  import {
5
6
  L4_APP_WEBSOCKET_ENDPOINT,
6
7
  L4_APP_WEBSOCKET_PROTOCOL,
@@ -97,15 +98,16 @@ function allowHttpRequest(
97
98
  response: ServerResponse,
98
99
  hostname: string
99
100
  ): boolean {
100
- let pathname: string
101
+ let requestUrl: URL
101
102
  try {
102
- pathname = new URL(request.url ?? '/', `http://${request.headers.host ?? hostname}`).pathname
103
+ requestUrl = new URL(request.url ?? '/', `http://${request.headers.host ?? hostname}`)
103
104
  } catch {
104
105
  response.statusCode = 400
105
106
  response.end('Bad Request')
106
107
  return false
107
108
  }
108
109
 
110
+ const pathname = requestUrl.pathname
109
111
  if (isPublicHttpPath(pathname) && pathname !== LOGIN_PAGE_PATH) return true
110
112
 
111
113
  const authRuntime = getL1WebAuthRuntimeBridge()
@@ -119,9 +121,11 @@ function allowHttpRequest(
119
121
  }
120
122
  if (pathname === LOGIN_PAGE_PATH) {
121
123
  if (!authenticated) return true
124
+ const returnTo = L2ResolveAuthReturnTo(requestUrl.searchParams.get('returnTo'))
122
125
  response.statusCode = 302
123
126
  response.setHeader('Cache-Control', 'no-store')
124
- response.setHeader('Location', '/')
127
+ // 保留已校验的原路径,避免将 /.//host 规范化为协议相对 Location。
128
+ response.setHeader('Location', encodeURI(returnTo).replace(/%25/g, '%'))
125
129
  response.end()
126
130
  return false
127
131
  }
@@ -146,7 +150,7 @@ function allowHttpRequest(
146
150
 
147
151
  response.statusCode = 302
148
152
  response.setHeader('Cache-Control', 'no-store')
149
- response.setHeader('Location', LOGIN_PAGE_PATH)
153
+ response.setHeader('Location', L2BuildLoginHref(`${requestUrl.pathname}${requestUrl.search}`))
150
154
  response.end()
151
155
  return false
152
156
  }
@@ -624,7 +624,7 @@ async function assertConnectionCredentials(
624
624
  }
625
625
 
626
626
  async function prepareProjectDefaultChanges(
627
- current: JsonRecord,
627
+ removed: Set<string>,
628
628
  candidate: JsonRecord,
629
629
  input: L4ModelSettingsReplaceInput,
630
630
  runtime: ModelRuntime,
@@ -633,16 +633,14 @@ async function prepareProjectDefaultChanges(
633
633
  projectDefaults: L4ModelSettingsPreview['projectDefaults']
634
634
  files: Array<{ path: string; value: JsonRecord }>
635
635
  }> {
636
- const previousKeys = collectedModelKeys(current)
637
636
  const nextKeys = collectedModelKeys(candidate)
638
- const removed = new Set([...previousKeys].filter((key) => !nextKeys.has(key)))
639
637
  const replacements = new Map<string, L4ModelOption>()
640
638
  for (const { from, to } of input.modelReplacements ?? []) {
641
639
  const fromKey = collectedModelKey(from.provider, from.modelId)
642
640
  const toKey = collectedModelKey(to.provider, to.modelId)
643
641
  if (fromKey === toKey) throw new Error('模型替换来源和目标不能相同')
644
642
  if (replacements.has(fromKey)) throw new Error('模型替换来源不能重复')
645
- if (!previousKeys.has(fromKey) || !removed.has(fromKey)) {
643
+ if (!removed.has(fromKey)) {
646
644
  throw new Error(`替换来源 ${from.provider}/${from.modelId} 必须是本次移除的已收录模型`)
647
645
  }
648
646
  const target = models.find(
@@ -658,13 +656,6 @@ async function prepareProjectDefaultChanges(
658
656
  files: Array<{ path: string; value: JsonRecord }>
659
657
  } = { projectDefaults: [], files: [] }
660
658
  if (!removed.size) return result
661
- for (const preset of normalizePresets(candidate)) {
662
- if (removed.has(collectedModelKey(preset.provider, preset.modelId))) {
663
- throw new Error(
664
- `模型 ${preset.provider}/${preset.modelId} 仍被常用组合引用,请先移除对应组合`
665
- )
666
- }
667
- }
668
659
  // 仅在移除模型时检查已知项目,不扫描文件系统寻找任意项目配置。
669
660
  const projects = await listL4PiDirectorySummaries(true)
670
661
  for (const { cwd } of projects) {
@@ -681,11 +672,20 @@ async function prepareProjectDefaultChanges(
681
672
  const provider = merged.getDefaultProvider()
682
673
  const modelId = merged.getDefaultModel()
683
674
  if (!provider || !modelId || !removed.has(collectedModelKey(provider, modelId))) continue
675
+ const from: L4ModelSelection = {
676
+ provider,
677
+ modelId,
678
+ thinkingLevel: merged.getDefaultThinkingLevel() ?? 'off'
679
+ }
684
680
  const target = replacements.get(collectedModelKey(provider, modelId))
685
681
  if (!target) {
686
- throw new Error(
687
- `项目“${cwd}”仍将 ${provider}/${modelId} 设为默认模型,请先在“项目默认”中调整`
688
- )
682
+ const value = { ...settings }
683
+ delete value.defaultProvider
684
+ delete value.defaultModel
685
+ delete value.defaultThinkingLevel
686
+ result.projectDefaults.push({ cwd, from, to: null })
687
+ result.files.push({ path, value })
688
+ continue
689
689
  }
690
690
  const projectModels = await availableModelOptions(runtime, cwd, candidate)
691
691
  if (
@@ -695,11 +695,6 @@ async function prepareProjectDefaultChanges(
695
695
  ) {
696
696
  throw new Error(`替换目标 ${target.provider}/${target.modelId} 在项目“${cwd}”中不可用`)
697
697
  }
698
- const from: L4ModelSelection = {
699
- provider,
700
- modelId,
701
- thinkingLevel: merged.getDefaultThinkingLevel() ?? 'off'
702
- }
703
698
  const thinkingLevel = target.thinkingLevels.includes(from.thinkingLevel)
704
699
  ? from.thinkingLevel
705
700
  : target.thinkingLevels[0]
@@ -785,11 +780,25 @@ async function prepareModelSettings(input: L4ModelSettingsReplaceInput): Promise
785
780
  : await ModelRuntime.create({ modelsPath: null, allowModelNetwork: false })
786
781
  candidate = mergeModelsRoot(current, input, currentRuntime, defaults)
787
782
  }
783
+ const nextKeys = collectedModelKeys(candidate)
784
+ const removed = new Set([...collectedModelKeys(current)].filter((key) => !nextKeys.has(key)))
785
+ if (removed.size && Array.isArray(candidate.modelPresets)) {
786
+ candidate = {
787
+ ...candidate,
788
+ modelPresets: candidate.modelPresets.filter(
789
+ (preset) =>
790
+ !isRecord(preset) ||
791
+ typeof preset.provider !== 'string' ||
792
+ typeof preset.modelId !== 'string' ||
793
+ !removed.has(collectedModelKey(preset.provider, preset.modelId))
794
+ )
795
+ }
796
+ }
788
797
  const runtime = await validateModelsRoot(candidate)
789
798
  const models = await availableModelOptions(runtime, undefined, candidate)
790
799
  await assertConnectionCredentials(current, candidate, runtime)
791
800
  assertPresetsValid(normalizePresets(candidate), models, normalizePresets(current))
792
- const result = await prepareProjectDefaultChanges(current, candidate, input, runtime, models)
801
+ const result = await prepareProjectDefaultChanges(removed, candidate, input, runtime, models)
793
802
  result.files.push({ path, value: candidate })
794
803
  return result
795
804
  }
@@ -97,7 +97,7 @@ export interface L4ModelReplacement {
97
97
  }
98
98
 
99
99
  export interface L4ModelSettingsPreview {
100
- projectDefaults: Array<{ cwd: string; from: L4ModelSelection; to: L4ModelSelection }>
100
+ projectDefaults: Array<{ cwd: string; from: L4ModelSelection; to: L4ModelSelection | null }>
101
101
  }
102
102
 
103
103
  export type L4ModelSettingsReplaceInput =
@@ -425,6 +425,24 @@ export class L4PiChatWorker {
425
425
  readCapabilities: this.capabilities.rules,
426
426
  onInput: (event) =>
427
427
  this.validateFinalInput(event.text, event.images, event.streamingBehavior),
428
+ onMessageEnd: ({ message }) => {
429
+ if (
430
+ this.disposed ||
431
+ !this.oversizedToolCallAbortRequested ||
432
+ message.role !== 'assistant' ||
433
+ (message.stopReason !== 'aborted' && message.stopReason !== 'error')
434
+ ) {
435
+ return
436
+ }
437
+ return {
438
+ message: {
439
+ ...message,
440
+ stopReason: 'error',
441
+ // Pi 按错误文本识别可重试失败,保留其已支持的重试提示。
442
+ errorMessage: `工具参数流超过 ${L4_PI_CHAT_MAX_STREAMING_TOOL_ARGUMENT_CHARS} 字符上限,本次响应中的工具均未执行。Please retry your request.`
443
+ }
444
+ }
445
+ },
428
446
  onTree: () => this.resetBranchRuntime(),
429
447
  onCommand: (args, toolCallId) => {
430
448
  const signal = this.commandLifetime.signal
@@ -862,9 +880,8 @@ export class L4PiChatWorker {
862
880
  this.nativeUi.cancelAll()
863
881
  this.discardAcceptedInputs()
864
882
  const session = await this.ensureSession()
865
- session.abortRetry()
866
- session.abortCompaction()
867
- session.agent.abort()
883
+ this.oversizedToolCallAbortRequested = false
884
+ await session.abort()
868
885
  }
869
886
 
870
887
  async navigateTree(entryId: string): Promise<boolean> {
@@ -1604,9 +1621,9 @@ export class L4PiChatWorker {
1604
1621
  const oversized = findOversizedStreamingToolCall(message)
1605
1622
  if (!oversized) return false
1606
1623
 
1607
- if (!this.oversizedToolCallAbortRequested) {
1624
+ if (!this.oversizedToolCallAbortRequested && !this.session?.agent.signal?.aborted) {
1608
1625
  this.oversizedToolCallAbortRequested = true
1609
- console.warn('[Pi Desk][PiChatWorker] 工具参数流过大,已中止当前 Agent', {
1626
+ console.warn('[Pi Desk][PiChatWorker] 工具参数流过大,已中止当前运行,交由 Pi 重试策略处理', {
1610
1627
  sessionId: this.sessionManager.getSessionId(),
1611
1628
  toolCallId: oversized.toolCallId,
1612
1629
  toolName: oversized.toolName,
@@ -45,6 +45,7 @@ import {
45
45
  const L4_PLUGIN_PUSH_MAX_JSON_BYTES = 64 * 1024
46
46
  const L4_PLUGIN_LIFECYCLE_TIMEOUT_MS = 1_500
47
47
  const L4_PLUGIN_LOAD_TIMEOUT_MS = 15_000
48
+ const L4_PLUGIN_RECOVERY_DELAY_MS = 10_000
48
49
  const L4_PLUGIN_BROWSER_TOTAL_BYTES = 128 * 1024 * 1024
49
50
  const L4PiPluginPushDataSchema = z.record(z.string(), z.json())
50
51
  const L4PiSourceSchema = z
@@ -153,6 +154,22 @@ class L4PiPluginPhaseError extends Error {
153
154
  }
154
155
  }
155
156
 
157
+ class L4PiPluginLoadTimeoutError extends L4PiPluginPhaseError {
158
+ constructor(
159
+ phase: L4PiPluginDiagnosticPhase,
160
+ readonly execution: Promise<void>
161
+ ) {
162
+ super(phase, new Error('插件加载超时,已停止本次加载'))
163
+ this.name = 'L4PiPluginLoadTimeoutError'
164
+ }
165
+ }
166
+
167
+ interface L4PiPluginRecovery {
168
+ installedPath: string
169
+ settled: Promise<void>
170
+ controller: AbortController
171
+ }
172
+
156
173
  interface L4PiPluginOwner {
157
174
  source: string
158
175
  packageName: string | null
@@ -163,6 +180,7 @@ interface L4PiPluginOwner {
163
180
  pluginNames: Set<string>
164
181
  activeCalls: Set<AbortController>
165
182
  subscriptions: Set<PluginDisposer>
183
+ pendingCleanup: Set<Promise<void>>
166
184
  beforeReload: PiDeskPluginLifecycle['beforeReload'] | null
167
185
  disposer: PluginDisposer | null
168
186
  }
@@ -269,11 +287,15 @@ async function runLifecycleWithTimeout(input: {
269
287
  }): Promise<void> {
270
288
  let timer: ReturnType<typeof setTimeout> | null = null
271
289
  const timeoutMs = L4_PLUGIN_LIFECYCLE_TIMEOUT_MS
290
+ const execution = Promise.resolve().then(input.execute)
291
+ input.owner.pendingCleanup.add(execution)
292
+ const release = (): void => {
293
+ input.owner.pendingCleanup.delete(execution)
294
+ }
295
+ void execution.then(release, release)
272
296
  try {
273
297
  const completed = await Promise.race([
274
- Promise.resolve()
275
- .then(input.execute)
276
- .then(() => true),
298
+ execution.then(() => true),
277
299
  new Promise<false>((resolveTimeout) => {
278
300
  timer = setTimeout(() => resolveTimeout(false), timeoutMs)
279
301
  })
@@ -307,6 +329,7 @@ export class L4PiPluginOwnerRuntime {
307
329
  private readonly maintainingSources = new Set<string>()
308
330
  private readonly lifetime = new AbortController()
309
331
  private readonly packageDiagnostics = new Map<string, L4PiPluginPackageDiagnostic>()
332
+ private readonly recoveries = new Map<string, L4PiPluginRecovery>()
310
333
  private initializePromise: Promise<void> | null = null
311
334
  private initialized = false
312
335
  private disposed = false
@@ -386,6 +409,7 @@ export class L4PiPluginOwnerRuntime {
386
409
  ) {
387
410
  throw new L4PiPluginRuntimeBusyError()
388
411
  }
412
+ this.cancelRecovery(source)
389
413
  this.maintainingSources.add(source)
390
414
  try {
391
415
  while (this.owners.some((owner) => owner.source === source && owner.activeCalls.size > 0)) {
@@ -404,6 +428,7 @@ export class L4PiPluginOwnerRuntime {
404
428
  if (this.disposed || !this.acceptingCalls || this.changingSources.has(source)) {
405
429
  throw new L4PiPluginRuntimeBusyError()
406
430
  }
431
+ this.cancelRecovery(source)
407
432
  const owner = this.owners.find((item) => item.source === source)
408
433
 
409
434
  this.changingSources.add(source)
@@ -487,6 +512,7 @@ export class L4PiPluginOwnerRuntime {
487
512
  }
488
513
 
489
514
  this.acceptingCalls = false
515
+ for (const source of this.recoveries.keys()) this.cancelRecovery(source)
490
516
  for (const owner of this.owners) owner.draining = true
491
517
  await Promise.all(
492
518
  [...this.owners].reverse().map(async (owner) => {
@@ -648,6 +674,7 @@ export class L4PiPluginOwnerRuntime {
648
674
  if (this.disposed) return
649
675
  this.disposed = true
650
676
  this.lifetime.abort()
677
+ for (const source of this.recoveries.keys()) this.cancelRecovery(source)
651
678
  this.acceptingCalls = false
652
679
  this.changeListeners.clear()
653
680
  this.workSessionProvider = null
@@ -671,7 +698,56 @@ export class L4PiPluginOwnerRuntime {
671
698
  this.packageDiagnostics.clear()
672
699
  for (const item of await this.packageProvider()) {
673
700
  if (this.disposed) return
674
- await this.loadPackage(item.source, item.installedPath)
701
+ const settled = await this.loadPackage(item.source, item.installedPath)
702
+ if (settled && !this.disposed) {
703
+ this.recoveries.set(item.source, {
704
+ installedPath: item.installedPath,
705
+ settled: settled.execution,
706
+ controller: new AbortController()
707
+ })
708
+ }
709
+ }
710
+ for (const [source, recovery] of this.recoveries) {
711
+ void this.recoverSource(source, recovery)
712
+ }
713
+ }
714
+
715
+ private cancelRecovery(source: string): void {
716
+ const recovery = this.recoveries.get(source)
717
+ this.recoveries.delete(source)
718
+ recovery?.controller.abort()
719
+ }
720
+
721
+ private async recoverSource(source: string, recovery: L4PiPluginRecovery): Promise<void> {
722
+ const signal = recovery.controller.signal
723
+ let onAbort!: () => void
724
+ const canceled = new Promise<never>((_, reject) => {
725
+ onAbort = (): void => reject(signal.reason)
726
+ signal.addEventListener('abort', onAbort, { once: true })
727
+ })
728
+ try {
729
+ // 超时只结束等待;旧 setup 和清理真正结束后才允许自动创建新实例。
730
+ await Promise.race([recovery.settled, canceled])
731
+ signal.throwIfAborted()
732
+ console.info('[Pi Desk][GlobalPluginRuntime] 插件加载超时,稍后自动重试一次', {
733
+ source,
734
+ delayMs: L4_PLUGIN_RECOVERY_DELAY_MS
735
+ })
736
+ await delay(L4_PLUGIN_RECOVERY_DELAY_MS, undefined, { signal })
737
+ signal.throwIfAborted()
738
+ this.recoveries.delete(source)
739
+ await this.replaceSource(source, recovery.installedPath)
740
+ console.info('[Pi Desk][GlobalPluginRuntime] 插件自动重试成功', { source })
741
+ } catch (error) {
742
+ if (!signal.aborted) {
743
+ console.warn('[Pi Desk][GlobalPluginRuntime] 插件自动重试失败,不再自动加载', {
744
+ source,
745
+ message: errorMessage(error)
746
+ })
747
+ }
748
+ } finally {
749
+ signal.removeEventListener('abort', onAbort)
750
+ if (this.recoveries.get(source) === recovery) this.recoveries.delete(source)
675
751
  }
676
752
  }
677
753
 
@@ -681,9 +757,28 @@ export class L4PiPluginOwnerRuntime {
681
757
  globalInput: unknown
682
758
  ): Promise<void> {
683
759
  let timer: ReturnType<typeof setTimeout> | undefined
760
+ let phase: L4PiPluginDiagnosticPhase = 'entry'
761
+ let phaseStartedAt = performance.now()
762
+ const logPhase = (): void => {
763
+ const durationMs = Math.round(performance.now() - phaseStartedAt)
764
+ if (durationMs < 1_000) return
765
+ console.info('[Pi Desk][GlobalPluginRuntime] 插件加载阶段耗时', {
766
+ source: owner.source,
767
+ packageName: owner.packageName,
768
+ phase,
769
+ durationMs
770
+ })
771
+ }
772
+ const enterPhase = (next: L4PiPluginDiagnosticPhase): void => {
773
+ logPhase()
774
+ phase = next
775
+ phaseStartedAt = performance.now()
776
+ }
684
777
  const execution = (async (): Promise<void> => {
685
- if (entryInput !== undefined) await this.loadEntryOwner(owner, entryInput)
686
- else await this.loadLegacyOwner(owner, globalInput)
778
+ const onSetup = (): void => enterPhase('setup')
779
+ if (entryInput !== undefined) await this.loadEntryOwner(owner, entryInput, onSetup)
780
+ else await this.loadLegacyOwner(owner, globalInput, onSetup)
781
+ enterPhase('resources')
687
782
  try {
688
783
  await this.validateOwnerResources(owner)
689
784
  } catch (error) {
@@ -696,16 +791,20 @@ export class L4PiPluginOwnerRuntime {
696
791
  execution,
697
792
  new Promise<never>((_, reject) => {
698
793
  timer = setTimeout(() => {
699
- reject(new L4PiPluginPhaseError('setup', new Error('插件初始化超时,已放弃加载')))
794
+ reject(new L4PiPluginLoadTimeoutError(phase, execution))
700
795
  }, L4_PLUGIN_LOAD_TIMEOUT_MS)
701
796
  })
702
797
  ])
703
798
  } finally {
704
799
  if (timer) clearTimeout(timer)
800
+ logPhase()
705
801
  }
706
802
  }
707
803
 
708
- private async loadPackage(source: string, installedPath: string): Promise<void> {
804
+ private async loadPackage(
805
+ source: string,
806
+ installedPath: string
807
+ ): Promise<{ execution: Promise<void> } | null> {
709
808
  const startedAt = performance.now()
710
809
  const packageRoot = resolve(installedPath)
711
810
  let manifest: L4PiDeskPackageManifest
@@ -721,13 +820,13 @@ export class L4PiPluginOwnerRuntime {
721
820
  status: 'failed',
722
821
  error: { phase: 'package', message: errorMessage(error) }
723
822
  })
724
- return
823
+ return null
725
824
  }
726
825
 
727
826
  const entryInput = manifest.piDesk?.entry
728
827
  const globalInput = manifest.piDesk?.global
729
- if (entryInput === undefined && globalInput === undefined) return
730
- if (this.disposed) return
828
+ if (entryInput === undefined && globalInput === undefined) return null
829
+ if (this.disposed) return null
731
830
  const owner: L4PiPluginOwner = {
732
831
  source,
733
832
  packageName: typeof manifest.name === 'string' ? manifest.name : null,
@@ -738,6 +837,7 @@ export class L4PiPluginOwnerRuntime {
738
837
  pluginNames: new Set(),
739
838
  activeCalls: new Set(),
740
839
  subscriptions: new Set(),
840
+ pendingCleanup: new Set(),
741
841
  beforeReload: null,
742
842
  disposer: null
743
843
  }
@@ -783,12 +883,26 @@ export class L4PiPluginOwnerRuntime {
783
883
  message,
784
884
  durationMs: Math.round(performance.now() - startedAt)
785
885
  })
886
+ if (error instanceof L4PiPluginLoadTimeoutError) {
887
+ return {
888
+ execution: error.execution
889
+ .catch(() => {})
890
+ .then(async () => {
891
+ await Promise.allSettled([...owner.pendingCleanup])
892
+ })
893
+ }
894
+ }
786
895
  } finally {
787
896
  this.loadingOwners.delete(owner)
788
897
  }
898
+ return null
789
899
  }
790
900
 
791
- private async loadEntryOwner(owner: L4PiPluginOwner, entryInput: unknown): Promise<void> {
901
+ private async loadEntryOwner(
902
+ owner: L4PiPluginOwner,
903
+ entryInput: unknown,
904
+ onSetup: () => void
905
+ ): Promise<void> {
792
906
  const entryPath = packageEntryPath(owner.packageRoot, entryInput, 'piDesk.entry')
793
907
  const entryStat = await stat(entryPath)
794
908
  if (!entryStat.isFile()) throw new Error('Pi Desk Plugin entry is not a file')
@@ -806,6 +920,7 @@ export class L4PiPluginOwnerRuntime {
806
920
 
807
921
  const pluginName = parseL4PiPluginName(definition.name)
808
922
  this.claimPluginName(owner, pluginName)
923
+ onSetup()
809
924
  try {
810
925
  const lifecycle = normalizeSetupResult(
811
926
  await definition.setup(this.createPiDeskFacade(owner, pluginName, owner.packageRoot))
@@ -821,7 +936,11 @@ export class L4PiPluginOwnerRuntime {
821
936
  }
822
937
  }
823
938
 
824
- private async loadLegacyOwner(owner: L4PiPluginOwner, entryInput: unknown): Promise<void> {
939
+ private async loadLegacyOwner(
940
+ owner: L4PiPluginOwner,
941
+ entryInput: unknown,
942
+ onSetup: () => void
943
+ ): Promise<void> {
825
944
  const entryPath = packageEntryPath(owner.packageRoot, entryInput, 'piDesk.global')
826
945
  const entryStat = await stat(entryPath)
827
946
  if (!entryStat.isFile()) throw new Error('Global Plugin entry is not a file')
@@ -832,6 +951,7 @@ export class L4PiPluginOwnerRuntime {
832
951
  }
833
952
 
834
953
  const factory = loaded.default as GlobalPluginFactory
954
+ onSetup()
835
955
  try {
836
956
  const disposer = await factory(this.createLegacyApi(owner))
837
957
  if (disposer !== undefined && typeof disposer !== 'function') {
@@ -9,6 +9,8 @@ import {
9
9
  type ExtensionHandler,
10
10
  type InputEvent,
11
11
  type InputEventResult,
12
+ type MessageEndEvent,
13
+ type MessageEndEventResult,
12
14
  type SessionManager,
13
15
  type SessionBeforeTreeEvent
14
16
  } from '@earendil-works/pi-coding-agent'
@@ -40,6 +42,7 @@ interface InitializationOptions {
40
42
  resources: L4PiSessionResources
41
43
  readCapabilities: () => CapabilityMode | undefined
42
44
  onInput: ExtensionHandler<InputEvent, InputEventResult>
45
+ onMessageEnd: (event: MessageEndEvent) => MessageEndEventResult | undefined
43
46
  onTree?: () => void
44
47
  onCommand?: (args: string[], toolCallId: string) => Promise<L4PiDeskCommandResult>
45
48
  pluginEvents: ConstructorParameters<typeof L4PiSessionPluginRuntime>[0]
@@ -296,6 +299,12 @@ export class L4PiSessionInitialization {
296
299
  this.plugins.reset()
297
300
  }
298
301
  )
302
+ extension.handlers.set('message_end', [
303
+ async (event) => {
304
+ this.assertActive()
305
+ return this.options.onMessageEnd(event as MessageEndEvent)
306
+ }
307
+ ])
299
308
  extension.handlers.set('session_tree', [async () => this.options.onTree?.()])
300
309
  extension.handlers.set('session_before_tree', [
301
310
  async (event) => {
@@ -1 +1 @@
1
- RLKcK-RQF5FvEeDmS9uih
1
+ 8Yd33M0zPi4tbRn4xCaRY
@@ -70,6 +70,7 @@
70
70
  "/api/work-sessions/replace/route": "/api/work-sessions/replace",
71
71
  "/api/work-sessions/sort/route": "/api/work-sessions/sort",
72
72
  "/favicon.ico/route": "/favicon.ico",
73
- "/page": "/",
74
- "/login/page": "/login"
73
+ "/project-preview/page": "/project-preview",
74
+ "/login/page": "/login",
75
+ "/page": "/"
75
76
  }
@@ -4,11 +4,11 @@
4
4
  ],
5
5
  "devFiles": [],
6
6
  "lowPriorityFiles": [
7
- "static/RLKcK-RQF5FvEeDmS9uih/_buildManifest.js",
8
- "static/RLKcK-RQF5FvEeDmS9uih/_ssgManifest.js"
7
+ "static/8Yd33M0zPi4tbRn4xCaRY/_buildManifest.js",
8
+ "static/8Yd33M0zPi4tbRn4xCaRY/_ssgManifest.js"
9
9
  ],
10
10
  "rootMainFiles": [
11
- "static/chunks/webpack-d7248206b7fc7ba8.js",
11
+ "static/chunks/webpack-b60891a214e0925d.js",
12
12
  "static/chunks/e388c842-1ca92f1ea0dfe8ee.js",
13
13
  "static/chunks/64444-3e955f910569274d.js",
14
14
  "static/chunks/main-app-6230945a5a062fce.js"
@@ -65,8 +65,8 @@
65
65
  "dynamicRoutes": {},
66
66
  "notFoundRoutes": [],
67
67
  "preview": {
68
- "previewModeId": "724e14edd9a9e87d1681837f5b2774b6",
69
- "previewModeSigningKey": "f8047a56e43fd978b908fab8a310ae5a3a16442a0f73b160f9cc6bb676bbcac2",
70
- "previewModeEncryptionKey": "7923a8b78086226553ae708e242b307cb0ab5c60303f824f230f133813e9a2f8"
68
+ "previewModeId": "a562a6265bc1e086a56118191b369e7d",
69
+ "previewModeSigningKey": "a94b895c5b4ccc11f06e1814f3744b9654758dcf9329c29fc03b788ed85ecf27",
70
+ "previewModeEncryptionKey": "0d8101837a8bf01364db3fd1ead91c7964032274ab5ac99e0a864a49637f14f9"
71
71
  }
72
72
  }
@@ -2610,8 +2610,7 @@
2610
2610
  "client/l2_biz/workbench/file-workspace/l2-work-session-file-layout.tsx -> ./l2-file-workspace-view": {
2611
2611
  "id": 19991,
2612
2612
  "files": [
2613
- "static/chunks/31087-d8e5765d541d5c26.js",
2614
- "static/chunks/70669.07f3670bfb7d6dd2.js"
2613
+ "static/chunks/19991.8aa2e4b6df15fad6.js"
2615
2614
  ]
2616
2615
  },
2617
2616
  "client/l4_foundation/terminal/l4-terminal-renderer.ts -> @xterm/addon-fit": {
@@ -2,12 +2,12 @@ self.__SERVER_FILES_MANIFEST={
2
2
  "version": 1,
3
3
  "config": {
4
4
  "env": {
5
- "PI_DESK_APP_VERSION": "1.1.20-dev",
6
- "PI_DESK_VERSION_UPDATED_AT": "2026-10-25T17:12:44+08:00"
5
+ "PI_DESK_APP_VERSION": "1.1.21",
6
+ "PI_DESK_VERSION_UPDATED_AT": "2026-10-27T01:49:11+08:00"
7
7
  },
8
8
  "webpack": null,
9
9
  "typescript": {
10
- "ignoreBuildErrors": true
10
+ "ignoreBuildErrors": false
11
11
  },
12
12
  "typedRoutes": false,
13
13
  "distDir": "temp/build/pi-desk/release/.next",
@@ -2,12 +2,12 @@
2
2
  "version": 1,
3
3
  "config": {
4
4
  "env": {
5
- "PI_DESK_APP_VERSION": "1.1.20-dev",
6
- "PI_DESK_VERSION_UPDATED_AT": "2026-10-25T17:12:44+08:00"
5
+ "PI_DESK_APP_VERSION": "1.1.21",
6
+ "PI_DESK_VERSION_UPDATED_AT": "2026-10-27T01:49:11+08:00"
7
7
  },
8
8
  "webpack": null,
9
9
  "typescript": {
10
- "ignoreBuildErrors": true
10
+ "ignoreBuildErrors": false
11
11
  },
12
12
  "typedRoutes": false,
13
13
  "distDir": "temp/build/pi-desk/release/.next",
@@ -467,6 +467,12 @@
467
467
  "regex": "^/login(?:/)?$",
468
468
  "routeKeys": {},
469
469
  "namedRegex": "^/login(?:/)?$"
470
+ },
471
+ {
472
+ "page": "/project-preview",
473
+ "regex": "^/project\\-preview(?:/)?$",
474
+ "routeKeys": {},
475
+ "namedRegex": "^/project\\-preview(?:/)?$"
470
476
  }
471
477
  ],
472
478
  "dataRoutes": [],