@jetcrab/pi-desk 1.1.8 → 1.1.10-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.
- package/docs/plugins.md +15 -1
- package/package.json +7 -7
- package/src/client/l2_biz/auth/l2-login-biz.ts +4 -1
- package/src/client/l2_biz/settings/hooks/l2-use-auth-settings.ts +18 -1
- package/src/client/l2_biz/settings/l2-auth-settings-biz.ts +18 -1
- package/src/client/l2_biz/settings/l2-auth-settings.tsx +19 -5
- package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-runtime.ts +144 -23
- package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-view.tsx +9 -5
- package/src/client/l2_biz/workbench/file-workspace/l2-project-file-browser.tsx +94 -5
- package/src/client/l2_biz/workbench/file-workspace/l2-project-preview-biz.ts +48 -0
- package/src/client/l2_biz/workbench/git-workspace/l2-git-workspace-runtime.ts +26 -8
- package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +70 -47
- package/src/client/l2_biz/workbench/l2-work-session-columns.tsx +1 -0
- package/src/client/l2_biz/workbench/l2-workbench-chat-view.tsx +18 -14
- package/src/client/l2_biz/workbench/l2-workbench.tsx +37 -6
- package/src/client/l3_modules/conversation/l3-conversation-messages.tsx +28 -9
- package/src/client/l3_modules/project-files/l3-project-file-browser.tsx +27 -15
- package/src/client/l3_modules/project-files/l3-project-file-runtime.ts +7 -0
- package/src/client/l4_foundation/ui/code/l4-code-preferences.ts +1 -1
- package/src/common/l2_biz/auth/l2-auth-contract.ts +14 -1
- package/src/common/l2_biz/auth/l2-auth-messages.ts +27 -10
- package/src/common/l3_modules/app-runtime/l3-app-runtime-contract.ts +4 -1
- package/src/common/l3_modules/conversation/l3-conversation-locale-messages.ts +4 -0
- package/src/common/l3_modules/project-files/l3-project-files-locale-messages.ts +8 -0
- package/src/common/l4_foundation/file/l4-project-preview-contract.ts +25 -0
- package/src/server/l1_entry/api/l1-auth-login-route.ts +20 -6
- package/src/server/l1_entry/api/l1-auth-settings-routes.ts +1 -1
- package/src/server/l1_entry/api/l1-project-route.ts +14 -5
- package/src/server/l1_entry/node/l1-server.ts +4 -7
- package/src/server/l2_biz/app-runtime/l2-app-runtime.ts +6 -1
- package/src/server/l2_biz/auth/l2-auth.ts +52 -2
- package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +28 -0
- package/src/server/l2_biz/work-session/l2-work-session-manage.ts +8 -0
- package/src/server/l3_modules/app-runtime/l3-app-notification-runtime.ts +48 -14
- package/src/server/l4_foundation/auth/l4-auth-config.ts +19 -3
- package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +2 -1
- package/src/server/l4_foundation/pi/l4-pi-global-runtime.js +3 -13
- package/src/server/l4_foundation/pi/l4-pi-session-plugin-runtime.ts +60 -0
- package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
- 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 -1
- package/temp/build/pi-desk/release/.next/required-server-files.js +3 -3
- package/temp/build/pi-desk/release/.next/required-server-files.json +3 -3
- 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 +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- 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_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/auth/login/route.js +2 -4
- 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/health/route.js +1 -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/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 +64 -64
- package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/23054.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/25406.js +7 -7
- package/temp/build/pi-desk/release/.next/server/chunks/35985.js +3 -3
- package/temp/build/pi-desk/release/.next/server/chunks/49900.js +4 -0
- package/temp/build/pi-desk/release/.next/server/chunks/5034.js +1 -0
- package/temp/build/pi-desk/release/.next/server/chunks/50520.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/61211.js +2 -0
- package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/77953.js +1 -0
- package/temp/build/pi-desk/release/.next/server/chunks/8584.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/92386.js +4 -4
- package/temp/build/pi-desk/release/.next/server/chunks/93049.js +1 -0
- package/temp/build/pi-desk/release/.next/server/chunks/94296.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/98227.js +3 -0
- 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/chunks/45992-304a49aaaebd853e.js +13 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{34903-e4205c0becabea76.js → 47340-c1c09c77c1a88514.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/70669.07f3670bfb7d6dd2.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{layout-586f6f88483051a7.js → layout-839162b6cfae8347.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-4532a3bbee48e219.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-ca3020d12ae98f64.js +158 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{webpack-26261285e181235a.js → webpack-735430d61da52e9d.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/css/8a41765636e09e52.css +1 -0
- package/temp/build/pi-desk/release/.next/trace-build +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/31442.js +0 -3
- package/temp/build/pi-desk/release/.next/server/chunks/37681.js +0 -1
- package/temp/build/pi-desk/release/.next/server/chunks/65589.js +0 -4
- package/temp/build/pi-desk/release/.next/server/chunks/99672.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/45992-a39a014497ec4dd1.js +0 -13
- package/temp/build/pi-desk/release/.next/static/chunks/70669.8b4231a8675499e0.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/login/page-bf893718bb151f3c.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-c92ecc3b7351294d.js +0 -158
- package/temp/build/pi-desk/release/.next/static/css/3213ee28336bddc2.css +0 -1
- /package/temp/build/pi-desk/release/.next/static/{ZYYO9lnf3gpocPCjcjYmq → d7n5VhA6J-kI3A_-XJ4TQ}/_buildManifest.js +0 -0
- /package/temp/build/pi-desk/release/.next/static/{ZYYO9lnf3gpocPCjcjYmq → d7n5VhA6J-kI3A_-XJ4TQ}/_ssgManifest.js +0 -0
|
@@ -23,8 +23,21 @@ export const L2AuthSettingsGetResponseSchema = z
|
|
|
23
23
|
})
|
|
24
24
|
.strict()
|
|
25
25
|
|
|
26
|
+
export const L2AuthNewPasswordSchema = z
|
|
27
|
+
.string()
|
|
28
|
+
.min(12)
|
|
29
|
+
.max(256)
|
|
30
|
+
.refine(
|
|
31
|
+
(password) => password === password.trim() && !/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(password)
|
|
32
|
+
)
|
|
33
|
+
.refine(
|
|
34
|
+
(password) =>
|
|
35
|
+
[/[A-Z]/, /[a-z]/, /[0-9]/, /[\p{P}\p{S}]/u].filter((pattern) => pattern.test(password))
|
|
36
|
+
.length >= 3
|
|
37
|
+
)
|
|
38
|
+
|
|
26
39
|
export const L2AuthSettingsReplaceRequestSchema = z.union([
|
|
27
|
-
L2AuthLoginRequestSchema.extend({ password:
|
|
40
|
+
L2AuthLoginRequestSchema.extend({ password: L2AuthNewPasswordSchema }),
|
|
28
41
|
z.object({ password: z.null() }).strict()
|
|
29
42
|
])
|
|
30
43
|
export const L2AuthSettingsReplaceResponseSchema = z.object({}).strict()
|
|
@@ -9,8 +9,12 @@ export const L2_AUTH_MESSAGES = {
|
|
|
9
9
|
changePassword: 'Change password',
|
|
10
10
|
setPassword: 'Set a password',
|
|
11
11
|
newPassword: 'New password',
|
|
12
|
-
passwordHint:
|
|
13
|
-
|
|
12
|
+
passwordHint:
|
|
13
|
+
'12–256 characters; use at least three of: uppercase letters, lowercase letters, numbers and symbols. No leading/trailing spaces, line breaks or invisible control characters.',
|
|
14
|
+
generatePassword: 'Generate password',
|
|
15
|
+
generatePasswordHint: 'Generate a random 16-character password',
|
|
16
|
+
generatePasswordFailed: 'Unable to generate a password. Please try again.',
|
|
17
|
+
saveEffect: 'Keep your password safe. Saving requires all devices to sign in again.',
|
|
14
18
|
enable: 'Enable sign-in protection',
|
|
15
19
|
saveAndSignIn: 'Save and sign in again',
|
|
16
20
|
saving: 'Saving…',
|
|
@@ -27,7 +31,7 @@ export const L2_AUTH_MESSAGES = {
|
|
|
27
31
|
recoveryDescription:
|
|
28
32
|
'Delete this file on the device running the service, then refresh the page to restore access.',
|
|
29
33
|
settingsFailed: 'Unable to load or save sign-in protection settings.',
|
|
30
|
-
settingsInvalid: '
|
|
34
|
+
settingsInvalid: 'The password does not meet the requirements shown below the field.',
|
|
31
35
|
configUnavailable:
|
|
32
36
|
'Unable to read sign-in protection settings. Repair the configuration or delete auth.json and set it up again.',
|
|
33
37
|
crossOriginDenied: 'Cross-site changes to sign-in protection are not allowed.',
|
|
@@ -36,8 +40,13 @@ export const L2_AUTH_MESSAGES = {
|
|
|
36
40
|
submitting: 'Signing in…',
|
|
37
41
|
loginFailed: 'Unable to sign in',
|
|
38
42
|
invalidResponse: 'Invalid response from the sign-in service',
|
|
39
|
-
invalidCredentials: '
|
|
40
|
-
invalidInput: '
|
|
43
|
+
invalidCredentials: 'Sign-in failed. Check your sign-in details and try again.',
|
|
44
|
+
invalidInput: 'Sign-in failed. Check your sign-in details and try again.',
|
|
45
|
+
unavailable: 'Sign-in is temporarily unavailable. Please try again later.',
|
|
46
|
+
tooManyAttempts: 'Too many sign-in attempts. Please try again later.',
|
|
47
|
+
loginAlertTitle: 'Unusual sign-in activity',
|
|
48
|
+
loginAlertDescription:
|
|
49
|
+
'More than 5 consecutive sign-in failures. Check whether these attempts were yours.',
|
|
41
50
|
invalidClient: 'Invalid client identifier'
|
|
42
51
|
},
|
|
43
52
|
'zh-CN': {
|
|
@@ -50,8 +59,12 @@ export const L2_AUTH_MESSAGES = {
|
|
|
50
59
|
changePassword: '修改密码',
|
|
51
60
|
setPassword: '设置密码',
|
|
52
61
|
newPassword: '新密码',
|
|
53
|
-
passwordHint:
|
|
54
|
-
|
|
62
|
+
passwordHint:
|
|
63
|
+
'12–256 个字符,至少包含大写字母、小写字母、数字、符号中的三类;不能有首尾空白、换行或不可见控制字符。',
|
|
64
|
+
generatePassword: '生成密码',
|
|
65
|
+
generatePasswordHint: '随机生成 16 位密码',
|
|
66
|
+
generatePasswordFailed: '生成密码失败,请重试。',
|
|
67
|
+
saveEffect: '请妥善保存密码。保存后,所有设备都需要重新登录。',
|
|
55
68
|
enable: '启用登录保护',
|
|
56
69
|
saveAndSignIn: '保存并重新登录',
|
|
57
70
|
saving: '正在保存…',
|
|
@@ -66,7 +79,7 @@ export const L2_AUTH_MESSAGES = {
|
|
|
66
79
|
recovery: '忘记密码?',
|
|
67
80
|
recoveryDescription: '在运行服务的设备上删除下面的文件,然后刷新页面,即可恢复访问。',
|
|
68
81
|
settingsFailed: '登录保护设置处理失败。',
|
|
69
|
-
settingsInvalid: '
|
|
82
|
+
settingsInvalid: '密码不符合要求,请按输入框下方的规则调整。',
|
|
70
83
|
configUnavailable: '登录保护配置无法读取,请修复配置或删除 auth.json 后重新设置。',
|
|
71
84
|
crossOriginDenied: '不允许跨站修改登录保护。',
|
|
72
85
|
password: '密码',
|
|
@@ -74,8 +87,12 @@ export const L2_AUTH_MESSAGES = {
|
|
|
74
87
|
submitting: '正在登录',
|
|
75
88
|
loginFailed: '登录失败',
|
|
76
89
|
invalidResponse: '登录服务响应无效',
|
|
77
|
-
invalidCredentials: '
|
|
78
|
-
invalidInput: '
|
|
90
|
+
invalidCredentials: '登录失败,请检查登录信息后重试。',
|
|
91
|
+
invalidInput: '登录失败,请检查登录信息后重试。',
|
|
92
|
+
unavailable: '登录暂不可用,请稍后重试。',
|
|
93
|
+
tooManyAttempts: '登录尝试过多,请稍后重试。',
|
|
94
|
+
loginAlertTitle: '登录异常提醒',
|
|
95
|
+
loginAlertDescription: '登录已连续失败超过 5 次,请确认是否为本人操作。',
|
|
79
96
|
invalidClient: '客户端标识无效'
|
|
80
97
|
}
|
|
81
98
|
} as const
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { L4TimestampMsSchema } from '@common/l4_foundation/l4-timestamp-contract'
|
|
10
10
|
|
|
11
11
|
export const L3AppNotificationIdSchema = z.string().uuid()
|
|
12
|
+
export const L3AppNotificationKeySchema = z.string().trim().min(1).max(256)
|
|
12
13
|
export const L3AppNotificationLevelSchema = z.enum(['info', 'success', 'warning', 'error'])
|
|
13
14
|
export const L3AppNotificationTitleSchema = z.string().trim().min(1).max(120)
|
|
14
15
|
export const L3AppNotificationDescriptionSchema = z.string().trim().max(320).nullable()
|
|
@@ -51,7 +52,8 @@ export const L3AppNotificationChangesSchema = z
|
|
|
51
52
|
.object({
|
|
52
53
|
level: L3AppNotificationLevelSchema.optional(),
|
|
53
54
|
title: L3AppNotificationTitleSchema.optional(),
|
|
54
|
-
description: L3AppNotificationDescriptionSchema.optional()
|
|
55
|
+
description: L3AppNotificationDescriptionSchema.optional(),
|
|
56
|
+
event: L3AppNotificationEventSchema.nullable().optional()
|
|
55
57
|
})
|
|
56
58
|
.strict()
|
|
57
59
|
.refine((changes) => Object.keys(changes).length > 0, {
|
|
@@ -173,6 +175,7 @@ export const L3PluginNotificationEventInputSchema = z
|
|
|
173
175
|
|
|
174
176
|
export const L3PluginNotificationPublishInputSchema = z
|
|
175
177
|
.object({
|
|
178
|
+
key: L3AppNotificationKeySchema.optional(),
|
|
176
179
|
level: L3AppNotificationLevelSchema,
|
|
177
180
|
title: L3AppNotificationTitleSchema,
|
|
178
181
|
description: z.string().trim().max(320).optional(),
|
|
@@ -38,6 +38,8 @@ export const L3_CONVERSATION_LOCALE_MESSAGES = {
|
|
|
38
38
|
thinkingLoading: 'Loading thoughts…',
|
|
39
39
|
thinkingUnavailable: 'Thoughts unavailable',
|
|
40
40
|
thinkingProcess: 'Reasoning',
|
|
41
|
+
thinkingTruncated: 'Showing the last {{count}} characters',
|
|
42
|
+
copyThinking: 'Copy full reasoning',
|
|
41
43
|
streamInterrupted: 'The response stream ended before the model completed its answer.',
|
|
42
44
|
requestTimedOut: 'The model did not complete its response in time.',
|
|
43
45
|
modelFailed: 'The model request failed without a detailed error.',
|
|
@@ -183,6 +185,8 @@ export const L3_CONVERSATION_LOCALE_MESSAGES = {
|
|
|
183
185
|
thinkingLoading: '正在加载思考内容…',
|
|
184
186
|
thinkingUnavailable: '思考内容不可用',
|
|
185
187
|
thinkingProcess: '思考过程',
|
|
188
|
+
thinkingTruncated: '仅显示最近 {{count}} 个字符',
|
|
189
|
+
copyThinking: '复制完整思考',
|
|
186
190
|
streamInterrupted: '模型响应流意外中断:连接在回答完成前被关闭。',
|
|
187
191
|
requestTimedOut: '模型请求超时:在限定时间内未收到完整响应。',
|
|
188
192
|
modelFailed: '模型请求失败:服务端未提供具体错误信息。',
|
|
@@ -13,6 +13,10 @@ export const L3_PROJECT_FILES_LOCALE_MESSAGES = {
|
|
|
13
13
|
noChangedFiles: 'No changed files',
|
|
14
14
|
noChanges: 'No changes',
|
|
15
15
|
gitBranches: 'Git branches',
|
|
16
|
+
gitBranchReadOnly: 'Git branch (read-only)',
|
|
17
|
+
selectPreviewDirectory: 'Switch preview directory',
|
|
18
|
+
previewDirectories: 'Preview directories',
|
|
19
|
+
sessionDirectory: 'Original folder',
|
|
16
20
|
unavailable: 'Unavailable',
|
|
17
21
|
projectFiles: '{{name}} project files',
|
|
18
22
|
searchFiles: 'Search filenames',
|
|
@@ -107,6 +111,10 @@ export const L3_PROJECT_FILES_LOCALE_MESSAGES = {
|
|
|
107
111
|
noChangedFiles: '没有变更文件',
|
|
108
112
|
noChanges: '没有变更',
|
|
109
113
|
gitBranches: 'Git 分支',
|
|
114
|
+
gitBranchReadOnly: 'Git 分支(只读)',
|
|
115
|
+
selectPreviewDirectory: '切换预览目录',
|
|
116
|
+
previewDirectories: '预览目录',
|
|
117
|
+
sessionDirectory: '原目录',
|
|
110
118
|
unavailable: '不可用',
|
|
111
119
|
projectFiles: '{{name}} 项目文件',
|
|
112
120
|
searchFiles: '搜索文件名',
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const L4_PROJECT_PREVIEW_PLUGIN_KEY = 'project-preview'
|
|
4
|
+
export const L4_PROJECT_PREVIEW_MAX_DIRECTORIES = 32
|
|
5
|
+
|
|
6
|
+
export const L4ProjectPreviewStateSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
directories: z.array(z.string().min(1).max(4096)).max(L4_PROJECT_PREVIEW_MAX_DIRECTORIES),
|
|
9
|
+
defaultDirectory: z.string().min(1).max(4096).nullable()
|
|
10
|
+
})
|
|
11
|
+
.strict()
|
|
12
|
+
|
|
13
|
+
export type L4ProjectPreviewState = z.infer<typeof L4ProjectPreviewStateSchema>
|
|
14
|
+
|
|
15
|
+
export function readL4ProjectPreviewState(value: unknown): L4ProjectPreviewState {
|
|
16
|
+
const result = L4ProjectPreviewStateSchema.safeParse(value)
|
|
17
|
+
return result.success ? result.data : { directories: [], defaultDirectory: null }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function projectPreviewDirectoryKey(directory: string): string {
|
|
21
|
+
const normalized = directory.replaceAll('\\', '/').replace(/\/+$/, '') || '/'
|
|
22
|
+
return /^[a-z]:/i.test(normalized) || normalized.startsWith('//')
|
|
23
|
+
? normalized.toLowerCase()
|
|
24
|
+
: normalized
|
|
25
|
+
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
L2AuthLoginResponseSchema
|
|
6
6
|
} from '@common/l2_biz/auth/l2-auth-contract'
|
|
7
7
|
import { authenticateL2WebLogin } from '@server/l2_biz/auth/l2-auth'
|
|
8
|
-
import { L4AuthConfigError } from '@server/l4_foundation/auth/l4-auth-config'
|
|
8
|
+
import { L4AuthBusyError, L4AuthConfigError } from '@server/l4_foundation/auth/l4-auth-config'
|
|
9
9
|
import {
|
|
10
10
|
createL4WebAuthCookieHeader,
|
|
11
11
|
shouldUseL4SecureWebCookie
|
|
@@ -25,18 +25,24 @@ export async function POST(request: Request): Promise<Response> {
|
|
|
25
25
|
try {
|
|
26
26
|
payload = await request.json()
|
|
27
27
|
} catch {
|
|
28
|
-
return createL4ApiErrorResponse(400, '
|
|
28
|
+
return createL4ApiErrorResponse(400, '登录失败,请检查登录信息后重试。', {
|
|
29
|
+
key: 'auth:invalidInput'
|
|
30
|
+
})
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
const input = L2AuthLoginRequestSchema.safeParse(payload)
|
|
32
34
|
if (!input.success)
|
|
33
|
-
return createL4ApiErrorResponse(400, '
|
|
35
|
+
return createL4ApiErrorResponse(400, '登录失败,请检查登录信息后重试。', {
|
|
36
|
+
key: 'auth:invalidInput'
|
|
37
|
+
})
|
|
34
38
|
|
|
35
39
|
try {
|
|
36
40
|
const result = await authenticateL2WebLogin(input.data)
|
|
37
41
|
if (!result) {
|
|
38
42
|
console.warn('[Pi Desk][AuthLoginApi] 登录凭据无效', { clientId })
|
|
39
|
-
return createL4ApiErrorResponse(401, '
|
|
43
|
+
return createL4ApiErrorResponse(401, '登录失败,请检查登录信息后重试。', {
|
|
44
|
+
key: 'auth:invalidCredentials'
|
|
45
|
+
})
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
const response = createL4ApiSuccessResponse(L2AuthLoginResponseSchema.parse({}))
|
|
@@ -53,13 +59,21 @@ export async function POST(request: Request): Promise<Response> {
|
|
|
53
59
|
console.info('[Pi Desk][AuthLoginApi] 登录成功', { clientId })
|
|
54
60
|
return response
|
|
55
61
|
} catch (error) {
|
|
62
|
+
if (error instanceof L4AuthBusyError) {
|
|
63
|
+
const response = createL4ApiErrorResponse(429, error.message, { key: 'auth:tooManyAttempts' })
|
|
64
|
+
response.headers.set('Retry-After', '1')
|
|
65
|
+
response.headers.set('Cache-Control', 'no-store')
|
|
66
|
+
return response
|
|
67
|
+
}
|
|
56
68
|
console.error('[Pi Desk][AuthLoginApi] 登录处理失败', {
|
|
57
69
|
clientId,
|
|
58
70
|
errorName: error instanceof Error ? error.name : 'UnknownError'
|
|
59
71
|
})
|
|
60
72
|
if (error instanceof L4AuthConfigError) {
|
|
61
|
-
return createL4ApiErrorResponse(503,
|
|
73
|
+
return createL4ApiErrorResponse(503, '登录暂不可用,请稍后重试。', {
|
|
74
|
+
key: 'auth:unavailable'
|
|
75
|
+
})
|
|
62
76
|
}
|
|
63
|
-
return createL4ApiErrorResponse(500, '
|
|
77
|
+
return createL4ApiErrorResponse(500, '登录暂不可用,请稍后重试。', { key: 'auth:unavailable' })
|
|
64
78
|
}
|
|
65
79
|
}
|
|
@@ -65,7 +65,7 @@ async function handle(request: Request, action: 'get' | 'replace'): Promise<Resp
|
|
|
65
65
|
|
|
66
66
|
const input = L2AuthSettingsReplaceRequestSchema.safeParse(payload)
|
|
67
67
|
if (!input.success) {
|
|
68
|
-
return createL4ApiErrorResponse(400, '
|
|
68
|
+
return createL4ApiErrorResponse(400, '密码不符合要求,请按输入框下方的规则调整。', {
|
|
69
69
|
key: 'auth:settingsInvalid'
|
|
70
70
|
})
|
|
71
71
|
}
|
|
@@ -35,17 +35,26 @@ export function createL1ProjectRoute<I extends contracts.L3ProjectReadContext, O
|
|
|
35
35
|
key: 'errors:projectCwdAbsolute'
|
|
36
36
|
})
|
|
37
37
|
try {
|
|
38
|
+
let isCurrent: (() => boolean) | undefined
|
|
38
39
|
if (input.workId) {
|
|
39
|
-
const
|
|
40
|
+
const manage = getL2WorkSessionManage()
|
|
41
|
+
const session = await manage.getWorkSession(input.workId)
|
|
40
42
|
if (!session)
|
|
41
43
|
return createL4ApiErrorResponse(404, '工作会话不存在', { key: 'errors:sessionMissing' })
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
const context = manage.getProjectPreviewContext(session, input.cwd)
|
|
45
|
+
if (!context)
|
|
46
|
+
return createL4ApiErrorResponse(409, '工作会话项目已变化或预览目录未登记', {
|
|
44
47
|
key: 'errors:projectChanged'
|
|
45
48
|
})
|
|
46
|
-
input.cwd =
|
|
49
|
+
input.cwd = context.cwd
|
|
50
|
+
isCurrent = context.isCurrent
|
|
47
51
|
}
|
|
48
|
-
|
|
52
|
+
const result = await execute(input)
|
|
53
|
+
if (isCurrent && !isCurrent())
|
|
54
|
+
return createL4ApiErrorResponse(409, '工作会话项目已变化', {
|
|
55
|
+
key: 'errors:projectChanged'
|
|
56
|
+
})
|
|
57
|
+
return createL4ApiSuccessResponse(contract.response.parse(result))
|
|
49
58
|
} catch (error) {
|
|
50
59
|
console.warn('[Pi Desk][Project] 项目请求失败', {
|
|
51
60
|
path: contract.path,
|
|
@@ -113,12 +113,8 @@ function allowHttpRequest(
|
|
|
113
113
|
try {
|
|
114
114
|
authenticated = authRuntime?.isAuthenticated(request.headers.cookie) ?? false
|
|
115
115
|
} catch {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
503,
|
|
119
|
-
'登录保护配置无法读取,请修复配置或删除 auth.json 后重新设置',
|
|
120
|
-
'auth:configUnavailable'
|
|
121
|
-
)
|
|
116
|
+
console.error('[Pi Desk][NodeServer] 登录保护配置读取失败')
|
|
117
|
+
endHttpAuthError(response, 503, '登录暂不可用,请稍后重试。', 'auth:unavailable')
|
|
122
118
|
return false
|
|
123
119
|
}
|
|
124
120
|
if (pathname === LOGIN_PAGE_PATH) {
|
|
@@ -399,7 +395,8 @@ async function startServer(): Promise<void> {
|
|
|
399
395
|
try {
|
|
400
396
|
authenticated = authRuntime.isAuthenticated(request.headers.cookie)
|
|
401
397
|
} catch {
|
|
402
|
-
|
|
398
|
+
console.error('[Pi Desk][NodeServer] 握手认证配置读取失败')
|
|
399
|
+
rejectUpgrade(socket, 503, '登录暂不可用,请稍后重试。')
|
|
403
400
|
return
|
|
404
401
|
}
|
|
405
402
|
if (!authenticated) {
|
|
@@ -28,7 +28,12 @@ export { L3AppRuntimeTargetNotFoundError as L2AppRuntimeTargetNotFoundError }
|
|
|
28
28
|
export type L2AppRuntime = L3AppRuntime
|
|
29
29
|
|
|
30
30
|
export interface L2AppNotificationPublisher {
|
|
31
|
-
publish(input: {
|
|
31
|
+
publish(input: {
|
|
32
|
+
key?: string
|
|
33
|
+
level: L3AppNotificationLevel
|
|
34
|
+
title: string
|
|
35
|
+
description?: string
|
|
36
|
+
}): string
|
|
32
37
|
update(notificationId: string, changes: L3AppNotificationChanges): void
|
|
33
38
|
delete(notificationId: string): void
|
|
34
39
|
dispose(): void
|
|
@@ -19,12 +19,56 @@ import {
|
|
|
19
19
|
type L4AuthCredentials
|
|
20
20
|
} from '@server/l4_foundation/auth/l4-auth-config'
|
|
21
21
|
import { appendL4LoginRecord } from '@server/l4_foundation/auth/l4-login-record-store'
|
|
22
|
+
import {
|
|
23
|
+
getL3AppNotificationRuntime,
|
|
24
|
+
type L3AppNotificationPublisher,
|
|
25
|
+
type L3AppNotificationRuntime
|
|
26
|
+
} from '@server/l3_modules/app-runtime/l3-app-notification-runtime'
|
|
27
|
+
import { getL4HostSettingsStore } from '@server/l4_foundation/l4-host-settings-store'
|
|
28
|
+
import { L2_AUTH_MESSAGES } from '@common/l2_biz/auth/l2-auth-messages'
|
|
22
29
|
|
|
23
30
|
export const L2_WEB_SESSION_COOKIE_NAME = 'pi-desk-session'
|
|
24
31
|
const WEB_SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30
|
|
25
32
|
|
|
33
|
+
function recordLoginResult(success: boolean): void {
|
|
34
|
+
const runtime = getL3AppNotificationRuntime()
|
|
35
|
+
const loginAttempts = (globalThis.__piDeskLoginAttempts ??= new WeakMap())
|
|
36
|
+
let attempts = loginAttempts.get(runtime)
|
|
37
|
+
if (!attempts) {
|
|
38
|
+
attempts = { failures: 0, publisher: runtime.createPublisher() }
|
|
39
|
+
loginAttempts.set(runtime, attempts)
|
|
40
|
+
}
|
|
41
|
+
if (success) {
|
|
42
|
+
attempts.failures = 0
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
attempts.failures += 1
|
|
46
|
+
if (attempts.failures <= 5) return
|
|
47
|
+
if (attempts.failures === 6) {
|
|
48
|
+
console.warn('[Pi Desk][Auth] 连续登录失败超过阈值,发布登录异常提醒')
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const locale = getL4HostSettingsStore().getSnapshot().region.locale
|
|
52
|
+
const messages = L2_AUTH_MESSAGES[locale]
|
|
53
|
+
attempts.publisher.publish({
|
|
54
|
+
key: 'auth:login-failures',
|
|
55
|
+
level: 'warning',
|
|
56
|
+
title: messages.loginAlertTitle,
|
|
57
|
+
description: messages.loginAlertDescription
|
|
58
|
+
})
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('[Pi Desk][Auth] 登录异常提醒发布失败', {
|
|
61
|
+
errorName: error instanceof Error ? error.name : 'UnknownError'
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
26
66
|
declare global {
|
|
27
67
|
var __piDeskAuthMutation: Promise<void> | undefined
|
|
68
|
+
// 跨 HTTP 路由共享,计数和发布者跟随通知 Runtime 释放,不保存失败请求内容。
|
|
69
|
+
var __piDeskLoginAttempts:
|
|
70
|
+
| WeakMap<L3AppNotificationRuntime, { failures: number; publisher: L3AppNotificationPublisher }>
|
|
71
|
+
| undefined
|
|
28
72
|
}
|
|
29
73
|
|
|
30
74
|
export interface L2WebAuthLoginResult {
|
|
@@ -58,15 +102,20 @@ export async function authenticateL2WebLogin(
|
|
|
58
102
|
): Promise<L2WebAuthLoginResult | null> {
|
|
59
103
|
const credentials = readL4AuthCredentials()
|
|
60
104
|
if (!credentials) {
|
|
105
|
+
recordLoginResult(true)
|
|
61
106
|
return { cookieName: L2_WEB_SESSION_COOKIE_NAME, token: '', maxAgeSeconds: 0 }
|
|
62
107
|
}
|
|
63
108
|
const passwordMatches = await verifyL4AuthPassword(input.password, credentials.passwordHash)
|
|
64
|
-
|
|
65
|
-
// scrypt 计算期间凭据可能已被修改或删除,不为旧配置签发会话。
|
|
109
|
+
// scrypt 计算期间凭据可能已被修改或删除,不为旧配置签发会话或累计失败次数。
|
|
66
110
|
if (readL4AuthCredentials()?.sessionSecret !== credentials.sessionSecret) return null
|
|
111
|
+
if (!passwordMatches) {
|
|
112
|
+
recordLoginResult(false)
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
67
115
|
|
|
68
116
|
const loggedInAt = Date.now()
|
|
69
117
|
appendL4LoginRecord({ loggedInAt })
|
|
118
|
+
recordLoginResult(true)
|
|
70
119
|
return {
|
|
71
120
|
cookieName: L2_WEB_SESSION_COOKIE_NAME,
|
|
72
121
|
token: createL4WebAuthSessionToken(
|
|
@@ -101,6 +150,7 @@ export function replaceL2AuthSettings(
|
|
|
101
150
|
throw new L2AuthRequiredError()
|
|
102
151
|
}
|
|
103
152
|
writeL4AuthConfig(config)
|
|
153
|
+
recordLoginResult(true)
|
|
104
154
|
console.info('[Pi Desk][Auth] 登录保护已更新', { enabled: config !== null })
|
|
105
155
|
})
|
|
106
156
|
globalThis.__piDeskAuthMutation = operation.catch(() => undefined)
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import {
|
|
5
|
+
L4_PROJECT_PREVIEW_PLUGIN_KEY,
|
|
6
|
+
projectPreviewDirectoryKey,
|
|
7
|
+
readL4ProjectPreviewState
|
|
8
|
+
} from '@common/l4_foundation/file/l4-project-preview-contract'
|
|
3
9
|
import {
|
|
4
10
|
L2ChatModelContextGetRequestSchema,
|
|
5
11
|
L2ChatModelContextGetResponseSchema,
|
|
@@ -274,6 +280,28 @@ export class L2WorkSessionChatRuntime {
|
|
|
274
280
|
return record !== undefined && this.matches(record.source, source)
|
|
275
281
|
}
|
|
276
282
|
|
|
283
|
+
getProjectPreviewContext(
|
|
284
|
+
workSession: WorkSession,
|
|
285
|
+
directory: string
|
|
286
|
+
): { cwd: string; isCurrent: () => boolean } | null {
|
|
287
|
+
const record = this.recordsByWorkId.get(workSession.workId)
|
|
288
|
+
if (!record || record.workSession !== workSession) return null
|
|
289
|
+
const key = projectPreviewDirectoryKey(resolve(directory))
|
|
290
|
+
const originalCwd = key === projectPreviewDirectoryKey(resolve(workSession.cwd))
|
|
291
|
+
const registeredDirectory = (): string | undefined =>
|
|
292
|
+
readL4ProjectPreviewState(
|
|
293
|
+
record.state.runtime.plugins[L4_PROJECT_PREVIEW_PLUGIN_KEY]
|
|
294
|
+
).directories.find((item) => projectPreviewDirectoryKey(item) === key)
|
|
295
|
+
const cwd = originalCwd ? workSession.cwd : registeredDirectory()
|
|
296
|
+
if (!cwd) return null
|
|
297
|
+
const isCurrent = (): boolean =>
|
|
298
|
+
this.recordsByWorkId.get(workSession.workId) === record &&
|
|
299
|
+
record.acceptingCommands &&
|
|
300
|
+
this.matches(record.source, this.sourceFor(workSession)) &&
|
|
301
|
+
(originalCwd || registeredDirectory() !== undefined)
|
|
302
|
+
return isCurrent() ? { cwd, isCurrent } : null
|
|
303
|
+
}
|
|
304
|
+
|
|
277
305
|
refreshPluginMessages(): void {
|
|
278
306
|
for (const record of this.recordsByWorkId.values()) {
|
|
279
307
|
if (record.state.runtime.presentationMode === 'basic') continue
|
|
@@ -477,6 +477,14 @@ export class WorkSessionManage {
|
|
|
477
477
|
})
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
+
getProjectPreviewContext(
|
|
481
|
+
workSession: WorkSession,
|
|
482
|
+
directory: string
|
|
483
|
+
): { cwd: string; isCurrent: () => boolean } | null {
|
|
484
|
+
if (this.workSessionsById.get(workSession.workId) !== workSession) return null
|
|
485
|
+
return this.chat.getProjectPreviewContext(workSession, directory)
|
|
486
|
+
}
|
|
487
|
+
|
|
480
488
|
async replaceProjectGitBranch(
|
|
481
489
|
inputValue: L2WorkSessionGitBranchReplaceRequest
|
|
482
490
|
): Promise<L2WorkSessionGitBranchOperationResponse> {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
|
|
3
3
|
import { randomUUID } from 'node:crypto'
|
|
4
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
4
5
|
import {
|
|
5
6
|
L3AppNotificationChangesSchema,
|
|
7
|
+
L3AppNotificationKeySchema,
|
|
6
8
|
L3AppNotificationSchema,
|
|
7
9
|
L3PluginNotificationChangesSchema,
|
|
8
10
|
L3PluginNotificationPublishInputSchema,
|
|
@@ -21,6 +23,7 @@ const L3_PLUGIN_NOTIFICATION_EVENT_MAX_JSON_BYTES = 64 * 1024
|
|
|
21
23
|
type L3AppNotificationOwner = string | object
|
|
22
24
|
|
|
23
25
|
export interface L3AppNotificationPublishInput {
|
|
26
|
+
key?: string
|
|
24
27
|
level: L3AppNotificationLevel
|
|
25
28
|
title: string
|
|
26
29
|
description?: string
|
|
@@ -49,7 +52,10 @@ export class L3AppNotificationOwnerError extends Error {
|
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
export class L3AppNotificationRuntime {
|
|
52
|
-
private readonly ownersByNotificationId = new Map<
|
|
55
|
+
private readonly ownersByNotificationId = new Map<
|
|
56
|
+
string,
|
|
57
|
+
{ owner: L3AppNotificationOwner; key: string | undefined }
|
|
58
|
+
>()
|
|
53
59
|
private readonly unsubscribe: () => void
|
|
54
60
|
private disposed = false
|
|
55
61
|
|
|
@@ -100,6 +106,7 @@ export class L3AppNotificationRuntime {
|
|
|
100
106
|
const pluginName = L3PluginNameSchema.parse(pluginNameInput)
|
|
101
107
|
const normalized = L3PluginNotificationPublishInputSchema.parse(input)
|
|
102
108
|
return this.publish(pluginName, {
|
|
109
|
+
key: normalized.key,
|
|
103
110
|
level: normalized.level,
|
|
104
111
|
title: normalized.title,
|
|
105
112
|
...(normalized.description === undefined ? {} : { description: normalized.description }),
|
|
@@ -154,7 +161,42 @@ export class L3AppNotificationRuntime {
|
|
|
154
161
|
throw new Error('Plugin notification event data exceeds 64KB')
|
|
155
162
|
}
|
|
156
163
|
|
|
164
|
+
const key = L3AppNotificationKeySchema.optional().parse(input.key)
|
|
157
165
|
const current = this.runtime.readSnapshot().notifications
|
|
166
|
+
const existing =
|
|
167
|
+
key === undefined
|
|
168
|
+
? undefined
|
|
169
|
+
: current.find((notification) => {
|
|
170
|
+
const registration = this.ownersByNotificationId.get(notification.notificationId)
|
|
171
|
+
return registration?.owner === owner && registration.key === key
|
|
172
|
+
})
|
|
173
|
+
const notification = L3AppNotificationSchema.parse({
|
|
174
|
+
notificationId: existing?.notificationId ?? randomUUID(),
|
|
175
|
+
level: input.level,
|
|
176
|
+
title: input.title,
|
|
177
|
+
description: input.description ?? null,
|
|
178
|
+
createdAt: existing?.createdAt ?? Date.now(),
|
|
179
|
+
event: input.event ?? null
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
if (existing) {
|
|
183
|
+
const changes = {
|
|
184
|
+
level: notification.level,
|
|
185
|
+
title: notification.title,
|
|
186
|
+
description: notification.description,
|
|
187
|
+
event: notification.event
|
|
188
|
+
}
|
|
189
|
+
if (
|
|
190
|
+
existing.level !== changes.level ||
|
|
191
|
+
existing.title !== changes.title ||
|
|
192
|
+
existing.description !== changes.description ||
|
|
193
|
+
!isDeepStrictEqual(existing.event, changes.event)
|
|
194
|
+
) {
|
|
195
|
+
this.update(owner, existing.notificationId, changes)
|
|
196
|
+
}
|
|
197
|
+
return existing.notificationId
|
|
198
|
+
}
|
|
199
|
+
|
|
158
200
|
const oldest = current.length >= L3_APP_NOTIFICATION_MAX_ITEMS ? current[0] : undefined
|
|
159
201
|
if (oldest) {
|
|
160
202
|
this.runtime.applyInternal({
|
|
@@ -163,15 +205,7 @@ export class L3AppNotificationRuntime {
|
|
|
163
205
|
})
|
|
164
206
|
}
|
|
165
207
|
|
|
166
|
-
|
|
167
|
-
notificationId: randomUUID(),
|
|
168
|
-
level: input.level,
|
|
169
|
-
title: input.title,
|
|
170
|
-
description: input.description ?? null,
|
|
171
|
-
createdAt: Date.now(),
|
|
172
|
-
event: input.event ?? null
|
|
173
|
-
})
|
|
174
|
-
this.ownersByNotificationId.set(notification.notificationId, owner)
|
|
208
|
+
this.ownersByNotificationId.set(notification.notificationId, { owner, key })
|
|
175
209
|
try {
|
|
176
210
|
this.runtime.applyInternal({
|
|
177
211
|
key: 'notifications',
|
|
@@ -215,7 +249,7 @@ export class L3AppNotificationRuntime {
|
|
|
215
249
|
this.runtime.readSnapshot().notifications.map((notification) => notification.notificationId)
|
|
216
250
|
)
|
|
217
251
|
const notificationIds = [...this.ownersByNotificationId.entries()]
|
|
218
|
-
.filter(([,
|
|
252
|
+
.filter(([, registration]) => registration.owner === owner)
|
|
219
253
|
.map(([notificationId]) => notificationId)
|
|
220
254
|
for (const notificationId of notificationIds) {
|
|
221
255
|
if (!activeIds.has(notificationId)) {
|
|
@@ -230,9 +264,9 @@ export class L3AppNotificationRuntime {
|
|
|
230
264
|
}
|
|
231
265
|
|
|
232
266
|
private assertOwner(owner: L3AppNotificationOwner, notificationId: string): void {
|
|
233
|
-
const
|
|
234
|
-
if (!
|
|
235
|
-
if (
|
|
267
|
+
const registration = this.ownersByNotificationId.get(notificationId)
|
|
268
|
+
if (!registration) throw new L3AppNotificationNotFoundError(notificationId)
|
|
269
|
+
if (registration.owner !== owner) throw new L3AppNotificationOwnerError(notificationId)
|
|
236
270
|
}
|
|
237
271
|
|
|
238
272
|
private ensureActive(): void {
|
|
@@ -19,6 +19,15 @@ const ReadAuthConfigSchema = AuthConfigSchema.extend({
|
|
|
19
19
|
|
|
20
20
|
type AuthConfig = z.infer<typeof AuthConfigSchema>
|
|
21
21
|
|
|
22
|
+
let activePasswordVerifications = 0
|
|
23
|
+
|
|
24
|
+
export class L4AuthBusyError extends Error {
|
|
25
|
+
constructor() {
|
|
26
|
+
super('登录尝试过多,请稍后重试。')
|
|
27
|
+
this.name = 'L4AuthBusyError'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
export interface L4AuthCredentials extends AuthConfig {
|
|
23
32
|
sessionSecret: string
|
|
24
33
|
}
|
|
@@ -97,9 +106,16 @@ export async function verifyL4AuthPassword(
|
|
|
97
106
|
password: string,
|
|
98
107
|
passwordHash: string
|
|
99
108
|
): Promise<boolean> {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
if (activePasswordVerifications >= 2) throw new L4AuthBusyError()
|
|
110
|
+
activePasswordVerifications += 1
|
|
111
|
+
try {
|
|
112
|
+
const [, salt, expected] = passwordHash.split(':')
|
|
113
|
+
const actual = await derivePassword(password, salt)
|
|
114
|
+
return timingSafeEqual(actual, Buffer.from(expected, 'hex'))
|
|
115
|
+
} finally {
|
|
116
|
+
// 计算完成才释放,不随 HTTP 连接提前关闭而释放。
|
|
117
|
+
activePasswordVerifications -= 1
|
|
118
|
+
}
|
|
103
119
|
}
|
|
104
120
|
|
|
105
121
|
export function writeL4AuthConfig(config: AuthConfig | null): void {
|
|
@@ -444,7 +444,8 @@ export class L4PiChatWorker {
|
|
|
444
444
|
console.warn('[Pi Desk][PiChatWorker] 已忽略非法插件运行态更新', {
|
|
445
445
|
sessionId: this.sessionManager.getSessionId(),
|
|
446
446
|
cwd: this.cwd,
|
|
447
|
-
errorName: cause instanceof Error ? cause.name : 'UnknownError'
|
|
447
|
+
errorName: cause instanceof Error ? cause.name : 'UnknownError',
|
|
448
|
+
message: cause instanceof Error ? cause.message : String(cause)
|
|
448
449
|
})
|
|
449
450
|
}
|
|
450
451
|
}
|