@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/src/pages/AgentPage.tsx
CHANGED
|
@@ -2,10 +2,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
2
2
|
import { useNavigate, useOutletContext, useParams, useSearchParams } from "react-router-dom";
|
|
3
3
|
import { useChatStream, type SteerOutcome } from "@steerable/agent-ui";
|
|
4
4
|
import type { ChatMessage, SSEEvent } from "@steerable/agent-protocol";
|
|
5
|
-
import {
|
|
5
|
+
import { getHostBridge, hasHostBridge } from "@/lib/host-bridge";
|
|
6
6
|
import { trackBehavior } from "@/lib/insights";
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
createHostChatTransport,
|
|
9
9
|
regenerateChatMessage,
|
|
10
10
|
} from "@/lib/chat-transport";
|
|
11
11
|
import { ChatHeader } from "@/components/ChatHeader";
|
|
@@ -264,12 +264,12 @@ function AgentChatLoader({
|
|
|
264
264
|
useEffect(() => {
|
|
265
265
|
let cancelled = false;
|
|
266
266
|
void (async () => {
|
|
267
|
-
if (!
|
|
267
|
+
if (!hasHostBridge()) {
|
|
268
268
|
if (!cancelled) setInitialMessages([]);
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
271
|
try {
|
|
272
|
-
const bridge =
|
|
272
|
+
const bridge = getHostBridge()!;
|
|
273
273
|
const [response, live] = await Promise.all([
|
|
274
274
|
bridge.localBackend.request<{
|
|
275
275
|
messages?: ChatMessageWithMetadata[];
|
|
@@ -387,7 +387,7 @@ function AgentChatView({
|
|
|
387
387
|
? (projects.find((p) => p.id === chat.projectId) ?? null)
|
|
388
388
|
: null;
|
|
389
389
|
const transport = useMemo(
|
|
390
|
-
() =>
|
|
390
|
+
() => createHostChatTransport(chatId),
|
|
391
391
|
[chatId],
|
|
392
392
|
);
|
|
393
393
|
|
|
@@ -890,7 +890,7 @@ function AgentChatView({
|
|
|
890
890
|
updateRemoteFollowUps(() => []);
|
|
891
891
|
void (async () => {
|
|
892
892
|
try {
|
|
893
|
-
const response = await
|
|
893
|
+
const response = await getHostBridge()?.localBackend.request<{
|
|
894
894
|
messages?: ChatMessageWithMetadata[];
|
|
895
895
|
}>({
|
|
896
896
|
method: "GET",
|
|
@@ -923,7 +923,7 @@ function AgentChatView({
|
|
|
923
923
|
]);
|
|
924
924
|
|
|
925
925
|
useEffect(() => {
|
|
926
|
-
const bridge =
|
|
926
|
+
const bridge = getHostBridge();
|
|
927
927
|
if (!bridge?.onSuggestedReplies) return;
|
|
928
928
|
return bridge.onSuggestedReplies((payload) => {
|
|
929
929
|
if (payload.chatId !== chatId) return;
|
|
@@ -1048,8 +1048,8 @@ function AgentChatView({
|
|
|
1048
1048
|
// 分享当前对话:截取整个聊天面板区域(含 header + 消息 + 输入框),
|
|
1049
1049
|
// 由主进程 capturePage 后写入系统剪贴板,用户可直接粘贴到任何地方。
|
|
1050
1050
|
const handleShare = useCallback(async (): Promise<boolean> => {
|
|
1051
|
-
if (!
|
|
1052
|
-
const bridge =
|
|
1051
|
+
if (!hasHostBridge()) return false;
|
|
1052
|
+
const bridge = getHostBridge()!;
|
|
1053
1053
|
const panel = document.querySelector('.chat-panel-container');
|
|
1054
1054
|
const rect = panel?.getBoundingClientRect();
|
|
1055
1055
|
const result = await bridge.local?.captureScreenshot(
|
|
@@ -1173,7 +1173,7 @@ function AgentChatView({
|
|
|
1173
1173
|
header={
|
|
1174
1174
|
<ChatHeader
|
|
1175
1175
|
chat={chat}
|
|
1176
|
-
onBranchSwitched={
|
|
1176
|
+
onBranchSwitched={hasHostBridge() ? onBranchTick : undefined}
|
|
1177
1177
|
onInspectTask={ctx.inspectTask}
|
|
1178
1178
|
tasks={tasks}
|
|
1179
1179
|
chatSlots={ctx.chatSlots}
|
|
@@ -1183,7 +1183,7 @@ function AgentChatView({
|
|
|
1183
1183
|
/>
|
|
1184
1184
|
}
|
|
1185
1185
|
onRegenerate={
|
|
1186
|
-
|
|
1186
|
+
hasHostBridge()
|
|
1187
1187
|
? async (messageId) => {
|
|
1188
1188
|
// 后端 regenerate 流跑完后桌面 store 已是新分支投影;
|
|
1189
1189
|
// bump tick 重挂消息列表。
|
|
@@ -1217,11 +1217,11 @@ function AgentChatView({
|
|
|
1217
1217
|
finishedTasks={finishedTasks}
|
|
1218
1218
|
onInspectTask={handleInspectTask}
|
|
1219
1219
|
onDismissFinishedTask={dismissFinished}
|
|
1220
|
-
onShare={
|
|
1220
|
+
onShare={hasHostBridge() ? handleShare : undefined}
|
|
1221
1221
|
suggestedReplies={visibleSuggestedReplies}
|
|
1222
1222
|
onSelectSuggestion={handleSelectSuggestion}
|
|
1223
1223
|
onOpenSettings={
|
|
1224
|
-
|
|
1224
|
+
hasHostBridge() && settingsChrome("llm")
|
|
1225
1225
|
? () => setLlmSettingsOpen(true)
|
|
1226
1226
|
: undefined
|
|
1227
1227
|
}
|
|
@@ -1232,7 +1232,7 @@ function AgentChatView({
|
|
|
1232
1232
|
onSelectModel={setModelOverride}
|
|
1233
1233
|
onSelectEffort={setEffortOverride}
|
|
1234
1234
|
onOpenSettings={
|
|
1235
|
-
|
|
1235
|
+
hasHostBridge() && settingsChrome("llm")
|
|
1236
1236
|
? () => setLlmSettingsOpen(true)
|
|
1237
1237
|
: undefined
|
|
1238
1238
|
}
|
|
@@ -1298,7 +1298,7 @@ function AgentChatView({
|
|
|
1298
1298
|
</>
|
|
1299
1299
|
}
|
|
1300
1300
|
/>
|
|
1301
|
-
{
|
|
1301
|
+
{hasHostBridge() && settingsChrome("llm") && (
|
|
1302
1302
|
<LocalLlmSettingsModal
|
|
1303
1303
|
open={llmSettingsOpen}
|
|
1304
1304
|
onClose={() => setLlmSettingsOpen(false)}
|
|
@@ -1466,7 +1466,7 @@ function EmptyChatGate() {
|
|
|
1466
1466
|
value={inputValue}
|
|
1467
1467
|
onChange={setInputValue}
|
|
1468
1468
|
onSubmit={handleSubmit}
|
|
1469
|
-
disabled={!
|
|
1469
|
+
disabled={!hasHostBridge() || isCreating}
|
|
1470
1470
|
placeholder={
|
|
1471
1471
|
mode === "plan"
|
|
1472
1472
|
? t("Describe your goal. The Agent will make a plan first…")
|
|
@@ -1478,7 +1478,7 @@ function EmptyChatGate() {
|
|
|
1478
1478
|
selectedAgentId={ctx.selectedAgentId}
|
|
1479
1479
|
onSelectAgent={handleSelectAgent}
|
|
1480
1480
|
onOpenSettings={
|
|
1481
|
-
|
|
1481
|
+
hasHostBridge() && settingsChrome("llm")
|
|
1482
1482
|
? () => setLlmSettingsOpen(true)
|
|
1483
1483
|
: undefined
|
|
1484
1484
|
}
|
|
@@ -1489,15 +1489,15 @@ function EmptyChatGate() {
|
|
|
1489
1489
|
onSelectModel={setModelOverride}
|
|
1490
1490
|
onSelectEffort={setEffortOverride}
|
|
1491
1491
|
onOpenSettings={
|
|
1492
|
-
|
|
1492
|
+
hasHostBridge() && settingsChrome("llm")
|
|
1493
1493
|
? () => setLlmSettingsOpen(true)
|
|
1494
1494
|
: undefined
|
|
1495
1495
|
}
|
|
1496
|
-
disabled={!
|
|
1496
|
+
disabled={!hasHostBridge() || isCreating}
|
|
1497
1497
|
/>
|
|
1498
1498
|
}
|
|
1499
1499
|
leadingChrome={
|
|
1500
|
-
|
|
1500
|
+
hasHostBridge() && showProjectsChrome ? (
|
|
1501
1501
|
<ProjectPickerButton
|
|
1502
1502
|
projects={projects}
|
|
1503
1503
|
value={selectedProjectId}
|
|
@@ -1527,15 +1527,15 @@ function EmptyChatGate() {
|
|
|
1527
1527
|
{createError}
|
|
1528
1528
|
</p>
|
|
1529
1529
|
)}
|
|
1530
|
-
{!
|
|
1530
|
+
{!hasHostBridge() && (
|
|
1531
1531
|
<p className="text-xs text-agent-destructive">
|
|
1532
1532
|
{t(
|
|
1533
|
-
"Browser preview mode: no
|
|
1533
|
+
"Browser preview mode: no host bridge, so the chat list and streaming responses are unavailable.",
|
|
1534
1534
|
)}
|
|
1535
1535
|
</p>
|
|
1536
1536
|
)}
|
|
1537
1537
|
</div>
|
|
1538
|
-
{
|
|
1538
|
+
{hasHostBridge() && settingsChrome("llm") && (
|
|
1539
1539
|
<LocalLlmSettingsModal
|
|
1540
1540
|
open={llmSettingsOpen}
|
|
1541
1541
|
onClose={() => setLlmSettingsOpen(false)}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
LuShieldCheck,
|
|
17
17
|
} from 'react-icons/lu';
|
|
18
18
|
import { useOutletContext, useSearchParams } from 'react-router-dom';
|
|
19
|
-
import {
|
|
19
|
+
import { hasHostBridge } from '@/lib/host-bridge';
|
|
20
20
|
import { getLocale, t } from '@/i18n';
|
|
21
21
|
import { useAppRelease } from '@/components/SidebarRelease';
|
|
22
22
|
import { AgentsSettingsPanel } from '@/components/settings/AgentsSettingsPanel';
|
|
@@ -228,7 +228,7 @@ export function SettingsPage() {
|
|
|
228
228
|
if (isPortableEnabled()) {
|
|
229
229
|
list.push({ id: 'portable', label: t('Backup and migration'), Icon: LuDownload });
|
|
230
230
|
}
|
|
231
|
-
if (
|
|
231
|
+
if (hasHostBridge()) {
|
|
232
232
|
list.push({ id: 'python-runner', label: t('Python runtime'), Icon: LuCodeXml });
|
|
233
233
|
}
|
|
234
234
|
if (release.version) {
|
|
@@ -347,9 +347,9 @@ export function SettingsPage() {
|
|
|
347
347
|
) : null}
|
|
348
348
|
|
|
349
349
|
<div ref={contentRef} className="min-w-0 flex-1 max-w-3xl space-y-4">
|
|
350
|
-
{!
|
|
350
|
+
{!hasHostBridge() && (
|
|
351
351
|
<p className="rounded-agent-md border border-agent-destructive/20 bg-agent-destructive/10 p-2.5 text-xs text-agent-destructive">
|
|
352
|
-
{t('Browser preview mode: no
|
|
352
|
+
{t('Browser preview mode: no host bridge, so some settings are unavailable.')}
|
|
353
353
|
</p>
|
|
354
354
|
)}
|
|
355
355
|
|
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host-neutral bridge used by the renderer in desktop and browser modes.
|
|
3
|
-
*
|
|
4
|
-
* Implementations are `tauri-bridge.ts` (desktop) and `http-bridge.ts`
|
|
5
|
-
* (browser server); both must satisfy {@link HostBridge}.
|
|
6
|
-
*
|
|
7
|
-
* `window.electron` and `getElectronBridge()` remain compatibility aliases for
|
|
8
|
-
* existing products. New integrations should use `window.steerableHost` and
|
|
9
|
-
* `getHostBridge()`.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { SSEEvent } from '@steerable/agent-protocol';
|
|
13
|
-
import { getHttpBridge } from './http-bridge';
|
|
14
|
-
import { getTauriBridge } from './tauri-bridge';
|
|
15
|
-
|
|
16
|
-
export type LocalBackendRequestInput = {
|
|
17
|
-
method: string;
|
|
18
|
-
path: string;
|
|
19
|
-
body?: unknown;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type LocalBackendStreamEvent =
|
|
23
|
-
| { type: 'data'; chunk: string; parsed?: SSEEvent }
|
|
24
|
-
| { type: 'end'; status: number }
|
|
25
|
-
| { type: 'error'; error: string };
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* W4-1: mirror of the approval algebra's decision variants
|
|
29
|
-
* (`steerable_agent_runtime.approval.APPROVAL_KINDS` minus `timed_out`,
|
|
30
|
-
* which the sidecar synthesizes itself on timeout).
|
|
31
|
-
*/
|
|
32
|
-
export type ApprovalDecisionKind =
|
|
33
|
-
| 'allow_once'
|
|
34
|
-
| 'allow_for_session'
|
|
35
|
-
| 'allow_always'
|
|
36
|
-
| 'deny_once'
|
|
37
|
-
| 'deny_for_session'
|
|
38
|
-
| 'deny_always'
|
|
39
|
-
| 'abort';
|
|
40
|
-
|
|
41
|
-
export interface ApprovalPromptRequest {
|
|
42
|
-
requestId: string;
|
|
43
|
-
toolName: string;
|
|
44
|
-
arguments: Record<string, unknown>;
|
|
45
|
-
mode: string;
|
|
46
|
-
category: string;
|
|
47
|
-
round: number;
|
|
48
|
-
/** 发起审批的对话。缺省时卡片不绑定会话(旧请求)。 */
|
|
49
|
-
chatId?: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* W8: mirror of the sidecar's `ask_user.request` reverse-call payload
|
|
54
|
-
* (`HostAskUserHandler` in steerable_sidecar.host_tools). `questions`
|
|
55
|
-
* entries match `AskUserQuestionsPayload['questions'][number]` from
|
|
56
|
-
* `@steerable/agent-protocol`; kept structurally typed here so the
|
|
57
|
-
* renderer's compile graph stays free of main-process imports.
|
|
58
|
-
*/
|
|
59
|
-
export interface AskUserPromptRequest {
|
|
60
|
-
requestId: string;
|
|
61
|
-
intro: string;
|
|
62
|
-
questions: Array<Record<string, unknown>>;
|
|
63
|
-
/** 发起提问的对话。缺省时卡片不绑定会话(旧请求)。 */
|
|
64
|
-
chatId?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Mirror of `TerminalSession` / `TerminalSpawnOptions` in
|
|
69
|
-
* `src/terminal-manager.ts`. Kept inline (rather than cross-imported)
|
|
70
|
-
* for the same reason as the rest of this file — we don't want the
|
|
71
|
-
* renderer's compile graph to pull in main-process modules.
|
|
72
|
-
*/
|
|
73
|
-
export interface TerminalSession {
|
|
74
|
-
id: string;
|
|
75
|
-
shell: string;
|
|
76
|
-
pid: number;
|
|
77
|
-
cwd: string;
|
|
78
|
-
cols: number;
|
|
79
|
-
rows: number;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface TerminalSpawnOptions {
|
|
83
|
-
shell?: string;
|
|
84
|
-
cwd?: string;
|
|
85
|
-
env?: Record<string, string>;
|
|
86
|
-
cols?: number;
|
|
87
|
-
rows?: number;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export type AppReleasePhase =
|
|
91
|
-
| 'disabled'
|
|
92
|
-
| 'idle'
|
|
93
|
-
| 'checking'
|
|
94
|
-
| 'downloading'
|
|
95
|
-
| 'ready'
|
|
96
|
-
| 'installing'
|
|
97
|
-
| 'error';
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 桌面版本与更新。`version` 是当前安装版本,`availableVersion` 是待安装版本。
|
|
101
|
-
* 与主进程 `AppReleaseSnapshot` 对齐。
|
|
102
|
-
*/
|
|
103
|
-
export interface AppReleaseSnapshot {
|
|
104
|
-
version: string;
|
|
105
|
-
enabled: boolean;
|
|
106
|
-
phase: AppReleasePhase;
|
|
107
|
-
availableVersion?: string;
|
|
108
|
-
percent?: number;
|
|
109
|
-
message?: string;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export type PythonRunnerPhase =
|
|
113
|
-
| 'idle'
|
|
114
|
-
| 'downloading'
|
|
115
|
-
| 'verifying'
|
|
116
|
-
| 'extracting'
|
|
117
|
-
| 'ready'
|
|
118
|
-
| 'error';
|
|
119
|
-
|
|
120
|
-
export interface PythonRunnerSnapshot {
|
|
121
|
-
supported: boolean;
|
|
122
|
-
source: 'default' | 'url' | 'local';
|
|
123
|
-
phase: PythonRunnerPhase;
|
|
124
|
-
percent?: number;
|
|
125
|
-
downloadedBytes?: number;
|
|
126
|
-
totalBytes?: number;
|
|
127
|
-
message?: string;
|
|
128
|
-
defaultUrl?: string;
|
|
129
|
-
configuredUrl?: string;
|
|
130
|
-
activeRunner?: string;
|
|
131
|
-
configuredRunner?: string;
|
|
132
|
-
restartRequired: boolean;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface HostBridge {
|
|
136
|
-
runtime: 'local';
|
|
137
|
-
platform: NodeJS.Platform;
|
|
138
|
-
local?: {
|
|
139
|
-
selectDirectory: (options?: {
|
|
140
|
-
title?: string;
|
|
141
|
-
}) => Promise<{ canceled: boolean; filePaths: string[] }>;
|
|
142
|
-
saveTextFile?: (options: {
|
|
143
|
-
title?: string;
|
|
144
|
-
defaultPath?: string;
|
|
145
|
-
content: string;
|
|
146
|
-
}) => Promise<{ canceled: boolean; filePath?: string }>;
|
|
147
|
-
/** 截取窗口(或指定 DIP 区域)并写入系统剪贴板。 */
|
|
148
|
-
captureScreenshot: (rect?: {
|
|
149
|
-
x: number;
|
|
150
|
-
y: number;
|
|
151
|
-
width: number;
|
|
152
|
-
height: number;
|
|
153
|
-
}) => Promise<
|
|
154
|
-
| { success: true; width: number; height: number }
|
|
155
|
-
| { success: false; error: string }
|
|
156
|
-
>;
|
|
157
|
-
};
|
|
158
|
-
localBackend: {
|
|
159
|
-
request: <T>(input: LocalBackendRequestInput) => Promise<T>;
|
|
160
|
-
startStream: (
|
|
161
|
-
input: LocalBackendRequestInput,
|
|
162
|
-
onEvent: (payload: LocalBackendStreamEvent) => void,
|
|
163
|
-
) => Promise<string | null>;
|
|
164
|
-
cancelStream: (streamId: string) => void;
|
|
165
|
-
/** 轮中转向:注入一条用户消息到运行中的 CoreLoop 回合;false = 无可转向回合。 */
|
|
166
|
-
steerChat?: (chatId: string, content: string) => Promise<boolean>;
|
|
167
|
-
};
|
|
168
|
-
/** 会话附件:把源文件路径 / base64 字节拷贝进会话空间(`attachments:save`)。 */
|
|
169
|
-
attachments?: {
|
|
170
|
-
save: (input: {
|
|
171
|
-
chatId: string;
|
|
172
|
-
files: Array<{ path?: string; name?: string; data?: string }>;
|
|
173
|
-
}) => Promise<{
|
|
174
|
-
files: Array<{ name: string; path: string; size: number; error?: string }>;
|
|
175
|
-
}>;
|
|
176
|
-
};
|
|
177
|
-
/**
|
|
178
|
-
* App menu IPC — `Cmd+N` / 文件 → 新建对话 fires `menu:new-chat`
|
|
179
|
-
* (see src/main.ts:createMenu). Renderer subscribes via
|
|
180
|
-
* `onMenuNewChat(cb)` and *must* call `offMenuNewChat()` on unmount
|
|
181
|
-
* to clear the listener (otherwise multiple AgentSidebar mounts
|
|
182
|
-
* during HMR would queue up duplicate callbacks).
|
|
183
|
-
*/
|
|
184
|
-
/** 把当前界面语言告诉主进程,以便重建应用菜单。浏览器预览里没有。 */
|
|
185
|
-
setLocale?: (locale: string) => void;
|
|
186
|
-
onMenuNewChat?: (callback: () => void) => void;
|
|
187
|
-
offMenuNewChat?: () => void;
|
|
188
|
-
/**
|
|
189
|
-
* App menu IPC — `Cmd+T` / 视图 → 打开终端 fires `menu:open-terminal`.
|
|
190
|
-
* The terminal is a toggleable panel inside AgentLayout (not a separate
|
|
191
|
-
* window, not a route), so the renderer just flips layout state. Same
|
|
192
|
-
* subscribe/off pairing as `onMenuNewChat`.
|
|
193
|
-
*/
|
|
194
|
-
onMenuOpenTerminal?: (callback: () => void) => void;
|
|
195
|
-
offMenuOpenTerminal?: () => void;
|
|
196
|
-
/**
|
|
197
|
-
* App menu IPC — `Cmd+V` / 编辑 → 粘贴 fires `menu:paste`.
|
|
198
|
-
* WKWebView does not expose that pasteboard to the page, so the renderer
|
|
199
|
-
* reads it through `readClipboard`.
|
|
200
|
-
*/
|
|
201
|
-
onMenuPaste?: (callback: () => void) => void;
|
|
202
|
-
offMenuPaste?: () => void;
|
|
203
|
-
/**
|
|
204
|
-
* OS pasteboard. `files` are copied paths or a screenshot PNG.
|
|
205
|
-
* Text is empty when the pasteboard holds files, so the path is not
|
|
206
|
-
* inserted into the composer as well.
|
|
207
|
-
*/
|
|
208
|
-
readClipboard?: () => Promise<{
|
|
209
|
-
text: string;
|
|
210
|
-
files: Array<{
|
|
211
|
-
name: string;
|
|
212
|
-
path?: string;
|
|
213
|
-
dataBase64?: string;
|
|
214
|
-
mime?: string;
|
|
215
|
-
}>;
|
|
216
|
-
}>;
|
|
217
|
-
/** Plain text from the OS pasteboard. Empty when the clipboard has no text. */
|
|
218
|
-
readClipboardText?: () => Promise<string>;
|
|
219
|
-
/**
|
|
220
|
-
* 异步 AI 标题就绪通知。Backend 在每条 chat 首条助手回复完成后 fire-and-forget
|
|
221
|
-
* 跑 LLM 生成标题;完成时通过这个通道把 `{chatId, title}` 推过来。返回的函数
|
|
222
|
-
* 解除订阅(preload 用 add/remove 配对,不是 removeAllListeners——可以多处订阅)。
|
|
223
|
-
*/
|
|
224
|
-
onChatTitleUpdated?: (
|
|
225
|
-
callback: (payload: { chatId: string; title: string }) => void,
|
|
226
|
-
) => () => void;
|
|
227
|
-
/**
|
|
228
|
-
* 回合追问建议就绪。Backend 在助手回复完成后推 `{chatId, messageId, suggestions}`
|
|
229
|
-
* (先启发式、后 LLM 替换)。返回解除订阅函数。
|
|
230
|
-
*/
|
|
231
|
-
onSuggestedReplies?: (
|
|
232
|
-
callback: (payload: {
|
|
233
|
-
chatId: string;
|
|
234
|
-
messageId: string;
|
|
235
|
-
suggestions: string[];
|
|
236
|
-
}) => void,
|
|
237
|
-
) => () => void;
|
|
238
|
-
/**
|
|
239
|
-
* 会话补建通知。Backend 在「向一个本地不存在的 chatId 首次发送」时按 URL
|
|
240
|
-
* 里的 id 现场补建会话(见 router.handleStream),随后广播 `chat-created`
|
|
241
|
-
* ({chatId, agentId});渲染端据此 refresh 一次侧栏,避免"URL 能聊、列表
|
|
242
|
-
* 里却找不到这条会话"。返回解除订阅函数。
|
|
243
|
-
*/
|
|
244
|
-
onChatCreated?: (
|
|
245
|
-
callback: (payload: { chatId: string; agentId?: string | null }) => void,
|
|
246
|
-
) => () => void;
|
|
247
|
-
/**
|
|
248
|
-
* 4.6a 后台任务状态推送。任务到达终态或 worktree 合并/丢弃完成时主进程
|
|
249
|
-
* 广播;载荷只有 chatId/taskId,面板收到后重新拉列表。
|
|
250
|
-
*/
|
|
251
|
-
onTaskUpdated?: (
|
|
252
|
-
callback: (payload: { chatId: string; taskId: string }) => void,
|
|
253
|
-
) => () => void;
|
|
254
|
-
/**
|
|
255
|
-
* 场景包广播事件订阅(3.1 通用化):按通道名订阅主进程/BS 后端的
|
|
256
|
-
* 广播事件,载荷由订阅方(包)自行收窄校验。shell 不知道包的事件
|
|
257
|
-
* 语义——包的通道名约定带包前缀(如 `<pack>:updated`)。返回解除
|
|
258
|
-
* 订阅函数。
|
|
259
|
-
*/
|
|
260
|
-
onPackEvent?: (
|
|
261
|
-
channel: string,
|
|
262
|
-
callback: (payload: unknown) => void,
|
|
263
|
-
) => () => void;
|
|
264
|
-
/**
|
|
265
|
-
* 后台任务推理时间线推送。任务流的 reasoning / 工具 / 文本增量经主进程
|
|
266
|
-
* 广播;右侧过程栏订阅后不用轮询。
|
|
267
|
-
*/
|
|
268
|
-
onTaskProcess?: (
|
|
269
|
-
callback: (payload: {
|
|
270
|
-
chatId: string;
|
|
271
|
-
taskId: string;
|
|
272
|
-
timeline: unknown;
|
|
273
|
-
live: boolean;
|
|
274
|
-
}) => void,
|
|
275
|
-
) => () => void;
|
|
276
|
-
/**
|
|
277
|
-
* W4-1 审批代数:sidecar ApprovalExecutor 的请示经主进程广播到 renderer。
|
|
278
|
-
* onRequest 订阅返回 unsubscribe;decide 把用户的 7 变体决定送回主进程。
|
|
279
|
-
*/
|
|
280
|
-
approval?: {
|
|
281
|
-
onRequest: (callback: (request: ApprovalPromptRequest) => void) => () => void;
|
|
282
|
-
decide: (decision: {
|
|
283
|
-
requestId: string;
|
|
284
|
-
kind: ApprovalDecisionKind;
|
|
285
|
-
reason?: string;
|
|
286
|
-
}) => Promise<void>;
|
|
287
|
-
pending: () => Promise<ApprovalPromptRequest[]>;
|
|
288
|
-
};
|
|
289
|
-
/**
|
|
290
|
-
* W8 结构化提问:sidecar ask_user 工具的请示经主进程/BS 服务器广播到
|
|
291
|
-
* renderer。onRequest 订阅返回 unsubscribe;answer 把答案映射送回
|
|
292
|
-
* (空映射 = 用户未作答,模型自行推进)。
|
|
293
|
-
*/
|
|
294
|
-
askUser?: {
|
|
295
|
-
onRequest: (callback: (request: AskUserPromptRequest) => void) => () => void;
|
|
296
|
-
answer: (reply: {
|
|
297
|
-
requestId: string;
|
|
298
|
-
answers: Record<string, string | string[]>;
|
|
299
|
-
}) => Promise<void>;
|
|
300
|
-
pending: () => Promise<AskUserPromptRequest[]>;
|
|
301
|
-
};
|
|
302
|
-
terminal?: {
|
|
303
|
-
/** Ensures a session exists (reuses last one if alive). */
|
|
304
|
-
ensure: (options?: TerminalSpawnOptions) => Promise<TerminalSession>;
|
|
305
|
-
write: (id: string, data: string) => Promise<boolean>;
|
|
306
|
-
resize: (id: string, cols: number, rows: number) => Promise<boolean>;
|
|
307
|
-
onData: (
|
|
308
|
-
callback: (payload: { sessionId: string; chunk: string }) => void,
|
|
309
|
-
) => () => void;
|
|
310
|
-
onExit: (
|
|
311
|
-
callback: (payload: {
|
|
312
|
-
sessionId: string;
|
|
313
|
-
code: number;
|
|
314
|
-
signal: string | null;
|
|
315
|
-
}) => void,
|
|
316
|
-
) => () => void;
|
|
317
|
-
};
|
|
318
|
-
/**
|
|
319
|
-
* 桌面版本与更新。Electron / Tauri 提供;浏览器预览没有这一段。
|
|
320
|
-
*/
|
|
321
|
-
app?: {
|
|
322
|
-
snapshot: () => Promise<AppReleaseSnapshot>;
|
|
323
|
-
check: () => Promise<AppReleaseSnapshot>;
|
|
324
|
-
install: () => Promise<AppReleaseSnapshot>;
|
|
325
|
-
onState: (callback: (snapshot: AppReleaseSnapshot) => void) => () => void;
|
|
326
|
-
};
|
|
327
|
-
/** Desktop-only Python child interpreter management for run_code. */
|
|
328
|
-
pythonRunner?: {
|
|
329
|
-
snapshot: () => Promise<PythonRunnerSnapshot>;
|
|
330
|
-
download: (url?: string) => Promise<PythonRunnerSnapshot>;
|
|
331
|
-
cancel: () => Promise<PythonRunnerSnapshot>;
|
|
332
|
-
pickLocal: () => Promise<string | null>;
|
|
333
|
-
useLocal: (path: string) => Promise<PythonRunnerSnapshot>;
|
|
334
|
-
useDefault: () => Promise<PythonRunnerSnapshot>;
|
|
335
|
-
restart: () => Promise<void>;
|
|
336
|
-
onState: (callback: (snapshot: PythonRunnerSnapshot) => void) => () => void;
|
|
337
|
-
};
|
|
338
|
-
// 场景包的 invoke 命名空间(如包 preload 贡献的 `<pack>` / `<pack>Mock`)
|
|
339
|
-
// 不进本接口——包用自己的结构化收窄访问器(见各包 web/bridge.ts),
|
|
340
|
-
// shell 桥接口保持产品中立(3.1)。
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** @deprecated Use `HostBridge`. */
|
|
344
|
-
export type ElectronBridge = HostBridge;
|
|
345
|
-
|
|
346
|
-
declare global {
|
|
347
|
-
interface Window {
|
|
348
|
-
steerableHost?: HostBridge;
|
|
349
|
-
electron?: ElectronBridge;
|
|
350
|
-
__TAURI_INTERNALS__?: object;
|
|
351
|
-
/**
|
|
352
|
-
* BS server(src/server/)托管 index.html 时注入的引导信息;存在即代表
|
|
353
|
-
* 当前跑在浏览器-服务器模式,getElectronBridge() 会返回 HTTP 实现。
|
|
354
|
-
*/
|
|
355
|
-
__DEEPPATH_BS__?: {
|
|
356
|
-
platform: NodeJS.Platform;
|
|
357
|
-
flavor: string;
|
|
358
|
-
brandName: string;
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
export function getHostBridge(): HostBridge | null {
|
|
364
|
-
if (typeof window === 'undefined') return null;
|
|
365
|
-
if (window.steerableHost) return window.steerableHost;
|
|
366
|
-
if (window.electron) return window.electron;
|
|
367
|
-
if (window.__TAURI_INTERNALS__ && window.__DEEPPATH_BS__) {
|
|
368
|
-
return getTauriBridge();
|
|
369
|
-
}
|
|
370
|
-
if (window.__DEEPPATH_BS__) return getHttpBridge();
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/** @deprecated Use `getHostBridge`. */
|
|
375
|
-
export const getElectronBridge = getHostBridge;
|
|
376
|
-
|
|
377
|
-
export function isDesktopHost(): boolean {
|
|
378
|
-
if (typeof window === 'undefined') return false;
|
|
379
|
-
return Boolean(
|
|
380
|
-
window.steerableHost ||
|
|
381
|
-
window.electron ||
|
|
382
|
-
(window.__TAURI_INTERNALS__ && window.__DEEPPATH_BS__),
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/** @deprecated Use `isDesktopHost` when desktop-only behavior is required. */
|
|
387
|
-
export function isElectron(): boolean {
|
|
388
|
-
return getHostBridge() !== null;
|
|
389
|
-
}
|