@jetcrab/pi-desk 1.1.16 → 1.1.20-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/docs/plugins.md +3 -1
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-runtime.ts +1 -1
  5. package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-reload.ts +42 -0
  6. package/src/client/l2_biz/workbench/l2-work-session-sidebar.tsx +19 -1
  7. package/src/client/l2_biz/workbench/l2-workbench-chat.ts +45 -0
  8. package/src/client/l2_biz/workbench/l2-workbench.tsx +4 -0
  9. package/src/client/l3_modules/conversation/l3-conversation-markdown.module.css +1 -0
  10. package/src/client/l3_modules/conversation/l3-conversation-markdown.tsx +10 -1
  11. package/src/client/l3_modules/project-files/l3-markdown-preview.tsx +10 -1
  12. package/src/client/l3_modules/project-files/l3-project-document-view.tsx +2 -0
  13. package/src/client/l3_modules/project-files/l3-project-file-browser.tsx +171 -229
  14. package/src/client/l3_modules/project-files/l3-project-file-runtime.ts +133 -70
  15. package/src/client/l3_modules/project-files/l3-project-reader.ts +4 -3
  16. package/src/client/l3_modules/project-files/views/l3-project-search-controls.tsx +201 -0
  17. package/src/client/l3_modules/project-files/views/l3-project-search-results.tsx +333 -0
  18. package/src/client/l4_foundation/ui/code/l4-code-preview.tsx +47 -3
  19. package/src/client/l4_foundation/ui/markdown/l4-markdown-remark.ts +89 -0
  20. package/src/client/l4_foundation/ui/markdown/l4-markdown.module.css +58 -0
  21. package/src/client/l4_foundation/ui/markdown/l4-markdown.tsx +108 -0
  22. package/src/common/l2_biz/work-session/l2-work-session-file-contract.ts +4 -3
  23. package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +6 -0
  24. package/src/common/l3_modules/project-files/l3-project-files-contract.ts +2 -3
  25. package/src/common/l3_modules/project-files/l3-project-files-locale-messages.ts +56 -10
  26. package/src/common/l4_foundation/file/l4-project-file-contract.ts +51 -1
  27. package/src/common/l4_foundation/locale/l4-host-error-messages.ts +11 -0
  28. package/src/common/l4_foundation/locale/l4-locale-messages.ts +16 -0
  29. package/src/instrumentation.ts +0 -2
  30. package/src/server/l1_entry/api/l1-project-read-routes.ts +6 -4
  31. package/src/server/l1_entry/api/l1-project-route.ts +9 -8
  32. package/src/server/l1_entry/api/l1-work-session-file-error.ts +23 -0
  33. package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +0 -3
  34. package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +30 -58
  35. package/src/server/l2_biz/work-session/l2-work-session-manage.ts +0 -4
  36. package/src/server/l4_foundation/file/l4-project-file-index.ts +276 -0
  37. package/src/server/l4_foundation/file/l4-project-search.ts +399 -0
  38. package/src/server/l4_foundation/file/l4-ripgrep-search.ts +192 -0
  39. package/src/server/l4_foundation/file/l4-work-session-project-file.ts +8 -332
  40. package/src/server/l4_foundation/pi/l4-pi-package-maintenance.ts +0 -6
  41. package/src/server/l4_foundation/pi/l4-pi-plugin-capability-probe.ts +1 -3
  42. package/src/server/l4_foundation/pi/l4-pi-plugin-probe.ts +1 -3
  43. package/src/server/l4_foundation/pi/l4-pi-runner-process.ts +3 -15
  44. package/src/server/l4_foundation/process/l4-process-tree.js +31 -5
  45. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  46. package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
  47. package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
  48. package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -1
  49. package/temp/build/pi-desk/release/.next/required-server-files.js +4 -3
  50. package/temp/build/pi-desk/release/.next/required-server-files.json +4 -3
  51. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  52. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  53. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +1 -1
  54. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  57. package/temp/build/pi-desk/release/.next/server/app/_not-found/page.js +1 -1
  58. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  59. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  60. package/temp/build/pi-desk/release/.next/server/app/api/project-files/get/route.js +2 -1
  61. package/temp/build/pi-desk/release/.next/server/app/api/project-files/list/route.js +2 -1
  62. package/temp/build/pi-desk/release/.next/server/app/api/project-files/reveal/route.js +1 -1
  63. package/temp/build/pi-desk/release/.next/server/app/api/project-files/search/route.js +2 -1
  64. package/temp/build/pi-desk/release/.next/server/app/api/project-git/get/route.js +2 -1
  65. package/temp/build/pi-desk/release/.next/server/app/api/project-git-branches/list/route.js +2 -1
  66. package/temp/build/pi-desk/release/.next/server/app/api/project-git-changes/list/route.js +2 -1
  67. package/temp/build/pi-desk/release/.next/server/app/api/project-git-diff/get/route.js +2 -1
  68. package/temp/build/pi-desk/release/.next/server/app/api/project-git-heads/get/route.js +2 -1
  69. package/temp/build/pi-desk/release/.next/server/app/api/project-git-log/list/route.js +2 -1
  70. package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
  71. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  72. package/temp/build/pi-desk/release/.next/server/app/page.js +77 -77
  73. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  74. package/temp/build/pi-desk/release/.next/server/chunks/{33658.js → 10881.js} +8 -8
  75. package/temp/build/pi-desk/release/.next/server/chunks/23054.js +1 -1
  76. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +3 -3
  77. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +5 -5
  78. package/temp/build/pi-desk/release/.next/server/chunks/50520.js +2 -4
  79. package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
  80. package/temp/build/pi-desk/release/.next/server/chunks/70780.js +1 -0
  81. package/temp/build/pi-desk/release/.next/server/chunks/74528.js +1 -1
  82. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +1 -1
  83. package/temp/build/pi-desk/release/.next/server/instrumentation.js +1 -1
  84. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  85. package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
  86. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  87. package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
  88. package/temp/build/pi-desk/release/.next/static/chunks/27611-0890482fe76bd9d6.js +238 -0
  89. package/temp/build/pi-desk/release/.next/static/chunks/{31087-ba426d09d6f68bfd.js → 31087-d8e5765d541d5c26.js} +1 -1
  90. package/temp/build/pi-desk/release/.next/static/chunks/{49516-cc4dcd84d64ab4fe.js → 51474-8dc714cb76b2e60a.js} +2 -2
  91. package/temp/build/pi-desk/release/.next/static/chunks/77592-5bef6acb8e7c4b96.js +13 -0
  92. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-a50c456648537afb.js +3 -0
  93. package/temp/build/pi-desk/release/.next/static/chunks/app/{page-0069319a4fb97211.js → page-0789c43551b29dd2.js} +4 -4
  94. package/temp/build/pi-desk/release/.next/static/chunks/{webpack-40a782fc8a3535b9.js → webpack-d7248206b7fc7ba8.js} +1 -1
  95. package/temp/build/pi-desk/release/.next/static/css/250a6b93cc2222d1.css +1 -0
  96. package/temp/build/pi-desk/release/.next/static/css/6dbae2922f88ca31.css +1 -0
  97. package/temp/build/pi-desk/release/.next/static/css/a4cf28fd76c70165.css +1 -0
  98. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  99. package/temp/build/pi-desk/release/.next/server/chunks/8584.js +0 -2
  100. package/temp/build/pi-desk/release/.next/static/chunks/39654-b5fedd762ea0312e.js +0 -238
  101. package/temp/build/pi-desk/release/.next/static/chunks/75130-d2ca74894aff8085.js +0 -13
  102. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-c0e1c5cef8620cf8.js +0 -3
  103. package/temp/build/pi-desk/release/.next/static/css/6427d02b6edf406f.css +0 -1
  104. package/temp/build/pi-desk/release/.next/static/css/949d0d1a99d5de92.css +0 -1
  105. /package/temp/build/pi-desk/release/.next/static/{DqamF28k0ShQLbNVzXDIj → RLKcK-RQF5FvEeDmS9uih}/_buildManifest.js +0 -0
  106. /package/temp/build/pi-desk/release/.next/static/{DqamF28k0ShQLbNVzXDIj → RLKcK-RQF5FvEeDmS9uih}/_ssgManifest.js +0 -0
@@ -0,0 +1,108 @@
1
+ 'use client'
2
+
3
+ import {
4
+ CheckIcon,
5
+ InfoIcon,
6
+ LightbulbIcon,
7
+ MessageSquareWarningIcon,
8
+ MinusIcon,
9
+ OctagonAlertIcon,
10
+ TriangleAlertIcon
11
+ } from 'lucide-react'
12
+ import type { ComponentProps } from 'react'
13
+ import { useTranslation } from 'react-i18next'
14
+ import { defaultRemarkPlugins, type Components, type StreamdownProps } from 'streamdown'
15
+ import { cn } from '@client/l4_foundation/lib/l4-utils'
16
+ import { remarkL4Markdown } from './l4-markdown-remark'
17
+ import styles from './l4-markdown.module.css'
18
+
19
+ export { L4_MARKDOWN_ALLOWED_TAGS } from './l4-markdown-remark'
20
+
21
+ export const L4_MARKDOWN_REMARK_PLUGINS: NonNullable<StreamdownProps['remarkPlugins']> = [
22
+ ...Object.values(defaultRemarkPlugins),
23
+ remarkL4Markdown
24
+ ]
25
+ export const L4_MARKDOWN_CLASS_NAME = styles.markdown
26
+
27
+ const alerts = {
28
+ note: { icon: InfoIcon, label: 'markdownNote' },
29
+ tip: { icon: LightbulbIcon, label: 'markdownTip' },
30
+ important: { icon: MessageSquareWarningIcon, label: 'markdownImportant' },
31
+ warning: { icon: TriangleAlertIcon, label: 'markdownWarning' },
32
+ caution: { icon: OctagonAlertIcon, label: 'markdownCaution' }
33
+ } as const
34
+
35
+ function MarkdownStatus({
36
+ 'data-pi-markdown-progress': progress,
37
+ ...props
38
+ }: ComponentProps<'input'> & {
39
+ node?: unknown
40
+ 'data-pi-markdown-progress'?: string
41
+ }): React.JSX.Element {
42
+ const { t } = useTranslation('common')
43
+ delete props.node
44
+ if (props.type !== 'checkbox') return <input {...props} />
45
+ const state = progress ? 'progress' : props.checked ? 'completed' : 'pending'
46
+ const marker = progress ? '[-]' : props.checked ? '[x]' : '[ ]'
47
+ const label = t(
48
+ progress ? 'markdownProgress' : props.checked ? 'markdownCompleted' : 'markdownPending'
49
+ )
50
+
51
+ return (
52
+ <span className={styles.status} data-state={state} role="img" aria-label={label} title={label}>
53
+ {progress ? (
54
+ <MinusIcon aria-hidden="true" />
55
+ ) : props.checked ? (
56
+ <CheckIcon aria-hidden="true" />
57
+ ) : null}
58
+ <span className="sr-only" aria-hidden="true">
59
+ {marker}
60
+ </span>
61
+ </span>
62
+ )
63
+ }
64
+
65
+ function MarkdownBlockquote({
66
+ 'data-pi-markdown-alert': kind,
67
+ className,
68
+ children,
69
+ ...props
70
+ }: ComponentProps<'blockquote'> & {
71
+ node?: unknown
72
+ 'data-pi-markdown-alert'?: string
73
+ }): React.JSX.Element {
74
+ const { t } = useTranslation('common')
75
+ delete props.node
76
+ const alert = Object.entries(alerts).find(([name]) => name === kind)?.[1]
77
+
78
+ if (!alert) {
79
+ return (
80
+ <blockquote
81
+ {...props}
82
+ data-streamdown="blockquote"
83
+ className={cn(
84
+ 'my-4 border-muted-foreground/30 border-l-4 pl-4 text-muted-foreground italic',
85
+ className
86
+ )}
87
+ >
88
+ {children}
89
+ </blockquote>
90
+ )
91
+ }
92
+
93
+ const Icon = alert.icon
94
+ return (
95
+ <aside className={styles.alert} role="note" aria-label={t(alert.label)}>
96
+ <div className={styles.alertTitle}>
97
+ <Icon aria-hidden="true" />
98
+ {t(alert.label)}
99
+ </div>
100
+ <div className={styles.alertBody}>{children}</div>
101
+ </aside>
102
+ )
103
+ }
104
+
105
+ export const L4_MARKDOWN_COMPONENTS: Components = {
106
+ input: MarkdownStatus,
107
+ blockquote: MarkdownBlockquote
108
+ }
@@ -10,6 +10,7 @@ import {
10
10
  L4ProjectFileEntrySchema,
11
11
  L4ProjectFileListSchema,
12
12
  L4ProjectFileSearchSchema,
13
+ L4ProjectSearchInputSchema,
13
14
  L4ProjectFileContentSchema,
14
15
  L4ProjectDirectoryPathSchema,
15
16
  L4ProjectFileImageMimeTypeSchema,
@@ -47,9 +48,9 @@ export const L2WorkSessionFileListRequestSchema = L2WorkSessionFileProjectRefSch
47
48
 
48
49
  export const L2WorkSessionFileListResponseSchema = L4ProjectFileListSchema
49
50
 
50
- export const L2WorkSessionFileSearchRequestSchema = L2WorkSessionFileProjectRefSchema.extend({
51
- query: z.string().trim().min(1).max(200)
52
- }).strict()
51
+ export const L2WorkSessionFileSearchRequestSchema = L2WorkSessionFileProjectRefSchema.extend(
52
+ L4ProjectSearchInputSchema.shape
53
+ ).strict()
53
54
 
54
55
  export const L2WorkSessionFileSearchResponseSchema = L4ProjectFileSearchSchema
55
56
 
@@ -202,6 +202,9 @@ export const L2_WORKBENCH_LOCALE_MESSAGES = {
202
202
  deleteSessionTitle: 'Delete work session',
203
203
  copyProjectPath: 'Copy project path',
204
204
  renameSessionTitle: 'Edit title',
205
+ reloadChatData: 'Reload chat data',
206
+ reloadChatDataFailed: 'Could not reload chat data',
207
+ reloadChatSourceChanged: 'The session has changed. Select it again.',
205
208
  sessionTitleLabel: 'Session title',
206
209
  renameSessionFailed: 'Could not save the session title',
207
210
  sidebarSessions: 'Work sessions',
@@ -645,6 +648,9 @@ export const L2_WORKBENCH_LOCALE_MESSAGES = {
645
648
  deleteSessionTitle: '删除工作会话',
646
649
  copyProjectPath: '复制项目路径',
647
650
  renameSessionTitle: '修改标题',
651
+ reloadChatData: '重新加载聊天数据',
652
+ reloadChatDataFailed: '重新加载聊天数据失败',
653
+ reloadChatSourceChanged: '会话已变化,请重新选择会话',
648
654
  sessionTitleLabel: '会话标题',
649
655
  renameSessionFailed: '保存会话标题失败',
650
656
  sidebarSessions: '工作会话',
@@ -6,6 +6,7 @@ import {
6
6
  L4ProjectFileImagePreviewModeSchema,
7
7
  L4ProjectFileListSchema,
8
8
  L4ProjectFileSearchSchema,
9
+ L4ProjectSearchInputSchema,
9
10
  L4ProjectFileContentSchema
10
11
  } from '@common/l4_foundation/file/l4-project-file-contract'
11
12
  import {
@@ -67,9 +68,7 @@ export const L3ProjectFileList = {
67
68
  } as const
68
69
  export const L3ProjectFileSearch = {
69
70
  path: '/api/project-files/search',
70
- request: L3ProjectReadContextSchema.extend({
71
- query: z.string().trim().min(1).max(200)
72
- }).strict(),
71
+ request: L3ProjectReadContextSchema.extend(L4ProjectSearchInputSchema.shape).strict(),
73
72
  response: L4ProjectFileSearchSchema
74
73
  } as const
75
74
  export const L3ProjectFileGet = {
@@ -19,16 +19,39 @@ export const L3_PROJECT_FILES_LOCALE_MESSAGES = {
19
19
  sessionDirectory: 'Original folder',
20
20
  unavailable: 'Unavailable',
21
21
  projectFiles: '{{name}} project files',
22
- searchFiles: 'Search filenames',
23
- closeSearch: 'Close file search',
22
+ searchFiles: 'Search filenames or paths',
23
+ searchAll: 'Search files or content',
24
+ searchContent: 'Search file content',
25
+ searchScope: 'Search scope',
26
+ searchScopeAll: 'All',
27
+ searchScopeFiles: 'Files',
28
+ searchScopeContent: 'Content',
29
+ searchCaseSensitive: 'Match case',
30
+ searchWholeWord: 'Match whole word',
31
+ searchRegex: 'Use regular expression',
32
+ searchResults: 'Search results',
33
+ searchStart: 'Search this project',
34
+ searchTryOptions: 'Try another query or adjust the search options.',
35
+ searchFailed: 'Could not search the project. Please try again.',
36
+ searchCounts: '{{files}} files · {{lines}} matching lines',
37
+ searchShownCounts: 'Shown: {{counts}}',
38
+ searchFileHeading: 'Filenames',
39
+ searchContentHeading: 'Content',
40
+ searchContentCounts: '{{lines}} matching lines · {{files}} files',
41
+ searchProjectRoot: 'Project root',
42
+ searchExpandFiles: 'Show {{count}} more files',
43
+ searchOpenLine: 'Open {{path}}, line {{line}}',
44
+ searchLineUnavailable: 'The file content has changed and the matching line no longer exists.',
45
+ searchTextLimit: 'Content search: previewable UTF-8 text up to 5 MiB.',
46
+ closeSearch: 'Exit search',
24
47
  layout: 'File organization',
25
48
  directory: 'Folders',
26
49
  repository: 'Repositories',
27
50
  changesOnly: 'Changes only',
28
- searchProject: 'Search project files',
29
- noMatches: 'No matching files',
51
+ searchProject: 'Search project',
52
+ noMatches: 'No matching results',
30
53
  searching: 'Searching…',
31
- resultLimit: 'Showing the first 100 results.',
54
+ resultLimit: 'Results are limited. Refine your query or narrow the search scope.',
32
55
  copied: 'Copied.',
33
56
  copyFailed: 'Could not copy. Please try again.',
34
57
  revealRequested: 'Opened the system file manager.',
@@ -119,16 +142,39 @@ export const L3_PROJECT_FILES_LOCALE_MESSAGES = {
119
142
  sessionDirectory: '原目录',
120
143
  unavailable: '不可用',
121
144
  projectFiles: '{{name}} 项目文件',
122
- searchFiles: '搜索文件名',
123
- closeSearch: '关闭文件搜索',
145
+ searchFiles: '搜索文件名或路径',
146
+ searchAll: '搜索文件或内容',
147
+ searchContent: '搜索文件内容',
148
+ searchScope: '搜索范围',
149
+ searchScopeAll: '全部',
150
+ searchScopeFiles: '文件',
151
+ searchScopeContent: '内容',
152
+ searchCaseSensitive: '区分大小写',
153
+ searchWholeWord: '全词匹配',
154
+ searchRegex: '使用正则表达式',
155
+ searchResults: '搜索结果',
156
+ searchStart: '搜索这个项目',
157
+ searchTryOptions: '试试其他关键词,或调整搜索选项。',
158
+ searchFailed: '项目搜索失败,请重试。',
159
+ searchCounts: '{{files}} 个文件 · {{lines}} 个命中行',
160
+ searchShownCounts: '已显示:{{counts}}',
161
+ searchFileHeading: '文件名',
162
+ searchContentHeading: '内容',
163
+ searchContentCounts: '{{lines}} 个命中行 · {{files}} 个文件',
164
+ searchProjectRoot: '项目根目录',
165
+ searchExpandFiles: '展开其余 {{count}} 个文件',
166
+ searchOpenLine: '打开 {{path}} 第 {{line}} 行',
167
+ searchLineUnavailable: '文件内容已变化,搜索命中行已不存在',
168
+ searchTextLimit: '正文搜索:可预览的 UTF-8 文本,不超过 5 MiB。',
169
+ closeSearch: '退出搜索',
124
170
  layout: '文件组织方式',
125
171
  directory: '目录',
126
172
  repository: '仓库',
127
173
  changesOnly: '只看变更',
128
- searchProject: '搜索项目文件',
129
- noMatches: '没有匹配文件',
174
+ searchProject: '搜索项目',
175
+ noMatches: '没有匹配结果',
130
176
  searching: '正在搜索…',
131
- resultLimit: '结果较多,仅显示前 100 条。',
177
+ resultLimit: '结果较多,已截断显示。请细化关键词或缩小搜索范围。',
132
178
  copied: '已复制。',
133
179
  copyFailed: '复制失败,请重试。',
134
180
  revealRequested: '已请求运行端文件管理器定位。',
@@ -3,6 +3,8 @@ import { z } from 'zod'
3
3
  export const L4_PROJECT_FILE_PATH_MAX_LENGTH = 4096
4
4
  export const L4_PROJECT_FILE_LIST_MAX_ENTRIES = 5000
5
5
  export const L4_PROJECT_FILE_SEARCH_MAX_RESULTS = 100
6
+ export const L4_PROJECT_CONTENT_SEARCH_MAX_RESULTS = 200
7
+ export const L4_PROJECT_SEARCH_TEXT_MAX_LENGTH = 1000
6
8
  export const L4_PROJECT_TEXT_FILE_MAX_BYTES = 5 * 1024 * 1024
7
9
  export const L4_PROJECT_CODE_PARSE_MAX_BYTES = 1024 * 1024
8
10
  export const L4_PROJECT_IMAGE_FILE_MAX_BYTES = 10 * 1024 * 1024
@@ -76,9 +78,53 @@ export const L4ProjectFileListSchema = z
76
78
  })
77
79
  .strict()
78
80
 
81
+ export const L4ProjectSearchInputSchema = z
82
+ .object({
83
+ query: z
84
+ .string()
85
+ .min(1)
86
+ .max(200)
87
+ .refine(
88
+ (value) => value.trim().length > 0 && !/[\0\r\n]/.test(value),
89
+ '搜索词必须是非空单行文本'
90
+ ),
91
+ scope: z.enum(['all', 'files', 'content']).default('all'),
92
+ caseSensitive: z.boolean().default(false),
93
+ wholeWord: z.boolean().default(false),
94
+ regex: z.boolean().default(false)
95
+ })
96
+ .strict()
97
+
98
+ export const L4ProjectSearchRangeSchema = z
99
+ .object({ start: z.number().int().nonnegative(), end: z.number().int().nonnegative() })
100
+ .strict()
101
+ .refine((range) => range.start <= range.end, '搜索命中区间无效')
102
+
103
+ export const L4ProjectFileMatchSchema = z
104
+ .object({ path: L4ProjectFilePathSchema, ranges: z.array(L4ProjectSearchRangeSchema) })
105
+ .strict()
106
+ .refine(
107
+ (match) => match.ranges.every((range) => range.end <= match.path.length),
108
+ '文件名命中区间越界'
109
+ )
110
+
111
+ export const L4ProjectContentMatchSchema = z
112
+ .object({
113
+ path: L4ProjectFilePathSchema,
114
+ line: z.number().int().positive(),
115
+ text: z.string().max(L4_PROJECT_SEARCH_TEXT_MAX_LENGTH),
116
+ ranges: z.array(L4ProjectSearchRangeSchema)
117
+ })
118
+ .strict()
119
+ .refine(
120
+ (match) => match.ranges.every((range) => range.end <= match.text.length),
121
+ '正文命中区间越界'
122
+ )
123
+
79
124
  export const L4ProjectFileSearchSchema = z
80
125
  .object({
81
- matches: z.array(L4ProjectFilePathSchema).max(L4_PROJECT_FILE_SEARCH_MAX_RESULTS),
126
+ fileMatches: z.array(L4ProjectFileMatchSchema).max(L4_PROJECT_FILE_SEARCH_MAX_RESULTS),
127
+ contentMatches: z.array(L4ProjectContentMatchSchema).max(L4_PROJECT_CONTENT_SEARCH_MAX_RESULTS),
82
128
  truncated: z.boolean()
83
129
  })
84
130
  .strict()
@@ -103,6 +149,10 @@ export const L4ProjectFileContentSchema = z.discriminatedUnion('kind', [
103
149
 
104
150
  export type L4ProjectFileEntry = z.infer<typeof L4ProjectFileEntrySchema>
105
151
  export type L4ProjectFileList = z.infer<typeof L4ProjectFileListSchema>
152
+ export type L4ProjectSearchInput = z.infer<typeof L4ProjectSearchInputSchema>
153
+ export type L4ProjectSearchRange = z.infer<typeof L4ProjectSearchRangeSchema>
154
+ export type L4ProjectFileMatch = z.infer<typeof L4ProjectFileMatchSchema>
155
+ export type L4ProjectContentMatch = z.infer<typeof L4ProjectContentMatchSchema>
106
156
  export type L4ProjectFileSearch = z.infer<typeof L4ProjectFileSearchSchema>
107
157
  export type L4ProjectFileContent = z.infer<typeof L4ProjectFileContentSchema>
108
158
  export type L4ProjectFileImagePreviewMode = z.infer<typeof L4ProjectFileImagePreviewModeSchema>
@@ -15,6 +15,12 @@ export const L4_HOST_ERROR_MESSAGES = {
15
15
  imageTooLarge: 'This image is too large for online preview',
16
16
  previewUnsupported: 'This file type cannot be previewed online',
17
17
  fileReadFailed: 'Could not read the project file',
18
+ projectSearchPatternInvalid: 'The search expression is invalid or uses unsupported syntax',
19
+ projectSearchUnavailable:
20
+ 'The project search engine is unavailable. Reinstall the complete runtime dependencies',
21
+ projectSearchTimeout: 'Project search timed out. Narrow the search scope and try again',
22
+ projectSearchReadFailed: 'Could not read project search files or results',
23
+ projectSearchCancelled: 'Project search was cancelled',
18
24
  revealFailed: 'Could not open the file location',
19
25
  repositoryMissing: 'Git repository not found',
20
26
  gitOutsideProject: 'The Git target is outside the current project',
@@ -169,6 +175,11 @@ export const L4_HOST_ERROR_MESSAGES = {
169
175
  imageTooLarge: '图片过大,暂不支持在线预览',
170
176
  previewUnsupported: '当前文件类型不支持在线预览',
171
177
  fileReadFailed: '读取项目文件失败',
178
+ projectSearchPatternInvalid: '搜索表达式无效或包含不支持的语法',
179
+ projectSearchUnavailable: '项目搜索引擎不可用,请重新安装完整运行依赖',
180
+ projectSearchTimeout: '项目搜索超时,请缩小搜索范围后重试',
181
+ projectSearchReadFailed: '无法读取项目搜索文件或结果',
182
+ projectSearchCancelled: '项目搜索已取消',
172
183
  revealFailed: '打开所在文件夹失败',
173
184
  repositoryMissing: 'Git 仓库不存在',
174
185
  gitOutsideProject: 'Git 目标不在当前项目目录内',
@@ -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: '代码查看器加载失败,请重新打开文件。',
@@ -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(contracts.L3ProjectFileSearch, (input) =>
24
- searchL4WorkSessionProjectFiles(input.cwd, input.query)
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
- console.warn('[Pi Desk][Project] 项目请求失败', {
60
- path: contract.path,
61
- cwd: input.cwd,
62
- workId: input.workId,
63
- error: error instanceof Error ? error.message : String(error)
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' })
@@ -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