@steerable/agent-shell-web 0.6.69 → 0.6.71

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/agent-shell-web",
3
- "version": "0.6.69",
3
+ "version": "0.6.71",
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-protocol": "0.6.69",
49
- "@steerable/agent-ui": "0.6.69"
48
+ "@steerable/agent-protocol": "0.6.71",
49
+ "@steerable/agent-ui": "0.6.71"
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.69"
62
+ "@steerable/pack-sdk": "0.6.71"
63
63
  },
64
64
  "scripts": {
65
65
  "gen": "echo agent-shell-web",
@@ -129,6 +129,18 @@ export interface PythonRunnerSnapshot {
129
129
  restartRequired: boolean;
130
130
  }
131
131
 
132
+ export interface HostClipboardFile {
133
+ name: string;
134
+ path?: string;
135
+ dataBase64?: string;
136
+ mime?: string;
137
+ }
138
+
139
+ export interface HostClipboard {
140
+ text: string;
141
+ files: HostClipboardFile[];
142
+ }
143
+
132
144
  export interface HostBridge {
133
145
  runtime: 'local';
134
146
  platform: NodeJS.Platform;
@@ -197,25 +209,17 @@ export interface HostBridge {
197
209
  offMenuOpenTerminal?: () => void;
198
210
  /**
199
211
  * App menu IPC — `Cmd+V` / 编辑 → 粘贴 fires `menu:paste`.
200
- * WKWebView does not expose that pasteboard to the page, so the renderer
201
- * reads it through `readClipboard`.
212
+ * WKWebView does not expose that pasteboard to the page, so the host reads
213
+ * it before emitting the event.
202
214
  */
203
- onMenuPaste?: (callback: () => void) => void;
215
+ onMenuPaste?: (callback: (clipboard: HostClipboard) => void) => void;
204
216
  offMenuPaste?: () => void;
205
217
  /**
206
218
  * OS pasteboard. `files` are copied paths or a screenshot PNG.
207
219
  * Text is empty when the pasteboard holds files, so the path is not
208
220
  * inserted into the composer as well.
209
221
  */
210
- readClipboard?: () => Promise<{
211
- text: string;
212
- files: Array<{
213
- name: string;
214
- path?: string;
215
- dataBase64?: string;
216
- mime?: string;
217
- }>;
218
- }>;
222
+ readClipboard?: () => Promise<HostClipboard>;
219
223
  /** Plain text from the OS pasteboard. Empty when the clipboard has no text. */
220
224
  readClipboardText?: () => Promise<string>;
221
225
  /**
@@ -4,18 +4,13 @@
4
4
  * and writes it into the focused field.
5
5
  */
6
6
 
7
- export interface HostClipboardFile {
8
- name: string;
9
- path?: string;
10
- dataBase64?: string;
11
- mime?: string;
12
- }
7
+ import {
8
+ getHostBridge,
9
+ type HostClipboard,
10
+ type HostClipboardFile,
11
+ } from './host-bridge';
13
12
 
14
- export interface HostClipboard {
15
- text: string;
16
- files: HostClipboardFile[];
17
- }
18
- import { getHostBridge } from './host-bridge';
13
+ export type { HostClipboardFile } from './host-bridge';
19
14
 
20
15
  let pending = false;
21
16
  let lastEditable: HTMLElement | null = null;
@@ -44,8 +39,8 @@ export function installHostPaste(): void {
44
39
  if (isEditable(target)) lastEditable = target;
45
40
  });
46
41
  const bridge = getHostBridge();
47
- bridge?.onMenuPaste?.(() => {
48
- requestHostPaste();
42
+ bridge?.onMenuPaste?.((clipboard) => {
43
+ insertHostClipboard(clipboard);
49
44
  });
50
45
  }
51
46
 
@@ -58,8 +53,7 @@ export function requestHostPaste(target?: HTMLElement | null): void {
58
53
  pending = true;
59
54
  void readHostClipboard(readClipboard, readText)
60
55
  .then((clip) => {
61
- if (clip.files.length > 0) insertHostFiles(clip.files, target ?? null);
62
- if (clip.text) insertHostText(clip.text, target ?? null);
56
+ insertHostClipboard(clip, target ?? null);
63
57
  })
64
58
  .catch((error: unknown) => {
65
59
  console.warn('[host-paste] clipboard read failed', error);
@@ -100,6 +94,11 @@ export function hostClipboardAvailable(): boolean {
100
94
  );
101
95
  }
102
96
 
97
+ function insertHostClipboard(clipboard: HostClipboard, target: HTMLElement | null = null): void {
98
+ if (clipboard.files.length > 0) insertHostFiles(clipboard.files, target);
99
+ if (clipboard.text) insertHostText(clipboard.text, target);
100
+ }
101
+
103
102
  function isEditable(target: EventTarget | null): target is HTMLElement {
104
103
  if (isTerminalInput(target) || !(target instanceof HTMLElement)) return false;
105
104
  if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) return true;
@@ -10,22 +10,23 @@ import { listen, type UnlistenFn } from '@tauri-apps/api/event';
10
10
  import html2canvas from 'html2canvas';
11
11
  import type {
12
12
  AppReleaseSnapshot,
13
+ HostClipboard,
13
14
  HostBridge,
14
15
  PythonRunnerSnapshot,
15
16
  } from './host-bridge';
16
17
  import { createHttpBridge } from './http-bridge';
17
18
 
18
- type VoidCallback = () => void;
19
+ type EventCallback<T> = (payload: T) => void;
19
20
 
20
- function createMenuSubscription(event: string): {
21
- on(callback: VoidCallback): void;
21
+ function createMenuSubscription<T = void>(event: string): {
22
+ on(callback: EventCallback<T>): void;
22
23
  off(): void;
23
24
  } {
24
- const subscriptions = new Map<VoidCallback, Promise<UnlistenFn>>();
25
+ const subscriptions = new Map<EventCallback<T>, Promise<UnlistenFn>>();
25
26
  return {
26
27
  on(callback) {
27
28
  if (subscriptions.has(callback)) return;
28
- const subscription = listen(event, () => callback());
29
+ const subscription = listen<T>(event, ({ payload }) => callback(payload));
29
30
  subscriptions.set(callback, subscription);
30
31
  },
31
32
  off() {
@@ -42,7 +43,7 @@ export function createTauriBridge(): HostBridge {
42
43
  const bridge = createHttpBridge();
43
44
  const newChatMenu = createMenuSubscription('menu:new-chat');
44
45
  const terminalMenu = createMenuSubscription('menu:open-terminal');
45
- const pasteMenu = createMenuSubscription('menu:paste');
46
+ const pasteMenu = createMenuSubscription<HostClipboard>('menu:paste');
46
47
 
47
48
  return {
48
49
  ...bridge,
@@ -92,11 +93,7 @@ export function createTauriBridge(): HostBridge {
92
93
  offMenuOpenTerminal: terminalMenu.off,
93
94
  onMenuPaste: pasteMenu.on,
94
95
  offMenuPaste: pasteMenu.off,
95
- readClipboard: () =>
96
- invoke<{
97
- text: string;
98
- files: Array<{ name: string; path?: string; dataBase64?: string; mime?: string }>;
99
- }>('host_read_clipboard'),
96
+ readClipboard: () => invoke<HostClipboard>('host_read_clipboard'),
100
97
  readClipboardText: () => invoke<string>('host_read_clipboard_text'),
101
98
  app: {
102
99
  snapshot: () => invoke<AppReleaseSnapshot>('app_release_snapshot'),