@allbluecn/web-app 0.23.6 → 0.24.0
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/package.json +2 -2
- package/src/components/ai/global-harness-sidebar/__tests__/global-ai-prompts.test.ts +0 -2
- package/src/components/harness/ai-chat-history/components/conversation-item.tsx +6 -0
- package/src/components/harness/ai-chat-history/types.ts +4 -0
- package/src/components/harness/chat-ui.tsx +20 -0
- package/src/components/harness/composer/harness-composer.tsx +43 -10
- package/src/components/harness/composer/harness-engine-control.tsx +129 -0
- package/src/components/harness/harness-workbench-page.tsx +129 -17
- package/src/components/layout/sidebar-03/__tests__/user-menu-cloud-logout.test.tsx +185 -0
- package/src/components/layout/sidebar-03/app-sidebar.tsx +2 -9
- package/src/components/layout/sidebar-03/user-menu.tsx +27 -50
- package/src/components/shared-kit/auth/auth-client.ts +4 -0
- package/src/config/sidebar-routes.tsx +0 -13
- package/src/lib/__tests__/desktop-logout.test.ts +239 -0
- package/src/lib/__tests__/session.test.ts +85 -2
- package/src/lib/auth/__tests__/page-gate.test.ts +37 -1
- package/src/lib/auth/anonymous-safe.ts +5 -0
- package/src/lib/auth/page-gate.ts +11 -1
- package/src/lib/changelog/sdk-versions.ts +1 -1
- package/src/lib/desktop-env.ts +25 -0
- package/src/lib/desktop-logout.ts +117 -0
- package/src/lib/hotkeys/__tests__/registry.test.ts +4 -11
- package/src/lib/hotkeys/registry.ts +0 -8
- package/src/lib/session.ts +70 -0
- package/src/paraglide/README.md +1 -1
- package/src/paraglide/messages/_index.d.ts +12 -2
- package/src/paraglide/messages/_index.js +12 -2
- package/src/paraglide/messages/{agent_desktop_title.d.ts → harness_engine_cloud.d.ts} +5 -5
- package/src/paraglide/messages/harness_engine_cloud.js +29 -0
- package/src/paraglide/messages/harness_engine_label.d.ts +16 -0
- package/src/paraglide/messages/harness_engine_label.js +29 -0
- package/src/paraglide/messages/harness_engine_local.d.ts +16 -0
- package/src/paraglide/messages/harness_engine_local.js +29 -0
- package/src/paraglide/messages/harness_engine_localbadge1.d.ts +17 -0
- package/src/paraglide/messages/harness_engine_localbadge1.js +30 -0
- package/src/paraglide/messages/harness_engine_localhint1.d.ts +17 -0
- package/src/paraglide/messages/harness_engine_localhint1.js +30 -0
- package/src/paraglide/messages/harness_engine_localnoattachments2.d.ts +17 -0
- package/src/paraglide/messages/harness_engine_localnoattachments2.js +30 -0
- package/src/paraglide/messages/harness_engine_switchlocked1.d.ts +17 -0
- package/src/paraglide/messages/harness_engine_switchlocked1.js +30 -0
- package/src/paraglide/messages/harness_localworkspace_choose1.d.ts +17 -0
- package/src/paraglide/messages/harness_localworkspace_choose1.js +30 -0
- package/src/paraglide/messages/harness_localworkspace_dialogdescription2.d.ts +17 -0
- package/src/paraglide/messages/harness_localworkspace_dialogdescription2.js +30 -0
- package/src/paraglide/messages/harness_localworkspace_dialogtitle2.d.ts +17 -0
- package/src/paraglide/messages/harness_localworkspace_dialogtitle2.js +30 -0
- package/src/paraglide/messages/harness_localworkspace_manage1.d.ts +17 -0
- package/src/paraglide/messages/harness_localworkspace_manage1.js +30 -0
- package/src/paraglide/messages/harness_localworkspace_required1.d.ts +17 -0
- package/src/paraglide/messages/harness_localworkspace_required1.js +30 -0
- package/src/routeTree.gen.ts +0 -23
- package/src/routes/__tests__/-edition-routing.test.tsx +48 -7
- package/src/routes/__tests__/-login-admin-redirect.test.ts +99 -0
- package/src/routes/_login/login/route.tsx +18 -3
- package/src/routes/_nav/design/canvas/$id/route.tsx +5 -0
- package/src/routes/_nav/harness/$threadId.lazy.tsx +19 -2
- package/src/routes/_nav/harness/$threadId.tsx +8 -2
- package/src/routes/_nav/knowledge/$kbId/route.tsx +6 -2
- package/src/routes/_nav/route.tsx +5 -1
- package/src/routes/_nav/settings/account/route.tsx +5 -0
- package/src/routes/_nav/settings/team/route.tsx +5 -0
- package/src/routes/api/auth/$.ts +87 -2
- package/src/routes/api/auth/__tests__/auth-session-desktop.test.ts +194 -0
- package/src/server/__tests__/chat-conversations.test.ts +13 -3
- package/src/server/__tests__/desktop-bootstrap.test.ts +0 -1
- package/src/server/ai-gateway/__tests__/local-agent-adapter.test.ts +110 -0
- package/src/server/ai-gateway/__tests__/unified-router-local-agent.test.ts +63 -0
- package/src/server/ai-gateway/chat-handler.ts +68 -2
- package/src/server/ai-gateway/chat-stream-builder.ts +52 -0
- package/src/server/ai-gateway/local-agent-adapter.ts +232 -0
- package/src/server/ai-gateway/unified-router.ts +49 -6
- package/src/server/desktop-guest.ts +5 -1
- package/src/server/serverFns/auth/session.ts +19 -10
- package/src/server/serverFns/chat/conversations.ts +122 -1
- package/src/components/desktop-agent/__tests__/agent-chat.test.tsx +0 -194
- package/src/components/desktop-agent/agent-chat.tsx +0 -222
- package/src/lib/desktop-agent/__tests__/connection.test.ts +0 -310
- package/src/lib/desktop-agent/__tests__/errors.test.ts +0 -43
- package/src/lib/desktop-agent/connection.ts +0 -93
- package/src/lib/desktop-agent/errors.ts +0 -21
- package/src/paraglide/messages/agent_desktop_title.js +0 -29
- package/src/paraglide/messages/common_hotkey_desc_agentabort1.d.ts +0 -17
- package/src/paraglide/messages/common_hotkey_desc_agentabort1.js +0 -30
- package/src/routes/-tests/agent-gate.test.ts +0 -45
- package/src/routes/_nav/agent/route.lazy.tsx +0 -63
- package/src/routes/_nav/agent/route.tsx +0 -21
- /package/src/components/{desktop-agent → harness/local-agent}/__tests__/agent-tool-card.test.tsx +0 -0
- /package/src/components/{desktop-agent → harness/local-agent}/__tests__/agent-workspace-card.test.tsx +0 -0
- /package/src/components/{desktop-agent → harness/local-agent}/agent-tool-card.tsx +0 -0
- /package/src/components/{desktop-agent → harness/local-agent}/agent-workspace-card.tsx +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// 不触真实 JWT 解密、不真读写 DESKTOP_USER_DATA_DIR 下的 remote-auth.json
|
|
9
9
|
// (#578 fs 实现勿真写盘)。desktop 判定走 DEPLOY_TARGET env stub
|
|
10
10
|
// (isDesktopServer 调用时读 env,见 desktop-guest.ts:40)。
|
|
11
|
-
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
12
12
|
import { getCurrentSession } from '../session'
|
|
13
13
|
import type { Session } from '../session'
|
|
14
14
|
|
|
@@ -45,6 +45,11 @@ const REMOTE_AUTH = {
|
|
|
45
45
|
const REQ = () => new Request('http://localhost/api/auth/session')
|
|
46
46
|
|
|
47
47
|
describe('getCurrentSession 桌面回落(A10 优先级链)', () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
// 真实桌面后端由 Electron 主进程注入该 env(fail-open 自检的正常路径)
|
|
50
|
+
vi.stubEnv('DESKTOP_USER_DATA_DIR', '/tmp/allblue-desktop-test')
|
|
51
|
+
})
|
|
52
|
+
|
|
48
53
|
afterEach(() => {
|
|
49
54
|
vi.unstubAllEnvs()
|
|
50
55
|
vi.clearAllMocks()
|
|
@@ -72,10 +77,45 @@ describe('getCurrentSession 桌面回落(A10 优先级链)', () => {
|
|
|
72
77
|
expect(session?.expires).toBe('never')
|
|
73
78
|
})
|
|
74
79
|
|
|
75
|
-
it('①c authjs 无效 + desktop + remoteAuth 无 →
|
|
80
|
+
it('①c authjs 无效 + desktop + remoteAuth 无 → guest 本地影子身份(桌面免登录)', async () => {
|
|
76
81
|
vi.stubEnv('DEPLOY_TARGET', 'desktop')
|
|
77
82
|
getSessionMock.mockResolvedValue(null)
|
|
78
83
|
readRemoteAuthMock.mockResolvedValue(null)
|
|
84
|
+
const session = await getCurrentSession(REQ())
|
|
85
|
+
// 桌面端不存在「未登录」态:guest 是本地合法身份,必须在本鉴权单点补齐
|
|
86
|
+
// (否则 account-team / design-detail 等软鉴权端点把 guest 判成未登录 →
|
|
87
|
+
// 路由 beforeLoad 收 unauthorized → 自动跳 /login,实测 P0)
|
|
88
|
+
expect(session?.user?.id).toBe('local-guest')
|
|
89
|
+
expect(session?.user?.isGuest).toBe(true)
|
|
90
|
+
expect(session?.expires).toBe('never')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('①d authjs 读取出错 + desktop + remoteAuth 有 → 先回落云身份(不静默降级 guest)', async () => {
|
|
94
|
+
vi.stubEnv('DEPLOY_TARGET', 'desktop')
|
|
95
|
+
getSessionMock.mockRejectedValue(new Error('JWTSessionError'))
|
|
96
|
+
readRemoteAuthMock.mockResolvedValue(REMOTE_AUTH)
|
|
97
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
98
|
+
const session = await getCurrentSession(REQ())
|
|
99
|
+
// cloud 态遇坏 cookie / AUTH_SECRET 轮换:必须保住云身份归属——若直接回 guest,
|
|
100
|
+
// 界面看起来「数据全空」,期间写入还会落到 local-guest 属主(错主不可逆)
|
|
101
|
+
expect(session?.user?.id).toBe('cloud-user-abc')
|
|
102
|
+
expect(session?.user?.isGuest).toBe(false)
|
|
103
|
+
expect(readRemoteAuthMock).toHaveBeenCalled()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('①e authjs 读取出错 + desktop + remoteAuth 无 → 回落 guest,本地不掉线', async () => {
|
|
107
|
+
vi.stubEnv('DEPLOY_TARGET', 'desktop')
|
|
108
|
+
getSessionMock.mockRejectedValue(new Error('JWTSessionError'))
|
|
109
|
+
readRemoteAuthMock.mockResolvedValue(null)
|
|
110
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
111
|
+
const session = await getCurrentSession(REQ())
|
|
112
|
+
expect(session?.user?.id).toBe('local-guest')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('①f authjs 读取出错 + 非 desktop → null(web 未登录语义不变)', async () => {
|
|
116
|
+
vi.stubEnv('DEPLOY_TARGET', '')
|
|
117
|
+
getSessionMock.mockRejectedValue(new Error('JWTSessionError'))
|
|
118
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
79
119
|
expect(await getCurrentSession(REQ())).toBeNull()
|
|
80
120
|
})
|
|
81
121
|
|
|
@@ -93,3 +133,46 @@ describe('getCurrentSession 桌面回落(A10 优先级链)', () => {
|
|
|
93
133
|
expect(readRemoteAuthMock).not.toHaveBeenCalled()
|
|
94
134
|
})
|
|
95
135
|
})
|
|
136
|
+
|
|
137
|
+
describe('桌面 guest 回落的安全自检(fail-open 告警,进程级一次)', () => {
|
|
138
|
+
// 模块级 warn-once 标记随实例存在:每个用例用 resetModules + 动态 import
|
|
139
|
+
// 拿全新实例,否则第二条会被前一条的标记吞掉
|
|
140
|
+
async function importFreshSession(env: { desktopUserDataDir?: string }) {
|
|
141
|
+
vi.resetModules()
|
|
142
|
+
vi.stubEnv('DEPLOY_TARGET', 'desktop')
|
|
143
|
+
vi.stubEnv('DESKTOP_USER_DATA_DIR', env.desktopUserDataDir ?? '')
|
|
144
|
+
vi.doMock('start-authjs', () => ({ getSession: async () => null }))
|
|
145
|
+
vi.doMock('@/lib/auth/auth.config', () => ({ getAuthConfig: async () => ({}) }))
|
|
146
|
+
vi.doMock('@/server/remote-auth', () => ({ readRemoteAuth: async () => null }))
|
|
147
|
+
return import('../session')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
afterEach(() => {
|
|
151
|
+
vi.unstubAllEnvs()
|
|
152
|
+
vi.restoreAllMocks()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('DEPLOY_TARGET=desktop 且无 DESKTOP_USER_DATA_DIR → console.error 一次(cloud 部署误设信号)', async () => {
|
|
156
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
157
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
158
|
+
|
|
159
|
+
const { getCurrentSession: fresh } = await importFreshSession({})
|
|
160
|
+
expect((await fresh(REQ()))?.user?.id).toBe('local-guest')
|
|
161
|
+
expect((await fresh(REQ()))?.user?.id).toBe('local-guest')
|
|
162
|
+
|
|
163
|
+
const securityAlerts = errorSpy.mock.calls.filter((args) => String(args[0]).includes('DESKTOP_USER_DATA_DIR'))
|
|
164
|
+
expect(securityAlerts).toHaveLength(1)
|
|
165
|
+
expect(String(securityAlerts[0]?.[0])).toContain('安全边界')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('已注入 DESKTOP_USER_DATA_DIR(正常桌面壳)→ 不告警', async () => {
|
|
169
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
170
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
171
|
+
|
|
172
|
+
const { getCurrentSession: fresh } = await importFreshSession({ desktopUserDataDir: '/tmp/allblue-desktop' })
|
|
173
|
+
expect((await fresh(REQ()))?.user?.id).toBe('local-guest')
|
|
174
|
+
|
|
175
|
+
const securityAlerts = errorSpy.mock.calls.filter((args) => String(args[0]).includes('DESKTOP_USER_DATA_DIR'))
|
|
176
|
+
expect(securityAlerts).toHaveLength(0)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import { isRedirect } from '@tanstack/react-router'
|
|
3
|
+
|
|
4
|
+
// 桌面判定双通道(SSR/服务端导航读 DEPLOY_TARGET,客户端导航读 desktopBridge
|
|
5
|
+
// 运行时事实):两者任一为真都不应把本地用户踢去 /login(桌面免登录 P0)
|
|
6
|
+
const H = vi.hoisted(() => ({ desktopServer: false, desktopClient: false }))
|
|
7
|
+
|
|
8
|
+
vi.mock('@/server/desktop-guest', async (importOriginal) => {
|
|
9
|
+
const actual = await importOriginal<typeof import('@/server/desktop-guest')>()
|
|
10
|
+
return { ...actual, isDesktopServer: () => H.desktopServer }
|
|
11
|
+
})
|
|
12
|
+
vi.mock('@/lib/desktop-env', () => ({ isDesktopClient: () => H.desktopClient }))
|
|
13
|
+
|
|
3
14
|
import { requireSessionPage } from '../page-gate'
|
|
4
15
|
|
|
5
16
|
function makeArgs(session: { user?: { id?: string } } | null) {
|
|
@@ -10,6 +21,11 @@ function makeArgs(session: { user?: { id?: string } } | null) {
|
|
|
10
21
|
}
|
|
11
22
|
|
|
12
23
|
describe('requireSessionPage', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
H.desktopServer = false
|
|
26
|
+
H.desktopClient = false
|
|
27
|
+
})
|
|
28
|
+
|
|
13
29
|
it('已登录放行(不抛错)', () => {
|
|
14
30
|
expect(() => requireSessionPage(makeArgs({ user: { id: 'u1' } }))).not.toThrow()
|
|
15
31
|
})
|
|
@@ -26,4 +42,24 @@ describe('requireSessionPage', () => {
|
|
|
26
42
|
expect(options?.to).toBe('/login')
|
|
27
43
|
expect(options?.search?.redirect).toBe('/settings/account')
|
|
28
44
|
})
|
|
45
|
+
|
|
46
|
+
it('桌面(服务端 SSR:DEPLOY_TARGET=desktop):未登录也放行', () => {
|
|
47
|
+
H.desktopServer = true
|
|
48
|
+
expect(() => requireSessionPage(makeArgs(null))).not.toThrow()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('桌面(客户端导航:desktopBridge 在场):未登录也放行', () => {
|
|
52
|
+
H.desktopClient = true
|
|
53
|
+
expect(() => requireSessionPage(makeArgs(null))).not.toThrow()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('web(两通道都为假):行为不变,未登录仍跳 /login', () => {
|
|
57
|
+
let caught: unknown
|
|
58
|
+
try {
|
|
59
|
+
requireSessionPage(makeArgs(null))
|
|
60
|
+
} catch (e) {
|
|
61
|
+
caught = e
|
|
62
|
+
}
|
|
63
|
+
expect(isRedirect(caught as never)).toBe(true)
|
|
64
|
+
})
|
|
29
65
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AnyRouter } from '@tanstack/react-router'
|
|
2
|
+
import { isDesktopClient } from '@/lib/desktop-env'
|
|
2
3
|
|
|
3
4
|
const UNAUTHORIZED_NAME = 'UnauthorizedError'
|
|
4
5
|
const UNAUTHORIZED_STATUS = 401
|
|
@@ -30,6 +31,10 @@ export function navigateToLoginOnUnauthorized(
|
|
|
30
31
|
router: Pick<AnyRouter, 'state' | 'navigate'>,
|
|
31
32
|
): boolean {
|
|
32
33
|
if (!isUnauthorizedError(error)) return false
|
|
34
|
+
// 桌面端免登录:401 不等于「需要登录」(本地 guest 已是合法身份;个别端点的
|
|
35
|
+
// 401 是数据/云态问题),自动跳登录会把用户从正在编辑的页面踢走。登录云端
|
|
36
|
+
// 由用户在 settings/sync 自主发起,本兜底在桌面壳内不动作。
|
|
37
|
+
if (isDesktopClient()) return false
|
|
33
38
|
const loc = router.state.location
|
|
34
39
|
router.navigate({
|
|
35
40
|
to: '/login',
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { redirect } from '@tanstack/react-router'
|
|
2
|
+
import { isDesktopServer } from '@/server/desktop-guest'
|
|
3
|
+
import { isDesktopClient } from '@/lib/desktop-env'
|
|
2
4
|
|
|
3
5
|
interface SessionLike {
|
|
4
6
|
user?: { id?: string } | null
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* 「需登录页面」beforeLoad gate:未登录跳登录并携带回跳。直接用于路由 beforeLoad。
|
|
11
|
+
*
|
|
12
|
+
* 桌面端(Electron + 本地 PGlite)免登录:guest 影子会话即合法本地身份,是否
|
|
13
|
+
* 登录云端由用户在 settings/sync 自主决定——一律放行,不自动踢 /login。
|
|
14
|
+
* 判定必须同时覆盖两种执行环境:SSR/服务端导航(isDesktopServer,读
|
|
15
|
+
* DEPLOY_TARGET)与客户端导航(isDesktopClient,读 desktopBridge 运行时事实)。
|
|
16
|
+
*/
|
|
8
17
|
export function requireSessionPage(args: {
|
|
9
18
|
context: { session: SessionLike | null }
|
|
10
19
|
location: { pathname: string; searchStr: string }
|
|
11
20
|
}): void {
|
|
21
|
+
if (isDesktopServer() || isDesktopClient()) return
|
|
12
22
|
if (!args.context.session?.user?.id) {
|
|
13
23
|
throw redirect({
|
|
14
24
|
to: '/login',
|
|
@@ -112,7 +112,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
112
112
|
"items": [
|
|
113
113
|
{
|
|
114
114
|
"name": "@allbluecn/web-app",
|
|
115
|
-
"version": "v0.
|
|
115
|
+
"version": "v0.24.0",
|
|
116
116
|
"lastUpdate": "Nov 1, 2026",
|
|
117
117
|
"source": "apps/web/package.json",
|
|
118
118
|
"docsUrl": "https://github.com/allbluecn/allblue"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// 桌面端运行时判定(客户端安全):Electron preload 注入的 window.desktopBridge
|
|
6
|
+
// 是「当前页面确实跑在桌面壳里」的运行时事实。
|
|
7
|
+
//
|
|
8
|
+
// 为什么不能只用 isDesktopServer():它读 process.env.DEPLOY_TARGET,只在
|
|
9
|
+
// sidecar(Node 服务端)进程里成立。TanStack Router 的 beforeLoad 在**客户端
|
|
10
|
+
// 导航时也会重跑**,浏览器上下文没有 DEPLOY_TARGET → false → requireSessionPage
|
|
11
|
+
// 把桌面 guest 误判成「未登录」踢去 /login(桌面本地优先实测缺陷)。
|
|
12
|
+
//
|
|
13
|
+
// 为什么不用 isDesktopEdition()(VITE_DEPLOY_TARGET 构建期常量):那是「构建
|
|
14
|
+
// 目标」判定;Electron 壳以运行时 env 启动 sidecar 时 bundle 未必内联 desktop,
|
|
15
|
+
// 会 false negative。桥对象的判定口径与 session-guard / sync-indicator /
|
|
16
|
+
// cloud-login 一致(它们都以 getDesktopBridge() 为准)。
|
|
17
|
+
//
|
|
18
|
+
// SSR 恒 false(无 window):服务端侧由 isDesktopServer() 覆盖,两者在
|
|
19
|
+
// beforeLoad 里「服务端 SSR 放行 + 客户端导航放行」形成闭环,无 hydration 分叉。
|
|
20
|
+
import { getDesktopBridge } from './desktop-bridge-types'
|
|
21
|
+
|
|
22
|
+
/** 是否运行在桌面壳渲染进程(客户端);SSR / 浏览器恒 false */
|
|
23
|
+
export function isDesktopClient(): boolean {
|
|
24
|
+
return getDesktopBridge() !== null
|
|
25
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
// 桌面云态登出编排(桌面本地优先 P2-A9 反向转移,从 sidebar-03/user-menu.tsx
|
|
19
|
+
// 抽出为可测纯编排:零 React / 零 i18n / 零 DOM 依赖,副作用全由调用方注入)。
|
|
20
|
+
//
|
|
21
|
+
// 编排顺序(裁定口径,改动需同步单测):
|
|
22
|
+
// ① 置转移锁(复用 bind 锁文件——引擎每 tick 判定同源,「转移进行中引擎勿 tick」;
|
|
23
|
+
// 旧壳无此 IPC ⇒ lock 省略,视为置锁成功,锁为双保险);
|
|
24
|
+
// ② transfer(serverFn reverseTransferOwnershipFn:校验 remoteAuth/会话本人 →
|
|
25
|
+
// 单事务 cloud→guest + outbox/row_state 同事务清空 + 重建 guest 行;事务提交后
|
|
26
|
+
// server 侧已清 remote-auth——崩窗口收敛);
|
|
27
|
+
// ③ 双会话清理:logoutRemote(幂等 re-clear remote-auth)+ clearCloudSession
|
|
28
|
+
// (清 cloud-session.json,引擎 guest 守卫回 idle)→ 解锁(先清云会话再解锁,
|
|
29
|
+
// 引擎恢复 tick 前云会话已不可用);
|
|
30
|
+
// ④ clearQueries + 整页 reload(reload 后 getSessionFn 回落 guest 会话)。
|
|
31
|
+
//
|
|
32
|
+
// 失败路径(保持既有语义):
|
|
33
|
+
// - 置锁失败(抛错或 ok=false):notify('failed') + 返回 lock-failed,不转移、不
|
|
34
|
+
// reload(留在云态,数据仍是云属主,可重试);
|
|
35
|
+
// - 转移失败:尽力解锁 + notify('failed') + 返回 transfer-failed,不清会话、不
|
|
36
|
+
// reload(单事务已回滚,重试幂等);
|
|
37
|
+
// - 会话清理/解锁失败:全部 best-effort 吞掉,**不阻塞** ④(云会话已由 serverFn
|
|
38
|
+
// 侧清理,解锁抛错也不阻止 reload——reload 后引擎按 guest 守卫重新判定)。
|
|
39
|
+
|
|
40
|
+
/** 用户可见提示事件(文案由调用方决定,本模块不持有 i18n) */
|
|
41
|
+
export type DesktopLogoutNotice = 'in-progress' | 'failed'
|
|
42
|
+
|
|
43
|
+
/** 编排终态:lock-failed / transfer-failed 表示留在云态(未 reload) */
|
|
44
|
+
export type DesktopLogoutStatus = 'done' | 'lock-failed' | 'transfer-failed'
|
|
45
|
+
|
|
46
|
+
export interface DesktopLogoutDeps {
|
|
47
|
+
/** 置转移锁(旧壳无 IPC ⇒ 省略 = 视为成功) */
|
|
48
|
+
lock?: () => Promise<{ ok: boolean; error?: string }>
|
|
49
|
+
/** 反向转移(失败必须抛出,单事务回滚语义由 serverFn 保证) */
|
|
50
|
+
transfer: (cloudUserId: string) => Promise<unknown>
|
|
51
|
+
/** 清 remote-auth(幂等 best-effort,失败不得阻塞后续) */
|
|
52
|
+
logoutRemote: () => Promise<unknown>
|
|
53
|
+
/** 清 cloud-session.json(旧壳无 IPC ⇒ 省略 = no-op) */
|
|
54
|
+
clearCloudSession?: () => Promise<unknown>
|
|
55
|
+
/** 解转移锁(失败不得阻塞后续) */
|
|
56
|
+
unlock?: () => Promise<unknown>
|
|
57
|
+
/** 清 react-query 缓存 */
|
|
58
|
+
clearQueries: () => void
|
|
59
|
+
/** 整页 reload */
|
|
60
|
+
reload: () => void
|
|
61
|
+
/** 提示回调(in-progress 在置锁前,failed 在失败路径) */
|
|
62
|
+
notify?: (notice: DesktopLogoutNotice) => void
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 执行桌面云态登出编排。**调用方负责在桌面壳存在且无本地 authjs 会话时调用**
|
|
67
|
+
* (云态判别式),本地凭据登出仍走 signOut 链。
|
|
68
|
+
*/
|
|
69
|
+
export async function runDesktopLogout(
|
|
70
|
+
cloudUserId: string,
|
|
71
|
+
deps: DesktopLogoutDeps,
|
|
72
|
+
): Promise<DesktopLogoutStatus> {
|
|
73
|
+
deps.notify?.('in-progress')
|
|
74
|
+
|
|
75
|
+
// ① 转移锁
|
|
76
|
+
let lockOk = true
|
|
77
|
+
if (deps.lock) {
|
|
78
|
+
try {
|
|
79
|
+
lockOk = (await deps.lock()).ok
|
|
80
|
+
} catch {
|
|
81
|
+
lockOk = false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!lockOk) {
|
|
85
|
+
deps.notify?.('failed')
|
|
86
|
+
return 'lock-failed'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ② 反向转移(失败:单事务回滚留在云态,尽力解锁后可重试)
|
|
90
|
+
try {
|
|
91
|
+
await deps.transfer(cloudUserId)
|
|
92
|
+
} catch {
|
|
93
|
+
await bestEffort(deps.unlock)
|
|
94
|
+
deps.notify?.('failed')
|
|
95
|
+
return 'transfer-failed'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ③ 双会话清理 + 解锁(全部尽力而为:serverFn 已清 remote-auth,此处兜底)
|
|
99
|
+
await bestEffort(deps.logoutRemote)
|
|
100
|
+
await bestEffort(deps.clearCloudSession)
|
|
101
|
+
await bestEffort(deps.unlock)
|
|
102
|
+
|
|
103
|
+
// ④ 缓存清空 + 整页刷新(reload 后 desktop-state 回 guest)
|
|
104
|
+
deps.clearQueries()
|
|
105
|
+
deps.reload()
|
|
106
|
+
return 'done'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** best-effort:缺省跳过、抛错吞掉(失败路径不得阻塞主流程) */
|
|
110
|
+
async function bestEffort(action?: () => Promise<unknown>): Promise<void> {
|
|
111
|
+
if (!action) return
|
|
112
|
+
try {
|
|
113
|
+
await action()
|
|
114
|
+
} catch {
|
|
115
|
+
/* 尽力而为 */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -23,8 +23,8 @@ import { appHotkeys, findAppHotkeyConflicts, formatAppHotkeyForBadge, CATEGORY_O
|
|
|
23
23
|
const DECLARED_IDS = Object.keys(appHotkeys) as AppHotkeyId[]
|
|
24
24
|
|
|
25
25
|
describe("appHotkeys(defineAppHotkeys 官方声明)", () => {
|
|
26
|
-
it("恰好
|
|
27
|
-
expect(DECLARED_IDS).toHaveLength(
|
|
26
|
+
it("恰好 25 条声明,且 id 与迁移前注册表对齐", () => {
|
|
27
|
+
expect(DECLARED_IDS).toHaveLength(25)
|
|
28
28
|
expect(DECLARED_IDS).toEqual(
|
|
29
29
|
expect.arrayContaining([
|
|
30
30
|
"shortcuts-panel",
|
|
@@ -34,7 +34,6 @@ describe("appHotkeys(defineAppHotkeys 官方声明)", () => {
|
|
|
34
34
|
"copy-message",
|
|
35
35
|
"chat-prev",
|
|
36
36
|
"chat-next",
|
|
37
|
-
"agent-abort",
|
|
38
37
|
"command-palette",
|
|
39
38
|
"toggle-theme",
|
|
40
39
|
"goto-dashboard",
|
|
@@ -83,15 +82,9 @@ describe("appHotkeys(defineAppHotkeys 官方声明)", () => {
|
|
|
83
82
|
}
|
|
84
83
|
})
|
|
85
84
|
|
|
86
|
-
it("非序列条目为
|
|
85
|
+
it("非序列条目为 9 条 chord(Mod+/ 等)", () => {
|
|
87
86
|
const chords = DECLARED_IDS.map((id) => appHotkeys[id]!).filter((e) => typeof e.hotkey === "string")
|
|
88
|
-
expect(chords).toHaveLength(
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it("agent-abort(本地文件助手中止运行):Mod+. 聊天分类——输入框内同样生效(ignoreInputs auto 对 Mod 组合放行)", () => {
|
|
92
|
-
const entry = appHotkeys["agent-abort"]
|
|
93
|
-
expect(entry.hotkey).toBe("Mod+.")
|
|
94
|
-
expect(entry.meta).toEqual({ descriptionKey: "common_hotkey_desc_agentAbort", category: "聊天" })
|
|
87
|
+
expect(chords).toHaveLength(9)
|
|
95
88
|
})
|
|
96
89
|
|
|
97
90
|
it("现网声明零冲突(findAppHotkeyConflicts)", () => {
|
|
@@ -74,14 +74,6 @@ export const appHotkeys = defineAppHotkeys({
|
|
|
74
74
|
hotkey: "ArrowDown",
|
|
75
75
|
meta: { descriptionKey: "common_hotkey_desc_chatNext", category: "聊天" },
|
|
76
76
|
},
|
|
77
|
-
// 本地文件助手(桌面 P3-B5)中止运行:绑定点 components/desktop-agent/agent-chat.tsx
|
|
78
|
-
// (仅运行中 enabled)。Mod+. 取 macOS「Command-句点 = 取消」惯例;Escape 已被
|
|
79
|
-
// close-panel 全局占用(同 scope 即冲突)。Mod 组合在 ignoreInputs auto 下输入框
|
|
80
|
-
// 内同样生效(边输入边中止)
|
|
81
|
-
"agent-abort": {
|
|
82
|
-
hotkey: "Mod+.",
|
|
83
|
-
meta: { descriptionKey: "common_hotkey_desc_agentAbort", category: "聊天" },
|
|
84
|
-
},
|
|
85
77
|
"command-palette": {
|
|
86
78
|
hotkey: "Mod+K",
|
|
87
79
|
meta: { descriptionKey: "common_hotkey_desc_commandPalette", category: "导航" },
|
package/src/lib/session.ts
CHANGED
|
@@ -22,6 +22,11 @@ import { getAuthConfig } from './auth/auth.config'
|
|
|
22
22
|
// desktop-session-merge 为纯数据组装 + remote-auth 纯 fs 读取(node:fs +
|
|
23
23
|
// env 守卫),无 prisma/server 重依赖链,客户端/云端部署零副作用。
|
|
24
24
|
import { getDesktopRemoteSession } from '@/server/desktop-session-merge'
|
|
25
|
+
// 桌面免登录(本地优先 P0 修复):guest 影子身份是本地合法身份,必须在**鉴权
|
|
26
|
+
// 单点**生效——requireAuth / server seam / 各 serverFn 的软鉴权(getAccountTeamFn、
|
|
27
|
+
// design/detail 等)全部复用本函数,只在这里补一次即可让全站口径一致;否则
|
|
28
|
+
// 这些端点把 guest 判成未登录,路由 beforeLoad 收到 unauthorized 后踢 /login。
|
|
29
|
+
import { buildGuestSession, isDesktopServer } from '@/server/desktop-guest'
|
|
25
30
|
|
|
26
31
|
export type SessionUser = NonNullable<AuthSession['user']> & {
|
|
27
32
|
id?: string
|
|
@@ -43,6 +48,51 @@ export type SessionUser = NonNullable<AuthSession['user']> & {
|
|
|
43
48
|
|
|
44
49
|
export type Session = Omit<AuthSession, 'user'> & { user?: SessionUser | null }
|
|
45
50
|
|
|
51
|
+
/** desktop guest 降级的 warn-once 标记(进程级;沿用 requireAuth 的排查线索语义:
|
|
52
|
+
* 桌面单机 guest 合法,但服务器部署误设 DEPLOY_TARGET=desktop 时匿名请求会变成
|
|
53
|
+
* 共享 guest 身份(fail-open)——至少留一条日志) */
|
|
54
|
+
let warnedDesktopGuest = false
|
|
55
|
+
|
|
56
|
+
/** fail-open 安全自检的 warn/error-once 标记(进程级) */
|
|
57
|
+
let warnedDesktopSecurity = false
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 桌面回落入口的安全自检(进程级一次)。
|
|
61
|
+
*
|
|
62
|
+
* `DEPLOY_TARGET=desktop` 在本仓是**安全边界**而不只是功能开关:它让匿名请求
|
|
63
|
+
* 变成共享的本地 guest 身份(AI / SSE / anyhub 文件代理等全部 getCurrentSession
|
|
64
|
+
* 消费点一起生效)。真正的桌面后端由 Electron 主进程注入 `DESKTOP_USER_DATA_DIR`
|
|
65
|
+
* (PGlite 数据目录 + remote-auth.json 的根)——该 env 缺失而 DEPLOY_TARGET 仍是
|
|
66
|
+
* desktop,最可能的解释是「云部署误设了 DEPLOY_TARGET」。
|
|
67
|
+
*
|
|
68
|
+
* 当前口径:**只 console.error 告警,不硬失败**(开发/测试也放行)。生产环境是否
|
|
69
|
+
* 直接拒绝启动/拒绝 guest 回落由控制者另行裁决;在此之前保留可用的同时留下显眼
|
|
70
|
+
* 线索。硬失败点应加在本函数之后(返回 guest 之前)。
|
|
71
|
+
*/
|
|
72
|
+
function checkDesktopFallbackEnvOnce(): void {
|
|
73
|
+
if (warnedDesktopSecurity) return
|
|
74
|
+
warnedDesktopSecurity = true
|
|
75
|
+
if (process.env.DESKTOP_USER_DATA_DIR) return
|
|
76
|
+
console.error(
|
|
77
|
+
'[session] 安全告警:DEPLOY_TARGET=desktop 但未设置 DESKTOP_USER_DATA_DIR。\n' +
|
|
78
|
+
' DEPLOY_TARGET=desktop 是安全边界:匿名请求会被当作本地 guest 身份(AI / SSE / 文件代理等全部放行)。\n' +
|
|
79
|
+
' 若非桌面单机部署,请立即移除 DEPLOY_TARGET=desktop 以恢复鉴权;\n' +
|
|
80
|
+
' 桌面壳会由 Electron 主进程注入 DESKTOP_USER_DATA_DIR(PGlite 目录 / remote-auth.json 根)。',
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function guestSession() {
|
|
85
|
+
checkDesktopFallbackEnvOnce()
|
|
86
|
+
if (!warnedDesktopGuest) {
|
|
87
|
+
warnedDesktopGuest = true
|
|
88
|
+
console.warn(
|
|
89
|
+
'[session] DEPLOY_TARGET=desktop:无会话请求已回落到本地 guest 身份。' +
|
|
90
|
+
'若这是服务器/云端部署,请移除 DEPLOY_TARGET=desktop 以恢复鉴权。',
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
return buildGuestSession()
|
|
94
|
+
}
|
|
95
|
+
|
|
46
96
|
export async function getCurrentSession(request?: Request): Promise<Session | null> {
|
|
47
97
|
if (!request) return null
|
|
48
98
|
try {
|
|
@@ -56,12 +106,32 @@ export async function getCurrentSession(request?: Request): Promise<Session | nu
|
|
|
56
106
|
// 604 处自动受益;非 desktop 恒 null(desktop 判定先于 fs 读取,零 IO)。
|
|
57
107
|
const remoteSession = await getDesktopRemoteSession()
|
|
58
108
|
if (remoteSession) return remoteSession
|
|
109
|
+
// 桌面免登录(本地优先 P0):既无 cloud 身份也未登录时,guest 影子用户即
|
|
110
|
+
// 本机当前用户(与 getSessionFn / requireAuth 的 guest 分支同一身份)。
|
|
111
|
+
// 放在本单点而非各端点:否则 account-team / design-detail 等「软鉴权」端点
|
|
112
|
+
// 把 guest 判成未登录 → beforeLoad unauthorized → 自动跳 /login(实测 P0)。
|
|
113
|
+
if (isDesktopServer()) return guestSession()
|
|
59
114
|
return session
|
|
60
115
|
} catch (error) {
|
|
61
116
|
// JWT 解密失败(AUTH_SECRET 轮换 / 跨应用同名 cookie 污染)等效于无 session:
|
|
62
117
|
// 返回 null 走未登录分支;@auth/core 会自行清理坏 cookie。
|
|
63
118
|
// 不兜底时 JWTSessionError 会炸穿所有未捕获的调用点(route beforeLoad / serverFn)。
|
|
64
119
|
console.warn('[session] 读取 session 失败(坏 cookie 或 auth 配置异常),降级为未登录:', error instanceof Error ? error.message : error)
|
|
120
|
+
// 桌面端不存在「未登录」态:坏 cookie / 鉴权配置异常不得让本地用户掉线
|
|
121
|
+
// (否则首屏 session 为 null → 页面守卫与控制台全是登录引导)。
|
|
122
|
+
//
|
|
123
|
+
// 但「authjs 抛错」≠「本机无身份」:桌面 cloud 态的真身份在 remote-auth.json,
|
|
124
|
+
// 必须先试云身份再回落 guest——直接回 guest 会让云账号用户看起来「数据全空」,
|
|
125
|
+
// 期间写入还会落到 local-guest 属主(数据错主,后续反向转移也救不回来)。
|
|
126
|
+
if (isDesktopServer()) {
|
|
127
|
+
try {
|
|
128
|
+
const remoteSession = await getDesktopRemoteSession()
|
|
129
|
+
if (remoteSession) return remoteSession
|
|
130
|
+
} catch (remoteError) {
|
|
131
|
+
console.warn('[session] 桌面云身份回落失败,降级本地 guest:', remoteError instanceof Error ? remoteError.message : remoteError)
|
|
132
|
+
}
|
|
133
|
+
return guestSession()
|
|
134
|
+
}
|
|
65
135
|
return null
|
|
66
136
|
}
|
|
67
137
|
}
|
package/src/paraglide/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Auto-generated i18n message functions. Import `messages.js` to use translated strings.
|
|
4
4
|
|
|
5
|
-
Compiled from: `/Users/urmynami/HelloAI/AllBlue-Workspace/AllBlue/.worktrees/feat-local-
|
|
5
|
+
Compiled from: `/Users/urmynami/HelloAI/AllBlue-Workspace/AllBlue/.worktrees/feat-harness-local-agent/apps/web/project.inlang`
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
## What is this folder?
|
|
@@ -26,7 +26,6 @@ export * from "./agent_desktop_errorgeneric1.js";
|
|
|
26
26
|
export * from "./agent_desktop_errorunauthorized1.js";
|
|
27
27
|
export * from "./agent_desktop_errorunavailable1.js";
|
|
28
28
|
export * from "./agent_desktop_pagedescription1.js";
|
|
29
|
-
export * from "./agent_desktop_title.js";
|
|
30
29
|
export * from "./agent_desktop_toolcountfiles2.js";
|
|
31
30
|
export * from "./agent_desktop_toolcounthits2.js";
|
|
32
31
|
export * from "./agent_desktop_toolcountlines2.js";
|
|
@@ -1837,7 +1836,6 @@ export * from "./common_hotkey_cat_chat.js";
|
|
|
1837
1836
|
export * from "./common_hotkey_cat_edit.js";
|
|
1838
1837
|
export * from "./common_hotkey_cat_navigation.js";
|
|
1839
1838
|
export * from "./common_hotkey_cat_view.js";
|
|
1840
|
-
export * from "./common_hotkey_desc_agentabort1.js";
|
|
1841
1839
|
export * from "./common_hotkey_desc_chatnext1.js";
|
|
1842
1840
|
export * from "./common_hotkey_desc_chatprev1.js";
|
|
1843
1841
|
export * from "./common_hotkey_desc_closepanel1.js";
|
|
@@ -2548,9 +2546,21 @@ export * from "./harness_dock_storyempty1.js";
|
|
|
2548
2546
|
export * from "./harness_dock_storynotfound2.js";
|
|
2549
2547
|
export * from "./harness_dock_todo.js";
|
|
2550
2548
|
export * from "./harness_dock_unknownresource1.js";
|
|
2549
|
+
export * from "./harness_engine_cloud.js";
|
|
2550
|
+
export * from "./harness_engine_label.js";
|
|
2551
|
+
export * from "./harness_engine_local.js";
|
|
2552
|
+
export * from "./harness_engine_localbadge1.js";
|
|
2553
|
+
export * from "./harness_engine_localhint1.js";
|
|
2554
|
+
export * from "./harness_engine_localnoattachments2.js";
|
|
2555
|
+
export * from "./harness_engine_switchlocked1.js";
|
|
2551
2556
|
export * from "./harness_exportspec1.js";
|
|
2552
2557
|
export * from "./harness_exportspechtml2.js";
|
|
2553
2558
|
export * from "./harness_ghostthinking1.js";
|
|
2559
|
+
export * from "./harness_localworkspace_choose1.js";
|
|
2560
|
+
export * from "./harness_localworkspace_dialogdescription2.js";
|
|
2561
|
+
export * from "./harness_localworkspace_dialogtitle2.js";
|
|
2562
|
+
export * from "./harness_localworkspace_manage1.js";
|
|
2563
|
+
export * from "./harness_localworkspace_required1.js";
|
|
2554
2564
|
export * from "./harness_media_code.js";
|
|
2555
2565
|
export * from "./harness_media_nocode1.js";
|
|
2556
2566
|
export * from "./harness_media_nospec1.js";
|
|
@@ -28,7 +28,6 @@ export * from './agent_desktop_errorgeneric1.js'
|
|
|
28
28
|
export * from './agent_desktop_errorunauthorized1.js'
|
|
29
29
|
export * from './agent_desktop_errorunavailable1.js'
|
|
30
30
|
export * from './agent_desktop_pagedescription1.js'
|
|
31
|
-
export * from './agent_desktop_title.js'
|
|
32
31
|
export * from './agent_desktop_toolcountfiles2.js'
|
|
33
32
|
export * from './agent_desktop_toolcounthits2.js'
|
|
34
33
|
export * from './agent_desktop_toolcountlines2.js'
|
|
@@ -1839,7 +1838,6 @@ export * from './common_hotkey_cat_chat.js'
|
|
|
1839
1838
|
export * from './common_hotkey_cat_edit.js'
|
|
1840
1839
|
export * from './common_hotkey_cat_navigation.js'
|
|
1841
1840
|
export * from './common_hotkey_cat_view.js'
|
|
1842
|
-
export * from './common_hotkey_desc_agentabort1.js'
|
|
1843
1841
|
export * from './common_hotkey_desc_chatnext1.js'
|
|
1844
1842
|
export * from './common_hotkey_desc_chatprev1.js'
|
|
1845
1843
|
export * from './common_hotkey_desc_closepanel1.js'
|
|
@@ -2550,9 +2548,21 @@ export * from './harness_dock_storyempty1.js'
|
|
|
2550
2548
|
export * from './harness_dock_storynotfound2.js'
|
|
2551
2549
|
export * from './harness_dock_todo.js'
|
|
2552
2550
|
export * from './harness_dock_unknownresource1.js'
|
|
2551
|
+
export * from './harness_engine_cloud.js'
|
|
2552
|
+
export * from './harness_engine_label.js'
|
|
2553
|
+
export * from './harness_engine_local.js'
|
|
2554
|
+
export * from './harness_engine_localbadge1.js'
|
|
2555
|
+
export * from './harness_engine_localhint1.js'
|
|
2556
|
+
export * from './harness_engine_localnoattachments2.js'
|
|
2557
|
+
export * from './harness_engine_switchlocked1.js'
|
|
2553
2558
|
export * from './harness_exportspec1.js'
|
|
2554
2559
|
export * from './harness_exportspechtml2.js'
|
|
2555
2560
|
export * from './harness_ghostthinking1.js'
|
|
2561
|
+
export * from './harness_localworkspace_choose1.js'
|
|
2562
|
+
export * from './harness_localworkspace_dialogdescription2.js'
|
|
2563
|
+
export * from './harness_localworkspace_dialogtitle2.js'
|
|
2564
|
+
export * from './harness_localworkspace_manage1.js'
|
|
2565
|
+
export * from './harness_localworkspace_required1.js'
|
|
2556
2566
|
export * from './harness_media_code.js'
|
|
2557
2567
|
export * from './harness_media_nocode1.js'
|
|
2558
2568
|
export * from './harness_media_nospec1.js'
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* | output |
|
|
3
3
|
* | --- |
|
|
4
|
-
* | "
|
|
4
|
+
* | "Cloud model" |
|
|
5
5
|
*
|
|
6
|
-
* @param {
|
|
6
|
+
* @param {Harness_Engine_CloudInputs} inputs
|
|
7
7
|
* @param {{ locale?: "zh" | "en" }} options
|
|
8
8
|
* @returns {LocalizedString}
|
|
9
9
|
*/
|
|
10
|
-
export const
|
|
10
|
+
export const harness_engine_cloud: ((inputs?: Harness_Engine_CloudInputs, options?: {
|
|
11
11
|
locale?: "zh" | "en";
|
|
12
|
-
}) => LocalizedString) & import("../runtime.js").MessageMetadata<
|
|
12
|
+
}) => LocalizedString) & import("../runtime.js").MessageMetadata<Harness_Engine_CloudInputs, {
|
|
13
13
|
locale?: "zh" | "en";
|
|
14
14
|
}, {}>;
|
|
15
15
|
export type LocalizedString = import("../runtime.js").LocalizedString;
|
|
16
|
-
export type
|
|
16
|
+
export type Harness_Engine_CloudInputs = {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5
|
+
|
|
6
|
+
/** @typedef {{}} Harness_Engine_CloudInputs */
|
|
7
|
+
|
|
8
|
+
const zh_harness_engine_cloud = /** @type {(inputs: Harness_Engine_CloudInputs) => LocalizedString} */ () => {
|
|
9
|
+
return /** @type {LocalizedString} */ (`云端模型`)
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const en_harness_engine_cloud = /** @type {(inputs: Harness_Engine_CloudInputs) => LocalizedString} */ () => {
|
|
13
|
+
return /** @type {LocalizedString} */ (`Cloud model`)
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* | output |
|
|
18
|
+
* | --- |
|
|
19
|
+
* | "Cloud model" |
|
|
20
|
+
*
|
|
21
|
+
* @param {Harness_Engine_CloudInputs} inputs
|
|
22
|
+
* @param {{ locale?: "zh" | "en" }} options
|
|
23
|
+
* @returns {LocalizedString}
|
|
24
|
+
*/
|
|
25
|
+
export const harness_engine_cloud = /** @type {((inputs?: Harness_Engine_CloudInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Harness_Engine_CloudInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
|
|
26
|
+
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
|
27
|
+
if (locale === "en") return en_harness_engine_cloud(inputs)
|
|
28
|
+
return zh_harness_engine_cloud(inputs)
|
|
29
|
+
});
|