@jetcrab/pi-desk 1.1.13-dev → 1.1.16-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 (268) hide show
  1. package/package.json +2 -2
  2. package/src/app/api/model-settings/preview/route.ts +1 -0
  3. package/src/client/l2_biz/settings/hooks/l2-use-model-provider-settings.ts +54 -10
  4. package/src/client/l2_biz/settings/hooks/l2-use-model-settings.ts +37 -9
  5. package/src/client/l2_biz/settings/l2-model-settings-biz.ts +26 -12
  6. package/src/client/l2_biz/settings/views/l2-model-provider-settings.tsx +3 -2
  7. package/src/client/l2_biz/settings/views/l2-model-replacement-preview.tsx +34 -0
  8. package/src/client/l4_foundation/ui/l4-confirm-dialog.tsx +9 -1
  9. package/src/common/l2_biz/model-settings/l2-model-settings-contract.ts +23 -1
  10. package/src/common/l2_biz/settings/l2-settings-messages.ts +8 -0
  11. package/src/server/l1_entry/api/l1-model-settings-preview-route.ts +32 -0
  12. package/src/server/l2_biz/model-settings/l2-model-settings.ts +9 -0
  13. package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +159 -52
  14. package/src/server/l4_foundation/model-settings/l4-model-settings-store.ts +161 -38
  15. package/src/server/l4_foundation/model-settings/l4-model-settings-types.ts +11 -0
  16. package/src/server/l4_foundation/model-settings/l4-model-settings.ts +1 -0
  17. package/src/server/l4_foundation/pi/l4-pi-package-maintenance-runner.mts +56 -65
  18. package/src/server/l4_foundation/pi/l4-pi-package-maintenance.ts +2 -0
  19. package/src/server/l4_foundation/pi/l4-pi-package-reinstall-runner.mts +49 -0
  20. package/src/server/l4_foundation/pi/l4-pi-package-root-consistency.ts +6 -2
  21. package/src/server/l4_foundation/pi/l4-pi-plugin-probe.ts +3 -1
  22. package/src/server/l4_foundation/pi/l4-pi-runner-process.ts +9 -0
  23. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  24. package/temp/build/pi-desk/release/.next/app-path-routes-manifest.json +1 -0
  25. package/temp/build/pi-desk/release/.next/build-manifest.json +2 -2
  26. package/temp/build/pi-desk/release/.next/required-server-files.js +2 -2
  27. package/temp/build/pi-desk/release/.next/required-server-files.json +2 -2
  28. package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
  29. package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
  30. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  31. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  32. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +1 -1
  33. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  34. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  35. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
  37. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  38. package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +1 -1
  39. package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
  40. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
  41. package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
  42. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
  43. package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
  44. package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
  45. package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
  46. package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
  47. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  48. package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
  49. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
  50. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
  51. package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
  52. package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
  53. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
  54. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -0
  55. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route_client-reference-manifest.js +1 -0
  56. package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
  57. package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
  58. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
  59. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
  60. package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
  61. package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
  62. package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
  63. package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
  64. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
  65. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
  66. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
  67. package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
  68. package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
  69. package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
  70. package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
  71. package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
  72. package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
  73. package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
  74. package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
  75. package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
  76. package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
  77. package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
  78. package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
  79. package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +1 -1
  80. package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +1 -1
  81. package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
  82. package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +1 -1
  83. package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +1 -1
  84. package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +1 -1
  85. package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +1 -1
  86. package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +1 -1
  87. package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +1 -1
  88. package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +1 -1
  89. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
  90. package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
  91. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
  92. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
  93. package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
  94. package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
  95. package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
  96. package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
  97. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
  98. package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
  99. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
  100. package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
  101. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
  102. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
  103. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
  104. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
  105. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
  106. package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/sort/route.js +1 -1
  107. package/temp/build/pi-desk/release/.next/server/app/favicon.ico/route.js +1 -1
  108. package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
  109. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  110. package/temp/build/pi-desk/release/.next/server/app/page.js +16 -16
  111. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  112. package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +1 -0
  113. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +1 -1
  114. package/temp/build/pi-desk/release/.next/server/chunks/50520.js +5 -5
  115. package/temp/build/pi-desk/release/.next/server/chunks/78262.js +3 -3
  116. package/temp/build/pi-desk/release/.next/server/chunks/82499.js +4 -4
  117. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  118. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  119. package/temp/build/pi-desk/release/.next/static/chunks/app/{api/auth-settings/get/route-5137efe26947191b.js → _global-error/page-9969fe0baa238786.js} +1 -1
  120. package/temp/build/pi-desk/release/.next/static/chunks/app/{api/auth-settings/replace/route-5137efe26947191b.js → _not-found/page-9969fe0baa238786.js} +1 -1
  121. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth/login/route-9969fe0baa238786.js +1 -0
  122. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth/logout/route-9969fe0baa238786.js +1 -0
  123. package/temp/build/pi-desk/release/.next/static/chunks/app/{_global-error/page-5137efe26947191b.js → api/auth-settings/get/route-9969fe0baa238786.js} +1 -1
  124. package/temp/build/pi-desk/release/.next/static/chunks/app/{_not-found/page-5137efe26947191b.js → api/auth-settings/replace/route-9969fe0baa238786.js} +1 -1
  125. package/temp/build/pi-desk/release/.next/static/chunks/app/api/capability-modes/get/route-9969fe0baa238786.js +1 -0
  126. package/temp/build/pi-desk/release/.next/static/chunks/app/api/capability-modes/replace/route-9969fe0baa238786.js +1 -0
  127. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/context/get/route-9969fe0baa238786.js +1 -0
  128. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/images/get/route-9969fe0baa238786.js +1 -0
  129. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/messages/get/route-9969fe0baa238786.js +1 -0
  130. package/temp/build/pi-desk/release/.next/static/chunks/app/api/health/route-9969fe0baa238786.js +1 -0
  131. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-check/get/route-9969fe0baa238786.js +1 -0
  132. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/del/route-9969fe0baa238786.js +1 -0
  133. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/get/route-9969fe0baa238786.js +1 -0
  134. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/replace/route-9969fe0baa238786.js +1 -0
  135. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-catalog/list/route-9969fe0baa238786.js +1 -0
  136. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/get/route-9969fe0baa238786.js +1 -0
  137. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/preview/route-9969fe0baa238786.js +1 -0
  138. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/replace/route-9969fe0baa238786.js +1 -0
  139. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directories/list/route-9969fe0baa238786.js +1 -0
  140. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-entries/list/route-9969fe0baa238786.js +1 -0
  141. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-ignores/list/route-9969fe0baa238786.js +1 -0
  142. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-ignores/replace/route-9969fe0baa238786.js +1 -0
  143. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/models/list/route-9969fe0baa238786.js +1 -0
  144. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/session-user-messages/list/route-9969fe0baa238786.js +1 -0
  145. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/sessions/list/route-9969fe0baa238786.js +1 -0
  146. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/get/route-9969fe0baa238786.js +1 -0
  147. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/search/route-9969fe0baa238786.js +1 -0
  148. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/settings-get/route-9969fe0baa238786.js +1 -0
  149. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/settings-replace/route-9969fe0baa238786.js +1 -0
  150. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/add/route-9969fe0baa238786.js +1 -0
  151. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/apply/route-9969fe0baa238786.js +1 -0
  152. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/batch/route-9969fe0baa238786.js +1 -0
  153. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route-9969fe0baa238786.js +1 -0
  154. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/del/route-9969fe0baa238786.js +1 -0
  155. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/enabled-set/route-9969fe0baa238786.js +1 -0
  156. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/get/route-9969fe0baa238786.js +1 -0
  157. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route-9969fe0baa238786.js +1 -0
  158. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/list/route-9969fe0baa238786.js +1 -0
  159. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/reload/route-9969fe0baa238786.js +1 -0
  160. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/update/route-9969fe0baa238786.js +1 -0
  161. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/get/route-9969fe0baa238786.js +1 -0
  162. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/list/route-9969fe0baa238786.js +1 -0
  163. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/reveal/route-9969fe0baa238786.js +1 -0
  164. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/search/route-9969fe0baa238786.js +1 -0
  165. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git/get/route-9969fe0baa238786.js +1 -0
  166. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-branches/list/route-9969fe0baa238786.js +1 -0
  167. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-changes/list/route-9969fe0baa238786.js +1 -0
  168. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-diff/get/route-9969fe0baa238786.js +1 -0
  169. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-heads/get/route-9969fe0baa238786.js +1 -0
  170. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-log/list/route-9969fe0baa238786.js +1 -0
  171. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-model-default/get/route-9969fe0baa238786.js +1 -0
  172. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-model-default/replace/route-9969fe0baa238786.js +1 -0
  173. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/get/route-9969fe0baa238786.js +1 -0
  174. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/list/route-9969fe0baa238786.js +1 -0
  175. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/replace/route-9969fe0baa238786.js +1 -0
  176. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skills/list/route-9969fe0baa238786.js +1 -0
  177. package/temp/build/pi-desk/release/.next/static/chunks/app/api/task-center/images/get/route-9969fe0baa238786.js +1 -0
  178. package/temp/build/pi-desk/release/.next/static/chunks/app/api/task-center/messages/get/route-9969fe0baa238786.js +1 -0
  179. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-git-branch/add/route-9969fe0baa238786.js +1 -0
  180. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-git-branch/replace/route-9969fe0baa238786.js +1 -0
  181. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-tree/get/route-9969fe0baa238786.js +1 -0
  182. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-tree-entry/get/route-9969fe0baa238786.js +1 -0
  183. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/add/route-9969fe0baa238786.js +1 -0
  184. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/branch/route-9969fe0baa238786.js +1 -0
  185. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/del/route-9969fe0baa238786.js +1 -0
  186. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/rename/route-9969fe0baa238786.js +1 -0
  187. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/replace/route-9969fe0baa238786.js +1 -0
  188. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/sort/route-9969fe0baa238786.js +1 -0
  189. package/temp/build/pi-desk/release/.next/static/chunks/app/{layout-358978b7cfe69a5f.js → layout-c0e1c5cef8620cf8.js} +2 -2
  190. package/temp/build/pi-desk/release/.next/static/chunks/app/page-df6045b319f52088.js +158 -0
  191. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/app-error-9969fe0baa238786.js +1 -0
  192. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/forbidden-9969fe0baa238786.js +1 -0
  193. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/unauthorized-9969fe0baa238786.js +1 -0
  194. package/temp/build/pi-desk/release/.next/static/css/6427d02b6edf406f.css +1 -0
  195. package/temp/build/pi-desk/release/.next/static/uj2IMtGMwxhDXhTVi7sw5/_buildManifest.js +1 -0
  196. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  197. package/temp/build/pi-desk/release/.next/static/YdOQ7klRBI5tJb5NpDVLW/_buildManifest.js +0 -1
  198. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth/login/route-5137efe26947191b.js +0 -1
  199. package/temp/build/pi-desk/release/.next/static/chunks/app/api/auth/logout/route-5137efe26947191b.js +0 -1
  200. package/temp/build/pi-desk/release/.next/static/chunks/app/api/capability-modes/get/route-5137efe26947191b.js +0 -1
  201. package/temp/build/pi-desk/release/.next/static/chunks/app/api/capability-modes/replace/route-5137efe26947191b.js +0 -1
  202. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/context/get/route-5137efe26947191b.js +0 -1
  203. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/images/get/route-5137efe26947191b.js +0 -1
  204. package/temp/build/pi-desk/release/.next/static/chunks/app/api/chat/messages/get/route-5137efe26947191b.js +0 -1
  205. package/temp/build/pi-desk/release/.next/static/chunks/app/api/health/route-5137efe26947191b.js +0 -1
  206. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-check/get/route-5137efe26947191b.js +0 -1
  207. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/del/route-5137efe26947191b.js +0 -1
  208. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/get/route-5137efe26947191b.js +0 -1
  209. package/temp/build/pi-desk/release/.next/static/chunks/app/api/mcp-settings/replace/route-5137efe26947191b.js +0 -1
  210. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-catalog/list/route-5137efe26947191b.js +0 -1
  211. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/get/route-5137efe26947191b.js +0 -1
  212. package/temp/build/pi-desk/release/.next/static/chunks/app/api/model-settings/replace/route-5137efe26947191b.js +0 -1
  213. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directories/list/route-5137efe26947191b.js +0 -1
  214. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-entries/list/route-5137efe26947191b.js +0 -1
  215. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-ignores/list/route-5137efe26947191b.js +0 -1
  216. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/directory-ignores/replace/route-5137efe26947191b.js +0 -1
  217. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/models/list/route-5137efe26947191b.js +0 -1
  218. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/session-user-messages/list/route-5137efe26947191b.js +0 -1
  219. package/temp/build/pi-desk/release/.next/static/chunks/app/api/pi/sessions/list/route-5137efe26947191b.js +0 -1
  220. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/get/route-5137efe26947191b.js +0 -1
  221. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/search/route-5137efe26947191b.js +0 -1
  222. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/settings-get/route-5137efe26947191b.js +0 -1
  223. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugin-catalog/settings-replace/route-5137efe26947191b.js +0 -1
  224. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/add/route-5137efe26947191b.js +0 -1
  225. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/apply/route-5137efe26947191b.js +0 -1
  226. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/batch/route-5137efe26947191b.js +0 -1
  227. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route-5137efe26947191b.js +0 -1
  228. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/del/route-5137efe26947191b.js +0 -1
  229. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/enabled-set/route-5137efe26947191b.js +0 -1
  230. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/get/route-5137efe26947191b.js +0 -1
  231. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route-5137efe26947191b.js +0 -1
  232. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/list/route-5137efe26947191b.js +0 -1
  233. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/reload/route-5137efe26947191b.js +0 -1
  234. package/temp/build/pi-desk/release/.next/static/chunks/app/api/plugins/update/route-5137efe26947191b.js +0 -1
  235. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/get/route-5137efe26947191b.js +0 -1
  236. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/list/route-5137efe26947191b.js +0 -1
  237. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/reveal/route-5137efe26947191b.js +0 -1
  238. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-files/search/route-5137efe26947191b.js +0 -1
  239. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git/get/route-5137efe26947191b.js +0 -1
  240. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-branches/list/route-5137efe26947191b.js +0 -1
  241. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-changes/list/route-5137efe26947191b.js +0 -1
  242. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-diff/get/route-5137efe26947191b.js +0 -1
  243. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-heads/get/route-5137efe26947191b.js +0 -1
  244. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-git-log/list/route-5137efe26947191b.js +0 -1
  245. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-model-default/get/route-5137efe26947191b.js +0 -1
  246. package/temp/build/pi-desk/release/.next/static/chunks/app/api/project-model-default/replace/route-5137efe26947191b.js +0 -1
  247. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/get/route-5137efe26947191b.js +0 -1
  248. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/list/route-5137efe26947191b.js +0 -1
  249. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skill-files/replace/route-5137efe26947191b.js +0 -1
  250. package/temp/build/pi-desk/release/.next/static/chunks/app/api/skills/list/route-5137efe26947191b.js +0 -1
  251. package/temp/build/pi-desk/release/.next/static/chunks/app/api/task-center/images/get/route-5137efe26947191b.js +0 -1
  252. package/temp/build/pi-desk/release/.next/static/chunks/app/api/task-center/messages/get/route-5137efe26947191b.js +0 -1
  253. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-git-branch/add/route-5137efe26947191b.js +0 -1
  254. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-git-branch/replace/route-5137efe26947191b.js +0 -1
  255. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-tree/get/route-5137efe26947191b.js +0 -1
  256. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-session-tree-entry/get/route-5137efe26947191b.js +0 -1
  257. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/add/route-5137efe26947191b.js +0 -1
  258. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/branch/route-5137efe26947191b.js +0 -1
  259. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/del/route-5137efe26947191b.js +0 -1
  260. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/rename/route-5137efe26947191b.js +0 -1
  261. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/replace/route-5137efe26947191b.js +0 -1
  262. package/temp/build/pi-desk/release/.next/static/chunks/app/api/work-sessions/sort/route-5137efe26947191b.js +0 -1
  263. package/temp/build/pi-desk/release/.next/static/chunks/app/page-cc05e71bf51adb57.js +0 -158
  264. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/app-error-5137efe26947191b.js +0 -1
  265. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/forbidden-5137efe26947191b.js +0 -1
  266. package/temp/build/pi-desk/release/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5137efe26947191b.js +0 -1
  267. package/temp/build/pi-desk/release/.next/static/css/095a062e18f96657.css +0 -1
  268. /package/temp/build/pi-desk/release/.next/static/{YdOQ7klRBI5tJb5NpDVLW → uj2IMtGMwxhDXhTVi7sw5}/_ssgManifest.js +0 -0
@@ -97,6 +97,20 @@ interface L2PluginPackageMetadata {
97
97
 
98
98
  type L2PluginMutation = 'add' | 'update' | 'del' | 'enable' | 'disable'
99
99
 
100
+ interface L2PluginMutationInput {
101
+ mutation: L2PluginMutation
102
+ source: string
103
+ operationSource: string
104
+ reinstall: boolean
105
+ options: Omit<L2PluginManagementInstallRequest, 'source'>
106
+ }
107
+
108
+ interface L2PluginPreparedMutation {
109
+ request: L4PiDeskPackageMaintenanceRequest | null
110
+ updateTag: string | null
111
+ pluginName: string | null
112
+ }
113
+
100
114
  export interface L2PluginManagementWorkSessions {
101
115
  refreshPluginMessages: () => void
102
116
  runPluginRestart: <T>(operation: () => Promise<T>) => Promise<T>
@@ -402,6 +416,9 @@ export class L2PluginManagement {
402
416
  this.assertOpen()
403
417
  const results: L2PluginManagementBatchResponse['results'] = []
404
418
  const seen = new Set<string>()
419
+ const snapshot =
420
+ input.action === 'add' || input.action === 'update' ? await this.readSources() : null
421
+ const mutations: L2PluginMutationInput[] = []
405
422
  const items = input.action === 'add' ? input.items : input.sources.map((source) => ({ source }))
406
423
  for (const item of items) {
407
424
  try {
@@ -413,9 +430,9 @@ export class L2PluginManagement {
413
430
  seen.add(identity)
414
431
  if (input.action === 'add') {
415
432
  const { source: _source, ...options } = item
416
- await this.reinstall(source, undefined, options)
433
+ mutations.push(this.acceptMutation('add', source, snapshot!, true, undefined, options))
417
434
  } else if (input.action === 'update') {
418
- await this.update(source)
435
+ mutations.push(this.acceptMutation('update', source, snapshot!))
419
436
  } else if (input.action === 'del') {
420
437
  await this.del(source)
421
438
  } else {
@@ -426,6 +443,7 @@ export class L2PluginManagement {
426
443
  results.push({ source: item.source, error: errorMessage(error).slice(0, 4000) })
427
444
  }
428
445
  }
446
+ if (mutations.length > 0) this.enqueueMutations(mutations)
429
447
  return { snapshot: await this.readSnapshot(false), results }
430
448
  }
431
449
 
@@ -673,7 +691,13 @@ export class L2PluginManagement {
673
691
  }
674
692
 
675
693
  reload(mode?: 'normal' | 'basic'): Promise<L2PluginManagementSnapshot> {
676
- if (this.activeSource) {
694
+ if (
695
+ this.activeSource ||
696
+ [...this.operations].some(
697
+ ([source, operation]) =>
698
+ operation.phase !== 'failed' && !this.deferredMaintenance.has(source)
699
+ )
700
+ ) {
677
701
  return Promise.reject(
678
702
  new L2PluginManagementOperationConflictError('维护仍在执行或等待,请先完成当前操作')
679
703
  )
@@ -691,14 +715,34 @@ export class L2PluginManagement {
691
715
  previousSource?: string,
692
716
  options: Omit<L2PluginManagementInstallRequest, 'source'> = {}
693
717
  ): Promise<L2PluginManagementSnapshot> {
718
+ this.assertOpen()
719
+ const snapshot = await this.readSources()
720
+ const input = this.acceptMutation(
721
+ mutation,
722
+ source,
723
+ snapshot,
724
+ reinstall,
725
+ previousSource,
726
+ options
727
+ )
728
+ this.enqueueMutations([input])
729
+ return this.readSnapshot(false)
730
+ }
731
+
732
+ private acceptMutation(
733
+ mutation: L2PluginMutation,
734
+ source: string,
735
+ snapshot: L4PiPluginSourceSnapshot,
736
+ reinstall = false,
737
+ previousSource?: string,
738
+ options: Omit<L2PluginManagementInstallRequest, 'source'> = {}
739
+ ): L2PluginMutationInput {
694
740
  this.assertOpen()
695
741
  if (isL4PiDeskSafeMode()) {
696
742
  throw new L2PluginManagementOperationConflictError(
697
743
  '基础模式不执行插件维护,请通过基础聊天修复或正常启动后维护'
698
744
  )
699
745
  }
700
- const snapshot = await this.readSources()
701
- this.assertOpen()
702
746
  const existing = snapshot.sources.find(
703
747
  (item) => l2PluginSourceIdentity(item.source) === l2PluginSourceIdentity(source)
704
748
  )
@@ -733,36 +777,66 @@ export class L2PluginManagement {
733
777
  this.operations.set(operationSource, { action: mutation, phase: 'queued', message: null })
734
778
  this.updates.clear(operationSource)
735
779
  this.updates.clear(source)
736
- this.publish()
780
+ return { mutation, source, operationSource, reinstall, options }
781
+ }
782
+
783
+ private enqueueMutations(inputs: readonly L2PluginMutationInput[]): void {
737
784
  void this.runExclusive(async () => {
738
- this.activeSource = operationSource
739
- try {
740
- await this.executeMutation(mutation, source, operationSource, reinstall, options)
741
- if (!this.deferredMaintenance.has(operationSource)) this.operations.delete(operationSource)
742
- } catch (error) {
743
- this.setOperation(operationSource, 'failed', errorMessage(error))
744
- } finally {
745
- this.updates.clear(operationSource)
746
- this.updates.clear(source)
747
- this.sourceCache = null
748
- this.activeSource = null
749
- this.publish()
785
+ const startedAt = performance.now()
786
+ // 本批全部准备收尾后才写正式目录;错误按项收集,不让早退遗留预检进程。
787
+ const candidates = await Promise.all(
788
+ inputs.map(async (input) => {
789
+ try {
790
+ const prepared = await this.prepareMutation(input)
791
+ this.assertOpen()
792
+ this.setOperation(input.operationSource, 'queued', '候选检查完成,等待应用')
793
+ return { input, prepared }
794
+ } catch (error) {
795
+ this.setOperation(input.operationSource, 'failed', errorMessage(error))
796
+ return null
797
+ }
798
+ })
799
+ )
800
+ if (inputs.length > 1) {
801
+ console.info('[Pi Desk][PluginManagement] 批量并行准备收尾', {
802
+ count: inputs.length,
803
+ failedCount: candidates.filter((item) => item === null).length,
804
+ durationMs: Math.round(performance.now() - startedAt)
805
+ })
806
+ }
807
+ for (const candidate of candidates) {
808
+ if (!candidate) continue
809
+ const { input, prepared } = candidate
810
+ const { source, operationSource } = input
811
+ this.activeSource = operationSource
812
+ try {
813
+ this.assertOpen()
814
+ if (prepared) await this.executeMutation(input, prepared)
815
+ if (!this.deferredMaintenance.has(operationSource))
816
+ this.operations.delete(operationSource)
817
+ } catch (error) {
818
+ this.setOperation(operationSource, 'failed', errorMessage(error))
819
+ } finally {
820
+ this.updates.clear(operationSource)
821
+ this.updates.clear(source)
822
+ this.sourceCache = null
823
+ this.activeSource = null
824
+ this.publish()
825
+ }
750
826
  }
751
827
  }).catch((error: unknown) => {
752
- if (!this.disposed) this.setOperation(operationSource, 'failed', errorMessage(error))
828
+ if (this.disposed) return
829
+ for (const input of inputs)
830
+ this.setOperation(input.operationSource, 'failed', errorMessage(error))
753
831
  })
754
- return this.readSnapshot(false)
832
+ this.publish()
755
833
  }
756
834
 
757
- private async executeMutation(
758
- mutation: L2PluginMutation,
759
- source: string,
760
- operationSource: string,
761
- reinstall: boolean,
762
- options: Omit<L2PluginManagementInstallRequest, 'source'>
763
- ): Promise<void> {
764
- const runtime = getL4PiGlobalPluginRuntime()
765
- const waiting = (reason: string): void => this.setOperation(operationSource, 'waiting', reason)
835
+ private async prepareMutation(
836
+ input: L2PluginMutationInput
837
+ ): Promise<L2PluginPreparedMutation | null> {
838
+ const { mutation, source, operationSource, reinstall, options } = input
839
+ this.assertOpen()
766
840
  const current = this.createPackageManager()
767
841
  this.throwSettingsErrors(current.settingsManager)
768
842
  const configured = current.packageManager
@@ -771,10 +845,12 @@ export class L2PluginManagement {
771
845
  const target = configured.find((item) => item.source === operationSource)
772
846
  let request: L4PiDeskPackageMaintenanceRequest | null = null
773
847
  let updateTag: string | null = null
848
+ let pluginName: string | null = null
774
849
  if (mutation !== 'enable' && mutation !== 'disable') {
775
850
  const packageRootError = await readL4PiPackageRootConsistencyError({
776
851
  agentDir: this.agentDir,
777
- configured
852
+ configured,
853
+ repairSource: mutation === 'add' && reinstall ? operationSource : undefined
778
854
  })
779
855
  if (packageRootError) throw new L2PluginManagementPackageRootError(packageRootError)
780
856
  if (mutation !== 'add' && !target) throw new L2PluginManagementNotFoundError(source)
@@ -796,7 +872,7 @@ export class L2PluginManagement {
796
872
  const version = resolved.source.slice(resolved.source.lastIndexOf('@') + 1)
797
873
  if (!installed.version)
798
874
  throw new L2PluginManagementInvalidPackageError('无法读取插件已安装版本')
799
- if (!isL2PluginNewerVersion(version, installed.version)) return
875
+ if (!isL2PluginNewerVersion(version, installed.version)) return null
800
876
  }
801
877
  request = {
802
878
  ...(npmName && mutation !== 'del' && (reinstall || target || mutation === 'update')
@@ -805,7 +881,22 @@ export class L2PluginManagement {
805
881
  ...('registry' in resolved && resolved.registry ? { registry: resolved.registry } : {})
806
882
  }
807
883
  if (mutation !== 'del')
808
- await this.probeCandidate(resolved.source, operationSource, request.registry)
884
+ pluginName = await this.probeCandidate(resolved.source, request.registry)
885
+ }
886
+ return { request, updateTag, pluginName }
887
+ }
888
+
889
+ private async executeMutation(
890
+ input: L2PluginMutationInput,
891
+ prepared: L2PluginPreparedMutation
892
+ ): Promise<void> {
893
+ const { mutation, source, operationSource } = input
894
+ const { request, updateTag, pluginName } = prepared
895
+ const runtime = getL4PiGlobalPluginRuntime()
896
+ const waiting = (reason: string): void => this.setOperation(operationSource, 'waiting', reason)
897
+ if (request) {
898
+ // 先前项可能安装失败或注册同名插件,不能依赖并行准备时的旧结论。
899
+ this.assertPluginNameAvailable(pluginName, operationSource)
809
900
  if (updateTag) {
810
901
  setL4PiPluginUpdateTag(operationSource, updateTag, this.agentDir)
811
902
  this.updates.clear(operationSource)
@@ -820,6 +911,20 @@ export class L2PluginManagement {
820
911
  await runtime.withSourceIdle(
821
912
  operationSource,
822
913
  async () => {
914
+ if (request) {
915
+ const current = this.createPackageManager()
916
+ this.throwSettingsErrors(current.settingsManager)
917
+ const configured = current.packageManager
918
+ .listConfiguredPackages()
919
+ .filter((item) => item.scope === 'user')
920
+ const rootError = await readL4PiPackageRootConsistencyError({
921
+ agentDir: this.agentDir,
922
+ configured,
923
+ repairSource: mutation === 'add' && input.reinstall ? operationSource : undefined
924
+ })
925
+ if (rootError) throw new L2PluginManagementPackageRootError(rootError)
926
+ }
927
+ this.assertOpen()
823
928
  this.setOperation(
824
929
  operationSource,
825
930
  'applying',
@@ -997,11 +1102,7 @@ export class L2PluginManagement {
997
1102
  return writing() && this.sourceCache ? this.sourceCache : snapshot
998
1103
  }
999
1104
 
1000
- private async probeCandidate(
1001
- source: string,
1002
- activeSource: string,
1003
- registry?: string
1004
- ): Promise<void> {
1105
+ private async probeCandidate(source: string, registry?: string): Promise<string | null> {
1005
1106
  const root = join(this.stagingRoot, randomUUID())
1006
1107
  const stagingAgentDir = join(root, 'agent')
1007
1108
  const stagingCwd = join(root, 'workspace')
@@ -1031,7 +1132,8 @@ export class L2PluginManagement {
1031
1132
  await runL4PiPackageMaintenance({
1032
1133
  maintenance: { action: 'install', source, ...(registry ? { registry } : {}) },
1033
1134
  agentDir: stagingAgentDir,
1034
- cwd: stagingCwd
1135
+ cwd: stagingCwd,
1136
+ signal: this.lifetime.signal
1035
1137
  })
1036
1138
 
1037
1139
  this.assertOpen()
@@ -1047,7 +1149,12 @@ export class L2PluginManagement {
1047
1149
 
1048
1150
  await this.verifyPackageVersion(source, candidate.installedPath)
1049
1151
 
1050
- const diagnostics = await runL4PiPluginProbe(stagingCwd, stagingAgentDir, true)
1152
+ const diagnostics = await runL4PiPluginProbe(
1153
+ stagingCwd,
1154
+ stagingAgentDir,
1155
+ true,
1156
+ this.lifetime.signal
1157
+ )
1051
1158
  if (diagnostics.loadError) {
1052
1159
  throw new L2PluginManagementInvalidPackageError(diagnostics.loadError)
1053
1160
  }
@@ -1056,21 +1163,10 @@ export class L2PluginManagement {
1056
1163
  throw new L2PluginManagementInvalidPackageError(failed.error.message)
1057
1164
  }
1058
1165
 
1059
- const candidatePluginName = diagnostics.packages[0]?.pluginName
1060
- if (candidatePluginName) {
1061
- const conflict = getL4PiGlobalPluginRuntime()
1062
- .readDiagnostics()
1063
- .packages.find(
1064
- (item) => item.source !== activeSource && item.pluginName === candidatePluginName
1065
- )
1066
- if (conflict) {
1067
- throw new L2PluginManagementInvalidPackageError(
1068
- `pluginName 已由其他 Package 注册:${candidatePluginName}`
1069
- )
1070
- }
1071
- }
1166
+ const candidatePluginName = diagnostics.packages[0]?.pluginName ?? null
1072
1167
  this.assertOpen()
1073
1168
  console.info('[Pi Desk][PluginManagement] 候选插件隔离预检通过', { source })
1169
+ return candidatePluginName
1074
1170
  } catch (error) {
1075
1171
  if (error instanceof L2PluginManagementInvalidPackageError) throw error
1076
1172
  throw new L2PluginManagementInvalidPackageError(errorMessage(error))
@@ -1084,6 +1180,17 @@ export class L2PluginManagement {
1084
1180
  }
1085
1181
  }
1086
1182
 
1183
+ private assertPluginNameAvailable(pluginName: string | null, source: string): void {
1184
+ if (!pluginName) return
1185
+ const conflict = getL4PiGlobalPluginRuntime()
1186
+ .readDiagnostics()
1187
+ .packages.some((item) => item.source !== source && item.pluginName === pluginName)
1188
+ if (conflict)
1189
+ throw new L2PluginManagementInvalidPackageError(
1190
+ `pluginName 已由其他 Package 注册:${pluginName}`
1191
+ )
1192
+ }
1193
+
1087
1194
  private restartWhenQuiet(mode?: 'normal' | 'basic'): Promise<L2PluginManagementSnapshot> {
1088
1195
  return this.workSessions.runPluginRestart(async () => {
1089
1196
  const runtime = getL4PiGlobalPluginRuntime()
@@ -31,6 +31,7 @@ import type {
31
31
  L4ModelPreset,
32
32
  L4ModelProviderConfig,
33
33
  L4ModelSelection,
34
+ L4ModelSettingsPreview,
34
35
  L4ModelSettingsReplaceInput
35
36
  } from './l4-model-settings-types'
36
37
 
@@ -99,17 +100,47 @@ function readJsonRecord(path: string): JsonRecord {
99
100
  return value
100
101
  }
101
102
 
102
- function writeJsonRecordAtomic(path: string, value: JsonRecord): void {
103
+ function writeTextAtomic(path: string, content: string): void {
103
104
  mkdirSync(dirname(path), { recursive: true })
104
105
  const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`
105
106
  try {
106
- writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
107
+ writeFileSync(temporaryPath, content, 'utf8')
107
108
  renameSync(temporaryPath, path)
108
109
  } finally {
109
110
  if (existsSync(temporaryPath)) unlinkSync(temporaryPath)
110
111
  }
111
112
  }
112
113
 
114
+ function writeJsonRecordAtomic(path: string, value: JsonRecord): void {
115
+ writeTextAtomic(path, `${JSON.stringify(value, null, 2)}\n`)
116
+ }
117
+
118
+ function writeRecordsWithRollback(files: Array<{ path: string; value: JsonRecord }>): void {
119
+ const originals = files.map(({ path }) => (existsSync(path) ? readFileSync(path, 'utf8') : null))
120
+ let written = 0
121
+ try {
122
+ for (const { path, value } of files) {
123
+ writeJsonRecordAtomic(path, value)
124
+ written += 1
125
+ }
126
+ } catch (cause) {
127
+ for (let index = written - 1; index >= 0; index -= 1) {
128
+ const path = files[index].path
129
+ const content = originals[index]
130
+ try {
131
+ if (content === null) unlinkSync(path)
132
+ else writeTextAtomic(path, content)
133
+ } catch (error) {
134
+ console.error('[Pi Desk][ModelSettings] 配置文件回滚失败', {
135
+ path,
136
+ message: error instanceof Error ? error.message : String(error)
137
+ })
138
+ }
139
+ }
140
+ throw cause
141
+ }
142
+ }
143
+
113
144
  function serializeWrite<T>(task: () => Promise<T>): Promise<T> {
114
145
  const result = writeQueue.then(task, task)
115
146
  writeQueue = result.then(
@@ -592,10 +623,41 @@ async function assertConnectionCredentials(
592
623
  }
593
624
  }
594
625
 
595
- async function assertRemovalReferences(current: JsonRecord, candidate: JsonRecord): Promise<void> {
626
+ async function prepareProjectDefaultChanges(
627
+ current: JsonRecord,
628
+ candidate: JsonRecord,
629
+ input: L4ModelSettingsReplaceInput,
630
+ runtime: ModelRuntime,
631
+ models: L4ModelOption[]
632
+ ): Promise<{
633
+ projectDefaults: L4ModelSettingsPreview['projectDefaults']
634
+ files: Array<{ path: string; value: JsonRecord }>
635
+ }> {
636
+ const previousKeys = collectedModelKeys(current)
596
637
  const nextKeys = collectedModelKeys(candidate)
597
- const removed = new Set([...collectedModelKeys(current)].filter((key) => !nextKeys.has(key)))
598
- if (!removed.size) return
638
+ const removed = new Set([...previousKeys].filter((key) => !nextKeys.has(key)))
639
+ const replacements = new Map<string, L4ModelOption>()
640
+ for (const { from, to } of input.modelReplacements ?? []) {
641
+ const fromKey = collectedModelKey(from.provider, from.modelId)
642
+ const toKey = collectedModelKey(to.provider, to.modelId)
643
+ if (fromKey === toKey) throw new Error('模型替换来源和目标不能相同')
644
+ if (replacements.has(fromKey)) throw new Error('模型替换来源不能重复')
645
+ if (!previousKeys.has(fromKey) || !removed.has(fromKey)) {
646
+ throw new Error(`替换来源 ${from.provider}/${from.modelId} 必须是本次移除的已收录模型`)
647
+ }
648
+ const target = models.find(
649
+ (model) => model.provider === to.provider && model.modelId === to.modelId
650
+ )
651
+ if (!nextKeys.has(toKey) || !target) {
652
+ throw new Error(`替换目标 ${to.provider}/${to.modelId} 未收录或不可用`)
653
+ }
654
+ replacements.set(fromKey, target)
655
+ }
656
+ const result: {
657
+ projectDefaults: L4ModelSettingsPreview['projectDefaults']
658
+ files: Array<{ path: string; value: JsonRecord }>
659
+ } = { projectDefaults: [], files: [] }
660
+ if (!removed.size) return result
599
661
  for (const preset of normalizePresets(candidate)) {
600
662
  if (removed.has(collectedModelKey(preset.provider, preset.modelId))) {
601
663
  throw new Error(
@@ -606,17 +668,60 @@ async function assertRemovalReferences(current: JsonRecord, candidate: JsonRecor
606
668
  // 仅在移除模型时检查已知项目,不扫描文件系统寻找任意项目配置。
607
669
  const projects = await listL4PiDirectorySummaries(true)
608
670
  for (const { cwd } of projects) {
609
- const settings = readJsonRecord(join(cwd, CONFIG_DIR_NAME, 'settings.json'))
671
+ const path = join(cwd, CONFIG_DIR_NAME, 'settings.json')
672
+ const settings = readJsonRecord(path)
610
673
  if (
611
- typeof settings.defaultProvider === 'string' &&
612
- typeof settings.defaultModel === 'string' &&
613
- removed.has(collectedModelKey(settings.defaultProvider, settings.defaultModel))
674
+ !['defaultProvider', 'defaultModel', 'defaultThinkingLevel'].some((key) =>
675
+ Object.hasOwn(settings, key)
676
+ )
614
677
  ) {
678
+ continue
679
+ }
680
+ const merged = SettingsManager.create(cwd, getAgentDir())
681
+ const provider = merged.getDefaultProvider()
682
+ const modelId = merged.getDefaultModel()
683
+ if (!provider || !modelId || !removed.has(collectedModelKey(provider, modelId))) continue
684
+ const target = replacements.get(collectedModelKey(provider, modelId))
685
+ if (!target) {
615
686
  throw new Error(
616
- `项目“${cwd}”仍将 ${settings.defaultProvider}/${settings.defaultModel} 设为默认模型,请先在“项目默认”中调整`
687
+ `项目“${cwd}”仍将 ${provider}/${modelId} 设为默认模型,请先在“项目默认”中调整`
617
688
  )
618
689
  }
690
+ const projectModels = await availableModelOptions(runtime, cwd, candidate)
691
+ if (
692
+ !projectModels.some(
693
+ (model) => model.provider === target.provider && model.modelId === target.modelId
694
+ )
695
+ ) {
696
+ throw new Error(`替换目标 ${target.provider}/${target.modelId} 在项目“${cwd}”中不可用`)
697
+ }
698
+ const from: L4ModelSelection = {
699
+ provider,
700
+ modelId,
701
+ thinkingLevel: merged.getDefaultThinkingLevel() ?? 'off'
702
+ }
703
+ const thinkingLevel = target.thinkingLevels.includes(from.thinkingLevel)
704
+ ? from.thinkingLevel
705
+ : target.thinkingLevels[0]
706
+ if (!thinkingLevel)
707
+ throw new Error(`替换目标 ${target.provider}/${target.modelId} 没有可用思考等级`)
708
+ const to: L4ModelSelection = {
709
+ provider: target.provider,
710
+ modelId: target.modelId,
711
+ thinkingLevel
712
+ }
713
+ result.projectDefaults.push({ cwd, from, to })
714
+ result.files.push({
715
+ path,
716
+ value: {
717
+ ...settings,
718
+ defaultProvider: to.provider,
719
+ defaultModel: to.modelId,
720
+ defaultThinkingLevel: to.thinkingLevel
721
+ }
722
+ })
619
723
  }
724
+ return result
620
725
  }
621
726
 
622
727
  export async function readL4ModelSettings(): Promise<{
@@ -654,37 +759,55 @@ export async function readL4ModelSettings(): Promise<{
654
759
  return result
655
760
  }
656
761
 
762
+ async function prepareModelSettings(input: L4ModelSettingsReplaceInput): Promise<{
763
+ projectDefaults: L4ModelSettingsPreview['projectDefaults']
764
+ files: Array<{ path: string; value: JsonRecord }>
765
+ }> {
766
+ const path = modelsPath()
767
+ let current: JsonRecord
768
+ try {
769
+ current = readJsonRecord(path)
770
+ } catch (error) {
771
+ if (input.nativeConfig === undefined) throw error
772
+ // 原生编辑是损坏配置的修复入口,不能要求旧文件先成功解析。
773
+ current = {}
774
+ }
775
+ let candidate: JsonRecord
776
+ if (input.nativeConfig !== undefined) {
777
+ candidate = input.nativeConfig
778
+ } else {
779
+ const currentRuntime = await ModelRuntime.create({ allowModelNetwork: false })
780
+ const error = currentRuntime.getError()
781
+ if (error) throw new Error(`当前模型配置不可用,请先通过原生配置修正:${error}`)
782
+ const defaults =
783
+ input.accountModels === undefined
784
+ ? currentRuntime
785
+ : await ModelRuntime.create({ modelsPath: null, allowModelNetwork: false })
786
+ candidate = mergeModelsRoot(current, input, currentRuntime, defaults)
787
+ }
788
+ const runtime = await validateModelsRoot(candidate)
789
+ const models = await availableModelOptions(runtime, undefined, candidate)
790
+ await assertConnectionCredentials(current, candidate, runtime)
791
+ assertPresetsValid(normalizePresets(candidate), models, normalizePresets(current))
792
+ const result = await prepareProjectDefaultChanges(current, candidate, input, runtime, models)
793
+ result.files.push({ path, value: candidate })
794
+ return result
795
+ }
796
+
797
+ export async function previewL4ModelSettings(
798
+ input: L4ModelSettingsReplaceInput
799
+ ): Promise<L4ModelSettingsPreview> {
800
+ return serializeWrite(async () => {
801
+ const { projectDefaults } = await prepareModelSettings(input)
802
+ return { projectDefaults }
803
+ })
804
+ }
805
+
657
806
  export async function replaceL4ModelSettings(input: L4ModelSettingsReplaceInput): Promise<void> {
658
807
  await serializeWrite(async () => {
659
- const path = modelsPath()
660
808
  try {
661
- let current: JsonRecord
662
- try {
663
- current = readJsonRecord(path)
664
- } catch (error) {
665
- if (input.nativeConfig === undefined) throw error
666
- // 原生编辑是损坏配置的修复入口,不能要求旧文件先成功解析。
667
- current = {}
668
- }
669
- let candidate: JsonRecord
670
- if (input.nativeConfig !== undefined) {
671
- candidate = input.nativeConfig
672
- } else {
673
- const currentRuntime = await ModelRuntime.create({ allowModelNetwork: false })
674
- const error = currentRuntime.getError()
675
- if (error) throw new Error(`当前模型配置不可用,请先通过原生配置修正:${error}`)
676
- const defaults =
677
- input.accountModels === undefined
678
- ? currentRuntime
679
- : await ModelRuntime.create({ modelsPath: null, allowModelNetwork: false })
680
- candidate = mergeModelsRoot(current, input, currentRuntime, defaults)
681
- }
682
- const runtime = await validateModelsRoot(candidate)
683
- const models = await availableModelOptions(runtime, undefined, candidate)
684
- await assertConnectionCredentials(current, candidate, runtime)
685
- await assertRemovalReferences(current, candidate)
686
- assertPresetsValid(normalizePresets(candidate), models, normalizePresets(current))
687
- writeJsonRecordAtomic(path, candidate)
809
+ const { files } = await prepareModelSettings(input)
810
+ writeRecordsWithRollback(files)
688
811
  } catch (cause) {
689
812
  console.error('[Pi Desk][ModelSettings] 模型配置保存失败', {
690
813
  message: cause instanceof Error ? cause.message : String(cause)
@@ -91,11 +91,21 @@ export interface L4ModelAccount {
91
91
  models: L4ModelConfig[]
92
92
  }
93
93
 
94
+ export interface L4ModelReplacement {
95
+ from: { provider: string; modelId: string }
96
+ to: { provider: string; modelId: string }
97
+ }
98
+
99
+ export interface L4ModelSettingsPreview {
100
+ projectDefaults: Array<{ cwd: string; from: L4ModelSelection; to: L4ModelSelection }>
101
+ }
102
+
94
103
  export type L4ModelSettingsReplaceInput =
95
104
  | {
96
105
  providers?: L4ModelProviderConfig[]
97
106
  presets?: L4ModelPreset[]
98
107
  accountModels?: L4AccountModelSelection[]
108
+ modelReplacements?: L4ModelReplacement[]
99
109
  nativeConfig?: never
100
110
  }
101
111
  | {
@@ -103,6 +113,7 @@ export type L4ModelSettingsReplaceInput =
103
113
  providers?: never
104
114
  presets?: never
105
115
  accountModels?: never
116
+ modelReplacements?: never
106
117
  }
107
118
 
108
119
  export interface L4ModelCatalogRequest {
@@ -5,6 +5,7 @@ export {
5
5
  readL4ModelSettings,
6
6
  filterL4CollectedModels,
7
7
  readL4ProjectModelDefault,
8
+ previewL4ModelSettings,
8
9
  replaceL4ModelSettings,
9
10
  replaceL4ProjectModelDefault
10
11
  } from './l4-model-settings-store'