@jetcrab/pi-desk 1.1.18-dev → 1.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/plugins.md +82 -2
- package/next.config.ts +1 -0
- package/package.json +4 -3
- package/src/app/page.tsx +6 -1
- package/src/app/project-preview/page.tsx +10 -0
- package/src/client/l1_entry/pages/l1-home-page.tsx +30 -0
- package/src/client/l1_entry/pages/l1-project-preview-page.tsx +33 -0
- package/src/client/l1_entry/providers/l1-app-providers.tsx +18 -15
- package/src/client/l1_entry/providers/l1-app-runtime-provider.tsx +2 -2
- package/src/client/l2_biz/auth/l2-login-biz.ts +3 -0
- package/src/client/l2_biz/auth/l2-login.tsx +2 -3
- package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +6 -1
- package/src/client/l2_biz/plugin-host/l2-plugin-contribution-host.tsx +71 -10
- package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +9 -0
- package/src/client/l2_biz/plugin-host/l2-plugin-session-header-layout.ts +18 -0
- package/src/client/l2_biz/plugin-host/l2-plugin-session-header.tsx +247 -0
- package/src/client/l2_biz/plugin-host/l2-plugin-widgets.tsx +50 -0
- package/src/client/l2_biz/plugin-host/views/l2-plugin-session-header-action.tsx +112 -0
- package/src/client/l2_biz/project-preview/hooks/l2-use-project-picker.ts +76 -0
- package/src/client/l2_biz/project-preview/hooks/l2-use-project-preview.ts +214 -0
- package/src/client/l2_biz/project-preview/l2-project-preview-biz.ts +49 -4
- package/src/client/l2_biz/project-preview/l2-project-preview-dialog.tsx +42 -450
- package/src/client/l2_biz/project-preview/l2-project-preview-page.tsx +61 -0
- package/src/client/l2_biz/project-preview/l2-project-preview.tsx +342 -0
- package/src/client/l2_biz/project-preview/runtime/l2-project-history-runtime.ts +9 -3
- package/src/client/l2_biz/project-preview/views/l2-project-picker.tsx +168 -0
- package/src/client/l2_biz/settings/hooks/l2-use-model-settings.ts +34 -31
- package/src/client/l2_biz/settings/views/l2-model-replacement-preview.tsx +7 -1
- package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-runtime.ts +1 -1
- package/src/client/l2_biz/workbench/file-workspace/l2-project-preview-biz.ts +4 -3
- package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-reload.ts +42 -0
- package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +28 -21
- package/src/client/l2_biz/workbench/l2-work-session-columns.module.css +32 -0
- package/src/client/l2_biz/workbench/l2-work-session-columns.tsx +55 -16
- package/src/client/l2_biz/workbench/l2-work-session-sidebar.tsx +38 -9
- package/src/client/l2_biz/workbench/l2-workbench-chat-composer.tsx +12 -1
- package/src/client/l2_biz/workbench/l2-workbench-chat.ts +45 -0
- package/src/client/l2_biz/workbench/l2-workbench-initialization-notice.tsx +8 -3
- package/src/client/l2_biz/workbench/l2-workbench-plugin-slots.ts +13 -0
- package/src/client/l2_biz/workbench/l2-workbench.tsx +19 -2
- package/src/client/l3_modules/conversation/l3-conversation-markdown.tsx +10 -1
- package/src/client/l3_modules/conversation/l3-conversation-viewport.tsx +12 -6
- package/src/client/l3_modules/project-files/l3-markdown-preview.module.css +1 -1
- package/src/client/l3_modules/project-files/l3-markdown-preview.tsx +10 -1
- package/src/client/l3_modules/project-files/l3-project-document-git.ts +4 -3
- package/src/client/l3_modules/project-files/l3-project-document-view.tsx +6 -1
- package/src/client/l3_modules/project-files/l3-project-file-browser.tsx +171 -229
- package/src/client/l3_modules/project-files/l3-project-file-runtime.ts +133 -70
- package/src/client/l3_modules/project-files/l3-project-reader.ts +26 -37
- package/src/client/l3_modules/project-files/views/l3-project-search-controls.tsx +201 -0
- package/src/client/l3_modules/project-files/views/l3-project-search-results.tsx +333 -0
- package/src/client/l4_foundation/lib/l4-client-id.tsx +11 -0
- package/src/client/l4_foundation/plugin-host/l4-plugin-host-runtime.ts +316 -35
- package/src/client/l4_foundation/ui/code/l4-code-preview.tsx +47 -3
- package/src/client/l4_foundation/ui/markdown/l4-markdown-remark.ts +89 -0
- package/src/client/l4_foundation/ui/markdown/l4-markdown.module.css +58 -0
- package/src/client/l4_foundation/ui/markdown/l4-markdown.tsx +108 -0
- package/src/common/l2_biz/auth/l2-auth-return-to.ts +41 -0
- package/src/common/l2_biz/model-settings/l2-model-settings-contract.ts +5 -1
- package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +4 -0
- package/src/common/l2_biz/project-preview/l2-project-history-messages.ts +28 -0
- package/src/common/l2_biz/project-preview/l2-project-preview-navigation.ts +5 -0
- package/src/common/l2_biz/settings/l2-settings-messages.ts +4 -3
- package/src/common/l2_biz/work-session/l2-work-session-file-contract.ts +4 -3
- package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +8 -0
- package/src/common/l3_modules/project-files/l3-project-files-contract.ts +2 -3
- package/src/common/l3_modules/project-files/l3-project-files-locale-messages.ts +56 -10
- package/src/common/l4_foundation/file/l4-project-file-contract.ts +51 -1
- package/src/common/l4_foundation/locale/l4-host-error-messages.ts +11 -0
- package/src/common/l4_foundation/locale/l4-locale-messages.ts +16 -0
- package/src/instrumentation.ts +0 -2
- package/src/server/l1_entry/api/l1-project-read-routes.ts +6 -4
- package/src/server/l1_entry/api/l1-project-route.ts +9 -8
- package/src/server/l1_entry/api/l1-work-session-file-error.ts +23 -0
- package/src/server/l1_entry/node/l1-server.ts +8 -4
- package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +0 -3
- package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +30 -58
- package/src/server/l2_biz/work-session/l2-work-session-manage.ts +0 -4
- package/src/server/l4_foundation/file/l4-project-file-index.ts +276 -0
- package/src/server/l4_foundation/file/l4-project-search.ts +399 -0
- package/src/server/l4_foundation/file/l4-ripgrep-search.ts +192 -0
- package/src/server/l4_foundation/file/l4-work-session-project-file.ts +8 -332
- package/src/server/l4_foundation/model-settings/l4-model-settings-store.ts +29 -20
- package/src/server/l4_foundation/model-settings/l4-model-settings-types.ts +1 -1
- package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +22 -5
- package/src/server/l4_foundation/pi/l4-pi-package-maintenance.ts +0 -6
- package/src/server/l4_foundation/pi/l4-pi-plugin-capability-probe.ts +1 -3
- package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +133 -13
- package/src/server/l4_foundation/pi/l4-pi-plugin-probe.ts +1 -3
- package/src/server/l4_foundation/pi/l4-pi-runner-process.ts +2 -2
- package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +9 -0
- package/src/server/l4_foundation/process/l4-process-tree.js +23 -4
- package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
- package/temp/build/pi-desk/release/.next/app-path-routes-manifest.json +3 -2
- package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -2
- package/temp/build/pi-desk/release/.next/required-server-files.js +4 -3
- package/temp/build/pi-desk/release/.next/required-server-files.json +4 -3
- package/temp/build/pi-desk/release/.next/routes-manifest.json +6 -0
- package/temp/build/pi-desk/release/.next/server/app/_global-error/page.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth/logout/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth-settings/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/capability-modes/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/chat/images/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/chat/messages/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/mcp-check/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/del/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/mcp-settings/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/model-catalog/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/model-settings/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/model-settings/preview/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/model-settings/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/directories/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-entries/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/directory-ignores/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/models/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/session-user-messages/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/pi/sessions/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/search/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugin-catalog/settings-replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/add/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/browser-resources/[resourceGroup]/[...resourcePath]/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/host-runtime/[runtimeVersion]/[...resourcePath]/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/project-model-default/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/skill-files/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/skill-files/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/skill-files/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/skills/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/task-center/images/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/task-center/messages/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/add/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-session-git-branch/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-session-tree-entry/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/add/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/branch/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/del/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/rename/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/replace/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/work-sessions/sort/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/favicon.ico/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/page.js +72 -72
- package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -0
- package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -0
- package/temp/build/pi-desk/release/.next/server/app-paths-manifest.json +3 -2
- package/temp/build/pi-desk/release/.next/server/chunks/10912.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/20460.js +15 -0
- package/temp/build/pi-desk/release/.next/server/chunks/23054.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/25406.js +10 -10
- package/temp/build/pi-desk/release/.next/server/chunks/35985.js +3 -17
- package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/48180.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/50520.js +2 -4
- package/temp/build/pi-desk/release/.next/server/chunks/56830.js +235 -0
- package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/70780.js +1 -0
- package/temp/build/pi-desk/release/.next/server/chunks/74528.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/78262.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/79686.js +62 -0
- package/temp/build/pi-desk/release/.next/server/chunks/80780.js +1 -0
- package/temp/build/pi-desk/release/.next/server/chunks/81363.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/82499.js +4 -4
- package/temp/build/pi-desk/release/.next/server/chunks/92386.js +8 -8
- package/temp/build/pi-desk/release/.next/server/instrumentation.js +1 -1
- package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
- package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
- package/temp/build/pi-desk/release/.next/static/8Yd33M0zPi4tbRn4xCaRY/_buildManifest.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/11937-1d0a9822f2c7995b.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/14825-5579aed34a631ad7.js +62 -0
- package/temp/build/pi-desk/release/.next/static/chunks/15302-9c807afc6c60de0c.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/19991.8aa2e4b6df15fad6.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/22092-6d1bfbefa825ec03.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/37313-d5970e2d0c270d22.js +238 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{49516-cc4dcd84d64ab4fe.js → 51474-8dc714cb76b2e60a.js} +2 -2
- package/temp/build/pi-desk/release/.next/static/chunks/57235-03ba1044f22b6c84.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/79267-7d21d7cdbd7cfca1.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/82371-f7ba4a07a5dbcf34.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/9038-03144ed96d613f75.js +15 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{error-0296a3b20cd1bf00.js → error-216b14b5da089ed3.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/layout-20718f1503910291.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-0d3d4827802108e5.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{not-found-2aab8b33ebbf3257.js → not-found-720cdf6a606b821a.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-6ebc1c877f6025dd.js +158 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-d70ee4e9409b970a.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/webpack-b60891a214e0925d.js +1 -0
- package/temp/build/pi-desk/release/.next/static/css/1e780a4936aa6ba3.css +1 -0
- package/temp/build/pi-desk/release/.next/static/css/6dbae2922f88ca31.css +1 -0
- package/temp/build/pi-desk/release/.next/static/css/f646ca6c07245c4c.css +1 -0
- package/temp/build/pi-desk/release/.next/trace-build +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/33658.js +0 -296
- package/temp/build/pi-desk/release/.next/server/chunks/8584.js +0 -2
- package/temp/build/pi-desk/release/.next/static/chunks/31087-ba426d09d6f68bfd.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/3814-35cfab0ce3aa7c41.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/39654-b5fedd762ea0312e.js +0 -238
- package/temp/build/pi-desk/release/.next/static/chunks/70669.07f3670bfb7d6dd2.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/74220-795a2e7185bd66cb.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/75130-d2ca74894aff8085.js +0 -13
- package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/81881-f11dbe7dc0d04ed9.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +0 -62
- package/temp/build/pi-desk/release/.next/static/chunks/app/layout-c0e1c5cef8620cf8.js +0 -3
- package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-84892b06243891f0.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-a984139e99586041.js +0 -158
- package/temp/build/pi-desk/release/.next/static/chunks/webpack-40a782fc8a3535b9.js +0 -1
- package/temp/build/pi-desk/release/.next/static/css/6427d02b6edf406f.css +0 -1
- package/temp/build/pi-desk/release/.next/static/css/79e06f05df9edff0.css +0 -1
- package/temp/build/pi-desk/release/.next/static/n2LrXQgq8UkbH_AzhR3fw/_buildManifest.js +0 -1
- /package/temp/build/pi-desk/release/.next/static/{n2LrXQgq8UkbH_AzhR3fw → 8Yd33M0zPi4tbRn4xCaRY}/_ssgManifest.js +0 -0
|
@@ -1,47 +1,31 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { spawn } from 'node:child_process'
|
|
4
4
|
import { open, readFile, readdir, realpath, stat } from 'node:fs/promises'
|
|
5
|
-
import {
|
|
6
|
-
import { promisify } from 'node:util'
|
|
5
|
+
import { dirname, isAbsolute, resolve } from 'node:path'
|
|
7
6
|
import sharp from 'sharp'
|
|
8
7
|
import {
|
|
9
8
|
L4_PROJECT_FILE_LIST_MAX_ENTRIES,
|
|
10
|
-
L4_PROJECT_FILE_SEARCH_MAX_RESULTS,
|
|
11
9
|
L4_PROJECT_IMAGE_FILE_MAX_BYTES,
|
|
12
10
|
L4_PROJECT_IMAGE_MAX_PIXELS,
|
|
13
11
|
L4_PROJECT_TEXT_FILE_MAX_BYTES,
|
|
14
12
|
type L4ProjectFileEntry,
|
|
15
13
|
type L4ProjectFileList,
|
|
16
|
-
type L4ProjectFileSearch,
|
|
17
14
|
type L4ProjectFileContent,
|
|
18
15
|
type L4ProjectFileImageMimeType,
|
|
19
16
|
type L4ProjectFileImagePreviewMode
|
|
20
17
|
} from '@common/l4_foundation/file/l4-project-file-contract'
|
|
21
|
-
import {
|
|
18
|
+
import { isL4PathInsideRoot as isInsideRoot } from './l4-file-path'
|
|
22
19
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const SEARCH_CACHE_TTL_MS = 30_000
|
|
29
|
-
const SEARCH_CACHE_MAX_CWDS = 20
|
|
30
|
-
const SEARCH_INDEX_MAX_FILES = 50_000
|
|
20
|
+
IGNORED_NAMES,
|
|
21
|
+
invalidateSearchIndex,
|
|
22
|
+
isSupportedProtocolName
|
|
23
|
+
} from './l4-project-file-index'
|
|
24
|
+
|
|
31
25
|
const IMAGE_COMPRESSION_MIN_BYTES = 512 * 1024
|
|
32
26
|
const IMAGE_COMPRESSION_MAX_WIDTH = 2560
|
|
33
27
|
const IMAGE_COMPRESSION_MIN_SAVING_RATIO = 0.15
|
|
34
28
|
|
|
35
|
-
const IGNORED_NAMES = new Set([
|
|
36
|
-
'.git',
|
|
37
|
-
'node_modules',
|
|
38
|
-
'.next',
|
|
39
|
-
'dist',
|
|
40
|
-
'build',
|
|
41
|
-
'coverage',
|
|
42
|
-
'.turbo'
|
|
43
|
-
])
|
|
44
|
-
|
|
45
29
|
export type L4WorkSessionProjectFileErrorKind =
|
|
46
30
|
| 'not-found'
|
|
47
31
|
| 'not-directory'
|
|
@@ -71,15 +55,8 @@ export function getL4WorkSessionProjectFileErrorKind(
|
|
|
71
55
|
|
|
72
56
|
export type L4WorkSessionProjectFileEntry = L4ProjectFileEntry
|
|
73
57
|
export type L4WorkSessionProjectFileList = L4ProjectFileList
|
|
74
|
-
export type L4WorkSessionProjectFileSearch = L4ProjectFileSearch
|
|
75
58
|
export type L4WorkSessionProjectFileGet = L4ProjectFileContent
|
|
76
59
|
|
|
77
|
-
interface SearchIndexCacheEntry {
|
|
78
|
-
files: string[]
|
|
79
|
-
truncated: boolean
|
|
80
|
-
expiresAt: number
|
|
81
|
-
}
|
|
82
|
-
|
|
83
60
|
interface ProjectFileRevealTarget {
|
|
84
61
|
target: string
|
|
85
62
|
directory: string
|
|
@@ -88,21 +65,6 @@ interface ProjectFileRevealTarget {
|
|
|
88
65
|
|
|
89
66
|
type ProjectFileRevealRunner = (command: string, args: string[]) => Promise<void>
|
|
90
67
|
|
|
91
|
-
const searchIndexCache = new Map<string, SearchIndexCacheEntry>()
|
|
92
|
-
const pendingSearchIndexes = new Map<string, Promise<SearchIndexCacheEntry>>()
|
|
93
|
-
|
|
94
|
-
function joinProtocolPath(parent: string, name: string): string {
|
|
95
|
-
return parent ? `${parent}/${name}` : name
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function isSupportedProtocolName(name: string): boolean {
|
|
99
|
-
return name.length > 0 && !name.includes('/') && !name.includes('\\') && !name.includes('\0')
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function pathContainsIgnoredSegment(path: string): boolean {
|
|
103
|
-
return path.split('/').some((segment) => IGNORED_NAMES.has(segment))
|
|
104
|
-
}
|
|
105
|
-
|
|
106
68
|
async function resolveProjectRoot(cwd: string): Promise<string> {
|
|
107
69
|
try {
|
|
108
70
|
const root = await realpath(cwd)
|
|
@@ -249,12 +211,6 @@ async function runProjectFileRevealCommand(command: string, args: string[]): Pro
|
|
|
249
211
|
})
|
|
250
212
|
}
|
|
251
213
|
|
|
252
|
-
function invalidateSearchIndex(root: string): void {
|
|
253
|
-
const key = fileSystemKey(root)
|
|
254
|
-
searchIndexCache.delete(key)
|
|
255
|
-
pendingSearchIndexes.delete(key)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
214
|
export async function listL4WorkSessionProjectFiles(
|
|
259
215
|
cwd: string,
|
|
260
216
|
protocolPath: string
|
|
@@ -318,286 +274,6 @@ export async function listL4WorkSessionProjectFiles(
|
|
|
318
274
|
}
|
|
319
275
|
}
|
|
320
276
|
|
|
321
|
-
const GIT_SEARCH_OPTIONS = {
|
|
322
|
-
timeout: 10_000,
|
|
323
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
324
|
-
env: { ...process.env, LC_ALL: 'C', GIT_OPTIONAL_LOCKS: '0' }
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
async function listFilesWithContainingGit(
|
|
328
|
-
root: string
|
|
329
|
-
): Promise<{ files: string[]; truncated: boolean } | null> {
|
|
330
|
-
try {
|
|
331
|
-
const { stdout: topLevelOutput } = await execFileAsync(
|
|
332
|
-
'git',
|
|
333
|
-
['-C', root, 'rev-parse', '--show-toplevel'],
|
|
334
|
-
GIT_SEARCH_OPTIONS
|
|
335
|
-
)
|
|
336
|
-
const topLevel = await realpath(topLevelOutput.trim())
|
|
337
|
-
const prefix = relative(topLevel, root).split(sep).join('/')
|
|
338
|
-
const { stdout } = await execFileAsync(
|
|
339
|
-
'git',
|
|
340
|
-
[
|
|
341
|
-
'-C',
|
|
342
|
-
topLevel,
|
|
343
|
-
'ls-files',
|
|
344
|
-
'--full-name',
|
|
345
|
-
'--cached',
|
|
346
|
-
'--others',
|
|
347
|
-
'--exclude-standard',
|
|
348
|
-
'-z',
|
|
349
|
-
'--',
|
|
350
|
-
prefix || '.'
|
|
351
|
-
],
|
|
352
|
-
GIT_SEARCH_OPTIONS
|
|
353
|
-
)
|
|
354
|
-
const prefixWithSlash = prefix ? `${prefix}/` : ''
|
|
355
|
-
const all = stdout
|
|
356
|
-
.split('\0')
|
|
357
|
-
.flatMap((path) => {
|
|
358
|
-
if (!path) return []
|
|
359
|
-
const projectPath = prefixWithSlash
|
|
360
|
-
? path.startsWith(prefixWithSlash)
|
|
361
|
-
? path.slice(prefixWithSlash.length)
|
|
362
|
-
: ''
|
|
363
|
-
: path
|
|
364
|
-
return projectPath ? [projectPath] : []
|
|
365
|
-
})
|
|
366
|
-
.filter(
|
|
367
|
-
(path) =>
|
|
368
|
-
isSupportedProtocolName(basename(path)) &&
|
|
369
|
-
!path.includes('\\') &&
|
|
370
|
-
!pathContainsIgnoredSegment(path)
|
|
371
|
-
)
|
|
372
|
-
return {
|
|
373
|
-
files: all.slice(0, SEARCH_INDEX_MAX_FILES),
|
|
374
|
-
truncated: all.length > SEARCH_INDEX_MAX_FILES
|
|
375
|
-
}
|
|
376
|
-
} catch {
|
|
377
|
-
return null
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function isPathInsideRepository(path: string, repositoryRoot: string): boolean {
|
|
382
|
-
return path === repositoryRoot || path.startsWith(`${repositoryRoot}/`)
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
async function listFilesInRepository(
|
|
386
|
-
projectRoot: string,
|
|
387
|
-
repositoryRoot: string,
|
|
388
|
-
nestedRepositoryRoots: readonly string[]
|
|
389
|
-
): Promise<{ files: string[]; truncated: boolean } | null> {
|
|
390
|
-
const absoluteRoot = repositoryRoot
|
|
391
|
-
? resolve(projectRoot, ...repositoryRoot.split('/'))
|
|
392
|
-
: projectRoot
|
|
393
|
-
try {
|
|
394
|
-
const { stdout } = await execFileAsync(
|
|
395
|
-
'git',
|
|
396
|
-
[
|
|
397
|
-
'-C',
|
|
398
|
-
absoluteRoot,
|
|
399
|
-
'ls-files',
|
|
400
|
-
'--full-name',
|
|
401
|
-
'--cached',
|
|
402
|
-
'--others',
|
|
403
|
-
'--exclude-standard',
|
|
404
|
-
'-z'
|
|
405
|
-
],
|
|
406
|
-
GIT_SEARCH_OPTIONS
|
|
407
|
-
)
|
|
408
|
-
const files = stdout.split('\0').flatMap((path) => {
|
|
409
|
-
if (!path || path.includes('\\') || pathContainsIgnoredSegment(path)) return []
|
|
410
|
-
const projectPath = joinProtocolPath(repositoryRoot, path)
|
|
411
|
-
if (
|
|
412
|
-
nestedRepositoryRoots.some((nestedRoot) => isPathInsideRepository(projectPath, nestedRoot))
|
|
413
|
-
) {
|
|
414
|
-
return []
|
|
415
|
-
}
|
|
416
|
-
return isSupportedProtocolName(basename(projectPath)) ? [projectPath] : []
|
|
417
|
-
})
|
|
418
|
-
return {
|
|
419
|
-
files: files.slice(0, SEARCH_INDEX_MAX_FILES),
|
|
420
|
-
truncated: files.length > SEARCH_INDEX_MAX_FILES
|
|
421
|
-
}
|
|
422
|
-
} catch {
|
|
423
|
-
return null
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
async function listFilesWithWalk(
|
|
428
|
-
root: string,
|
|
429
|
-
excludedRepositoryRoots: readonly string[] = []
|
|
430
|
-
): Promise<{ files: string[]; truncated: boolean }> {
|
|
431
|
-
const files: string[] = []
|
|
432
|
-
const queue: Array<{ absolute: string; relative: string }> = [{ absolute: root, relative: '' }]
|
|
433
|
-
let queueIndex = 0
|
|
434
|
-
|
|
435
|
-
while (queueIndex < queue.length) {
|
|
436
|
-
const current = queue[queueIndex]!
|
|
437
|
-
queueIndex += 1
|
|
438
|
-
let dirents
|
|
439
|
-
try {
|
|
440
|
-
dirents = await readdir(current.absolute, { withFileTypes: true })
|
|
441
|
-
} catch {
|
|
442
|
-
continue
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
for (const entry of dirents) {
|
|
446
|
-
if (IGNORED_NAMES.has(entry.name) || !isSupportedProtocolName(entry.name)) continue
|
|
447
|
-
const childRelative = joinProtocolPath(current.relative, entry.name)
|
|
448
|
-
if (entry.isDirectory()) {
|
|
449
|
-
if (excludedRepositoryRoots.includes(childRelative)) continue
|
|
450
|
-
queue.push({ absolute: resolve(current.absolute, entry.name), relative: childRelative })
|
|
451
|
-
continue
|
|
452
|
-
}
|
|
453
|
-
if (!entry.isFile()) continue
|
|
454
|
-
files.push(childRelative)
|
|
455
|
-
if (files.length >= SEARCH_INDEX_MAX_FILES) return { files, truncated: true }
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return { files, truncated: false }
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
async function listFilesWithRepositoryRoots(
|
|
463
|
-
projectRoot: string,
|
|
464
|
-
repositoryRoots: readonly string[]
|
|
465
|
-
): Promise<{ files: string[]; truncated: boolean }> {
|
|
466
|
-
const files = new Set<string>()
|
|
467
|
-
let truncated = false
|
|
468
|
-
const hasProjectRootRepository = repositoryRoots.includes('')
|
|
469
|
-
|
|
470
|
-
if (!hasProjectRootRepository) {
|
|
471
|
-
const workspaceFiles = await listFilesWithWalk(projectRoot, repositoryRoots.filter(Boolean))
|
|
472
|
-
for (const path of workspaceFiles.files) files.add(path)
|
|
473
|
-
truncated ||= workspaceFiles.truncated
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
for (const repositoryRoot of repositoryRoots) {
|
|
477
|
-
const nestedRoots = repositoryRoots.filter(
|
|
478
|
-
(candidate) =>
|
|
479
|
-
candidate !== repositoryRoot &&
|
|
480
|
-
(repositoryRoot === '' || candidate.startsWith(`${repositoryRoot}/`))
|
|
481
|
-
)
|
|
482
|
-
let listing = await listFilesInRepository(projectRoot, repositoryRoot, nestedRoots)
|
|
483
|
-
if (!listing) {
|
|
484
|
-
const absoluteRoot = repositoryRoot
|
|
485
|
-
? resolve(projectRoot, ...repositoryRoot.split('/'))
|
|
486
|
-
: projectRoot
|
|
487
|
-
const nestedRelativeRoots = nestedRoots.map((root) =>
|
|
488
|
-
repositoryRoot ? root.slice(repositoryRoot.length + 1) : root
|
|
489
|
-
)
|
|
490
|
-
const fallback = await listFilesWithWalk(absoluteRoot, nestedRelativeRoots)
|
|
491
|
-
listing = {
|
|
492
|
-
files: fallback.files.map((path) => joinProtocolPath(repositoryRoot, path)),
|
|
493
|
-
truncated: fallback.truncated
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
for (const path of listing.files) {
|
|
497
|
-
files.add(path)
|
|
498
|
-
if (files.size >= SEARCH_INDEX_MAX_FILES) {
|
|
499
|
-
return { files: [...files], truncated: true }
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
truncated ||= listing.truncated
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return { files: [...files], truncated }
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
async function getSearchIndex(root: string): Promise<SearchIndexCacheEntry> {
|
|
509
|
-
const key = fileSystemKey(root)
|
|
510
|
-
const now = Date.now()
|
|
511
|
-
const cached = searchIndexCache.get(key)
|
|
512
|
-
if (cached && cached.expiresAt > now) return cached
|
|
513
|
-
|
|
514
|
-
const active = pendingSearchIndexes.get(key)
|
|
515
|
-
if (active) return active
|
|
516
|
-
|
|
517
|
-
const load: Promise<SearchIndexCacheEntry> = Promise.resolve().then(async () => {
|
|
518
|
-
const repositoryRoots = await listL4WorkSessionGitRepositoryRoots(root)
|
|
519
|
-
const listing =
|
|
520
|
-
repositoryRoots.length > 0
|
|
521
|
-
? await listFilesWithRepositoryRoots(root, repositoryRoots)
|
|
522
|
-
: ((await listFilesWithContainingGit(root)) ?? (await listFilesWithWalk(root)))
|
|
523
|
-
const next: SearchIndexCacheEntry = {
|
|
524
|
-
...listing,
|
|
525
|
-
expiresAt: now + SEARCH_CACHE_TTL_MS
|
|
526
|
-
}
|
|
527
|
-
if (pendingSearchIndexes.get(key) === load) {
|
|
528
|
-
for (const [cacheKey, entry] of searchIndexCache) {
|
|
529
|
-
if (entry.expiresAt <= Date.now()) searchIndexCache.delete(cacheKey)
|
|
530
|
-
}
|
|
531
|
-
while (searchIndexCache.size >= SEARCH_CACHE_MAX_CWDS) {
|
|
532
|
-
const oldestKey = searchIndexCache.keys().next().value as string
|
|
533
|
-
searchIndexCache.delete(oldestKey)
|
|
534
|
-
}
|
|
535
|
-
searchIndexCache.set(key, next)
|
|
536
|
-
}
|
|
537
|
-
return next
|
|
538
|
-
})
|
|
539
|
-
pendingSearchIndexes.set(key, load)
|
|
540
|
-
try {
|
|
541
|
-
return await load
|
|
542
|
-
} finally {
|
|
543
|
-
if (pendingSearchIndexes.get(key) === load) pendingSearchIndexes.delete(key)
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function subsequenceGapScore(value: string, query: string): number | null {
|
|
548
|
-
let queryIndex = 0
|
|
549
|
-
let firstIndex = -1
|
|
550
|
-
let previousIndex = -1
|
|
551
|
-
let gaps = 0
|
|
552
|
-
|
|
553
|
-
for (
|
|
554
|
-
let valueIndex = 0;
|
|
555
|
-
valueIndex < value.length && queryIndex < query.length;
|
|
556
|
-
valueIndex += 1
|
|
557
|
-
) {
|
|
558
|
-
if (value[valueIndex] !== query[queryIndex]) continue
|
|
559
|
-
if (firstIndex < 0) firstIndex = valueIndex
|
|
560
|
-
if (previousIndex >= 0) gaps += valueIndex - previousIndex - 1
|
|
561
|
-
previousIndex = valueIndex
|
|
562
|
-
queryIndex += 1
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
return queryIndex === query.length ? firstIndex * 4 + gaps : null
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
function searchScore(path: string, query: string): number | null {
|
|
569
|
-
const normalizedPath = path.toLocaleLowerCase()
|
|
570
|
-
const name = basename(normalizedPath)
|
|
571
|
-
if (name === query) return 0
|
|
572
|
-
if (name.startsWith(query)) return 100 + name.length - query.length
|
|
573
|
-
const nameIndex = name.indexOf(query)
|
|
574
|
-
if (nameIndex >= 0) return 1_000 + nameIndex * 4 + name.length
|
|
575
|
-
const pathIndex = normalizedPath.indexOf(query)
|
|
576
|
-
if (pathIndex >= 0) return 10_000 + pathIndex * 4 + normalizedPath.length
|
|
577
|
-
const fuzzy = subsequenceGapScore(normalizedPath, query)
|
|
578
|
-
return fuzzy === null ? null : 100_000 + fuzzy + normalizedPath.length
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
export async function searchL4WorkSessionProjectFiles(
|
|
582
|
-
cwd: string,
|
|
583
|
-
query: string
|
|
584
|
-
): Promise<L4WorkSessionProjectFileSearch> {
|
|
585
|
-
const root = await resolveProjectRoot(cwd)
|
|
586
|
-
const index = await getSearchIndex(root)
|
|
587
|
-
const normalizedQuery = query.toLocaleLowerCase()
|
|
588
|
-
const matches = index.files
|
|
589
|
-
.flatMap((path) => {
|
|
590
|
-
const score = searchScore(path, normalizedQuery)
|
|
591
|
-
return score === null ? [] : [{ path, score }]
|
|
592
|
-
})
|
|
593
|
-
.sort((left, right) => left.score - right.score || left.path.localeCompare(right.path))
|
|
594
|
-
|
|
595
|
-
return {
|
|
596
|
-
matches: matches.slice(0, L4_PROJECT_FILE_SEARCH_MAX_RESULTS).map((match) => match.path),
|
|
597
|
-
truncated: index.truncated || matches.length > L4_PROJECT_FILE_SEARCH_MAX_RESULTS
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
277
|
export async function revealL4WorkSessionProjectFile(
|
|
602
278
|
cwd: string,
|
|
603
279
|
protocolPath: string,
|
|
@@ -624,7 +624,7 @@ async function assertConnectionCredentials(
|
|
|
624
624
|
}
|
|
625
625
|
|
|
626
626
|
async function prepareProjectDefaultChanges(
|
|
627
|
-
|
|
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 (!
|
|
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
|
-
|
|
687
|
-
|
|
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(
|
|
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
|
-
|
|
866
|
-
session.
|
|
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]
|
|
1626
|
+
console.warn('[Pi Desk][PiChatWorker] 工具参数流过大,已中止当前运行,交由 Pi 重试策略处理', {
|
|
1610
1627
|
sessionId: this.sessionManager.getSessionId(),
|
|
1611
1628
|
toolCallId: oversized.toolCallId,
|
|
1612
1629
|
toolName: oversized.toolName,
|
|
@@ -81,12 +81,6 @@ export async function runL4PiPackageMaintenance(input: {
|
|
|
81
81
|
'failed',
|
|
82
82
|
'包维护超过120秒,已停止;结果未确认,请检查配置后手动处理',
|
|
83
83
|
null
|
|
84
|
-
),
|
|
85
|
-
cleanupError: (_output, error) =>
|
|
86
|
-
new L4PiPackageMaintenanceError(
|
|
87
|
-
'failed',
|
|
88
|
-
`维护进程树未确认释放:${error instanceof Error ? error.message : String(error)}`,
|
|
89
|
-
null
|
|
90
84
|
)
|
|
91
85
|
})
|
|
92
86
|
if (code === 0) return
|
|
@@ -138,9 +138,7 @@ export async function runL4PiPluginCapabilityProbe(
|
|
|
138
138
|
env: { PI_DESK_PLUGIN_CAPABILITY_AGENT_DIR: agentDir, PI_DESK_PLUGIN_CAPABILITY_CWD: cwd },
|
|
139
139
|
maxOutputBytes: L4_PI_PLUGIN_CAPABILITY_PROBE_MAX_OUTPUT_BYTES,
|
|
140
140
|
timeoutMs: L4_PI_PLUGIN_CAPABILITY_PROBE_TIMEOUT_MS,
|
|
141
|
-
timeoutError: () => new Error('插件能力盘点超时')
|
|
142
|
-
cleanupError: (output, error) =>
|
|
143
|
-
new Error(`${output}\n能力盘点进程树清理失败`, { cause: error })
|
|
141
|
+
timeoutError: () => new Error('插件能力盘点超时')
|
|
144
142
|
})
|
|
145
143
|
if (code !== 0) throw new Error(stderr.trim() || `插件能力盘点进程退出码 ${code ?? 'null'}`)
|
|
146
144
|
const marker = stdout.lastIndexOf(L4_PI_PLUGIN_CAPABILITY_PROBE_RESULT_PREFIX)
|