@nextclaw/ui 0.12.35-beta.2 → 0.12.35-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/dist/assets/api-BTZui7nj.js +15 -0
  3. package/dist/assets/{book-open--T9dJHnp.js → book-open-qc0UVn6m.js} +1 -1
  4. package/dist/assets/channels-list-page-DBtU_M1Y.js +8 -0
  5. package/dist/assets/{chat-page-Doi-sasO.js → chat-page-BcoJYo91.js} +2 -2
  6. package/dist/assets/config-split-page-DW8tP9Bc.js +1 -0
  7. package/dist/assets/createLucideIcon-CA0sgoMJ.js +1 -0
  8. package/dist/assets/desktop-update-config-DARSwQG8.js +1 -0
  9. package/dist/assets/dist-D5zsy7pX.js +1 -0
  10. package/dist/assets/dist-DNxIa3pe.js +41 -0
  11. package/dist/assets/doc-browser-B9skxzVo.js +1 -0
  12. package/dist/assets/doc-browser-CwyITRXx.js +1 -0
  13. package/dist/assets/doc-browser-context-Do2qSrB8.js +1 -0
  14. package/dist/assets/{external-link-CZ9bQY0L.js → external-link-C9fbyi6k.js} +1 -1
  15. package/dist/assets/index-BYRP_BMj.css +1 -0
  16. package/dist/assets/index-DckPGTdM.js +103 -0
  17. package/dist/assets/mcp-marketplace-page-BiExW5wq.js +40 -0
  18. package/dist/assets/mcp-marketplace-page-Cj6MDSkE.js +1 -0
  19. package/dist/assets/model-config-DbdY7fF1.js +1 -0
  20. package/dist/assets/notice-card-CyiVcu_Z.js +5 -0
  21. package/dist/assets/play-D-s0C4MN.js +1 -0
  22. package/dist/assets/plus-DOwjM_kF.js +1 -0
  23. package/dist/assets/provider-scoped-model-input-Bk8jlpRm.js +1 -0
  24. package/dist/assets/providers-list-CJjNStDV.js +1 -0
  25. package/dist/assets/react-BaRotFd6.js +1 -0
  26. package/dist/assets/remote-gl8QckNc.js +1 -0
  27. package/dist/assets/runtime-config-page-BMFc1yzf.js +1 -0
  28. package/dist/assets/{save-B6wbBLkU.js → save-CG4Hb1u8.js} +1 -1
  29. package/dist/assets/{search-config-BtCNY431.js → search-config-DUOR26if.js} +1 -1
  30. package/dist/assets/{search-Czito3Dk.js → search-lWpOIHEn.js} +1 -1
  31. package/dist/assets/secrets-config-ZbLzjIRx.js +3 -0
  32. package/dist/assets/setting-row-CNfTKDjc.js +1 -0
  33. package/dist/assets/tabs-custom-BbZoysAl.js +1 -0
  34. package/dist/assets/tag-chip-Dl8JbhC7.js +1 -0
  35. package/dist/assets/x-D6GwT_8k.js +1 -0
  36. package/dist/index.html +15 -17
  37. package/package.json +8 -9
  38. package/src/app/components/app-presenter-provider.tsx +21 -0
  39. package/src/app/components/layout/sidebar.layout.test.tsx +2 -2
  40. package/src/app/components/layout/sidebar.tsx +3 -3
  41. package/src/app/index.tsx +5 -3
  42. package/src/app/presenters/app.presenter.ts +24 -0
  43. package/src/features/account/components/account-panel.tsx +11 -11
  44. package/src/features/account/index.ts +1 -4
  45. package/src/features/account/managers/account.manager.ts +52 -27
  46. package/src/features/account/stores/account.store.ts +0 -15
  47. package/src/features/apps/components/apps-panel.tsx +43 -0
  48. package/src/features/apps/index.ts +1 -0
  49. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +8 -8
  50. package/src/features/chat/components/layout/chat-sidebar.tsx +3 -2
  51. package/src/features/chat/managers/chat-session-list.manager.test.ts +58 -0
  52. package/src/features/chat/stores/chat-session-list.store.ts +58 -7
  53. package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
  54. package/src/features/panel-apps/components/panel-apps-list.tsx +6 -2
  55. package/src/features/panel-apps/index.ts +3 -1
  56. package/src/features/panel-apps/managers/panel-app-bridge.manager.test.ts +111 -0
  57. package/src/features/panel-apps/managers/panel-app-bridge.manager.ts +247 -0
  58. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.test.ts +23 -0
  59. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +42 -16
  60. package/src/features/remote/components/remote-access-page.test.tsx +2 -2
  61. package/src/features/remote/components/remote-access-page.tsx +9 -9
  62. package/src/features/remote/managers/remote-access.manager.ts +24 -41
  63. package/src/features/service-apps/components/service-action-authorization-dialog.tsx +78 -0
  64. package/src/features/service-apps/components/service-apps-panel.tsx +290 -0
  65. package/src/features/service-apps/hooks/use-service-apps.ts +66 -0
  66. package/src/features/service-apps/index.ts +3 -0
  67. package/src/features/service-apps/managers/service-action-authorization.manager.ts +10 -0
  68. package/src/features/service-apps/stores/service-action-authorization.store.ts +53 -0
  69. package/src/shared/components/doc-browser/doc-browser-context.test.tsx +150 -1
  70. package/src/shared/components/doc-browser/doc-browser-context.tsx +34 -443
  71. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +2 -2
  72. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +7 -0
  73. package/src/shared/components/doc-browser/doc-browser.test.tsx +82 -2
  74. package/src/shared/components/doc-browser/doc-browser.tsx +147 -135
  75. package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +272 -0
  76. package/src/shared/components/doc-browser/stores/doc-browser.store.ts +140 -0
  77. package/src/shared/components/doc-browser/types/doc-browser.types.ts +47 -0
  78. package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +47 -0
  79. package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +95 -0
  80. package/src/shared/lib/api/managers/client.manager.ts +2 -1
  81. package/src/shared/lib/i18n/runtime/doc-browser-labels.utils.ts +31 -0
  82. package/src/shared/lib/transport/local-transport.service.ts +18 -2
  83. package/src/shared/lib/transport/remote-transport.service.ts +19 -3
  84. package/src/shared/lib/transport/request-raw-api-response.utils.ts +1 -1
  85. package/dist/assets/api-D3uoaOR8.js +0 -15
  86. package/dist/assets/channels-list-page-CzDMCXR_.js +0 -8
  87. package/dist/assets/config-split-page-Dutq3R5o.js +0 -1
  88. package/dist/assets/createLucideIcon-Cwr2o6Zq.js +0 -1
  89. package/dist/assets/desktop-update-config-Dd4DdPH3.js +0 -1
  90. package/dist/assets/dist-e5WATjIW.js +0 -1
  91. package/dist/assets/doc-browser-DInzGY0W.js +0 -1
  92. package/dist/assets/doc-browser-DxVvmYQk.js +0 -1
  93. package/dist/assets/doc-browser-context-CLczyX2Z.js +0 -1
  94. package/dist/assets/es2015-BJw-hrex.js +0 -41
  95. package/dist/assets/i18n-tWKGEGxB.js +0 -1
  96. package/dist/assets/index-HvJDzfSU.css +0 -1
  97. package/dist/assets/index-dHwM4pEn.js +0 -103
  98. package/dist/assets/key-round-B8tTb3ZJ.js +0 -1
  99. package/dist/assets/mcp-marketplace-page-D-dydvbL.js +0 -1
  100. package/dist/assets/mcp-marketplace-page-n9Z25Cyk.js +0 -40
  101. package/dist/assets/model-config-8BVoSVXQ.js +0 -1
  102. package/dist/assets/notice-card-DUDbOvqx.js +0 -5
  103. package/dist/assets/play-CV_kwi9X.js +0 -1
  104. package/dist/assets/plus-BqfNq7Tu.js +0 -1
  105. package/dist/assets/provider-scoped-model-input-eO4XY0iY.js +0 -1
  106. package/dist/assets/providers-list-CjJmW-od.js +0 -1
  107. package/dist/assets/remote-DxTFnQm8.js +0 -1
  108. package/dist/assets/rotate-cw-BKEhWuWp.js +0 -1
  109. package/dist/assets/runtime-config-page-Ba6atCMq.js +0 -1
  110. package/dist/assets/secrets-config-Ddh05-J5.js +0 -3
  111. package/dist/assets/select-CmmDgG8o.js +0 -1
  112. package/dist/assets/setting-row-BUO6FeN1.js +0 -1
  113. package/dist/assets/tabs-custom-D3IZL9WX.js +0 -1
  114. package/dist/assets/tag-chip-Dm_pOtE3.js +0 -1
  115. package/dist/assets/x-DBP3WiC9.js +0 -1
  116. package/src/app/components/app-manager-provider.tsx +0 -20
  117. package/src/app/managers/app.manager.ts +0 -12
  118. /package/dist/assets/{config-hints-DGfwbumO.js → config-hints-CTqBnCDp.js} +0 -0
@@ -0,0 +1,140 @@
1
+ import { create } from 'zustand';
2
+ import { createJSONStorage, persist } from 'zustand/middleware';
3
+ import type {
4
+ DocBrowserMode,
5
+ DocBrowserState,
6
+ DocBrowserStateUpdate,
7
+ DocBrowserTab,
8
+ } from '@/shared/components/doc-browser/types/doc-browser.types';
9
+ import { createDefaultDocBrowserState } from '@/shared/components/doc-browser/utils/doc-browser-state.utils';
10
+
11
+ const DOC_BROWSER_STORAGE_KEY = 'nextclaw.doc-browser.state';
12
+ const DOC_BROWSER_STORAGE_VERSION = 1;
13
+ const DOC_BROWSER_MAX_PERSISTED_TABS = 8;
14
+
15
+ type PersistedDocBrowserState = Partial<DocBrowserState>;
16
+
17
+ type DocBrowserStore = {
18
+ snapshot: DocBrowserState;
19
+ setSnapshot: (next: DocBrowserStateUpdate) => void;
20
+ };
21
+
22
+ function isRecord(value: unknown): value is Record<string, unknown> {
23
+ return !!value && typeof value === 'object';
24
+ }
25
+
26
+ function isDocBrowserMode(value: unknown): value is DocBrowserMode {
27
+ return value === 'floating' || value === 'docked';
28
+ }
29
+
30
+ function normalizePersistedStringList(value: unknown, fallback: string): string[] {
31
+ if (!Array.isArray(value)) {
32
+ return [fallback];
33
+ }
34
+ const values = value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0);
35
+ return values.length > 0 ? values : [fallback];
36
+ }
37
+
38
+ function normalizePersistedNumber(value: unknown, min: number, max: number, fallback: number): number {
39
+ return typeof value === 'number' && Number.isFinite(value)
40
+ ? Math.min(max, Math.max(min, value))
41
+ : fallback;
42
+ }
43
+
44
+ function normalizePersistedDocBrowserTab(value: unknown): DocBrowserTab | null {
45
+ if (
46
+ !isRecord(value)
47
+ || typeof value.id !== 'string'
48
+ || typeof value.kind !== 'string'
49
+ || typeof value.title !== 'string'
50
+ || typeof value.currentUrl !== 'string'
51
+ || value.currentUrl.trim().length === 0
52
+ ) {
53
+ return null;
54
+ }
55
+ const history = normalizePersistedStringList(value.history, value.currentUrl);
56
+ const historyIndex = normalizePersistedNumber(value.historyIndex, 0, history.length - 1, history.length - 1);
57
+ const navVersion = normalizePersistedNumber(value.navVersion, 0, Number.MAX_SAFE_INTEGER, 0);
58
+ const dedupeKey = typeof value.dedupeKey === 'string' && value.dedupeKey.trim().length > 0
59
+ ? value.dedupeKey
60
+ : undefined;
61
+
62
+ return {
63
+ id: value.id,
64
+ kind: value.kind,
65
+ title: value.title,
66
+ currentUrl: history[historyIndex] ?? value.currentUrl,
67
+ dedupeKey,
68
+ history,
69
+ historyIndex,
70
+ navVersion,
71
+ };
72
+ }
73
+
74
+ function normalizePersistedDocBrowserState(value: unknown): DocBrowserState | null {
75
+ if (!isRecord(value)) {
76
+ return null;
77
+ }
78
+ const tabs = Array.isArray(value.tabs)
79
+ ? value.tabs
80
+ .map(normalizePersistedDocBrowserTab)
81
+ .filter((tab): tab is DocBrowserTab => tab !== null)
82
+ .slice(-DOC_BROWSER_MAX_PERSISTED_TABS)
83
+ : [];
84
+ if (tabs.length === 0) {
85
+ return null;
86
+ }
87
+ const persistedActiveTabId = typeof value.activeTabId === 'string' ? value.activeTabId : null;
88
+ const activeTabId = tabs.some((tab) => tab.id === persistedActiveTabId)
89
+ ? persistedActiveTabId ?? tabs[0].id
90
+ : tabs[0].id;
91
+
92
+ return {
93
+ isOpen: value.isOpen === true,
94
+ mode: isDocBrowserMode(value.mode) ? value.mode : 'docked',
95
+ tabs,
96
+ activeTabId,
97
+ };
98
+ }
99
+
100
+ function resolveDocBrowserStateUpdate(prev: DocBrowserState, next: DocBrowserStateUpdate): DocBrowserState {
101
+ if (typeof next === 'function') {
102
+ return next(prev);
103
+ }
104
+ return next;
105
+ }
106
+
107
+ export const useDocBrowserStore = create<DocBrowserStore>()(
108
+ persist(
109
+ (set) => ({
110
+ snapshot: createDefaultDocBrowserState(),
111
+ setSnapshot: (next) => set((prev) => ({
112
+ snapshot: resolveDocBrowserStateUpdate(prev.snapshot, next),
113
+ })),
114
+ }),
115
+ {
116
+ name: DOC_BROWSER_STORAGE_KEY,
117
+ version: DOC_BROWSER_STORAGE_VERSION,
118
+ storage: createJSONStorage(() => window.localStorage),
119
+ partialize: (state): { snapshot: PersistedDocBrowserState } => ({
120
+ snapshot: {
121
+ isOpen: state.snapshot.isOpen,
122
+ mode: state.snapshot.mode,
123
+ tabs: state.snapshot.tabs.slice(-DOC_BROWSER_MAX_PERSISTED_TABS),
124
+ activeTabId: state.snapshot.activeTabId,
125
+ },
126
+ }),
127
+ merge: (persistedState, currentState) => {
128
+ const persistedSnapshot = isRecord(persistedState) ? persistedState.snapshot : null;
129
+ const normalizedSnapshot = normalizePersistedDocBrowserState(persistedSnapshot);
130
+ if (!normalizedSnapshot) {
131
+ return currentState;
132
+ }
133
+ return {
134
+ ...currentState,
135
+ snapshot: normalizedSnapshot,
136
+ };
137
+ },
138
+ },
139
+ ),
140
+ );
@@ -0,0 +1,47 @@
1
+ export type DocBrowserMode = 'floating' | 'docked';
2
+ export type DocBrowserTabKind = 'docs' | 'content' | (string & {});
3
+
4
+ export type DocBrowserTab = {
5
+ id: string;
6
+ kind: DocBrowserTabKind;
7
+ title: string;
8
+ currentUrl: string;
9
+ dedupeKey?: string;
10
+ history: string[];
11
+ historyIndex: number;
12
+ /** Increments on parent-initiated navigation to trigger iframe remount */
13
+ navVersion: number;
14
+ };
15
+
16
+ export type DocBrowserOpenOptions = {
17
+ activate?: boolean;
18
+ dedupeKey?: string;
19
+ newTab?: boolean;
20
+ title?: string;
21
+ kind?: DocBrowserTabKind;
22
+ };
23
+
24
+ export type DocBrowserState = {
25
+ isOpen: boolean;
26
+ mode: DocBrowserMode;
27
+ tabs: DocBrowserTab[];
28
+ activeTabId: string;
29
+ };
30
+
31
+ export type DocBrowserStateUpdate = DocBrowserState | ((prev: DocBrowserState) => DocBrowserState);
32
+
33
+ export type DocBrowserActions = {
34
+ open: (url?: string, options?: DocBrowserOpenOptions) => void;
35
+ close: () => void;
36
+ toggleMode: () => void;
37
+ navigate: (url: string) => void;
38
+ syncUrl: (url: string) => void;
39
+ goBack: () => void;
40
+ goForward: () => void;
41
+ closeTab: (tabId: string) => void;
42
+ setActiveTab: (tabId: string) => void;
43
+ };
44
+
45
+ export type DocBrowserContextValue = DocBrowserState & DocBrowserActions & {
46
+ currentTab?: DocBrowserTab;
47
+ };
@@ -0,0 +1,47 @@
1
+ import type {
2
+ DocBrowserState,
3
+ DocBrowserTab,
4
+ DocBrowserTabKind,
5
+ } from '@/shared/components/doc-browser/types/doc-browser.types';
6
+ import {
7
+ getDefaultDocsUrl,
8
+ inferTabTitle,
9
+ } from './doc-browser-url.utils';
10
+
11
+ let tabCounter = 0;
12
+
13
+ function nextTabId(): string {
14
+ tabCounter += 1;
15
+ return `doc-tab-${Date.now()}-${tabCounter}`;
16
+ }
17
+
18
+ export function createDocBrowserTab(
19
+ url: string,
20
+ kind: DocBrowserTabKind,
21
+ title?: string,
22
+ dedupeKey?: string,
23
+ ): DocBrowserTab {
24
+ const tabTitle = title?.trim() || inferTabTitle(url, kind, kind === 'docs' ? 'Docs' : 'Detail');
25
+
26
+ return {
27
+ id: nextTabId(),
28
+ kind,
29
+ title: tabTitle,
30
+ currentUrl: url,
31
+ dedupeKey,
32
+ history: [url],
33
+ historyIndex: 0,
34
+ navVersion: 0,
35
+ };
36
+ }
37
+
38
+ export function createDefaultDocBrowserState(): DocBrowserState {
39
+ const initialUrl = getDefaultDocsUrl();
40
+ const initialTab = createDocBrowserTab(initialUrl, 'docs', 'Docs');
41
+ return {
42
+ isOpen: false,
43
+ mode: 'docked',
44
+ tabs: [initialTab],
45
+ activeTabId: initialTab.id,
46
+ };
47
+ }
@@ -0,0 +1,95 @@
1
+ import { getLanguage, type I18nLanguage } from '@/shared/lib/i18n';
2
+ import type { DocBrowserTabKind } from '@/shared/components/doc-browser/types/doc-browser.types';
3
+
4
+ const DOCS_PRIMARY_DOMAIN = 'docs.nextclaw.io';
5
+ const DOCS_PAGES_DEV = 'nextclaw-docs.pages.dev';
6
+ const DOCS_HOSTS = new Set([
7
+ DOCS_PRIMARY_DOMAIN,
8
+ `www.${DOCS_PRIMARY_DOMAIN}`,
9
+ DOCS_PAGES_DEV,
10
+ `www.${DOCS_PAGES_DEV}`,
11
+ ]);
12
+
13
+ export const DOCS_DEFAULT_BASE_URL = `https://${DOCS_PRIMARY_DOMAIN}`;
14
+ const DOCS_DEFAULT_GUIDE_PATH = '/guide/getting-started';
15
+
16
+ function toDocsLocale(language: I18nLanguage): 'en' | 'zh' {
17
+ return language === 'zh' ? 'zh' : 'en';
18
+ }
19
+
20
+ function ensureLocalizedDocsPath(pathname: string, locale: 'en' | 'zh'): string {
21
+ const normalized = pathname.startsWith('/') ? pathname : `/${pathname}`;
22
+
23
+ if (normalized === '/' || normalized === '') {
24
+ return `/${locale}/`;
25
+ }
26
+
27
+ if (/^\/(en|zh)(\/|$)/.test(normalized)) {
28
+ return normalized;
29
+ }
30
+
31
+ return `/${locale}${normalized}`;
32
+ }
33
+
34
+ function resolveLocalizedDocsUrl(url: string): string {
35
+ const locale = toDocsLocale(getLanguage());
36
+
37
+ try {
38
+ const parsed = new URL(url, DOCS_DEFAULT_BASE_URL);
39
+ if (!DOCS_HOSTS.has(parsed.hostname)) {
40
+ return parsed.toString();
41
+ }
42
+
43
+ parsed.pathname = ensureLocalizedDocsPath(parsed.pathname, locale);
44
+ return parsed.toString();
45
+ } catch {
46
+ return new URL(`/${locale}${DOCS_DEFAULT_GUIDE_PATH}`, DOCS_DEFAULT_BASE_URL).toString();
47
+ }
48
+ }
49
+
50
+ export function getDefaultDocsUrl(): string {
51
+ return resolveLocalizedDocsUrl(DOCS_DEFAULT_GUIDE_PATH);
52
+ }
53
+
54
+ export function normalizeDocUrl(url: string): string {
55
+ try {
56
+ return new URL(url).pathname.replace(/\.html$/, '').replace(/\/$/, '');
57
+ } catch {
58
+ return url;
59
+ }
60
+ }
61
+
62
+ export function inferTabTitle(url: string, kind: DocBrowserTabKind, fallback = 'Docs'): string {
63
+ try {
64
+ const parsed = new URL(url, DOCS_DEFAULT_BASE_URL);
65
+ if (parsed.protocol === 'data:') {
66
+ return kind === 'docs' ? fallback : 'Detail';
67
+ }
68
+
69
+ const segments = parsed.pathname.split('/').filter(Boolean);
70
+ const leaf = segments[segments.length - 1] ?? fallback;
71
+ return decodeURIComponent(leaf).replace(/[-_]/g, ' ').slice(0, 40) || fallback;
72
+ } catch {
73
+ return fallback;
74
+ }
75
+ }
76
+
77
+ export function isDocsUrl(url: string): boolean {
78
+ try {
79
+ const parsed = new URL(url, window.location.origin);
80
+ return DOCS_HOSTS.has(parsed.hostname);
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ export function inferTabKind(url: string): DocBrowserTabKind {
87
+ return isDocsUrl(url) ? 'docs' : 'content';
88
+ }
89
+
90
+ export function normalizeUrlByKind(url: string, kind: DocBrowserTabKind): string {
91
+ if (kind === 'docs') {
92
+ return resolveLocalizedDocsUrl(url);
93
+ }
94
+ return url;
95
+ }
@@ -11,11 +11,12 @@ import { appClient } from '@/shared/lib/transport';
11
11
  import type { ApiResponse } from '@/shared/lib/api/types';
12
12
 
13
13
  const nextclawUiTransport: NextClawTransport = {
14
- request: async <T>({ body, method, path, query, signal, timeoutMs }: NextClawTransportRequestInput): Promise<T> => {
14
+ request: async <T>({ body, headers, method, path, query, signal, timeoutMs }: NextClawTransportRequestInput): Promise<T> => {
15
15
  return await appClient.request({
16
16
  method,
17
17
  path: appendQueryToPath(path, serializeQuery(query)),
18
18
  ...(body !== undefined ? { body } : {}),
19
+ ...(headers ? { headers } : {}),
19
20
  ...(signal ? { signal } : {}),
20
21
  ...(timeoutMs !== undefined ? { timeoutMs } : {})
21
22
  });
@@ -10,6 +10,7 @@ export const DOC_BROWSER_LABELS = {
10
10
  docBrowserNewTab: { zh: '新建标签', en: 'New Tab' },
11
11
  docBrowserCloseTab: { zh: '关闭标签', en: 'Close Tab' },
12
12
  docBrowserTabUntitled: { zh: '未命名', en: 'Untitled' },
13
+ appsTitle: { zh: '应用', en: 'Apps' },
13
14
  panelAppsTitle: { zh: '面板应用', en: 'Panel Apps' },
14
15
  panelAppsLoading: { zh: '加载面板应用中...', en: 'Loading panel apps...' },
15
16
  panelAppsLoadFailed: { zh: '面板应用加载失败', en: 'Failed to load panel apps' },
@@ -26,4 +27,34 @@ export const DOC_BROWSER_LABELS = {
26
27
  panelAppsUnfavorite: { zh: '取消收藏面板应用', en: 'Unfavorite panel app' },
27
28
  panelAppsLastOpened: { zh: '上次打开', en: 'Last opened' },
28
29
  panelAppsUpdated: { zh: '更新于', en: 'Updated' },
30
+ serviceAppsTitle: { zh: '服务应用', en: 'Service Apps' },
31
+ serviceAppsLoading: { zh: '加载服务应用中...', en: 'Loading service apps...' },
32
+ serviceAppsLoadFailed: { zh: '服务应用加载失败', en: 'Failed to load service apps' },
33
+ serviceAppsEmpty: { zh: '暂无服务应用', en: 'No service apps yet' },
34
+ serviceAppsRefresh: { zh: '刷新服务应用', en: 'Refresh service apps' },
35
+ serviceAppsDiscoverActions: { zh: '发现运行时动作', en: 'Discover runtime actions' },
36
+ serviceAppsRestart: { zh: '重启服务应用', en: 'Restart service app' },
37
+ serviceAppsGrantedTo: { zh: '已授权给', en: 'Granted to' },
38
+ serviceAppsRevokeGrant: { zh: '撤销授权', en: 'Revoke grant' },
39
+ serviceAppsRuntimeState_matched: { zh: '已匹配', en: 'Matched' },
40
+ serviceAppsRuntimeState_missing: { zh: '运行时缺失', en: 'Missing' },
41
+ serviceAppsRuntimeState_undeclared: { zh: '未声明', en: 'Undeclared' },
42
+ serviceAppsCommand: { zh: '命令', en: 'Command' },
43
+ serviceAppsCwd: { zh: '工作目录', en: 'Working directory' },
44
+ serviceAppsManifest: { zh: 'Manifest', en: 'Manifest' },
45
+ serviceAppsLastStarted: { zh: '上次启动', en: 'Last started' },
46
+ serviceAppsLastReady: { zh: '上次就绪', en: 'Last ready' },
47
+ serviceAppsLastFailed: { zh: '上次失败', en: 'Last failed' },
48
+ serviceActionAuthorizationTitle: { zh: '授权服务动作', en: 'Authorize service action' },
49
+ serviceActionAuthorizationDescription: {
50
+ zh: '此面板应用正在请求调用本地服务应用动作。允许后,同一动作会为该面板应用记住授权。',
51
+ en: 'This panel app is requesting a local service action. If allowed, this action will stay granted for this panel app.',
52
+ },
53
+ serviceActionAuthorizationSource: { zh: '来源', en: 'Source' },
54
+ serviceActionAuthorizationAction: { zh: '动作', en: 'Action' },
55
+ serviceActionAuthorizationPurpose: { zh: '用途', en: 'Purpose' },
56
+ serviceActionAuthorizationRisk: { zh: '风险', en: 'Risk' },
57
+ serviceActionAuthorizationInput: { zh: '输入', en: 'Input' },
58
+ serviceActionAuthorizationReject: { zh: '拒绝', en: 'Reject' },
59
+ serviceActionAuthorizationAllow: { zh: '允许', en: 'Allow' },
29
60
  } as const;
@@ -6,9 +6,25 @@ import { resolveTransportWebSocketUrl } from './transport-websocket-url.utils';
6
6
 
7
7
  type EventHandler = (event: AppEvent) => void;
8
8
 
9
- function createTransportError(response: { ok: boolean; error?: { message?: string } }, fallback: string): Error {
9
+ function createTransportError(
10
+ response: {
11
+ ok: boolean;
12
+ error?: {
13
+ code?: string;
14
+ message?: string;
15
+ details?: Record<string, unknown>;
16
+ };
17
+ },
18
+ fallback: string
19
+ ): Error {
10
20
  if (!response.ok) {
11
- return new Error(response.error?.message || fallback);
21
+ const error = new Error(response.error?.message || fallback) as Error & {
22
+ code?: string;
23
+ details?: Record<string, unknown>;
24
+ };
25
+ error.code = response.error?.code;
26
+ error.details = response.error?.details;
27
+ return error;
12
28
  }
13
29
  return new Error(fallback);
14
30
  }
@@ -1,4 +1,4 @@
1
- import type { ApiError } from '@nextclaw/server';
1
+ import type { ApiError } from '@nextclaw/client-sdk';
2
2
  import type { AppEvent, AppTransport, RemoteRuntimeInfo, RequestInput, StreamInput, StreamSession } from './transport.types';
3
3
  import { resolveTransportWebSocketUrl } from './transport-websocket-url.utils';
4
4
 
@@ -6,6 +6,7 @@ type RemoteTarget = {
6
6
  method: string;
7
7
  path: string;
8
8
  body?: unknown;
9
+ headers?: Record<string, string>;
9
10
  };
10
11
 
11
12
  type RemoteBrowserFrame =
@@ -40,7 +41,15 @@ function normalizeApiError(body: unknown, status: number, fallback: string): Err
40
41
  if (typeof body === 'object' && body && 'ok' in body) {
41
42
  const typed = body as { ok?: boolean; error?: ApiError; data?: unknown };
42
43
  if (typed.ok === false && typed.error?.message) {
43
- return new Error(typed.error.message);
44
+ const error = new Error(typed.error.message) as Error & {
45
+ code?: string;
46
+ details?: Record<string, unknown>;
47
+ status?: number;
48
+ };
49
+ error.code = typed.error.code;
50
+ error.details = typed.error.details;
51
+ error.status = status;
52
+ return error;
44
53
  }
45
54
  if (typed.ok === true) {
46
55
  return new Error(fallback);
@@ -56,7 +65,13 @@ function unwrapApiBody<T>(body: unknown): T {
56
65
  if (typeof body === 'object' && body && 'ok' in body) {
57
66
  const typed = body as { ok?: boolean; error?: ApiError; data?: T };
58
67
  if (typed.ok === false) {
59
- throw new Error(typed.error?.message ?? 'Remote request failed.');
68
+ const error = new Error(typed.error?.message ?? 'Remote request failed.') as Error & {
69
+ code?: string;
70
+ details?: Record<string, unknown>;
71
+ };
72
+ error.code = typed.error?.code;
73
+ error.details = typed.error?.details;
74
+ throw error;
60
75
  }
61
76
  if (typed.ok === true) {
62
77
  return typed.data as T;
@@ -108,6 +123,7 @@ export class RemoteSessionMultiplexTransport implements AppTransport {
108
123
  target: {
109
124
  method: input.method,
110
125
  path: input.path,
126
+ ...(input.headers ? { headers: input.headers } : {}),
111
127
  ...(input.body !== undefined ? { body: input.body } : {})
112
128
  }
113
129
  });
@@ -1,4 +1,4 @@
1
- import type { ApiResponse } from '@nextclaw/server';
1
+ import type { ApiResponse } from '@nextclaw/client-sdk';
2
2
  import { systemStatusManager } from '@/features/system-status';
3
3
 
4
4
  function compactSnippet(text: string) {