@allbluecn/web-app 0.23.7 → 0.24.1
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 +57 -20
- package/src/components/harness/composer/harness-engine-control.tsx +129 -0
- package/src/components/harness/harness-workbench-page.tsx +140 -18
- package/src/components/layout/sidebar-03/app-sidebar.tsx +2 -9
- package/src/config/sidebar-routes.tsx +0 -13
- package/src/lib/changelog/sdk-versions.ts +1 -1
- package/src/lib/hotkeys/__tests__/registry.test.ts +4 -11
- package/src/lib/hotkeys/registry.ts +0 -8
- 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/_nav/harness/$threadId.lazy.tsx +19 -2
- package/src/routes/_nav/harness/$threadId.tsx +8 -2
- 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/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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5
|
+
|
|
6
|
+
/** @typedef {{}} Harness_Localworkspace_Dialogdescription2Inputs */
|
|
7
|
+
|
|
8
|
+
const zh_harness_localworkspace_dialogdescription2 = /** @type {(inputs: Harness_Localworkspace_Dialogdescription2Inputs) => LocalizedString} */ () => {
|
|
9
|
+
return /** @type {LocalizedString} */ (`pi 只能读取下方列出的目录;可随时撤销授权。`)
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const en_harness_localworkspace_dialogdescription2 = /** @type {(inputs: Harness_Localworkspace_Dialogdescription2Inputs) => LocalizedString} */ () => {
|
|
13
|
+
return /** @type {LocalizedString} */ (`pi can only read the folders listed below; revoke access at any time.`)
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* | output |
|
|
18
|
+
* | --- |
|
|
19
|
+
* | "pi can only read the folders listed below; revoke access at any time." |
|
|
20
|
+
*
|
|
21
|
+
* @param {Harness_Localworkspace_Dialogdescription2Inputs} inputs
|
|
22
|
+
* @param {{ locale?: "zh" | "en" }} options
|
|
23
|
+
* @returns {LocalizedString}
|
|
24
|
+
*/
|
|
25
|
+
const harness_localworkspace_dialogdescription2 = /** @type {((inputs?: Harness_Localworkspace_Dialogdescription2Inputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Harness_Localworkspace_Dialogdescription2Inputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
|
|
26
|
+
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
|
27
|
+
if (locale === "en") return en_harness_localworkspace_dialogdescription2(inputs)
|
|
28
|
+
return zh_harness_localworkspace_dialogdescription2(inputs)
|
|
29
|
+
});
|
|
30
|
+
export { harness_localworkspace_dialogdescription2 as "harness_localWorkspace_dialogDescription" }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { harness_localworkspace_dialogtitle2 as "harness_localWorkspace_dialogTitle" };
|
|
2
|
+
export type LocalizedString = import("../runtime.js").LocalizedString;
|
|
3
|
+
export type Harness_Localworkspace_Dialogtitle2Inputs = {};
|
|
4
|
+
/**
|
|
5
|
+
* | output |
|
|
6
|
+
* | --- |
|
|
7
|
+
* | "Local workspace" |
|
|
8
|
+
*
|
|
9
|
+
* @param {Harness_Localworkspace_Dialogtitle2Inputs} inputs
|
|
10
|
+
* @param {{ locale?: "zh" | "en" }} options
|
|
11
|
+
* @returns {LocalizedString}
|
|
12
|
+
*/
|
|
13
|
+
declare const harness_localworkspace_dialogtitle2: ((inputs?: Harness_Localworkspace_Dialogtitle2Inputs, options?: {
|
|
14
|
+
locale?: "zh" | "en";
|
|
15
|
+
}) => LocalizedString) & import("../runtime.js").MessageMetadata<Harness_Localworkspace_Dialogtitle2Inputs, {
|
|
16
|
+
locale?: "zh" | "en";
|
|
17
|
+
}, {}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5
|
+
|
|
6
|
+
/** @typedef {{}} Harness_Localworkspace_Dialogtitle2Inputs */
|
|
7
|
+
|
|
8
|
+
const zh_harness_localworkspace_dialogtitle2 = /** @type {(inputs: Harness_Localworkspace_Dialogtitle2Inputs) => LocalizedString} */ () => {
|
|
9
|
+
return /** @type {LocalizedString} */ (`本地工作区`)
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const en_harness_localworkspace_dialogtitle2 = /** @type {(inputs: Harness_Localworkspace_Dialogtitle2Inputs) => LocalizedString} */ () => {
|
|
13
|
+
return /** @type {LocalizedString} */ (`Local workspace`)
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* | output |
|
|
18
|
+
* | --- |
|
|
19
|
+
* | "Local workspace" |
|
|
20
|
+
*
|
|
21
|
+
* @param {Harness_Localworkspace_Dialogtitle2Inputs} inputs
|
|
22
|
+
* @param {{ locale?: "zh" | "en" }} options
|
|
23
|
+
* @returns {LocalizedString}
|
|
24
|
+
*/
|
|
25
|
+
const harness_localworkspace_dialogtitle2 = /** @type {((inputs?: Harness_Localworkspace_Dialogtitle2Inputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Harness_Localworkspace_Dialogtitle2Inputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
|
|
26
|
+
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
|
27
|
+
if (locale === "en") return en_harness_localworkspace_dialogtitle2(inputs)
|
|
28
|
+
return zh_harness_localworkspace_dialogtitle2(inputs)
|
|
29
|
+
});
|
|
30
|
+
export { harness_localworkspace_dialogtitle2 as "harness_localWorkspace_dialogTitle" }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { harness_localworkspace_manage1 as "harness_localWorkspace_manage" };
|
|
2
|
+
export type LocalizedString = import("../runtime.js").LocalizedString;
|
|
3
|
+
export type Harness_Localworkspace_Manage1Inputs = {};
|
|
4
|
+
/**
|
|
5
|
+
* | output |
|
|
6
|
+
* | --- |
|
|
7
|
+
* | "Manage authorized folders" |
|
|
8
|
+
*
|
|
9
|
+
* @param {Harness_Localworkspace_Manage1Inputs} inputs
|
|
10
|
+
* @param {{ locale?: "zh" | "en" }} options
|
|
11
|
+
* @returns {LocalizedString}
|
|
12
|
+
*/
|
|
13
|
+
declare const harness_localworkspace_manage1: ((inputs?: Harness_Localworkspace_Manage1Inputs, options?: {
|
|
14
|
+
locale?: "zh" | "en";
|
|
15
|
+
}) => LocalizedString) & import("../runtime.js").MessageMetadata<Harness_Localworkspace_Manage1Inputs, {
|
|
16
|
+
locale?: "zh" | "en";
|
|
17
|
+
}, {}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5
|
+
|
|
6
|
+
/** @typedef {{}} Harness_Localworkspace_Manage1Inputs */
|
|
7
|
+
|
|
8
|
+
const zh_harness_localworkspace_manage1 = /** @type {(inputs: Harness_Localworkspace_Manage1Inputs) => LocalizedString} */ () => {
|
|
9
|
+
return /** @type {LocalizedString} */ (`管理授权目录`)
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const en_harness_localworkspace_manage1 = /** @type {(inputs: Harness_Localworkspace_Manage1Inputs) => LocalizedString} */ () => {
|
|
13
|
+
return /** @type {LocalizedString} */ (`Manage authorized folders`)
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* | output |
|
|
18
|
+
* | --- |
|
|
19
|
+
* | "Manage authorized folders" |
|
|
20
|
+
*
|
|
21
|
+
* @param {Harness_Localworkspace_Manage1Inputs} inputs
|
|
22
|
+
* @param {{ locale?: "zh" | "en" }} options
|
|
23
|
+
* @returns {LocalizedString}
|
|
24
|
+
*/
|
|
25
|
+
const harness_localworkspace_manage1 = /** @type {((inputs?: Harness_Localworkspace_Manage1Inputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Harness_Localworkspace_Manage1Inputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
|
|
26
|
+
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
|
27
|
+
if (locale === "en") return en_harness_localworkspace_manage1(inputs)
|
|
28
|
+
return zh_harness_localworkspace_manage1(inputs)
|
|
29
|
+
});
|
|
30
|
+
export { harness_localworkspace_manage1 as "harness_localWorkspace_manage" }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { harness_localworkspace_required1 as "harness_localWorkspace_required" };
|
|
2
|
+
export type LocalizedString = import("../runtime.js").LocalizedString;
|
|
3
|
+
export type Harness_Localworkspace_Required1Inputs = {};
|
|
4
|
+
/**
|
|
5
|
+
* | output |
|
|
6
|
+
* | --- |
|
|
7
|
+
* | "Choose a local workspace first" |
|
|
8
|
+
*
|
|
9
|
+
* @param {Harness_Localworkspace_Required1Inputs} inputs
|
|
10
|
+
* @param {{ locale?: "zh" | "en" }} options
|
|
11
|
+
* @returns {LocalizedString}
|
|
12
|
+
*/
|
|
13
|
+
declare const harness_localworkspace_required1: ((inputs?: Harness_Localworkspace_Required1Inputs, options?: {
|
|
14
|
+
locale?: "zh" | "en";
|
|
15
|
+
}) => LocalizedString) & import("../runtime.js").MessageMetadata<Harness_Localworkspace_Required1Inputs, {
|
|
16
|
+
locale?: "zh" | "en";
|
|
17
|
+
}, {}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { getLocale, experimentalStaticLocale } from '../runtime.js';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
|
|
5
|
+
|
|
6
|
+
/** @typedef {{}} Harness_Localworkspace_Required1Inputs */
|
|
7
|
+
|
|
8
|
+
const zh_harness_localworkspace_required1 = /** @type {(inputs: Harness_Localworkspace_Required1Inputs) => LocalizedString} */ () => {
|
|
9
|
+
return /** @type {LocalizedString} */ (`请先选择本地工作目录`)
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const en_harness_localworkspace_required1 = /** @type {(inputs: Harness_Localworkspace_Required1Inputs) => LocalizedString} */ () => {
|
|
13
|
+
return /** @type {LocalizedString} */ (`Choose a local workspace first`)
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* | output |
|
|
18
|
+
* | --- |
|
|
19
|
+
* | "Choose a local workspace first" |
|
|
20
|
+
*
|
|
21
|
+
* @param {Harness_Localworkspace_Required1Inputs} inputs
|
|
22
|
+
* @param {{ locale?: "zh" | "en" }} options
|
|
23
|
+
* @returns {LocalizedString}
|
|
24
|
+
*/
|
|
25
|
+
const harness_localworkspace_required1 = /** @type {((inputs?: Harness_Localworkspace_Required1Inputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Harness_Localworkspace_Required1Inputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
|
|
26
|
+
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
|
|
27
|
+
if (locale === "en") return en_harness_localworkspace_required1(inputs)
|
|
28
|
+
return zh_harness_localworkspace_required1(inputs)
|
|
29
|
+
});
|
|
30
|
+
export { harness_localworkspace_required1 as "harness_localWorkspace_required" }
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -23,7 +23,6 @@ import { Route as LoginLoginRouteRouteImport } from './routes/_login/login/route
|
|
|
23
23
|
import { Route as LoginRegisterRouteRouteImport } from './routes/_login/register/route'
|
|
24
24
|
import { Route as LoginResetPasswordRouteRouteImport } from './routes/_login/reset-password/route'
|
|
25
25
|
import { Route as LoginVerifyEmailRouteRouteImport } from './routes/_login/verify-email/route'
|
|
26
|
-
import { Route as NavAgentRouteRouteImport } from './routes/_nav/agent/route'
|
|
27
26
|
import { Route as NavAppsRouteRouteImport } from './routes/_nav/apps/route'
|
|
28
27
|
import { Route as NavBugsRouteRouteImport } from './routes/_nav/bugs/route'
|
|
29
28
|
import { Route as NavChangelogRouteRouteImport } from './routes/_nav/changelog/route'
|
|
@@ -252,13 +251,6 @@ const LoginVerifyEmailRouteRoute = LoginVerifyEmailRouteRouteImport.update({
|
|
|
252
251
|
} as any).lazy(() =>
|
|
253
252
|
import('./routes/_login/verify-email/route.lazy').then((d) => d.Route),
|
|
254
253
|
)
|
|
255
|
-
const NavAgentRouteRoute = NavAgentRouteRouteImport.update({
|
|
256
|
-
id: '/agent',
|
|
257
|
-
path: '/agent',
|
|
258
|
-
getParentRoute: () => NavRouteRoute,
|
|
259
|
-
} as any).lazy(() =>
|
|
260
|
-
import('./routes/_nav/agent/route.lazy').then((d) => d.Route),
|
|
261
|
-
)
|
|
262
254
|
const NavAppsRouteRoute = NavAppsRouteRouteImport.update({
|
|
263
255
|
id: '/apps',
|
|
264
256
|
path: '/apps',
|
|
@@ -1131,7 +1123,6 @@ export interface FileRoutesByFullPath {
|
|
|
1131
1123
|
'/register': typeof LoginRegisterRouteRoute
|
|
1132
1124
|
'/reset-password': typeof LoginResetPasswordRouteRoute
|
|
1133
1125
|
'/verify-email': typeof LoginVerifyEmailRouteRoute
|
|
1134
|
-
'/agent': typeof NavAgentRouteRoute
|
|
1135
1126
|
'/apps': typeof NavAppsRouteRouteWithChildren
|
|
1136
1127
|
'/bugs': typeof NavBugsRouteRouteWithChildren
|
|
1137
1128
|
'/changelog': typeof NavChangelogRouteRoute
|
|
@@ -1271,7 +1262,6 @@ export interface FileRoutesByTo {
|
|
|
1271
1262
|
'/register': typeof LoginRegisterRouteRoute
|
|
1272
1263
|
'/reset-password': typeof LoginResetPasswordRouteRoute
|
|
1273
1264
|
'/verify-email': typeof LoginVerifyEmailRouteRoute
|
|
1274
|
-
'/agent': typeof NavAgentRouteRoute
|
|
1275
1265
|
'/apps': typeof NavAppsRouteRouteWithChildren
|
|
1276
1266
|
'/changelog': typeof NavChangelogRouteRoute
|
|
1277
1267
|
'/dashboard': typeof NavDashboardRouteRoute
|
|
@@ -1400,7 +1390,6 @@ export interface FileRoutesById {
|
|
|
1400
1390
|
'/_login/register': typeof LoginRegisterRouteRoute
|
|
1401
1391
|
'/_login/reset-password': typeof LoginResetPasswordRouteRoute
|
|
1402
1392
|
'/_login/verify-email': typeof LoginVerifyEmailRouteRoute
|
|
1403
|
-
'/_nav/agent': typeof NavAgentRouteRoute
|
|
1404
1393
|
'/_nav/apps': typeof NavAppsRouteRouteWithChildren
|
|
1405
1394
|
'/_nav/bugs': typeof NavBugsRouteRouteWithChildren
|
|
1406
1395
|
'/_nav/changelog': typeof NavChangelogRouteRoute
|
|
@@ -1542,7 +1531,6 @@ export interface FileRouteTypes {
|
|
|
1542
1531
|
| '/register'
|
|
1543
1532
|
| '/reset-password'
|
|
1544
1533
|
| '/verify-email'
|
|
1545
|
-
| '/agent'
|
|
1546
1534
|
| '/apps'
|
|
1547
1535
|
| '/bugs'
|
|
1548
1536
|
| '/changelog'
|
|
@@ -1682,7 +1670,6 @@ export interface FileRouteTypes {
|
|
|
1682
1670
|
| '/register'
|
|
1683
1671
|
| '/reset-password'
|
|
1684
1672
|
| '/verify-email'
|
|
1685
|
-
| '/agent'
|
|
1686
1673
|
| '/apps'
|
|
1687
1674
|
| '/changelog'
|
|
1688
1675
|
| '/dashboard'
|
|
@@ -1810,7 +1797,6 @@ export interface FileRouteTypes {
|
|
|
1810
1797
|
| '/_login/register'
|
|
1811
1798
|
| '/_login/reset-password'
|
|
1812
1799
|
| '/_login/verify-email'
|
|
1813
|
-
| '/_nav/agent'
|
|
1814
1800
|
| '/_nav/apps'
|
|
1815
1801
|
| '/_nav/bugs'
|
|
1816
1802
|
| '/_nav/changelog'
|
|
@@ -2073,13 +2059,6 @@ declare module '@tanstack/react-router' {
|
|
|
2073
2059
|
preLoaderRoute: typeof LoginVerifyEmailRouteRouteImport
|
|
2074
2060
|
parentRoute: typeof LoginRouteRoute
|
|
2075
2061
|
}
|
|
2076
|
-
'/_nav/agent': {
|
|
2077
|
-
id: '/_nav/agent'
|
|
2078
|
-
path: '/agent'
|
|
2079
|
-
fullPath: '/agent'
|
|
2080
|
-
preLoaderRoute: typeof NavAgentRouteRouteImport
|
|
2081
|
-
parentRoute: typeof NavRouteRoute
|
|
2082
|
-
}
|
|
2083
2062
|
'/_nav/apps': {
|
|
2084
2063
|
id: '/_nav/apps'
|
|
2085
2064
|
path: '/apps'
|
|
@@ -3372,7 +3351,6 @@ const NavChatRouteWithChildren =
|
|
|
3372
3351
|
NavChatRoute._addFileChildren(NavChatRouteChildren)
|
|
3373
3352
|
|
|
3374
3353
|
interface NavRouteRouteChildren {
|
|
3375
|
-
NavAgentRouteRoute: typeof NavAgentRouteRoute
|
|
3376
3354
|
NavAppsRouteRoute: typeof NavAppsRouteRouteWithChildren
|
|
3377
3355
|
NavBugsRouteRoute: typeof NavBugsRouteRouteWithChildren
|
|
3378
3356
|
NavChangelogRouteRoute: typeof NavChangelogRouteRoute
|
|
@@ -3394,7 +3372,6 @@ interface NavRouteRouteChildren {
|
|
|
3394
3372
|
}
|
|
3395
3373
|
|
|
3396
3374
|
const NavRouteRouteChildren: NavRouteRouteChildren = {
|
|
3397
|
-
NavAgentRouteRoute: NavAgentRouteRoute,
|
|
3398
3375
|
NavAppsRouteRoute: NavAppsRouteRouteWithChildren,
|
|
3399
3376
|
NavBugsRouteRoute: NavBugsRouteRouteWithChildren,
|
|
3400
3377
|
NavChangelogRouteRoute: NavChangelogRouteRoute,
|
|
@@ -25,9 +25,26 @@ export const Route = createLazyFileRoute('/_nav/harness/$threadId')({
|
|
|
25
25
|
// loader 推断类型是 serverFn 的 JSON 序列化形状(role 含 "tool"、parts 为
|
|
26
26
|
// JsonValue),与 HarnessWorkbenchPage 的 UIMessage 入参不结构兼容,
|
|
27
27
|
// 断言是承载性的(审计建议的「免断言」会使 tsc 报错)
|
|
28
|
-
const
|
|
28
|
+
const data = Route.useLoaderData() as {
|
|
29
|
+
messages: UIMessage[]
|
|
30
|
+
conversation: {
|
|
31
|
+
id: string
|
|
32
|
+
title: string | null
|
|
33
|
+
agentId: string | null
|
|
34
|
+
engine: "cloud" | "local"
|
|
35
|
+
localCwd: string | null
|
|
36
|
+
isArchived: boolean
|
|
37
|
+
} | null
|
|
38
|
+
}
|
|
29
39
|
// key 强制重建:HarnessWorkbenchPage 以 useState(initialThreadId) 持有会话状态,
|
|
30
40
|
// route param 变化时组件实例被复用,无 key 会残留上一线程的消息
|
|
31
|
-
return
|
|
41
|
+
return (
|
|
42
|
+
<HarnessWorkbenchPage
|
|
43
|
+
key={threadId}
|
|
44
|
+
initialThreadId={threadId}
|
|
45
|
+
initialMessages={data.messages}
|
|
46
|
+
initialConversation={data.conversation}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
32
49
|
},
|
|
33
50
|
})
|
|
@@ -17,10 +17,16 @@
|
|
|
17
17
|
|
|
18
18
|
import { createFileRoute } from '@tanstack/react-router'
|
|
19
19
|
import { listMessagesFn } from '@/server/serverFns/chat/list-messages'
|
|
20
|
+
import { getConversationFn } from '@/server/serverFns/chat/conversations'
|
|
20
21
|
|
|
21
22
|
export const Route = createFileRoute('/_nav/harness/$threadId')({
|
|
22
23
|
loader: async ({ params }) => {
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
// 会话元数据(engine/localCwd)与消息并行取:Harness 打开线程时要据此
|
|
25
|
+
// 还原「云端 / 本地工作区」引擎与目录选择,不能等首条消息再猜。
|
|
26
|
+
const [messages, conversation] = await Promise.all([
|
|
27
|
+
listMessagesFn({ data: { threadId: params.threadId } }),
|
|
28
|
+
getConversationFn({ data: { id: params.threadId } }),
|
|
29
|
+
])
|
|
30
|
+
return { messages, conversation }
|
|
25
31
|
},
|
|
26
32
|
})
|
|
@@ -142,7 +142,7 @@ describe("createConversationFn", () => {
|
|
|
142
142
|
|
|
143
143
|
expect(mocks.findFirst).not.toHaveBeenCalled()
|
|
144
144
|
expect(mocks.create).toHaveBeenCalledWith({
|
|
145
|
-
data: { userId, agentId: null },
|
|
145
|
+
data: { userId, agentId: null, engine: "cloud", localCwd: null },
|
|
146
146
|
include: { agent: { select: { name: true, avatarEmoji: true } } },
|
|
147
147
|
})
|
|
148
148
|
expect(result).toMatchObject({
|
|
@@ -173,7 +173,7 @@ describe("createConversationFn", () => {
|
|
|
173
173
|
},
|
|
174
174
|
})
|
|
175
175
|
expect(mocks.create).toHaveBeenCalledWith({
|
|
176
|
-
data: { userId, agentId },
|
|
176
|
+
data: { userId, agentId, engine: "cloud", localCwd: null },
|
|
177
177
|
include: { agent: { select: { name: true, avatarEmoji: true } } },
|
|
178
178
|
})
|
|
179
179
|
expect(result).toMatchObject({
|
|
@@ -198,7 +198,7 @@ describe("createConversationFn", () => {
|
|
|
198
198
|
|
|
199
199
|
expect(mocks.findFirst).not.toHaveBeenCalled()
|
|
200
200
|
expect(mocks.create).toHaveBeenCalledWith({
|
|
201
|
-
data: { userId, agentId: null },
|
|
201
|
+
data: { userId, agentId: null, engine: "cloud", localCwd: null },
|
|
202
202
|
include: { agent: { select: { name: true, avatarEmoji: true } } },
|
|
203
203
|
})
|
|
204
204
|
})
|
|
@@ -213,6 +213,8 @@ describe("listConversationsFn", () => {
|
|
|
213
213
|
updatedAt: now,
|
|
214
214
|
isArchived: false,
|
|
215
215
|
agentId: "agent-001",
|
|
216
|
+
engine: "cloud",
|
|
217
|
+
localCwd: null,
|
|
216
218
|
_count: { messages: 5 },
|
|
217
219
|
agent: { name: "Researcher", avatarEmoji: "🔬" },
|
|
218
220
|
interrupts: [{ id: "int-001" }],
|
|
@@ -223,6 +225,8 @@ describe("listConversationsFn", () => {
|
|
|
223
225
|
updatedAt: new Date("2026-08-16T08:00:00.000Z"),
|
|
224
226
|
isArchived: false,
|
|
225
227
|
agentId: null,
|
|
228
|
+
engine: "cloud",
|
|
229
|
+
localCwd: null,
|
|
226
230
|
_count: { messages: 0 },
|
|
227
231
|
agent: null,
|
|
228
232
|
interrupts: [],
|
|
@@ -240,6 +244,8 @@ describe("listConversationsFn", () => {
|
|
|
240
244
|
title: true,
|
|
241
245
|
isArchived: true,
|
|
242
246
|
agentId: true,
|
|
247
|
+
engine: true,
|
|
248
|
+
localCwd: true,
|
|
243
249
|
updatedAt: true,
|
|
244
250
|
_count: { select: { messages: true } },
|
|
245
251
|
agent: { select: { name: true, avatarEmoji: true } },
|
|
@@ -254,6 +260,8 @@ describe("listConversationsFn", () => {
|
|
|
254
260
|
messageCount: 5,
|
|
255
261
|
isArchived: false,
|
|
256
262
|
agentId: "agent-001",
|
|
263
|
+
engine: "cloud",
|
|
264
|
+
localCwd: null,
|
|
257
265
|
agentName: "Researcher",
|
|
258
266
|
agentEmoji: "🔬",
|
|
259
267
|
pendingInteraction: true,
|
|
@@ -265,6 +273,8 @@ describe("listConversationsFn", () => {
|
|
|
265
273
|
messageCount: 0,
|
|
266
274
|
isArchived: false,
|
|
267
275
|
agentId: null,
|
|
276
|
+
engine: "cloud",
|
|
277
|
+
localCwd: null,
|
|
268
278
|
agentName: null,
|
|
269
279
|
agentEmoji: null,
|
|
270
280
|
pendingInteraction: false,
|
|
@@ -83,7 +83,6 @@ vi.mock('@/server/entitlements-token', () => ({ generateEntitlementsToken: vi.fn
|
|
|
83
83
|
vi.mock('@/server/middlewares/rate-limit', () => ({ checkAuthRateLimit: vi.fn() }))
|
|
84
84
|
|
|
85
85
|
import { prisma } from '@allbluecn/database'
|
|
86
|
-
import { ensureBuiltinIndependentProject } from '@/server/builtin-project'
|
|
87
86
|
import { ensureLocalShadowUser } from '../desktop-bootstrap'
|
|
88
87
|
import { getSessionFn } from '../serverFns/auth/session'
|
|
89
88
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
6
|
+
import {
|
|
7
|
+
LOCAL_AGENT_GLOBAL_KEY,
|
|
8
|
+
LocalAgentAdapter,
|
|
9
|
+
extractLastUserText,
|
|
10
|
+
} from '../local-agent-adapter'
|
|
11
|
+
|
|
12
|
+
const original = (globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY]
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
if (original === undefined) delete (globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY]
|
|
16
|
+
else (globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY] = original
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function installService(run: (input: unknown) => AsyncIterable<unknown>) {
|
|
20
|
+
;(globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY] = {
|
|
21
|
+
runLocalAgentPrompt: run,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function options(overrides: Record<string, unknown> = {}) {
|
|
26
|
+
return {
|
|
27
|
+
messages: [{ role: 'user', content: '总结这个目录' }],
|
|
28
|
+
threadId: 'thread-1',
|
|
29
|
+
runId: 'run-1',
|
|
30
|
+
modelOptions: { cwd: '/tmp/workspace' },
|
|
31
|
+
...overrides,
|
|
32
|
+
} as never
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('extractLastUserText', () => {
|
|
36
|
+
it('取最后一条非空 user 文本(字符串 / text parts 两种形态)', () => {
|
|
37
|
+
expect(
|
|
38
|
+
extractLastUserText([
|
|
39
|
+
{ role: 'user', content: 'old' },
|
|
40
|
+
{ role: 'assistant', content: 'answer' },
|
|
41
|
+
{ role: 'user', content: [{ type: 'text', content: 'new' }] },
|
|
42
|
+
] as never),
|
|
43
|
+
).toBe('new')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('忽略空 user 文本,回退更早的一条', () => {
|
|
47
|
+
expect(
|
|
48
|
+
extractLastUserText([
|
|
49
|
+
{ role: 'user', content: 'first' },
|
|
50
|
+
{ role: 'user', content: ' ' },
|
|
51
|
+
] as never),
|
|
52
|
+
).toBe('first')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('LocalAgentAdapter', () => {
|
|
57
|
+
it('未装配桌面运行时 → RUN_ERROR,不抛错', async () => {
|
|
58
|
+
delete (globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY]
|
|
59
|
+
const events: Array<{ type: string; message?: string }> = []
|
|
60
|
+
for await (const event of new LocalAgentAdapter().chatStream(options())) {
|
|
61
|
+
events.push(event as never)
|
|
62
|
+
}
|
|
63
|
+
expect(events).toHaveLength(1)
|
|
64
|
+
expect(events[0]?.type).toBe('RUN_ERROR')
|
|
65
|
+
expect(events[0]?.message).toContain('本地 Agent 不可用')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('转发 pi 的 AG-UI 事件,并为 RUN_FINISHED 补 finishReason=stop', async () => {
|
|
69
|
+
const received: unknown[] = []
|
|
70
|
+
installService(async function* (input) {
|
|
71
|
+
received.push(input)
|
|
72
|
+
yield { type: 'RUN_STARTED', threadId: 'thread-1', runId: 'run-1' }
|
|
73
|
+
yield { type: 'TEXT_MESSAGE_START', messageId: 'm1', role: 'assistant' }
|
|
74
|
+
yield { type: 'TEXT_MESSAGE_CONTENT', messageId: 'm1', delta: '你好' }
|
|
75
|
+
yield { type: 'TEXT_MESSAGE_END', messageId: 'm1' }
|
|
76
|
+
yield { type: 'RUN_FINISHED', threadId: 'thread-1', runId: 'run-1' }
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const events: Array<Record<string, unknown>> = []
|
|
80
|
+
for await (const event of new LocalAgentAdapter().chatStream(options())) {
|
|
81
|
+
events.push(event as never)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
expect(received[0]).toMatchObject({
|
|
85
|
+
cwd: '/tmp/workspace',
|
|
86
|
+
sessionId: 'thread-1',
|
|
87
|
+
threadId: 'thread-1',
|
|
88
|
+
runId: 'run-1',
|
|
89
|
+
})
|
|
90
|
+
expect(events.map((event) => event.type)).toEqual([
|
|
91
|
+
'RUN_STARTED',
|
|
92
|
+
'TEXT_MESSAGE_START',
|
|
93
|
+
'TEXT_MESSAGE_CONTENT',
|
|
94
|
+
'TEXT_MESSAGE_END',
|
|
95
|
+
'RUN_FINISHED',
|
|
96
|
+
])
|
|
97
|
+
expect(events[4]).toMatchObject({ finishReason: 'stop', model: 'local-pi' })
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('缺少 cwd → RUN_ERROR', async () => {
|
|
101
|
+
installService(async function* () {})
|
|
102
|
+
const events: Array<{ type: string }> = []
|
|
103
|
+
for await (const event of new LocalAgentAdapter().chatStream(
|
|
104
|
+
options({ modelOptions: {} }),
|
|
105
|
+
)) {
|
|
106
|
+
events.push(event as never)
|
|
107
|
+
}
|
|
108
|
+
expect(events[0]?.type).toBe('RUN_ERROR')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
6
|
+
import { resolveUnifiedAiRoute } from '../unified-router'
|
|
7
|
+
import { LOCAL_AGENT_GLOBAL_KEY } from '../local-agent-adapter'
|
|
8
|
+
|
|
9
|
+
const originalTarget = process.env.DEPLOY_TARGET
|
|
10
|
+
const originalService = (globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY]
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
if (originalTarget === undefined) delete process.env.DEPLOY_TARGET
|
|
14
|
+
else process.env.DEPLOY_TARGET = originalTarget
|
|
15
|
+
if (originalService === undefined) delete (globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY]
|
|
16
|
+
else (globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY] = originalService
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const baseRequest = {
|
|
20
|
+
userId: 'local-guest',
|
|
21
|
+
messages: [{ role: 'user' as const, content: '看看这个目录' }],
|
|
22
|
+
threadId: 'thread-local-1',
|
|
23
|
+
engine: 'local' as const,
|
|
24
|
+
localCwd: '/tmp/work',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('resolveUnifiedAiRoute — 本地 Agent 引擎', () => {
|
|
28
|
+
it('非桌面部署 → 拒绝(云端不能走本地文件)', async () => {
|
|
29
|
+
process.env.DEPLOY_TARGET = 'web'
|
|
30
|
+
await expect(resolveUnifiedAiRoute(baseRequest)).rejects.toThrow(/仅在桌面客户端可用/)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('桌面但未装配运行时 → 明确报错而不是回落 BYOK', async () => {
|
|
34
|
+
process.env.DEPLOY_TARGET = 'desktop'
|
|
35
|
+
delete (globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY]
|
|
36
|
+
await expect(resolveUnifiedAiRoute(baseRequest)).rejects.toThrow(/运行时未装配/)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('桌面 + 运行时 + cwd → 装配 local 路由(adapter 由声明式 harness 包装)', async () => {
|
|
40
|
+
process.env.DEPLOY_TARGET = 'desktop'
|
|
41
|
+
;(globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY] = {
|
|
42
|
+
runLocalAgentPrompt: async function* () {},
|
|
43
|
+
}
|
|
44
|
+
const route = await resolveUnifiedAiRoute(baseRequest)
|
|
45
|
+
expect(route.kind).toBe('local')
|
|
46
|
+
if (route.kind === 'local') {
|
|
47
|
+
// 只需确认可迭代对象已装配;真正的事件映射在 local-agent-adapter 单测覆盖
|
|
48
|
+
expect(typeof route.stream[Symbol.asyncIterator]).toBe('function')
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('engine 缺省时不走本地链路(保持云端行为)', async () => {
|
|
53
|
+
process.env.DEPLOY_TARGET = 'desktop'
|
|
54
|
+
;(globalThis as Record<string, unknown>)[LOCAL_AGENT_GLOBAL_KEY] = {
|
|
55
|
+
runLocalAgentPrompt: async function* () {},
|
|
56
|
+
}
|
|
57
|
+
// 没有 engine=local:会进入 BYOK/hosted 解析(测试环境无 provider → NoAiAvailableError),
|
|
58
|
+
// 关键断言是错误不是「本地 Agent」相关,证明没有误走本地分支
|
|
59
|
+
await expect(
|
|
60
|
+
resolveUnifiedAiRoute({ ...baseRequest, engine: undefined }),
|
|
61
|
+
).rejects.not.toThrow(/本地 Agent/)
|
|
62
|
+
})
|
|
63
|
+
})
|