@steerable/agent-shell-web 0.6.55 → 0.6.56
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/README.md +3 -4
- package/package.json +4 -4
- package/src/AppShell.tsx +3 -3
- package/src/components/AgentSidebar.tsx +4 -4
- package/src/components/CreateProjectModal.tsx +2 -2
- package/src/components/SidebarRelease.tsx +4 -4
- package/src/components/TerminalPanel.tsx +3 -3
- package/src/components/TerminalView.tsx +4 -4
- package/src/components/chat/ApprovalModal.tsx +1 -1
- package/src/components/chat/ApprovalPromptProvider.tsx +4 -4
- package/src/components/chat/AskUserPromptProvider.tsx +3 -3
- package/src/components/chat/ChatProjectBadge.tsx +3 -3
- package/src/components/chat/TaskPanelModal.tsx +2 -2
- package/src/components/chat/TaskProcessPanel.tsx +2 -2
- package/src/components/chat/useChatTasks.ts +3 -3
- package/src/components/settings/AgentCapabilityFields.tsx +2 -2
- package/src/components/settings/AgentsSettingsPanel.tsx +3 -3
- package/src/components/settings/InsightsSettingsPanel.tsx +15 -15
- package/src/components/settings/LlmSettingsPanel.tsx +3 -3
- package/src/components/settings/McpSettingsPanel.tsx +12 -12
- package/src/components/settings/PythonRunnerSettingsPanel.tsx +4 -4
- package/src/components/settings/SecuritySettingsPanel.tsx +3 -3
- package/src/components/settings/SkillsSettingsPanel.tsx +7 -7
- package/src/components/settings/TelemetrySettingsPanel.tsx +5 -5
- package/src/components/settings/UsagePanel.tsx +3 -3
- package/src/components/settings/WebSearchSettingsPanel.tsx +5 -5
- package/src/hooks/useChatsAndAgents.ts +9 -9
- package/src/hooks/useProjects.ts +2 -2
- package/src/i18n/index.tsx +1 -1
- package/src/layouts/AgentLayout.tsx +6 -6
- package/src/lib/app-release.ts +1 -1
- package/src/lib/attachments.ts +2 -2
- package/src/lib/{browser-dev-electron-mock.ts → browser-dev-host-mock.ts} +10 -10
- package/src/lib/chat-transport.ts +10 -10
- package/src/lib/host-bridge.ts +371 -4
- package/src/lib/host-paste.ts +1 -1
- package/src/lib/http-bridge.ts +8 -8
- package/src/lib/insights.ts +2 -2
- package/src/lib/local-api.ts +5 -5
- package/src/lib/portable.ts +3 -3
- package/src/lib/slash-sources.ts +3 -3
- package/src/lib/tauri-bridge.ts +1 -1
- package/src/main.tsx +3 -3
- package/src/pages/AgentPage.tsx +23 -23
- package/src/pages/SettingsPage.tsx +4 -4
- package/src/lib/electron-bridge.ts +0 -389
package/README.md
CHANGED
|
@@ -17,10 +17,9 @@ bundle; there is no prebuilt `dist/` here. See
|
|
|
17
17
|
[`docs/spec/architecture.md`](https://github.com/pathlyapp/steerable/blob/develop/docs/spec/architecture.md)
|
|
18
18
|
for the tier model.
|
|
19
19
|
|
|
20
|
-
Renderer code reads native and backend capabilities through `HostBridge
|
|
21
|
-
Browser mode uses the HTTP/SSE adapter; Tauri mode
|
|
22
|
-
menu events over that same adapter.
|
|
23
|
-
`getElectronBridge()` remain deprecated compatibility aliases.
|
|
20
|
+
Renderer code reads native and backend capabilities through `HostBridge`
|
|
21
|
+
(`getHostBridge()`). Browser mode uses the HTTP/SSE adapter; Tauri mode
|
|
22
|
+
layers native commands and menu events over that same adapter.
|
|
24
23
|
|
|
25
24
|
Products that require authentication can register an `AppShellGate` from
|
|
26
25
|
`@steerable/agent-shell-web/auth/gate` before calling `bootstrap()`. An
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell-web",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.56",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Steerable framework — product-neutral renderer SPA source for @steerable/agent-shell (React + Vite). Consumed by product web entry points via the `@/` alias (anchored at this package's src/) and the createProductViteConfig factory. Published as a source package (src/), plus the compiled vite.base config factory (vite.base.js + .d.ts) for Node-side config loading.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"react-router-dom": "^6.30.0",
|
|
46
46
|
"rehype-raw": "^7.0.0",
|
|
47
47
|
"remark-gfm": "^4.0.1",
|
|
48
|
-
"@steerable/agent-
|
|
49
|
-
"@steerable/agent-
|
|
48
|
+
"@steerable/agent-ui": "0.6.56",
|
|
49
|
+
"@steerable/agent-protocol": "0.6.56"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@tailwindcss/vite": "^4.3.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"typescript": "^5.8.3",
|
|
60
60
|
"vite": "^5.4.21",
|
|
61
61
|
"vitest": "^3.2.4",
|
|
62
|
-
"@steerable/pack-sdk": "0.6.
|
|
62
|
+
"@steerable/pack-sdk": "0.6.56"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"gen": "echo agent-shell-web",
|
package/src/AppShell.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Outlet } from 'react-router-dom';
|
|
2
|
-
import {
|
|
2
|
+
import { hasHostBridge } from './lib/host-bridge';
|
|
3
3
|
import { isDemoMode } from './lib/demo-flag';
|
|
4
4
|
|
|
5
5
|
export function AppShell() {
|
|
@@ -13,9 +13,9 @@ export function AppShell() {
|
|
|
13
13
|
Demo · simulated data, no live model
|
|
14
14
|
</div>
|
|
15
15
|
) : (
|
|
16
|
-
!
|
|
16
|
+
!hasHostBridge() && (
|
|
17
17
|
<div className="border-t border-agent-border bg-agent-muted px-3 py-1 text-xs text-agent-muted-foreground">
|
|
18
|
-
Browser preview mode ·
|
|
18
|
+
Browser preview mode · no host bridge
|
|
19
19
|
</div>
|
|
20
20
|
)
|
|
21
21
|
)}
|
|
@@ -92,7 +92,7 @@ import {
|
|
|
92
92
|
import { RiPushpin2Fill, RiPushpin2Line } from "react-icons/ri";
|
|
93
93
|
import { parseChatTitle } from "@/lib/chat-title";
|
|
94
94
|
import { getDateGroupLabel, getDateGroupPriority } from "@/lib/date-groups";
|
|
95
|
-
import {
|
|
95
|
+
import { getHostBridge, hasHostBridge } from "@/lib/host-bridge";
|
|
96
96
|
import { hasGeneralSettingsChrome, hostToolChrome, settingsChrome } from "@/lib/host-tools";
|
|
97
97
|
import {
|
|
98
98
|
createProject,
|
|
@@ -290,7 +290,7 @@ export function AgentSidebar({
|
|
|
290
290
|
const navigate = useNavigate();
|
|
291
291
|
const location = useLocation();
|
|
292
292
|
const { chatId: currentChatId } = useParams<{ chatId?: string }>();
|
|
293
|
-
const bridge =
|
|
293
|
+
const bridge = getHostBridge();
|
|
294
294
|
const release = useAppRelease();
|
|
295
295
|
const onSettingsPage = location.pathname === "/settings";
|
|
296
296
|
// 插件页(及旧的 agents/skills/mcp 深链)高亮「插件」;其余 /settings 高亮底部综合设置。
|
|
@@ -385,7 +385,7 @@ export function AgentSidebar({
|
|
|
385
385
|
const backgroundIds = [...streamingChatIds].filter(
|
|
386
386
|
(id) => id !== currentChatId,
|
|
387
387
|
);
|
|
388
|
-
if (backgroundIds.length === 0 || !
|
|
388
|
+
if (backgroundIds.length === 0 || !hasHostBridge()) return;
|
|
389
389
|
|
|
390
390
|
let cancelled = false;
|
|
391
391
|
let ticking = false;
|
|
@@ -908,7 +908,7 @@ export function AgentSidebar({
|
|
|
908
908
|
normalizedChats.length,
|
|
909
909
|
]);
|
|
910
910
|
|
|
911
|
-
const hasElectron =
|
|
911
|
+
const hasElectron = hasHostBridge();
|
|
912
912
|
|
|
913
913
|
return (
|
|
914
914
|
<div className="flex h-full w-full flex-col border-r border-agent-border/60 bg-agent-muted/70 backdrop-blur-md">
|
|
@@ -7,7 +7,7 @@ import { createPortal } from 'react-dom';
|
|
|
7
7
|
import { LuFolder, LuFolderPlus, LuX } from 'react-icons/lu';
|
|
8
8
|
import { BRAND_NAME } from '@/brand';
|
|
9
9
|
import { ConfirmDialog } from '@/components/ConfirmDialog';
|
|
10
|
-
import {
|
|
10
|
+
import { getHostBridge } from '@/lib/host-bridge';
|
|
11
11
|
import { t } from '@/i18n';
|
|
12
12
|
|
|
13
13
|
export interface ProjectFormValues {
|
|
@@ -83,7 +83,7 @@ export function CreateProjectModal({
|
|
|
83
83
|
addSourceFolder(typed);
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
|
-
const picker =
|
|
86
|
+
const picker = getHostBridge()?.local?.selectDirectory;
|
|
87
87
|
if (!picker) {
|
|
88
88
|
setError(t('Enter a folder path'));
|
|
89
89
|
return;
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
type AppReleaseView,
|
|
5
5
|
} from '@/lib/app-release';
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
getHostBridge,
|
|
8
8
|
type AppReleaseSnapshot,
|
|
9
|
-
} from '@/lib/
|
|
9
|
+
} from '@/lib/host-bridge';
|
|
10
10
|
import { t } from '@/i18n';
|
|
11
11
|
|
|
12
12
|
export function useAppRelease(): AppReleaseView & { run: () => void } {
|
|
@@ -14,7 +14,7 @@ export function useAppRelease(): AppReleaseView & { run: () => void } {
|
|
|
14
14
|
const [confirmedCurrent, setConfirmedCurrent] = useState(false);
|
|
15
15
|
|
|
16
16
|
useEffect(() => {
|
|
17
|
-
const app =
|
|
17
|
+
const app = getHostBridge()?.app;
|
|
18
18
|
if (!app) return;
|
|
19
19
|
let alive = true;
|
|
20
20
|
void app.snapshot().then((next) => {
|
|
@@ -32,7 +32,7 @@ export function useAppRelease(): AppReleaseView & { run: () => void } {
|
|
|
32
32
|
}, []);
|
|
33
33
|
|
|
34
34
|
const run = useCallback(() => {
|
|
35
|
-
const app =
|
|
35
|
+
const app = getHostBridge()?.app;
|
|
36
36
|
if (!app || !snap || !snap.enabled) return;
|
|
37
37
|
if (snap.phase !== 'idle' && snap.phase !== 'error' && snap.phase !== 'ready') return;
|
|
38
38
|
const task = snap.phase === 'ready' ? app.install() : app.check();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Suspense, lazy, useEffect, useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { getHostBridge, hasHostBridge } from '@/lib/host-bridge';
|
|
3
3
|
import { t } from '@/i18n';
|
|
4
4
|
|
|
5
5
|
// 把 xterm 那一坨 (~300KB) 拆到独立 chunk —— 用户没打开终端就不下载。
|
|
@@ -17,7 +17,7 @@ const TerminalView = lazy(() =>
|
|
|
17
17
|
* xterm 视图,不杀 shell;重新打开时通过 `terminal:ensure` 的 replay
|
|
18
18
|
* buffer 补回面板关闭期间的输出。
|
|
19
19
|
*
|
|
20
|
-
* Electron 检测保留给浏览器预览 / dev 模式(没有 `window.
|
|
20
|
+
* Electron 检测保留给浏览器预览 / dev 模式(没有 `window.steerableHost` 就
|
|
21
21
|
* 连不到本地 PTY)。延迟到首个 effect 再渲染,避免检测闪烁。
|
|
22
22
|
*/
|
|
23
23
|
export function TerminalPanel({
|
|
@@ -33,7 +33,7 @@ export function TerminalPanel({
|
|
|
33
33
|
const [hasElectron, setHasElectron] = useState<boolean | null>(null);
|
|
34
34
|
|
|
35
35
|
useEffect(() => {
|
|
36
|
-
setHasElectron(
|
|
36
|
+
setHasElectron(hasHostBridge() && !!getHostBridge()?.terminal);
|
|
37
37
|
}, []);
|
|
38
38
|
|
|
39
39
|
if (hasElectron === null) {
|
|
@@ -5,7 +5,7 @@ import { WebLinksAddon } from '@xterm/addon-web-links';
|
|
|
5
5
|
import { LuListTodo } from 'react-icons/lu';
|
|
6
6
|
import '@xterm/xterm/css/xterm.css';
|
|
7
7
|
import { DockHeaderButton, DockPanelHeader } from '@/components/DockPanelHeader';
|
|
8
|
-
import {
|
|
8
|
+
import { getHostBridge } from '@/lib/host-bridge';
|
|
9
9
|
import { BRAND_NAME } from '@/brand';
|
|
10
10
|
import { t } from '@/i18n';
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ import { t } from '@/i18n';
|
|
|
14
14
|
* `terminal.*` bridge. Ported from `deeppath/apps/web/src/app/agent/terminal/
|
|
15
15
|
* TerminalView.tsx`, with these adaptations:
|
|
16
16
|
*
|
|
17
|
-
* - `window.
|
|
17
|
+
* - `window.steerableHost` direct access → `getHostBridge()` helper so the
|
|
18
18
|
* "no bridge" path is type-safe.
|
|
19
19
|
* - Removed `'use client'` directive (not a Next.js project).
|
|
20
20
|
* - Container is `h-full` instead of `h-screen` so the component is
|
|
@@ -45,11 +45,11 @@ export function TerminalView({
|
|
|
45
45
|
|
|
46
46
|
useEffect(() => {
|
|
47
47
|
if (!containerRef.current) return;
|
|
48
|
-
const bridge =
|
|
48
|
+
const bridge = getHostBridge();
|
|
49
49
|
const terminal = bridge?.terminal;
|
|
50
50
|
if (!terminal) {
|
|
51
51
|
setStatus('error');
|
|
52
|
-
setStatusMsg(t('
|
|
52
|
+
setStatusMsg(t('The host terminal is unavailable'));
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
type ReactNode,
|
|
10
10
|
} from 'react';
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
getHostBridge,
|
|
13
13
|
type ApprovalDecisionKind,
|
|
14
14
|
type ApprovalPromptRequest,
|
|
15
|
-
} from '@/lib/
|
|
15
|
+
} from '@/lib/host-bridge';
|
|
16
16
|
import { bindPromptToActiveChat, promptVisibleInChat } from './prompt-chat-scope';
|
|
17
17
|
|
|
18
18
|
interface ApprovalPromptContextValue {
|
|
@@ -37,7 +37,7 @@ export function ApprovalPromptProvider({ children }: { children: ReactNode }) {
|
|
|
37
37
|
const decidedRequestIds = useRef(new Set<string>());
|
|
38
38
|
|
|
39
39
|
useEffect(() => {
|
|
40
|
-
const bridge =
|
|
40
|
+
const bridge = getHostBridge();
|
|
41
41
|
if (!bridge?.approval) return;
|
|
42
42
|
let active = true;
|
|
43
43
|
const addRequests = (
|
|
@@ -76,7 +76,7 @@ export function ApprovalPromptProvider({ children }: { children: ReactNode }) {
|
|
|
76
76
|
}, []);
|
|
77
77
|
|
|
78
78
|
const decide = useCallback((requestId: string, kind: ApprovalDecisionKind) => {
|
|
79
|
-
const bridge =
|
|
79
|
+
const bridge = getHostBridge();
|
|
80
80
|
decidedRequestIds.current.add(requestId);
|
|
81
81
|
setQueue((previous) => previous.filter((request) => request.requestId !== requestId));
|
|
82
82
|
void bridge?.approval?.decide({ requestId, kind });
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useState,
|
|
9
9
|
type ReactNode,
|
|
10
10
|
} from 'react';
|
|
11
|
-
import {
|
|
11
|
+
import { getHostBridge, type AskUserPromptRequest } from '@/lib/host-bridge';
|
|
12
12
|
import { bindPromptToActiveChat, promptVisibleInChat } from './prompt-chat-scope';
|
|
13
13
|
|
|
14
14
|
type AnswerValue = string | string[];
|
|
@@ -35,7 +35,7 @@ export function AskUserPromptProvider({ children }: { children: ReactNode }) {
|
|
|
35
35
|
const answeredRequestIds = useRef(new Set<string>());
|
|
36
36
|
|
|
37
37
|
useEffect(() => {
|
|
38
|
-
const bridge =
|
|
38
|
+
const bridge = getHostBridge();
|
|
39
39
|
if (!bridge?.askUser) return;
|
|
40
40
|
let active = true;
|
|
41
41
|
const addRequests = (
|
|
@@ -75,7 +75,7 @@ export function AskUserPromptProvider({ children }: { children: ReactNode }) {
|
|
|
75
75
|
|
|
76
76
|
const answer = useCallback(
|
|
77
77
|
(requestId: string, answers: Record<string, AnswerValue>) => {
|
|
78
|
-
const bridge =
|
|
78
|
+
const bridge = getHostBridge();
|
|
79
79
|
answeredRequestIds.current.add(requestId);
|
|
80
80
|
setQueue((previous) => previous.filter((request) => request.requestId !== requestId));
|
|
81
81
|
void bridge?.askUser?.answer({ requestId, answers });
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
LuPlus,
|
|
11
11
|
} from 'react-icons/lu';
|
|
12
12
|
import { CreateProjectModal } from '@/components/CreateProjectModal';
|
|
13
|
-
import {
|
|
13
|
+
import { getHostBridge, hasHostBridge } from '@/lib/host-bridge';
|
|
14
14
|
import {
|
|
15
15
|
createProject,
|
|
16
16
|
updateChatProject,
|
|
@@ -219,8 +219,8 @@ export function ChatProjectBadge({
|
|
|
219
219
|
|
|
220
220
|
const changeFolder = () =>
|
|
221
221
|
run(async () => {
|
|
222
|
-
if (!
|
|
223
|
-
const result = await
|
|
222
|
+
if (!hasHostBridge() || !project) return;
|
|
223
|
+
const result = await getHostBridge()!.local?.selectDirectory({
|
|
224
224
|
title: t('Choose a new folder for "{name}"', { name: project.name }),
|
|
225
225
|
});
|
|
226
226
|
if (!result || result.canceled || result.filePaths.length === 0) return;
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
mergeTaskWorktree,
|
|
19
19
|
type LocalTask,
|
|
20
20
|
} from '@/lib/local-api';
|
|
21
|
-
import {
|
|
21
|
+
import { getHostBridge } from '@/lib/host-bridge';
|
|
22
22
|
import { t } from '@/i18n';
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -81,7 +81,7 @@ export function TaskPanelModal({
|
|
|
81
81
|
// 任务终态推送 → 重拉列表。preload 只透传 chatId/taskId/status,
|
|
82
82
|
// 记录本体以 SQLite 为准。
|
|
83
83
|
useEffect(() => {
|
|
84
|
-
const bridge =
|
|
84
|
+
const bridge = getHostBridge();
|
|
85
85
|
if (!bridge?.onTaskUpdated) return;
|
|
86
86
|
return bridge.onTaskUpdated((payload) => {
|
|
87
87
|
if (payload.chatId === chatId) void refresh();
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react
|
|
|
2
2
|
import { LuArrowDown, LuLoaderCircle, LuTerminal } from 'react-icons/lu';
|
|
3
3
|
import { DockHeaderButton, DockPanelHeader } from '@/components/DockPanelHeader';
|
|
4
4
|
import { getChildProcess, getTaskProcess } from '@/lib/local-api';
|
|
5
|
-
import {
|
|
5
|
+
import { getHostBridge } from '@/lib/host-bridge';
|
|
6
6
|
import { t } from '@/i18n';
|
|
7
7
|
import { Markdown } from '@/components/chat/Markdown';
|
|
8
8
|
import { TurnProcessGroup } from '@/components/chat/TurnProcessGroup';
|
|
@@ -154,7 +154,7 @@ export function TaskProcessPanel({
|
|
|
154
154
|
|
|
155
155
|
useEffect(() => {
|
|
156
156
|
if (childRecordId) return;
|
|
157
|
-
const bridge =
|
|
157
|
+
const bridge = getHostBridge();
|
|
158
158
|
if (!bridge?.onTaskProcess) return;
|
|
159
159
|
return bridge.onTaskProcess((payload) => {
|
|
160
160
|
if (payload.taskId !== inspected.id) return;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
12
|
import { listChatTasks, type LocalTask } from '@/lib/local-api';
|
|
13
|
-
import {
|
|
13
|
+
import { getHostBridge, hasHostBridge } from '@/lib/host-bridge';
|
|
14
14
|
|
|
15
15
|
export interface ChatTaskSummary {
|
|
16
16
|
total: number;
|
|
@@ -68,7 +68,7 @@ export function useChatTasks(chatId: string | null): ChatTasksState {
|
|
|
68
68
|
const seenRef = useRef<Map<string, LocalTask['status']> | null>(null);
|
|
69
69
|
|
|
70
70
|
const refresh = useCallback(async () => {
|
|
71
|
-
if (!chatId || !
|
|
71
|
+
if (!chatId || !hasHostBridge()) {
|
|
72
72
|
setTasks(NO_TASKS);
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
@@ -105,7 +105,7 @@ export function useChatTasks(chatId: string | null): ChatTasksState {
|
|
|
105
105
|
|
|
106
106
|
useEffect(() => {
|
|
107
107
|
if (!chatId) return;
|
|
108
|
-
const bridge =
|
|
108
|
+
const bridge = getHostBridge();
|
|
109
109
|
if (!bridge?.onTaskUpdated) return;
|
|
110
110
|
return bridge.onTaskUpdated((payload) => {
|
|
111
111
|
if (payload.chatId === chatId) void refresh();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { LuLoaderCircle, LuSearch } from 'react-icons/lu';
|
|
3
3
|
import { t } from '@/i18n';
|
|
4
|
-
import {
|
|
4
|
+
import { hasHostBridge } from '@/lib/host-bridge';
|
|
5
5
|
import {
|
|
6
6
|
listChatAgentSkills,
|
|
7
7
|
listChatAgentTools,
|
|
@@ -154,7 +154,7 @@ export function AgentCapabilityFields({
|
|
|
154
154
|
const [toolFilter, setToolFilter] = useState('');
|
|
155
155
|
|
|
156
156
|
const fetchCatalogs = useCallback(async () => {
|
|
157
|
-
if (!
|
|
157
|
+
if (!hasHostBridge()) return;
|
|
158
158
|
setLoading(true);
|
|
159
159
|
try {
|
|
160
160
|
const [skillRes, toolRes] = await Promise.all([
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from 'react-icons/lu';
|
|
8
8
|
import { ConfirmDialog } from '@/components/ConfirmDialog';
|
|
9
9
|
import { t } from '@/i18n';
|
|
10
|
-
import {
|
|
10
|
+
import { hasHostBridge } from '@/lib/host-bridge';
|
|
11
11
|
import {
|
|
12
12
|
archiveChatAgent,
|
|
13
13
|
createChatAgent,
|
|
@@ -101,7 +101,7 @@ export function AgentsSettingsPanel({
|
|
|
101
101
|
const [error, setError] = useState<string | null>(null);
|
|
102
102
|
|
|
103
103
|
const fetchAgents = useCallback(async () => {
|
|
104
|
-
if (!
|
|
104
|
+
if (!hasHostBridge()) return;
|
|
105
105
|
setLoading(true);
|
|
106
106
|
setError(null);
|
|
107
107
|
try {
|
|
@@ -151,7 +151,7 @@ export function AgentsSettingsPanel({
|
|
|
151
151
|
|
|
152
152
|
const handleSave = async () => {
|
|
153
153
|
const trimmedName = name.trim();
|
|
154
|
-
if (!trimmedName || !
|
|
154
|
+
if (!trimmedName || !hasHostBridge()) return;
|
|
155
155
|
setSaving(true);
|
|
156
156
|
setError(null);
|
|
157
157
|
try {
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|
|
2
2
|
import { LuChartBar } from 'react-icons/lu';
|
|
3
3
|
import { BRAND_NAME } from '@/brand';
|
|
4
4
|
import { t } from '@/i18n';
|
|
5
|
-
import {
|
|
5
|
+
import { getHostBridge, hasHostBridge } from '@/lib/host-bridge';
|
|
6
6
|
|
|
7
7
|
type InsightsWire = {
|
|
8
8
|
installId: string;
|
|
@@ -34,8 +34,8 @@ export function InsightsConsentBanner() {
|
|
|
34
34
|
const [saving, setSaving] = useState(false);
|
|
35
35
|
|
|
36
36
|
useEffect(() => {
|
|
37
|
-
if (!
|
|
38
|
-
void
|
|
37
|
+
if (!hasHostBridge()) return;
|
|
38
|
+
void getHostBridge()!
|
|
39
39
|
.localBackend.request<InsightsWire>({ method: 'GET', path: '/api/v2/local-settings/insights' })
|
|
40
40
|
.then((data) => {
|
|
41
41
|
if (!data.promptedAt) setOpen(true);
|
|
@@ -44,10 +44,10 @@ export function InsightsConsentBanner() {
|
|
|
44
44
|
}, []);
|
|
45
45
|
|
|
46
46
|
const save = useCallback(async (upload: boolean) => {
|
|
47
|
-
if (!
|
|
47
|
+
if (!hasHostBridge()) return;
|
|
48
48
|
setSaving(true);
|
|
49
49
|
try {
|
|
50
|
-
await
|
|
50
|
+
await getHostBridge()!.localBackend.request({
|
|
51
51
|
method: 'POST',
|
|
52
52
|
path: '/api/v2/local-settings/insights',
|
|
53
53
|
body: {
|
|
@@ -112,11 +112,11 @@ export function InsightsSettingsPanel() {
|
|
|
112
112
|
const [error, setError] = useState<string | null>(null);
|
|
113
113
|
|
|
114
114
|
const load = useCallback(async () => {
|
|
115
|
-
if (!
|
|
115
|
+
if (!hasHostBridge()) return;
|
|
116
116
|
setLoading(true);
|
|
117
117
|
setError(null);
|
|
118
118
|
try {
|
|
119
|
-
const res = await
|
|
119
|
+
const res = await getHostBridge()!.localBackend.request<InsightsWire>({
|
|
120
120
|
method: 'GET',
|
|
121
121
|
path: '/api/v2/local-settings/insights',
|
|
122
122
|
});
|
|
@@ -133,11 +133,11 @@ export function InsightsSettingsPanel() {
|
|
|
133
133
|
}, [load]);
|
|
134
134
|
|
|
135
135
|
const save = async () => {
|
|
136
|
-
if (!
|
|
136
|
+
if (!hasHostBridge()) return;
|
|
137
137
|
setSaving(true);
|
|
138
138
|
setError(null);
|
|
139
139
|
try {
|
|
140
|
-
const res = await
|
|
140
|
+
const res = await getHostBridge()!.localBackend.request<InsightsWire>({
|
|
141
141
|
method: 'POST',
|
|
142
142
|
path: '/api/v2/local-settings/insights',
|
|
143
143
|
body: {
|
|
@@ -162,15 +162,15 @@ export function InsightsSettingsPanel() {
|
|
|
162
162
|
};
|
|
163
163
|
|
|
164
164
|
const exportFile = async () => {
|
|
165
|
-
if (!
|
|
165
|
+
if (!hasHostBridge()) return;
|
|
166
166
|
setError(null);
|
|
167
167
|
try {
|
|
168
|
-
const bundle = await
|
|
168
|
+
const bundle = await getHostBridge()!.localBackend.request<unknown>({
|
|
169
169
|
method: 'GET',
|
|
170
170
|
path: '/api/v2/insights/export',
|
|
171
171
|
});
|
|
172
172
|
const text = `${JSON.stringify(bundle, null, 2)}\n`;
|
|
173
|
-
const saved = await
|
|
173
|
+
const saved = await getHostBridge()!.local?.saveTextFile?.({
|
|
174
174
|
title: t('Export local insight records'),
|
|
175
175
|
defaultPath: `deeppath-insights-${new Date().toISOString().slice(0, 10)}.json`,
|
|
176
176
|
content: text,
|
|
@@ -182,10 +182,10 @@ export function InsightsSettingsPanel() {
|
|
|
182
182
|
};
|
|
183
183
|
|
|
184
184
|
const uploadNow = async () => {
|
|
185
|
-
if (!
|
|
185
|
+
if (!hasHostBridge()) return;
|
|
186
186
|
setError(null);
|
|
187
187
|
try {
|
|
188
|
-
const res = await
|
|
188
|
+
const res = await getHostBridge()!.localBackend.request<{ ok: boolean; detail: string }>({
|
|
189
189
|
method: 'POST',
|
|
190
190
|
path: '/api/v2/insights/upload-local',
|
|
191
191
|
});
|
|
@@ -214,7 +214,7 @@ export function InsightsSettingsPanel() {
|
|
|
214
214
|
'Even if the user does not agree to upload, records stay on this device. You can export JSON and send it to the product team, or click "Upload now" once.',
|
|
215
215
|
)}
|
|
216
216
|
</p>
|
|
217
|
-
{!
|
|
217
|
+
{!hasHostBridge() ? (
|
|
218
218
|
<p className="text-[11px] text-agent-muted-foreground">{t('Open this in the desktop app')}</p>
|
|
219
219
|
) : loading ? (
|
|
220
220
|
<p className="text-[11px] text-agent-muted-foreground">{t('Loading…')}</p>
|
|
@@ -33,7 +33,7 @@ import { modelCapabilityChips } from '@/components/settings/model-capabilities';
|
|
|
33
33
|
import { ModelIdCombobox } from '@/components/settings/ModelIdCombobox';
|
|
34
34
|
import { SettingsSaveButton } from '@/components/settings/SettingsSaveButton';
|
|
35
35
|
import { t } from '@/i18n';
|
|
36
|
-
import {
|
|
36
|
+
import { hasHostBridge } from '@/lib/host-bridge';
|
|
37
37
|
import {
|
|
38
38
|
COMPAT_AUTO,
|
|
39
39
|
formStateFromOverrides,
|
|
@@ -124,7 +124,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
|
|
|
124
124
|
const [resolvedPreset, setResolvedPreset] = useState<ProviderPresetOverride | null>(null);
|
|
125
125
|
|
|
126
126
|
const reload = useCallback(async () => {
|
|
127
|
-
if (!
|
|
127
|
+
if (!hasHostBridge()) {
|
|
128
128
|
setError(t('Open this in the desktop app'));
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
@@ -344,7 +344,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
|
|
|
344
344
|
};
|
|
345
345
|
|
|
346
346
|
const handleSave = async () => {
|
|
347
|
-
if (!
|
|
347
|
+
if (!hasHostBridge()) {
|
|
348
348
|
setError(t('Open this in the desktop app'));
|
|
349
349
|
return;
|
|
350
350
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
LuTrash2,
|
|
11
11
|
} from 'react-icons/lu';
|
|
12
12
|
import { t } from '@/i18n';
|
|
13
|
-
import {
|
|
13
|
+
import { getHostBridge, hasHostBridge } from '@/lib/host-bridge';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* McpSettingsPanel — MCP 服务管理面板(JSON 导入 / 手动增改 / 启停 / 测试 /
|
|
@@ -19,8 +19,8 @@ import { getElectronBridge, isElectron } from '@/lib/electron-bridge';
|
|
|
19
19
|
* 渲染在 `/settings?section=plugins` 的 MCP 分类(AgentLayout 右侧内容区)。
|
|
20
20
|
*
|
|
21
21
|
* 数据与状态完全自管理:挂载时拉一次列表,操作后刷新。后端走
|
|
22
|
-
* local-backend REST(`/api/v2/mcp/servers
|
|
23
|
-
*
|
|
22
|
+
* local-backend REST(`/api/v2/mcp/servers*`),注册表持久化在用户数据目录的
|
|
23
|
+
* `agent-mcp-servers.json`。
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
/** GET /api/v2/mcp/servers 返回的服务条目(含工具缓存状态)。 */
|
|
@@ -68,11 +68,11 @@ export function McpSettingsPanel() {
|
|
|
68
68
|
const [error, setError] = useState<string | null>(null);
|
|
69
69
|
|
|
70
70
|
const fetchMcpServers = useCallback(async () => {
|
|
71
|
-
if (!
|
|
71
|
+
if (!hasHostBridge()) return;
|
|
72
72
|
setMcpLoading(true);
|
|
73
73
|
setError(null);
|
|
74
74
|
try {
|
|
75
|
-
const res = await
|
|
75
|
+
const res = await getHostBridge()!.localBackend.request<{ servers: McpServerInfo[] }>({
|
|
76
76
|
method: 'GET',
|
|
77
77
|
path: '/api/v2/mcp/servers',
|
|
78
78
|
});
|
|
@@ -116,7 +116,7 @@ export function McpSettingsPanel() {
|
|
|
116
116
|
};
|
|
117
117
|
|
|
118
118
|
const handleSaveMcpServer = async () => {
|
|
119
|
-
if (!
|
|
119
|
+
if (!hasHostBridge()) return;
|
|
120
120
|
setMcpSaving(true);
|
|
121
121
|
setError(null);
|
|
122
122
|
try {
|
|
@@ -142,13 +142,13 @@ export function McpSettingsPanel() {
|
|
|
142
142
|
bearerTokenEnvVar: mcpFormBearerEnv.trim() || undefined,
|
|
143
143
|
};
|
|
144
144
|
if (mcpEditingId) {
|
|
145
|
-
await
|
|
145
|
+
await getHostBridge()!.localBackend.request({
|
|
146
146
|
method: 'PUT',
|
|
147
147
|
path: `/api/v2/mcp/servers/${encodeURIComponent(mcpEditingId)}`,
|
|
148
148
|
body,
|
|
149
149
|
});
|
|
150
150
|
} else {
|
|
151
|
-
await
|
|
151
|
+
await getHostBridge()!.localBackend.request({
|
|
152
152
|
method: 'POST',
|
|
153
153
|
path: '/api/v2/mcp/servers',
|
|
154
154
|
body,
|
|
@@ -188,7 +188,7 @@ export function McpSettingsPanel() {
|
|
|
188
188
|
const handleToggleMcpServer = async (server: McpServerInfo) => {
|
|
189
189
|
setError(null);
|
|
190
190
|
try {
|
|
191
|
-
await
|
|
191
|
+
await getHostBridge()!.localBackend.request({
|
|
192
192
|
method: 'PUT',
|
|
193
193
|
path: `/api/v2/mcp/servers/${encodeURIComponent(server.id)}`,
|
|
194
194
|
body: { enabled: !server.enabled },
|
|
@@ -203,7 +203,7 @@ export function McpSettingsPanel() {
|
|
|
203
203
|
setMcpDeletingId(server.id);
|
|
204
204
|
setError(null);
|
|
205
205
|
try {
|
|
206
|
-
await
|
|
206
|
+
await getHostBridge()!.localBackend.request({
|
|
207
207
|
method: 'DELETE',
|
|
208
208
|
path: `/api/v2/mcp/servers/${encodeURIComponent(server.id)}`,
|
|
209
209
|
});
|
|
@@ -219,7 +219,7 @@ export function McpSettingsPanel() {
|
|
|
219
219
|
setMcpTestingId(server.id);
|
|
220
220
|
setError(null);
|
|
221
221
|
try {
|
|
222
|
-
const res = await
|
|
222
|
+
const res = await getHostBridge()!.localBackend.request<{
|
|
223
223
|
success: boolean;
|
|
224
224
|
toolCount: number;
|
|
225
225
|
tools: { name: string }[];
|
|
@@ -245,7 +245,7 @@ export function McpSettingsPanel() {
|
|
|
245
245
|
setMcpImporting(true);
|
|
246
246
|
setError(null);
|
|
247
247
|
try {
|
|
248
|
-
const res = await
|
|
248
|
+
const res = await getHostBridge()!.localBackend.request<{
|
|
249
249
|
added: { name: string }[];
|
|
250
250
|
skipped: string[];
|
|
251
251
|
}>({
|
|
@@ -2,9 +2,9 @@ import { useEffect, useState } from 'react';
|
|
|
2
2
|
import { LuCodeXml } from 'react-icons/lu';
|
|
3
3
|
import { t } from '@/i18n';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
getHostBridge,
|
|
6
6
|
type PythonRunnerSnapshot,
|
|
7
|
-
} from '@/lib/
|
|
7
|
+
} from '@/lib/host-bridge';
|
|
8
8
|
|
|
9
9
|
type Source = PythonRunnerSnapshot['source'];
|
|
10
10
|
|
|
@@ -20,7 +20,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
20
20
|
const [localPath, setLocalPath] = useState('');
|
|
21
21
|
|
|
22
22
|
useEffect(() => {
|
|
23
|
-
const runner =
|
|
23
|
+
const runner = getHostBridge()?.pythonRunner;
|
|
24
24
|
if (!runner) return;
|
|
25
25
|
let alive = true;
|
|
26
26
|
void runner.snapshot().then((next) => {
|
|
@@ -39,7 +39,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
39
39
|
}, []);
|
|
40
40
|
|
|
41
41
|
if (!snapshot?.supported) return null;
|
|
42
|
-
const runner =
|
|
42
|
+
const runner = getHostBridge()?.pythonRunner;
|
|
43
43
|
const busy =
|
|
44
44
|
snapshot.phase === 'downloading'
|
|
45
45
|
|| snapshot.phase === 'verifying'
|