@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
|
@@ -40,6 +40,14 @@ export const L4_LOCALE_MESSAGES = {
|
|
|
40
40
|
authenticationRequired: 'Please sign in',
|
|
41
41
|
authUnavailable: 'Sign-in service is not ready',
|
|
42
42
|
shuttingDown: 'The service is shutting down',
|
|
43
|
+
markdownPending: 'Not completed',
|
|
44
|
+
markdownCompleted: 'Completed',
|
|
45
|
+
markdownProgress: 'In progress',
|
|
46
|
+
markdownNote: 'Note',
|
|
47
|
+
markdownTip: 'Tip',
|
|
48
|
+
markdownImportant: 'Important',
|
|
49
|
+
markdownWarning: 'Warning',
|
|
50
|
+
markdownCaution: 'Caution',
|
|
43
51
|
codeReadOnly: 'Read-only code file',
|
|
44
52
|
codeLoading: 'Loading code viewer…',
|
|
45
53
|
codeFailed: 'Could not load code viewer. Reopen the file.',
|
|
@@ -118,6 +126,14 @@ export const L4_LOCALE_MESSAGES = {
|
|
|
118
126
|
authenticationRequired: '请先登录',
|
|
119
127
|
authUnavailable: '登录认证尚未就绪',
|
|
120
128
|
shuttingDown: '服务正在关闭',
|
|
129
|
+
markdownPending: '未完成',
|
|
130
|
+
markdownCompleted: '已完成',
|
|
131
|
+
markdownProgress: '进行中',
|
|
132
|
+
markdownNote: '说明',
|
|
133
|
+
markdownTip: '提示',
|
|
134
|
+
markdownImportant: '重要',
|
|
135
|
+
markdownWarning: '警告',
|
|
136
|
+
markdownCaution: '注意',
|
|
121
137
|
codeReadOnly: '只读代码文件',
|
|
122
138
|
codeLoading: '正在加载代码查看器…',
|
|
123
139
|
codeFailed: '代码查看器加载失败,请重新打开文件。',
|
package/src/instrumentation.ts
CHANGED
|
@@ -33,7 +33,6 @@ export async function register(): Promise<void> {
|
|
|
33
33
|
})
|
|
34
34
|
const manage = getL2WorkSessionManage()
|
|
35
35
|
initializeL2PluginManagement({
|
|
36
|
-
refreshPluginMessages: () => manage.refreshPluginMessages(),
|
|
37
36
|
runPluginRestart: (operation) => manage.runPluginRestart(operation)
|
|
38
37
|
})
|
|
39
38
|
registerL1WebAuthRuntime(getL1WebAuthRuntime())
|
|
@@ -70,7 +69,6 @@ export async function register(): Promise<void> {
|
|
|
70
69
|
setTimeout(() => {
|
|
71
70
|
void pluginRuntime.initialize().then(
|
|
72
71
|
() => {
|
|
73
|
-
manage.refreshPluginMessages()
|
|
74
72
|
console.info('[Pi Desk][Instrumentation] 插件加载尝试完成,基础聊天不依赖插件就绪')
|
|
75
73
|
},
|
|
76
74
|
(error: unknown) => {
|
|
@@ -2,9 +2,9 @@ import 'server-only'
|
|
|
2
2
|
import * as contracts from '@common/l3_modules/project-files/l3-project-files-contract'
|
|
3
3
|
import {
|
|
4
4
|
getL4WorkSessionProjectFile,
|
|
5
|
-
listL4WorkSessionProjectFiles
|
|
6
|
-
searchL4WorkSessionProjectFiles
|
|
5
|
+
listL4WorkSessionProjectFiles
|
|
7
6
|
} from '@server/l4_foundation/file/l4-work-session-project-file'
|
|
7
|
+
import { searchL4ProjectFiles } from '@server/l4_foundation/file/l4-project-search'
|
|
8
8
|
import {
|
|
9
9
|
getL4WorkSessionGitHeads,
|
|
10
10
|
getL4WorkSessionGitSnapshot
|
|
@@ -20,8 +20,10 @@ import { createL1ProjectRoute } from './l1-project-route'
|
|
|
20
20
|
export const projectFileList = createL1ProjectRoute(contracts.L3ProjectFileList, (input) =>
|
|
21
21
|
listL4WorkSessionProjectFiles(input.cwd, input.path)
|
|
22
22
|
)
|
|
23
|
-
export const projectFileSearch = createL1ProjectRoute(
|
|
24
|
-
|
|
23
|
+
export const projectFileSearch = createL1ProjectRoute(
|
|
24
|
+
contracts.L3ProjectFileSearch,
|
|
25
|
+
({ cwd, query, scope, caseSensitive, wholeWord, regex }, signal) =>
|
|
26
|
+
searchL4ProjectFiles(cwd, { query, scope, caseSensitive, wholeWord, regex }, signal)
|
|
25
27
|
)
|
|
26
28
|
export const projectFileGet = createL1ProjectRoute(contracts.L3ProjectFileGet, (input) =>
|
|
27
29
|
getL4WorkSessionProjectFile(input.cwd, input.path, input.imagePreviewMode)
|
|
@@ -13,7 +13,7 @@ import { createL1WorkSessionGitErrorResponse } from './l1-work-session-git-error
|
|
|
13
13
|
|
|
14
14
|
export function createL1ProjectRoute<I extends contracts.L3ProjectReadContext, O>(
|
|
15
15
|
contract: { path: string; request: z.ZodType<I>; response: z.ZodType<O> },
|
|
16
|
-
execute: (input: I) => Promise<O>
|
|
16
|
+
execute: (input: I, signal: AbortSignal) => Promise<O>
|
|
17
17
|
): (request: Request) => Promise<Response> {
|
|
18
18
|
return async (request: Request): Promise<Response> => {
|
|
19
19
|
let payload: unknown
|
|
@@ -49,19 +49,20 @@ export function createL1ProjectRoute<I extends contracts.L3ProjectReadContext, O
|
|
|
49
49
|
input.cwd = context.cwd
|
|
50
50
|
isCurrent = context.isCurrent
|
|
51
51
|
}
|
|
52
|
-
const result = await execute(input)
|
|
52
|
+
const result = await execute(input, request.signal)
|
|
53
53
|
if (isCurrent && !isCurrent())
|
|
54
54
|
return createL4ApiErrorResponse(409, '工作会话项目已变化', {
|
|
55
55
|
key: 'errors:projectChanged'
|
|
56
56
|
})
|
|
57
57
|
return createL4ApiSuccessResponse(contract.response.parse(result))
|
|
58
58
|
} catch (error) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
if (!request.signal.aborted)
|
|
60
|
+
console.warn('[Pi Desk][Project] 项目请求失败', {
|
|
61
|
+
path: contract.path,
|
|
62
|
+
cwd: input.cwd,
|
|
63
|
+
workId: input.workId,
|
|
64
|
+
error: error instanceof Error ? error.message : String(error)
|
|
65
|
+
})
|
|
65
66
|
if (getL4WorkSessionGitErrorKind(error) === 'read-failed') {
|
|
66
67
|
return createL4ApiErrorResponse(
|
|
67
68
|
500,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
|
|
3
3
|
import { getL4WorkSessionProjectFileErrorKind } from '@server/l4_foundation/file/l4-work-session-project-file'
|
|
4
|
+
import { L4ProjectSearchError } from '@server/l4_foundation/file/l4-project-search'
|
|
4
5
|
import { createL4ApiErrorResponse } from '@server/l4_foundation/http/l4-api-response'
|
|
5
6
|
|
|
6
7
|
export function createL1WorkSessionFileErrorResponse(error: unknown): Response | null {
|
|
@@ -11,6 +12,28 @@ export function createL1WorkSessionFileErrorResponse(error: unknown): Response |
|
|
|
11
12
|
return createL4ApiErrorResponse(409, '工作会话项目已变化', { key: 'errors:projectChanged' })
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
16
|
+
return createL4ApiErrorResponse(499, '项目搜索已取消', { key: 'errors:projectSearchCancelled' })
|
|
17
|
+
}
|
|
18
|
+
if (error instanceof L4ProjectSearchError) {
|
|
19
|
+
switch (error.kind) {
|
|
20
|
+
case 'invalid-pattern':
|
|
21
|
+
return createL4ApiErrorResponse(400, error.message, {
|
|
22
|
+
key: 'errors:projectSearchPatternInvalid'
|
|
23
|
+
})
|
|
24
|
+
case 'unavailable':
|
|
25
|
+
return createL4ApiErrorResponse(503, error.message, {
|
|
26
|
+
key: 'errors:projectSearchUnavailable'
|
|
27
|
+
})
|
|
28
|
+
case 'timeout':
|
|
29
|
+
return createL4ApiErrorResponse(504, error.message, { key: 'errors:projectSearchTimeout' })
|
|
30
|
+
case 'read-failed':
|
|
31
|
+
return createL4ApiErrorResponse(500, error.message, {
|
|
32
|
+
key: 'errors:projectSearchReadFailed'
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
switch (getL4WorkSessionProjectFileErrorKind(error)) {
|
|
15
38
|
case 'not-found':
|
|
16
39
|
return createL4ApiErrorResponse(404, '文件或目录不存在', { key: 'errors:fileMissing' })
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
|
|
2
2
|
import type { Duplex } from 'node:stream'
|
|
3
3
|
import { URL } from 'node:url'
|
|
4
|
+
import { L2BuildLoginHref, L2ResolveAuthReturnTo } from '@common/l2_biz/auth/l2-auth-return-to'
|
|
4
5
|
import {
|
|
5
6
|
L4_APP_WEBSOCKET_ENDPOINT,
|
|
6
7
|
L4_APP_WEBSOCKET_PROTOCOL,
|
|
@@ -97,15 +98,16 @@ function allowHttpRequest(
|
|
|
97
98
|
response: ServerResponse,
|
|
98
99
|
hostname: string
|
|
99
100
|
): boolean {
|
|
100
|
-
let
|
|
101
|
+
let requestUrl: URL
|
|
101
102
|
try {
|
|
102
|
-
|
|
103
|
+
requestUrl = new URL(request.url ?? '/', `http://${request.headers.host ?? hostname}`)
|
|
103
104
|
} catch {
|
|
104
105
|
response.statusCode = 400
|
|
105
106
|
response.end('Bad Request')
|
|
106
107
|
return false
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
const pathname = requestUrl.pathname
|
|
109
111
|
if (isPublicHttpPath(pathname) && pathname !== LOGIN_PAGE_PATH) return true
|
|
110
112
|
|
|
111
113
|
const authRuntime = getL1WebAuthRuntimeBridge()
|
|
@@ -119,9 +121,11 @@ function allowHttpRequest(
|
|
|
119
121
|
}
|
|
120
122
|
if (pathname === LOGIN_PAGE_PATH) {
|
|
121
123
|
if (!authenticated) return true
|
|
124
|
+
const returnTo = L2ResolveAuthReturnTo(requestUrl.searchParams.get('returnTo'))
|
|
122
125
|
response.statusCode = 302
|
|
123
126
|
response.setHeader('Cache-Control', 'no-store')
|
|
124
|
-
|
|
127
|
+
// 保留已校验的原路径,避免将 /.//host 规范化为协议相对 Location。
|
|
128
|
+
response.setHeader('Location', encodeURI(returnTo).replace(/%25/g, '%'))
|
|
125
129
|
response.end()
|
|
126
130
|
return false
|
|
127
131
|
}
|
|
@@ -146,7 +150,7 @@ function allowHttpRequest(
|
|
|
146
150
|
|
|
147
151
|
response.statusCode = 302
|
|
148
152
|
response.setHeader('Cache-Control', 'no-store')
|
|
149
|
-
response.setHeader('Location',
|
|
153
|
+
response.setHeader('Location', L2BuildLoginHref(`${requestUrl.pathname}${requestUrl.search}`))
|
|
150
154
|
response.end()
|
|
151
155
|
return false
|
|
152
156
|
}
|
|
@@ -112,7 +112,6 @@ interface L2PluginPreparedMutation {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
export interface L2PluginManagementWorkSessions {
|
|
115
|
-
refreshPluginMessages: () => void
|
|
116
115
|
runPluginRestart: <T>(operation: () => Promise<T>) => Promise<T>
|
|
117
116
|
}
|
|
118
117
|
|
|
@@ -621,7 +620,6 @@ export class L2PluginManagement {
|
|
|
621
620
|
if (change.current?.path) clearL4PiPluginNativeDiagnostics(change.current.path)
|
|
622
621
|
if (change.previous?.path) clearL4PiPluginNativeDiagnostics(change.previous.path)
|
|
623
622
|
this.capabilityCache = null
|
|
624
|
-
this.workSessions.refreshPluginMessages()
|
|
625
623
|
this.operations.delete(change.source)
|
|
626
624
|
} catch (error) {
|
|
627
625
|
this.setOperation(change.source, 'failed', errorMessage(error))
|
|
@@ -981,7 +979,6 @@ export class L2PluginManagement {
|
|
|
981
979
|
this.updates.clear(operationSource)
|
|
982
980
|
this.updates.clear(source)
|
|
983
981
|
this.capabilityCache = null
|
|
984
|
-
this.workSessions.refreshPluginMessages()
|
|
985
982
|
},
|
|
986
983
|
this.lifetime.signal,
|
|
987
984
|
waiting
|
|
@@ -108,7 +108,6 @@ interface L2WorkSessionChatRecord {
|
|
|
108
108
|
source: L2ChatSource
|
|
109
109
|
state: L2ChatSourceState
|
|
110
110
|
sourceEventVersion: number
|
|
111
|
-
forceFullSync: boolean
|
|
112
111
|
rawTemporaryMessages: Map<string, L4PiChatMessage>
|
|
113
112
|
metadata: L2WorkSessionChatMetadata
|
|
114
113
|
mainRunning: boolean
|
|
@@ -302,20 +301,6 @@ export class L2WorkSessionChatRuntime {
|
|
|
302
301
|
return isCurrent() ? { cwd, isCurrent } : null
|
|
303
302
|
}
|
|
304
303
|
|
|
305
|
-
refreshPluginMessages(): void {
|
|
306
|
-
for (const record of this.recordsByWorkId.values()) {
|
|
307
|
-
if (record.state.runtime.presentationMode === 'basic') continue
|
|
308
|
-
try {
|
|
309
|
-
this.reprojectRecord(record, record.state.runtime.presentationMode)
|
|
310
|
-
} catch (error) {
|
|
311
|
-
console.error('[Pi Desk][WorkSessionChatRuntime] 插件消息刷新失败,保留已有聊天', {
|
|
312
|
-
workId: record.source.workId,
|
|
313
|
-
message: error instanceof Error ? error.message : String(error)
|
|
314
|
-
})
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
304
|
setPresentation(source: L2ChatSource, mode: L2ChatMode): Promise<void> {
|
|
320
305
|
const record = this.currentRecord(source)
|
|
321
306
|
return this.enqueueCommand(record, async () => {
|
|
@@ -363,7 +348,6 @@ export class L2WorkSessionChatRuntime {
|
|
|
363
348
|
temporaryMessages,
|
|
364
349
|
runtime: { ...record.state.runtime, presentationMode: mode }
|
|
365
350
|
}
|
|
366
|
-
record.forceFullSync = true
|
|
367
351
|
record.sourceEventVersion += 1
|
|
368
352
|
this.publish({
|
|
369
353
|
type: 'source_event',
|
|
@@ -380,16 +364,14 @@ export class L2WorkSessionChatRuntime {
|
|
|
380
364
|
|
|
381
365
|
private async syncRecordRuntime(record: L2WorkSessionChatRecord): Promise<void> {
|
|
382
366
|
const source = record.source
|
|
383
|
-
const runtime =
|
|
384
|
-
|
|
367
|
+
const runtime = await getL4PiWorkSessionRuntime(source.sessionId).readChatRuntime()
|
|
368
|
+
const nextRuntime = L2ChatRuntimeSchema.parse({
|
|
369
|
+
...runtime,
|
|
385
370
|
presentationMode: record.state.runtime.presentationMode
|
|
386
371
|
})
|
|
387
372
|
if (!this.isCurrentSource(source)) return
|
|
388
|
-
if (!sameRuntime(record.state.runtime,
|
|
389
|
-
this.applySourceEvent(source, { type: 'runtime_update', runtime })
|
|
390
|
-
if (runtime.extensionMode === 'basic' && record.state.runtime.presentationMode !== 'basic') {
|
|
391
|
-
this.reprojectRecord(record, 'basic')
|
|
392
|
-
}
|
|
373
|
+
if (!sameRuntime(record.state.runtime, nextRuntime))
|
|
374
|
+
this.applySourceEvent(source, { type: 'runtime_update', runtime: nextRuntime })
|
|
393
375
|
}
|
|
394
376
|
|
|
395
377
|
createSync(sourceInput: L2ChatSource, cursor: L2ChatCursor): L2PreparedChatSourceSync {
|
|
@@ -413,24 +395,23 @@ export class L2WorkSessionChatRuntime {
|
|
|
413
395
|
})
|
|
414
396
|
}
|
|
415
397
|
|
|
416
|
-
const event: L2ChatSessionSyncEvent =
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
398
|
+
const event: L2ChatSessionSyncEvent = cursorMatches
|
|
399
|
+
? {
|
|
400
|
+
type: 'session_sync',
|
|
401
|
+
mode: 'incremental',
|
|
402
|
+
baseCursor: cursor,
|
|
403
|
+
messages: messages.slice(cursor.index + 1).map(withoutL3ConversationMessageDetail),
|
|
404
|
+
temporaryMessages,
|
|
405
|
+
runtime
|
|
406
|
+
}
|
|
407
|
+
: {
|
|
408
|
+
type: 'session_sync',
|
|
409
|
+
mode: 'full',
|
|
410
|
+
baseCursor: null,
|
|
411
|
+
messages: messages.map(withoutL3ConversationMessageDetail),
|
|
412
|
+
temporaryMessages,
|
|
413
|
+
runtime
|
|
414
|
+
}
|
|
434
415
|
|
|
435
416
|
return { event, watermark: record.sourceEventVersion }
|
|
436
417
|
}
|
|
@@ -548,12 +529,6 @@ export class L2WorkSessionChatRuntime {
|
|
|
548
529
|
} finally {
|
|
549
530
|
if (this.recordsByWorkId.get(source.workId) === record) {
|
|
550
531
|
await this.syncRecordRuntime(record)
|
|
551
|
-
if (
|
|
552
|
-
record.state.runtime.extensionMode === 'basic' &&
|
|
553
|
-
record.state.runtime.presentationMode !== 'basic'
|
|
554
|
-
) {
|
|
555
|
-
this.reprojectRecord(record, 'basic')
|
|
556
|
-
}
|
|
557
532
|
}
|
|
558
533
|
}
|
|
559
534
|
})
|
|
@@ -685,7 +660,6 @@ export class L2WorkSessionChatRuntime {
|
|
|
685
660
|
source,
|
|
686
661
|
state,
|
|
687
662
|
sourceEventVersion: 0,
|
|
688
|
-
forceFullSync: isL4PiDeskSafeMode(),
|
|
689
663
|
rawTemporaryMessages: new Map(),
|
|
690
664
|
metadata: {
|
|
691
665
|
sessionTitle: snapshot.sessionName ?? firstUserTitle,
|
|
@@ -714,6 +688,9 @@ export class L2WorkSessionChatRuntime {
|
|
|
714
688
|
if (this.recordsByWorkId.get(record.source.workId) !== record) return
|
|
715
689
|
const previousMetadata = record.metadata
|
|
716
690
|
const previousStatus = this.statusForRecord(record)
|
|
691
|
+
const basic =
|
|
692
|
+
record.state.runtime.extensionMode === 'basic' ||
|
|
693
|
+
record.state.runtime.presentationMode === 'basic'
|
|
717
694
|
|
|
718
695
|
switch (event.type) {
|
|
719
696
|
case 'message_start':
|
|
@@ -726,7 +703,7 @@ export class L2WorkSessionChatRuntime {
|
|
|
726
703
|
record.source,
|
|
727
704
|
false,
|
|
728
705
|
'temporary',
|
|
729
|
-
|
|
706
|
+
basic
|
|
730
707
|
)
|
|
731
708
|
})
|
|
732
709
|
return
|
|
@@ -744,7 +721,7 @@ export class L2WorkSessionChatRuntime {
|
|
|
744
721
|
record.source,
|
|
745
722
|
false,
|
|
746
723
|
'update',
|
|
747
|
-
|
|
724
|
+
basic
|
|
748
725
|
)
|
|
749
726
|
)
|
|
750
727
|
if (update) this.applySourceEvent(record.source, update)
|
|
@@ -761,7 +738,7 @@ export class L2WorkSessionChatRuntime {
|
|
|
761
738
|
record.source,
|
|
762
739
|
false,
|
|
763
740
|
'final',
|
|
764
|
-
|
|
741
|
+
basic
|
|
765
742
|
)
|
|
766
743
|
const update = buildL3ConversationMessageUpdate(current, publicFinal)
|
|
767
744
|
if (update) this.applySourceEvent(record.source, update)
|
|
@@ -801,12 +778,6 @@ export class L2WorkSessionChatRuntime {
|
|
|
801
778
|
if (!sameRuntime(record.state.runtime, runtime)) {
|
|
802
779
|
this.applySourceEvent(record.source, { type: 'runtime_update', runtime })
|
|
803
780
|
}
|
|
804
|
-
if (
|
|
805
|
-
runtime.extensionMode === 'basic' &&
|
|
806
|
-
record.state.runtime.presentationMode !== 'basic'
|
|
807
|
-
) {
|
|
808
|
-
this.reprojectRecord(record, 'basic')
|
|
809
|
-
}
|
|
810
781
|
return
|
|
811
782
|
}
|
|
812
783
|
case 'agent_started':
|
|
@@ -861,7 +832,8 @@ export class L2WorkSessionChatRuntime {
|
|
|
861
832
|
record.source,
|
|
862
833
|
true,
|
|
863
834
|
'final',
|
|
864
|
-
record.state.runtime.
|
|
835
|
+
record.state.runtime.extensionMode === 'basic' ||
|
|
836
|
+
record.state.runtime.presentationMode === 'basic'
|
|
865
837
|
)
|
|
866
838
|
record.state = { ...record.state, temporaryMessages }
|
|
867
839
|
}
|
|
@@ -250,10 +250,6 @@ export class WorkSessionManage {
|
|
|
250
250
|
})
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
refreshPluginMessages(): void {
|
|
254
|
-
this.chat.refreshPluginMessages()
|
|
255
|
-
}
|
|
256
|
-
|
|
257
253
|
runPluginRestart<T>(operation: () => Promise<T>): Promise<T> {
|
|
258
254
|
return this.runExclusive(async () => {
|
|
259
255
|
await this.ensureHydrated()
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import 'server-only'
|
|
2
|
+
|
|
3
|
+
import { execFile } from 'node:child_process'
|
|
4
|
+
import { readdir, realpath } from 'node:fs/promises'
|
|
5
|
+
import { basename, relative, resolve, sep } from 'node:path'
|
|
6
|
+
import { promisify } from 'node:util'
|
|
7
|
+
import { listL4WorkSessionGitRepositoryRoots } from '@server/l4_foundation/git/l4-work-session-git'
|
|
8
|
+
import { getL4FileSystemKey as fileSystemKey } from './l4-file-path'
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile)
|
|
11
|
+
const SEARCH_CACHE_TTL_MS = 30_000
|
|
12
|
+
const SEARCH_CACHE_MAX_CWDS = 20
|
|
13
|
+
const SEARCH_INDEX_MAX_FILES = 50_000
|
|
14
|
+
|
|
15
|
+
export const IGNORED_NAMES = new Set([
|
|
16
|
+
'.git',
|
|
17
|
+
'node_modules',
|
|
18
|
+
'.next',
|
|
19
|
+
'dist',
|
|
20
|
+
'build',
|
|
21
|
+
'coverage',
|
|
22
|
+
'.turbo'
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
interface SearchIndexCacheEntry {
|
|
26
|
+
files: string[]
|
|
27
|
+
truncated: boolean
|
|
28
|
+
expiresAt: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const searchIndexCache = new Map<string, SearchIndexCacheEntry>()
|
|
32
|
+
const pendingSearchIndexes = new Map<string, Promise<SearchIndexCacheEntry>>()
|
|
33
|
+
|
|
34
|
+
function joinProtocolPath(parent: string, name: string): string {
|
|
35
|
+
return parent ? `${parent}/${name}` : name
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isSupportedProtocolName(name: string): boolean {
|
|
39
|
+
return name.length > 0 && !name.includes('/') && !name.includes('\\') && !name.includes('\0')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pathContainsIgnoredSegment(path: string): boolean {
|
|
43
|
+
return path.split('/').some((segment) => IGNORED_NAMES.has(segment))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function invalidateSearchIndex(root: string): void {
|
|
47
|
+
const key = fileSystemKey(root)
|
|
48
|
+
searchIndexCache.delete(key)
|
|
49
|
+
pendingSearchIndexes.delete(key)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const GIT_SEARCH_OPTIONS = {
|
|
53
|
+
timeout: 10_000,
|
|
54
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
55
|
+
env: { ...process.env, LC_ALL: 'C', GIT_OPTIONAL_LOCKS: '0' }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function listFilesWithContainingGit(
|
|
59
|
+
root: string
|
|
60
|
+
): Promise<{ files: string[]; truncated: boolean } | null> {
|
|
61
|
+
try {
|
|
62
|
+
const { stdout: topLevelOutput } = await execFileAsync(
|
|
63
|
+
'git',
|
|
64
|
+
['-C', root, 'rev-parse', '--show-toplevel'],
|
|
65
|
+
GIT_SEARCH_OPTIONS
|
|
66
|
+
)
|
|
67
|
+
const topLevel = await realpath(topLevelOutput.trim())
|
|
68
|
+
const prefix = relative(topLevel, root).split(sep).join('/')
|
|
69
|
+
const { stdout } = await execFileAsync(
|
|
70
|
+
'git',
|
|
71
|
+
[
|
|
72
|
+
'-C',
|
|
73
|
+
topLevel,
|
|
74
|
+
'ls-files',
|
|
75
|
+
'--full-name',
|
|
76
|
+
'--cached',
|
|
77
|
+
'--others',
|
|
78
|
+
'--exclude-standard',
|
|
79
|
+
'-z',
|
|
80
|
+
'--',
|
|
81
|
+
prefix || '.'
|
|
82
|
+
],
|
|
83
|
+
GIT_SEARCH_OPTIONS
|
|
84
|
+
)
|
|
85
|
+
const prefixWithSlash = prefix ? `${prefix}/` : ''
|
|
86
|
+
const all = stdout
|
|
87
|
+
.split('\0')
|
|
88
|
+
.flatMap((path) => {
|
|
89
|
+
if (!path) return []
|
|
90
|
+
const projectPath = prefixWithSlash
|
|
91
|
+
? path.startsWith(prefixWithSlash)
|
|
92
|
+
? path.slice(prefixWithSlash.length)
|
|
93
|
+
: ''
|
|
94
|
+
: path
|
|
95
|
+
return projectPath ? [projectPath] : []
|
|
96
|
+
})
|
|
97
|
+
.filter(
|
|
98
|
+
(path) =>
|
|
99
|
+
isSupportedProtocolName(basename(path)) &&
|
|
100
|
+
!path.includes('\\') &&
|
|
101
|
+
!pathContainsIgnoredSegment(path)
|
|
102
|
+
)
|
|
103
|
+
return {
|
|
104
|
+
files: all.slice(0, SEARCH_INDEX_MAX_FILES),
|
|
105
|
+
truncated: all.length > SEARCH_INDEX_MAX_FILES
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isPathInsideRepository(path: string, repositoryRoot: string): boolean {
|
|
113
|
+
return path === repositoryRoot || path.startsWith(`${repositoryRoot}/`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function listFilesInRepository(
|
|
117
|
+
projectRoot: string,
|
|
118
|
+
repositoryRoot: string,
|
|
119
|
+
nestedRepositoryRoots: readonly string[]
|
|
120
|
+
): Promise<{ files: string[]; truncated: boolean } | null> {
|
|
121
|
+
const absoluteRoot = repositoryRoot
|
|
122
|
+
? resolve(projectRoot, ...repositoryRoot.split('/'))
|
|
123
|
+
: projectRoot
|
|
124
|
+
try {
|
|
125
|
+
const { stdout } = await execFileAsync(
|
|
126
|
+
'git',
|
|
127
|
+
[
|
|
128
|
+
'-C',
|
|
129
|
+
absoluteRoot,
|
|
130
|
+
'ls-files',
|
|
131
|
+
'--full-name',
|
|
132
|
+
'--cached',
|
|
133
|
+
'--others',
|
|
134
|
+
'--exclude-standard',
|
|
135
|
+
'-z'
|
|
136
|
+
],
|
|
137
|
+
GIT_SEARCH_OPTIONS
|
|
138
|
+
)
|
|
139
|
+
const files = stdout.split('\0').flatMap((path) => {
|
|
140
|
+
if (!path || path.includes('\\') || pathContainsIgnoredSegment(path)) return []
|
|
141
|
+
const projectPath = joinProtocolPath(repositoryRoot, path)
|
|
142
|
+
if (
|
|
143
|
+
nestedRepositoryRoots.some((nestedRoot) => isPathInsideRepository(projectPath, nestedRoot))
|
|
144
|
+
) {
|
|
145
|
+
return []
|
|
146
|
+
}
|
|
147
|
+
return isSupportedProtocolName(basename(projectPath)) ? [projectPath] : []
|
|
148
|
+
})
|
|
149
|
+
return {
|
|
150
|
+
files: files.slice(0, SEARCH_INDEX_MAX_FILES),
|
|
151
|
+
truncated: files.length > SEARCH_INDEX_MAX_FILES
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function listFilesWithWalk(
|
|
159
|
+
root: string,
|
|
160
|
+
excludedRepositoryRoots: readonly string[] = []
|
|
161
|
+
): Promise<{ files: string[]; truncated: boolean }> {
|
|
162
|
+
const files: string[] = []
|
|
163
|
+
const queue: Array<{ absolute: string; relative: string }> = [{ absolute: root, relative: '' }]
|
|
164
|
+
let queueIndex = 0
|
|
165
|
+
|
|
166
|
+
while (queueIndex < queue.length) {
|
|
167
|
+
const current = queue[queueIndex]!
|
|
168
|
+
queueIndex += 1
|
|
169
|
+
let dirents
|
|
170
|
+
try {
|
|
171
|
+
dirents = await readdir(current.absolute, { withFileTypes: true })
|
|
172
|
+
} catch {
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const entry of dirents) {
|
|
177
|
+
if (IGNORED_NAMES.has(entry.name) || !isSupportedProtocolName(entry.name)) continue
|
|
178
|
+
const childRelative = joinProtocolPath(current.relative, entry.name)
|
|
179
|
+
if (entry.isDirectory()) {
|
|
180
|
+
if (excludedRepositoryRoots.includes(childRelative)) continue
|
|
181
|
+
queue.push({ absolute: resolve(current.absolute, entry.name), relative: childRelative })
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
if (!entry.isFile()) continue
|
|
185
|
+
files.push(childRelative)
|
|
186
|
+
if (files.length >= SEARCH_INDEX_MAX_FILES) return { files, truncated: true }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { files, truncated: false }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function listFilesWithRepositoryRoots(
|
|
194
|
+
projectRoot: string,
|
|
195
|
+
repositoryRoots: readonly string[]
|
|
196
|
+
): Promise<{ files: string[]; truncated: boolean }> {
|
|
197
|
+
const files = new Set<string>()
|
|
198
|
+
let truncated = false
|
|
199
|
+
const hasProjectRootRepository = repositoryRoots.includes('')
|
|
200
|
+
|
|
201
|
+
if (!hasProjectRootRepository) {
|
|
202
|
+
const workspaceFiles = await listFilesWithWalk(projectRoot, repositoryRoots.filter(Boolean))
|
|
203
|
+
for (const path of workspaceFiles.files) files.add(path)
|
|
204
|
+
truncated ||= workspaceFiles.truncated
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const repositoryRoot of repositoryRoots) {
|
|
208
|
+
const nestedRoots = repositoryRoots.filter(
|
|
209
|
+
(candidate) =>
|
|
210
|
+
candidate !== repositoryRoot &&
|
|
211
|
+
(repositoryRoot === '' || candidate.startsWith(`${repositoryRoot}/`))
|
|
212
|
+
)
|
|
213
|
+
let listing = await listFilesInRepository(projectRoot, repositoryRoot, nestedRoots)
|
|
214
|
+
if (!listing) {
|
|
215
|
+
const absoluteRoot = repositoryRoot
|
|
216
|
+
? resolve(projectRoot, ...repositoryRoot.split('/'))
|
|
217
|
+
: projectRoot
|
|
218
|
+
const nestedRelativeRoots = nestedRoots.map((root) =>
|
|
219
|
+
repositoryRoot ? root.slice(repositoryRoot.length + 1) : root
|
|
220
|
+
)
|
|
221
|
+
const fallback = await listFilesWithWalk(absoluteRoot, nestedRelativeRoots)
|
|
222
|
+
listing = {
|
|
223
|
+
files: fallback.files.map((path) => joinProtocolPath(repositoryRoot, path)),
|
|
224
|
+
truncated: fallback.truncated
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const path of listing.files) {
|
|
228
|
+
files.add(path)
|
|
229
|
+
if (files.size >= SEARCH_INDEX_MAX_FILES) {
|
|
230
|
+
return { files: [...files], truncated: true }
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
truncated ||= listing.truncated
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { files: [...files], truncated }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function getSearchIndex(root: string): Promise<SearchIndexCacheEntry> {
|
|
240
|
+
const key = fileSystemKey(root)
|
|
241
|
+
const now = Date.now()
|
|
242
|
+
const cached = searchIndexCache.get(key)
|
|
243
|
+
if (cached && cached.expiresAt > now) return cached
|
|
244
|
+
|
|
245
|
+
const active = pendingSearchIndexes.get(key)
|
|
246
|
+
if (active) return active
|
|
247
|
+
|
|
248
|
+
const load: Promise<SearchIndexCacheEntry> = Promise.resolve().then(async () => {
|
|
249
|
+
const repositoryRoots = await listL4WorkSessionGitRepositoryRoots(root)
|
|
250
|
+
const listing =
|
|
251
|
+
repositoryRoots.length > 0
|
|
252
|
+
? await listFilesWithRepositoryRoots(root, repositoryRoots)
|
|
253
|
+
: ((await listFilesWithContainingGit(root)) ?? (await listFilesWithWalk(root)))
|
|
254
|
+
const next: SearchIndexCacheEntry = {
|
|
255
|
+
...listing,
|
|
256
|
+
expiresAt: now + SEARCH_CACHE_TTL_MS
|
|
257
|
+
}
|
|
258
|
+
if (pendingSearchIndexes.get(key) === load) {
|
|
259
|
+
for (const [cacheKey, entry] of searchIndexCache) {
|
|
260
|
+
if (entry.expiresAt <= Date.now()) searchIndexCache.delete(cacheKey)
|
|
261
|
+
}
|
|
262
|
+
while (searchIndexCache.size >= SEARCH_CACHE_MAX_CWDS) {
|
|
263
|
+
const oldestKey = searchIndexCache.keys().next().value as string
|
|
264
|
+
searchIndexCache.delete(oldestKey)
|
|
265
|
+
}
|
|
266
|
+
searchIndexCache.set(key, next)
|
|
267
|
+
}
|
|
268
|
+
return next
|
|
269
|
+
})
|
|
270
|
+
pendingSearchIndexes.set(key, load)
|
|
271
|
+
try {
|
|
272
|
+
return await load
|
|
273
|
+
} finally {
|
|
274
|
+
if (pendingSearchIndexes.get(key) === load) pendingSearchIndexes.delete(key)
|
|
275
|
+
}
|
|
276
|
+
}
|