@steerable/agent-shell-web 0.6.48 → 0.6.49

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.
@@ -8,7 +8,11 @@
8
8
  import { invoke } from '@tauri-apps/api/core';
9
9
  import { listen, type UnlistenFn } from '@tauri-apps/api/event';
10
10
  import html2canvas from 'html2canvas';
11
- import type { AppReleaseSnapshot, HostBridge } from './electron-bridge';
11
+ import type {
12
+ AppReleaseSnapshot,
13
+ HostBridge,
14
+ PythonRunnerSnapshot,
15
+ } from './electron-bridge';
12
16
  import { createHttpBridge } from './http-bridge';
13
17
 
14
18
  type VoidCallback = () => void;
@@ -38,6 +42,7 @@ export function createTauriBridge(): HostBridge {
38
42
  const bridge = createHttpBridge();
39
43
  const newChatMenu = createMenuSubscription('menu:new-chat');
40
44
  const terminalMenu = createMenuSubscription('menu:open-terminal');
45
+ const pasteMenu = createMenuSubscription('menu:paste');
41
46
 
42
47
  return {
43
48
  ...bridge,
@@ -85,6 +90,9 @@ export function createTauriBridge(): HostBridge {
85
90
  offMenuNewChat: newChatMenu.off,
86
91
  onMenuOpenTerminal: terminalMenu.on,
87
92
  offMenuOpenTerminal: terminalMenu.off,
93
+ onMenuPaste: pasteMenu.on,
94
+ offMenuPaste: pasteMenu.off,
95
+ readClipboardText: () => invoke<string>('host_read_clipboard_text'),
88
96
  app: {
89
97
  snapshot: () => invoke<AppReleaseSnapshot>('app_release_snapshot'),
90
98
  check: () => invoke<AppReleaseSnapshot>('app_release_check'),
@@ -106,6 +114,37 @@ export function createTauriBridge(): HostBridge {
106
114
  };
107
115
  },
108
116
  },
117
+ pythonRunner: {
118
+ snapshot: () => invoke<PythonRunnerSnapshot>('python_runner_snapshot'),
119
+ download: (url) =>
120
+ invoke<PythonRunnerSnapshot>('python_runner_download', {
121
+ url: url ?? null,
122
+ }),
123
+ cancel: () => invoke<PythonRunnerSnapshot>('python_runner_cancel'),
124
+ pickLocal: () => invoke<string | null>('python_runner_pick_local'),
125
+ useLocal: (path) =>
126
+ invoke<PythonRunnerSnapshot>('python_runner_use_local', { path }),
127
+ useDefault: () =>
128
+ invoke<PythonRunnerSnapshot>('python_runner_use_default'),
129
+ restart: () => invoke<void>('python_runner_restart'),
130
+ onState(callback) {
131
+ let unlisten: UnlistenFn | undefined;
132
+ let cancelled = false;
133
+ const pending = listen<PythonRunnerSnapshot>(
134
+ 'python-runner-state',
135
+ (event) => callback(event.payload),
136
+ );
137
+ void pending.then((stop) => {
138
+ if (cancelled) stop();
139
+ else unlisten = stop;
140
+ });
141
+ return () => {
142
+ cancelled = true;
143
+ if (unlisten) unlisten();
144
+ else void pending.then((stop) => stop());
145
+ };
146
+ },
147
+ },
109
148
  };
110
149
  }
111
150
 
package/src/main.tsx CHANGED
@@ -8,6 +8,7 @@ import { SettingsPage } from './pages/SettingsPage';
8
8
  import { getPackRoutes } from './packs/registry';
9
9
  import { getBrandLogoUrl, BRAND_NAME } from './brand';
10
10
  import { getAppShellGate } from './auth/gate';
11
+ import { installHostPaste } from './lib/host-paste';
11
12
  import './styles/index.css';
12
13
 
13
14
  // 浏览器 dev mock 的安装在 bootstrap() 开头(动态 import + DEV 门)。
@@ -98,6 +99,7 @@ export async function bootstrap(): Promise<void> {
98
99
  // (品牌 logo 由包经 setBrandLogoUrl 注入,见 brand.ts)。
99
100
  document.title = BRAND_NAME;
100
101
  document.querySelector<HTMLLinkElement>('link[rel="icon"]')?.setAttribute('href', getBrandLogoUrl());
102
+ installHostPaste();
101
103
 
102
104
  const rootEl = document.getElementById('root');
103
105
  if (!rootEl) {
@@ -1042,6 +1042,22 @@ function AgentChatView({
1042
1042
  ];
1043
1043
  }, [messages, remoteContent, chatId]);
1044
1044
  const effectiveIsStreaming = isStreaming || remoteStreaming;
1045
+
1046
+ useEffect(() => {
1047
+ if (!chatId) return;
1048
+ window.dispatchEvent(
1049
+ new CustomEvent("chat:streaming-change", {
1050
+ detail: { chatId, isStreaming: effectiveIsStreaming },
1051
+ }),
1052
+ );
1053
+ return () => {
1054
+ window.dispatchEvent(
1055
+ new CustomEvent("chat:streaming-change", {
1056
+ detail: { chatId, isStreaming: false },
1057
+ }),
1058
+ );
1059
+ };
1060
+ }, [chatId, effectiveIsStreaming]);
1045
1061
  const effectiveCurrentTurnActions: ExecutedAction[] = remoteStreaming
1046
1062
  ? ((liveStream.executedActions as ExecutedAction[] | undefined) ?? [])
1047
1063
  : currentTurnActions;
@@ -1101,6 +1117,7 @@ function AgentChatView({
1101
1117
  chatSlots={ctx.chatSlots}
1102
1118
  rightPanel={ctx.rightPanel}
1103
1119
  onToggleChatSlot={ctx.onToggleChatSlot}
1120
+ onToggleRightPanel={ctx.onToggleChatSlot}
1104
1121
  />
1105
1122
  }
1106
1123
  onRegenerate={
@@ -1,10 +1,12 @@
1
1
  import { useRef, useState } from 'react';
2
- import { LuActivity, LuBlocks, LuBot, LuChartBar, LuMonitor, LuNetwork, LuPlug, LuSearch, LuSettings, LuShieldCheck } from 'react-icons/lu';
2
+ import { LuActivity, LuBlocks, LuBot, LuChartBar, LuDownload, LuMonitor, LuNetwork, LuPlug, LuSearch, LuSettings, LuShieldCheck } from 'react-icons/lu';
3
3
  import { useOutletContext, useSearchParams } from 'react-router-dom';
4
4
  import { isElectron } from '@/lib/electron-bridge';
5
5
  import { AgentsSettingsPanel } from '@/components/settings/AgentsSettingsPanel';
6
6
  import { AppearanceSettingsPanel } from '@/components/settings/AppearanceSettingsPanel';
7
7
  import { AppUpdateSettingsPanel } from '@/components/settings/AppUpdateSettingsPanel';
8
+ import { PortableSettingsPanel } from '@/components/settings/PortableSettingsPanel';
9
+ import { PythonRunnerSettingsPanel } from '@/components/settings/PythonRunnerSettingsPanel';
8
10
  import { DiagnoseSettingsPanel } from '@/components/settings/DiagnoseSettingsPanel';
9
11
  import { InsightsSettingsPanel } from '@/components/settings/InsightsSettingsPanel';
10
12
  import {
@@ -21,6 +23,7 @@ import { UsagePanel } from '@/components/settings/UsagePanel';
21
23
  import { WebSearchSettingsPanel } from '@/components/settings/WebSearchSettingsPanel';
22
24
  import { getPackSettingsPanels } from '@/packs/registry';
23
25
  import { settingsChrome } from '@/lib/host-tools';
26
+ import { isPortableEnabled } from '@/lib/portable';
24
27
  import type { AgentOutletContext } from '@/layouts/AgentLayout';
25
28
 
26
29
  type SettingsSection = 'skills' | 'mcp' | 'agents' | 'general';
@@ -218,6 +221,17 @@ export function SettingsPage() {
218
221
  </section>
219
222
  ))}
220
223
 
224
+ {isPortableEnabled() && (
225
+ <section className="space-y-2" data-testid="settings-section-portable">
226
+ <h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
227
+ <LuDownload className="h-3.5 w-3.5 text-agent-muted-foreground" />
228
+ 备份与迁移
229
+ </h2>
230
+ <PortableSettingsPanel />
231
+ </section>
232
+ )}
233
+
234
+ <PythonRunnerSettingsPanel />
221
235
  <AppUpdateSettingsPanel />
222
236
  </>
223
237
  )}
@@ -164,3 +164,32 @@ body {
164
164
  .chat-input-surface:has(.chat-input-editor[data-composing]) .chat-input-mirror {
165
165
  visibility: hidden;
166
166
  }
167
+
168
+ /*
169
+ * Thin scrollbars everywhere. The system "always show" bar is about 15px.
170
+ * Kept in the base layer so a utility can still hide a scrollbar.
171
+ */
172
+ @layer base {
173
+ * {
174
+ scrollbar-width: thin;
175
+ scrollbar-color: color-mix(in oklab, var(--color-agent-foreground) 28%, transparent) transparent;
176
+ }
177
+
178
+ *::-webkit-scrollbar {
179
+ width: 6px;
180
+ height: 6px;
181
+ }
182
+
183
+ *::-webkit-scrollbar-track {
184
+ background: transparent;
185
+ }
186
+
187
+ *::-webkit-scrollbar-thumb {
188
+ background: color-mix(in oklab, var(--color-agent-foreground) 28%, transparent);
189
+ border-radius: 999px;
190
+ }
191
+
192
+ *::-webkit-scrollbar-thumb:hover {
193
+ background: color-mix(in oklab, var(--color-agent-foreground) 45%, transparent);
194
+ }
195
+ }
package/src/vite-env.d.ts CHANGED
@@ -10,6 +10,8 @@ interface ImportMetaEnv {
10
10
  readonly VITE_APPROVAL?: string;
11
11
  readonly VITE_CHAT_MODES?: string;
12
12
  readonly VITE_SETTINGS?: string;
13
+ /** `'true'` 才打开对话与配置的导出/导入。缺省关。 */
14
+ readonly VITE_PORTABLE?: string;
13
15
  }
14
16
 
15
17
  interface ImportMeta {
package/vite.base.js CHANGED
@@ -58,6 +58,7 @@ export function createProductViteConfig(options) {
58
58
  'import.meta.env.VITE_APPROVAL': JSON.stringify(product.approval ?? 'host'),
59
59
  'import.meta.env.VITE_CHAT_MODES': JSON.stringify(JSON.stringify(product.chatModes ?? ['agent', 'plan'])),
60
60
  'import.meta.env.VITE_SETTINGS': JSON.stringify(JSON.stringify(product.settings ?? {})),
61
+ 'import.meta.env.VITE_PORTABLE': JSON.stringify(product.portable === true ? 'true' : 'false'),
61
62
  },
62
63
  resolve: {
63
64
  alias: {