@yushaw/sanqian-chat 0.2.13 → 0.2.14

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 CHANGED
@@ -148,3 +148,36 @@ src/renderer/styles/
148
148
  - `useChatPanel.ts:86`: toggleMode 依赖 mode,每次 mode 变化重建
149
149
  - 可用 useRef 避免重建,但当前影响可忽略
150
150
  - 文件:`src/renderer/hooks/useAttachState.ts`, `src/renderer/hooks/useChatPanel.ts`
151
+
152
+
153
+ ## Changelog
154
+
155
+ ### 2026-01-11
156
+
157
+ **Ask AI 焦点管理**
158
+
159
+ - `ChatPanel.focusInput()` 方法:完整的焦点管理,支持 floating 和 embedded 模式
160
+ - macOS: `app.focus({ steal: true })` 确保应用获得前台焦点
161
+ - `floatingWindow.focus()` / `webContents.focus()` 确保窗口和 BrowserView 获得焦点
162
+ - IPC 消息通知渲染进程聚焦输入框
163
+ - `ChatAdapter.onFocusInput` 接口:渲染进程监听焦点事件
164
+ - `SanqianChatAPI.onFocusInput` preload API
165
+
166
+ **Bug 修复**
167
+
168
+ - 修复 session resources 发送前被删除的问题(移除了发送时的 `removeSessionResource` 调用)
169
+ - 修复 `AttachedResourceTags` 深色模式样式(使用正确的 CSS 变量)
170
+ - 清理 chip 布局的多余 padding/margin
171
+
172
+ ### 2026-01-10
173
+
174
+ **Session Resources 功能实现**
175
+
176
+ 新增应用主动推送临时上下文的能力(Session Resources):
177
+
178
+ - `ChatAdapter` 接口新增 `getSessionResources`、`onSessionResourceEvent`、`removeSessionResource` 方法
179
+ - `useChat` hook 新增 `sessionResources` 状态和 `removeSessionResource` 回调
180
+ - `FloatingChat` / `CompactChat` 组件支持显示 session resources chips
181
+ - IPC adapter 实现 session resources 相关 API
182
+
183
+ 详见设计文档:`docs/session-resources-design.md`
@@ -33,6 +33,24 @@ function createSanqianChatApi() {
33
33
  };
34
34
  ipcRenderer.on("sanqian-chat:localeChanged", handler);
35
35
  return () => ipcRenderer.removeListener("sanqian-chat:localeChanged", handler);
36
+ },
37
+ // Session Resources
38
+ getSessionResources: () => {
39
+ return ipcRenderer.sendSync("sanqian-chat:getSessionResourcesSync");
40
+ },
41
+ onSessionResourceEvent: (callback) => {
42
+ const handler = (_, event) => {
43
+ callback(event);
44
+ };
45
+ ipcRenderer.on("sanqian-chat:sessionResourceEvent", handler);
46
+ return () => ipcRenderer.removeListener("sanqian-chat:sessionResourceEvent", handler);
47
+ },
48
+ removeSessionResource: (fullId) => ipcRenderer.invoke("sanqian-chat:removeSessionResource", { fullId }),
49
+ // Focus
50
+ onFocusInput: (callback) => {
51
+ const handler = () => callback();
52
+ ipcRenderer.on("sanqian-chat:focusInput", handler);
53
+ return () => ipcRenderer.removeListener("sanqian-chat:focusInput", handler);
36
54
  }
37
55
  };
38
56
  }
@@ -63,6 +63,7 @@ interface ChatUiStrings {
63
63
  embedWindow: string;
64
64
  collapseSidebar: string;
65
65
  history: string;
66
+ remove: string;
66
67
  }
67
68
  type ChatThemeMode = 'light' | 'dark' | 'auto';
68
69
  type ChatFontSize = 'small' | 'normal' | 'large' | 'extra-large';
@@ -389,6 +390,50 @@ interface AttachmentMenuItem {
389
390
  /** Whether currently available (e.g., app connected) */
390
391
  available?: boolean;
391
392
  }
393
+ /**
394
+ * Session resource pushed by an app.
395
+ * Content is provided at push time (not fetched like attached_resources).
396
+ * Global scope - visible in all Chat UI instances, persists until app disconnects.
397
+ */
398
+ interface SessionResource {
399
+ /** Resource ID (auto-generated if not provided) */
400
+ id?: string;
401
+ /** Display title (required) */
402
+ title: string;
403
+ /** Content (required, format controlled by app) */
404
+ content: string;
405
+ /** Optional summary for UI tooltip */
406
+ summary?: string;
407
+ /** Optional icon (emoji or URL) */
408
+ icon?: string;
409
+ /** Optional resource type for styling */
410
+ type?: ResourceType;
411
+ }
412
+ /**
413
+ * Stored session resource with full metadata (internal use)
414
+ */
415
+ interface StoredSessionResource extends SessionResource {
416
+ /** Full ID: "appName:resourceId" */
417
+ fullId: string;
418
+ /** Source app name */
419
+ appName: string;
420
+ /** Push timestamp (ISO 8601) */
421
+ pushedAt: string;
422
+ }
423
+ /**
424
+ * Session resource event from backend
425
+ */
426
+ type SessionResourceEvent = {
427
+ type: 'resource_pushed';
428
+ resource: StoredSessionResource;
429
+ } | {
430
+ type: 'resource_removed';
431
+ resourceId: string;
432
+ source: 'app' | 'user';
433
+ } | {
434
+ type: 'resources_cleared';
435
+ appName: string;
436
+ };
392
437
 
393
438
  /**
394
439
  * Chat Adapter Interface
@@ -476,6 +521,8 @@ interface ChatAdapter {
476
521
  attachedContexts?: string[];
477
522
  /** Attached resource references (e.g., ["sanqian-notes:notes:abc123"]) - for getById */
478
523
  attachedResources?: string[];
524
+ /** Session resources pushed by apps (fullId list) */
525
+ sessionResources?: string[];
479
526
  }): Promise<{
480
527
  cancel: () => void;
481
528
  }>;
@@ -489,6 +536,14 @@ interface ChatAdapter {
489
536
  }>;
490
537
  /** Subscribe to locale change events */
491
538
  onLocaleChanged?(callback: (locale: string) => void): () => void;
539
+ /** Get current session resources from all connected apps */
540
+ getSessionResources?(): StoredSessionResource[];
541
+ /** Subscribe to session resource events (push/remove) */
542
+ onSessionResourceEvent?(callback: (event: SessionResourceEvent) => void): () => void;
543
+ /** Remove a session resource (user action from UI) */
544
+ removeSessionResource?(fullId: string): Promise<void>;
545
+ /** Subscribe to focus input events (e.g., from main process) */
546
+ onFocusInput?(callback: () => void): () => void;
492
547
  cleanup?(): void;
493
548
  }
494
549
  /** SDK adapter config */
@@ -575,4 +630,4 @@ declare function parseToolCalls(toolCalls: unknown): ToolCall[] | undefined;
575
630
  */
576
631
  declare function mergeConsecutiveAssistantMessages(rawMessages: ApiMessage[]): ChatMessage[];
577
632
 
578
- export { type ApiMessage, type AttachConfig, type AttachPosition, type AttachState, type AttachedResource, type AttachmentMenuItem, type AttachmentMenuItemType, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, type ChatMessage, type ChatPanelConfig, type ChatPanelMode, type ChatPanelPosition, type ChatThemeMode, type ChatUiConfigSerializable, type ChatUiStrings, type ConnectionErrorCode, type ConnectionStatus, type ContextProviderInfo, type ConversationDetail, type ConversationInfo, type FloatingWindowConfig, type HitlInterruptData, type Locale, type MessageBlock, type MessageRole, type ResourcePickerItem, type ResourcePickerState, type SdkAdapterConfig, type SendMessage, type StreamEvent, type ToolCall, type ToolCallStatus, type WindowPosition, createChatAdapter, createSdkAdapter, mergeConsecutiveAssistantMessages, parseToolCalls };
633
+ export { type ApiMessage, type AttachConfig, type AttachPosition, type AttachState, type AttachedResource, type AttachmentMenuItem, type AttachmentMenuItemType, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, type ChatMessage, type ChatPanelConfig, type ChatPanelMode, type ChatPanelPosition, type ChatThemeMode, type ChatUiConfigSerializable, type ChatUiStrings, type ConnectionErrorCode, type ConnectionStatus, type ContextProviderInfo, type ConversationDetail, type ConversationInfo, type FloatingWindowConfig, type HitlInterruptData, type Locale, type MessageBlock, type MessageRole, type ResourcePickerItem, type ResourcePickerState, type SdkAdapterConfig, type SendMessage, type SessionResource, type SessionResourceEvent, type StoredSessionResource, type StreamEvent, type ToolCall, type ToolCallStatus, type WindowPosition, createChatAdapter, createSdkAdapter, mergeConsecutiveAssistantMessages, parseToolCalls };
@@ -63,6 +63,7 @@ interface ChatUiStrings {
63
63
  embedWindow: string;
64
64
  collapseSidebar: string;
65
65
  history: string;
66
+ remove: string;
66
67
  }
67
68
  type ChatThemeMode = 'light' | 'dark' | 'auto';
68
69
  type ChatFontSize = 'small' | 'normal' | 'large' | 'extra-large';
@@ -389,6 +390,50 @@ interface AttachmentMenuItem {
389
390
  /** Whether currently available (e.g., app connected) */
390
391
  available?: boolean;
391
392
  }
393
+ /**
394
+ * Session resource pushed by an app.
395
+ * Content is provided at push time (not fetched like attached_resources).
396
+ * Global scope - visible in all Chat UI instances, persists until app disconnects.
397
+ */
398
+ interface SessionResource {
399
+ /** Resource ID (auto-generated if not provided) */
400
+ id?: string;
401
+ /** Display title (required) */
402
+ title: string;
403
+ /** Content (required, format controlled by app) */
404
+ content: string;
405
+ /** Optional summary for UI tooltip */
406
+ summary?: string;
407
+ /** Optional icon (emoji or URL) */
408
+ icon?: string;
409
+ /** Optional resource type for styling */
410
+ type?: ResourceType;
411
+ }
412
+ /**
413
+ * Stored session resource with full metadata (internal use)
414
+ */
415
+ interface StoredSessionResource extends SessionResource {
416
+ /** Full ID: "appName:resourceId" */
417
+ fullId: string;
418
+ /** Source app name */
419
+ appName: string;
420
+ /** Push timestamp (ISO 8601) */
421
+ pushedAt: string;
422
+ }
423
+ /**
424
+ * Session resource event from backend
425
+ */
426
+ type SessionResourceEvent = {
427
+ type: 'resource_pushed';
428
+ resource: StoredSessionResource;
429
+ } | {
430
+ type: 'resource_removed';
431
+ resourceId: string;
432
+ source: 'app' | 'user';
433
+ } | {
434
+ type: 'resources_cleared';
435
+ appName: string;
436
+ };
392
437
 
393
438
  /**
394
439
  * Chat Adapter Interface
@@ -476,6 +521,8 @@ interface ChatAdapter {
476
521
  attachedContexts?: string[];
477
522
  /** Attached resource references (e.g., ["sanqian-notes:notes:abc123"]) - for getById */
478
523
  attachedResources?: string[];
524
+ /** Session resources pushed by apps (fullId list) */
525
+ sessionResources?: string[];
479
526
  }): Promise<{
480
527
  cancel: () => void;
481
528
  }>;
@@ -489,6 +536,14 @@ interface ChatAdapter {
489
536
  }>;
490
537
  /** Subscribe to locale change events */
491
538
  onLocaleChanged?(callback: (locale: string) => void): () => void;
539
+ /** Get current session resources from all connected apps */
540
+ getSessionResources?(): StoredSessionResource[];
541
+ /** Subscribe to session resource events (push/remove) */
542
+ onSessionResourceEvent?(callback: (event: SessionResourceEvent) => void): () => void;
543
+ /** Remove a session resource (user action from UI) */
544
+ removeSessionResource?(fullId: string): Promise<void>;
545
+ /** Subscribe to focus input events (e.g., from main process) */
546
+ onFocusInput?(callback: () => void): () => void;
492
547
  cleanup?(): void;
493
548
  }
494
549
  /** SDK adapter config */
@@ -575,4 +630,4 @@ declare function parseToolCalls(toolCalls: unknown): ToolCall[] | undefined;
575
630
  */
576
631
  declare function mergeConsecutiveAssistantMessages(rawMessages: ApiMessage[]): ChatMessage[];
577
632
 
578
- export { type ApiMessage, type AttachConfig, type AttachPosition, type AttachState, type AttachedResource, type AttachmentMenuItem, type AttachmentMenuItemType, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, type ChatMessage, type ChatPanelConfig, type ChatPanelMode, type ChatPanelPosition, type ChatThemeMode, type ChatUiConfigSerializable, type ChatUiStrings, type ConnectionErrorCode, type ConnectionStatus, type ContextProviderInfo, type ConversationDetail, type ConversationInfo, type FloatingWindowConfig, type HitlInterruptData, type Locale, type MessageBlock, type MessageRole, type ResourcePickerItem, type ResourcePickerState, type SdkAdapterConfig, type SendMessage, type StreamEvent, type ToolCall, type ToolCallStatus, type WindowPosition, createChatAdapter, createSdkAdapter, mergeConsecutiveAssistantMessages, parseToolCalls };
633
+ export { type ApiMessage, type AttachConfig, type AttachPosition, type AttachState, type AttachedResource, type AttachmentMenuItem, type AttachmentMenuItemType, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, type ChatMessage, type ChatPanelConfig, type ChatPanelMode, type ChatPanelPosition, type ChatThemeMode, type ChatUiConfigSerializable, type ChatUiStrings, type ConnectionErrorCode, type ConnectionStatus, type ContextProviderInfo, type ConversationDetail, type ConversationInfo, type FloatingWindowConfig, type HitlInterruptData, type Locale, type MessageBlock, type MessageRole, type ResourcePickerItem, type ResourcePickerState, type SdkAdapterConfig, type SendMessage, type SessionResource, type SessionResourceEvent, type StoredSessionResource, type StreamEvent, type ToolCall, type ToolCallStatus, type WindowPosition, createChatAdapter, createSdkAdapter, mergeConsecutiveAssistantMessages, parseToolCalls };
@@ -413,7 +413,8 @@ function createChatAdapter(config) {
413
413
  {
414
414
  conversationId,
415
415
  persistHistory: config.persistHistory ?? false,
416
- attachedResources: options?.attachedResources
416
+ attachedResources: options?.attachedResources,
417
+ sessionResources: options?.sessionResources
417
418
  }
418
419
  );
419
420
  return processStreamEvents(stream, onEvent, sdk, (id) => {
@@ -426,6 +427,31 @@ function createChatAdapter(config) {
426
427
  if (id) {
427
428
  sdkInstance.sendHitlResponse(id, response);
428
429
  }
430
+ },
431
+ // Session Resources (delegated to shared helper)
432
+ ...createSessionResourceMethods(() => sdkInstance)
433
+ };
434
+ }
435
+ function createSessionResourceMethods(getSdk) {
436
+ return {
437
+ getSessionResources() {
438
+ const sdk = getSdk();
439
+ return sdk?.getSessionResources?.() || [];
440
+ },
441
+ onSessionResourceEvent(callback) {
442
+ const sdk = getSdk();
443
+ if (!sdk) return () => {
444
+ };
445
+ const handler = (resourceId) => {
446
+ callback({ type: "resource_removed", resourceId, source: "user" });
447
+ };
448
+ sdk.on("resourceRemoved", handler);
449
+ return () => sdk.off("resourceRemoved", handler);
450
+ },
451
+ async removeSessionResource(fullId) {
452
+ const sdk = getSdk();
453
+ if (!sdk) return;
454
+ await sdk.removeSessionResource?.(fullId);
429
455
  }
430
456
  };
431
457
  }
@@ -515,10 +541,10 @@ function processStreamEvents(stream, onEvent, sdk, setCurrentRunId) {
515
541
  return {
516
542
  cancel: () => {
517
543
  controller.abort();
518
- const sdkWithCancel = sdk;
519
- if (currentRunId && typeof sdkWithCancel.cancelRun === "function") {
544
+ const extendedSdk = sdk;
545
+ if (currentRunId && typeof extendedSdk.cancelRun === "function") {
520
546
  try {
521
- sdkWithCancel.cancelRun(currentRunId);
547
+ extendedSdk.cancelRun(currentRunId);
522
548
  } catch (e) {
523
549
  console.warn("[chat adapter] Failed to cancel run:", e);
524
550
  }
@@ -638,7 +664,8 @@ function createSdkAdapter(config) {
638
664
  sdkMessages,
639
665
  {
640
666
  conversationId,
641
- attachedResources: options?.attachedResources
667
+ attachedResources: options?.attachedResources,
668
+ sessionResources: options?.sessionResources
642
669
  }
643
670
  );
644
671
  return processStreamEvents(stream, onEvent, sdk, (id) => {
@@ -652,7 +679,9 @@ function createSdkAdapter(config) {
652
679
  if (id) {
653
680
  sdk.sendHitlResponse(id, response);
654
681
  }
655
- }
682
+ },
683
+ // Session Resources (delegated to shared helper)
684
+ ...createSessionResourceMethods(config.getSdk)
656
685
  };
657
686
  }
658
687
  // Annotate the CommonJS export names for ESM import in node:
@@ -374,7 +374,8 @@ function createChatAdapter(config) {
374
374
  {
375
375
  conversationId,
376
376
  persistHistory: config.persistHistory ?? false,
377
- attachedResources: options?.attachedResources
377
+ attachedResources: options?.attachedResources,
378
+ sessionResources: options?.sessionResources
378
379
  }
379
380
  );
380
381
  return processStreamEvents(stream, onEvent, sdk, (id) => {
@@ -387,6 +388,31 @@ function createChatAdapter(config) {
387
388
  if (id) {
388
389
  sdkInstance.sendHitlResponse(id, response);
389
390
  }
391
+ },
392
+ // Session Resources (delegated to shared helper)
393
+ ...createSessionResourceMethods(() => sdkInstance)
394
+ };
395
+ }
396
+ function createSessionResourceMethods(getSdk) {
397
+ return {
398
+ getSessionResources() {
399
+ const sdk = getSdk();
400
+ return sdk?.getSessionResources?.() || [];
401
+ },
402
+ onSessionResourceEvent(callback) {
403
+ const sdk = getSdk();
404
+ if (!sdk) return () => {
405
+ };
406
+ const handler = (resourceId) => {
407
+ callback({ type: "resource_removed", resourceId, source: "user" });
408
+ };
409
+ sdk.on("resourceRemoved", handler);
410
+ return () => sdk.off("resourceRemoved", handler);
411
+ },
412
+ async removeSessionResource(fullId) {
413
+ const sdk = getSdk();
414
+ if (!sdk) return;
415
+ await sdk.removeSessionResource?.(fullId);
390
416
  }
391
417
  };
392
418
  }
@@ -476,10 +502,10 @@ function processStreamEvents(stream, onEvent, sdk, setCurrentRunId) {
476
502
  return {
477
503
  cancel: () => {
478
504
  controller.abort();
479
- const sdkWithCancel = sdk;
480
- if (currentRunId && typeof sdkWithCancel.cancelRun === "function") {
505
+ const extendedSdk = sdk;
506
+ if (currentRunId && typeof extendedSdk.cancelRun === "function") {
481
507
  try {
482
- sdkWithCancel.cancelRun(currentRunId);
508
+ extendedSdk.cancelRun(currentRunId);
483
509
  } catch (e) {
484
510
  console.warn("[chat adapter] Failed to cancel run:", e);
485
511
  }
@@ -599,7 +625,8 @@ function createSdkAdapter(config) {
599
625
  sdkMessages,
600
626
  {
601
627
  conversationId,
602
- attachedResources: options?.attachedResources
628
+ attachedResources: options?.attachedResources,
629
+ sessionResources: options?.sessionResources
603
630
  }
604
631
  );
605
632
  return processStreamEvents(stream, onEvent, sdk, (id) => {
@@ -613,7 +640,9 @@ function createSdkAdapter(config) {
613
640
  if (id) {
614
641
  sdk.sendHitlResponse(id, response);
615
642
  }
616
- }
643
+ },
644
+ // Session Resources (delegated to shared helper)
645
+ ...createSessionResourceMethods(config.getSdk)
617
646
  };
618
647
  }
619
648
  export {
@@ -192,7 +192,7 @@ interface AppEmbeddingConfig {
192
192
  /**
193
193
  * Events emitted by SanqianAppClient
194
194
  */
195
- type AppClientEvent = 'connected' | 'registered' | 'disconnected' | 'error' | 'tool_call';
195
+ type AppClientEvent = 'connected' | 'registered' | 'disconnected' | 'error' | 'tool_call' | 'resourcePushed' | 'resourceRemoved';
196
196
  /**
197
197
  * Event handler types
198
198
  */
@@ -205,6 +205,8 @@ interface AppClientEventHandlers {
205
205
  name: string;
206
206
  arguments: Record<string, unknown>;
207
207
  }) => void;
208
+ resourcePushed: (resource: _yushaw_sanqian_sdk.StoredSessionResource) => void;
209
+ resourceRemoved: (resourceId: string) => void;
208
210
  }
209
211
 
210
212
  /**
@@ -284,6 +286,10 @@ declare class SanqianAppClient {
284
286
  * Subscribe to client events
285
287
  */
286
288
  on<E extends AppClientEvent>(event: E, handler: AppClientEventHandlers[E]): this;
289
+ /**
290
+ * Unsubscribe from client events
291
+ */
292
+ off<E extends AppClientEvent>(event: E, handler: AppClientEventHandlers[E]): this;
287
293
  /**
288
294
  * Remove all event listeners
289
295
  */
@@ -356,6 +362,44 @@ declare class SanqianAppClient {
356
362
  * List all available agents (not just own agents)
357
363
  */
358
364
  listAvailableAgents(): Promise<_yushaw_sanqian_sdk.AgentCapability[]>;
365
+ /**
366
+ * Push a temporary context resource to Sanqian Chat
367
+ * Resources are displayed as chips in the Chat UI and included in LLM context
368
+ *
369
+ * @example
370
+ * ```typescript
371
+ * const resource = await client.pushResource({
372
+ * id: 'editor-selection',
373
+ * title: 'Selected Text',
374
+ * content: '<selection>...</selection>',
375
+ * summary: 'First 50 chars...',
376
+ * icon: '📝',
377
+ * type: 'selection',
378
+ * });
379
+ * ```
380
+ */
381
+ pushResource(resource: _yushaw_sanqian_sdk.SessionResource): Promise<_yushaw_sanqian_sdk.StoredSessionResource>;
382
+ /**
383
+ * Remove a session resource by its full ID
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * await client.removeResource('my-app:editor-selection');
388
+ * ```
389
+ */
390
+ removeResource(resourceId: string): Promise<void>;
391
+ /**
392
+ * Get all current session resources
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * const resources = client.getSessionResources();
397
+ * for (const r of resources) {
398
+ * console.log(`${r.fullId}: ${r.title}`);
399
+ * }
400
+ * ```
401
+ */
402
+ getSessionResources(): _yushaw_sanqian_sdk.StoredSessionResource[];
359
403
  /**
360
404
  * Get the underlying SDK instance
361
405
  * @internal This is for internal use by other sanqian-chat components
@@ -425,6 +469,7 @@ interface ChatUiStrings {
425
469
  embedWindow: string;
426
470
  collapseSidebar: string;
427
471
  history: string;
472
+ remove: string;
428
473
  }
429
474
  type ChatThemeMode = 'light' | 'dark' | 'auto';
430
475
  type ChatFontSize = 'small' | 'normal' | 'large' | 'extra-large';
@@ -715,6 +760,7 @@ declare class ChatPanel {
715
760
  private registeredShortcuts;
716
761
  private stateSaveTimer;
717
762
  private activeStreams;
763
+ private sessionResourceCleanup;
718
764
  constructor(config: ChatPanelConfig);
719
765
  private hostResizeHandler;
720
766
  private setupHostWindowListeners;
@@ -727,6 +773,18 @@ declare class ChatPanel {
727
773
  * Show panel
728
774
  */
729
775
  show(): void;
776
+ /**
777
+ * Focus the chat input
778
+ *
779
+ * Industry best practice (VS Code, etc.):
780
+ * 1. Focus the window (for floating mode)
781
+ * 2. Focus webContents to transfer focus to BrowserView
782
+ * 3. Send message to renderer to focus the input element
783
+ *
784
+ * Note: webContents.focus() is crucial for BrowserView - without it,
785
+ * focus stays in the main content area even if window has focus.
786
+ */
787
+ focusInput(): void;
730
788
  /**
731
789
  * Hide panel
732
790
  */
@@ -834,6 +892,11 @@ declare class ChatPanel {
834
892
  private scheduleSaveState;
835
893
  private saveState;
836
894
  private getSdk;
895
+ /**
896
+ * Setup session resource event forwarding from SDK to renderer
897
+ * Called when SDK becomes available (on connect)
898
+ */
899
+ private setupSessionResourceEvents;
837
900
  private setupIpcHandlers;
838
901
  private cleanupIpcHandlers;
839
902
  }
@@ -192,7 +192,7 @@ interface AppEmbeddingConfig {
192
192
  /**
193
193
  * Events emitted by SanqianAppClient
194
194
  */
195
- type AppClientEvent = 'connected' | 'registered' | 'disconnected' | 'error' | 'tool_call';
195
+ type AppClientEvent = 'connected' | 'registered' | 'disconnected' | 'error' | 'tool_call' | 'resourcePushed' | 'resourceRemoved';
196
196
  /**
197
197
  * Event handler types
198
198
  */
@@ -205,6 +205,8 @@ interface AppClientEventHandlers {
205
205
  name: string;
206
206
  arguments: Record<string, unknown>;
207
207
  }) => void;
208
+ resourcePushed: (resource: _yushaw_sanqian_sdk.StoredSessionResource) => void;
209
+ resourceRemoved: (resourceId: string) => void;
208
210
  }
209
211
 
210
212
  /**
@@ -284,6 +286,10 @@ declare class SanqianAppClient {
284
286
  * Subscribe to client events
285
287
  */
286
288
  on<E extends AppClientEvent>(event: E, handler: AppClientEventHandlers[E]): this;
289
+ /**
290
+ * Unsubscribe from client events
291
+ */
292
+ off<E extends AppClientEvent>(event: E, handler: AppClientEventHandlers[E]): this;
287
293
  /**
288
294
  * Remove all event listeners
289
295
  */
@@ -356,6 +362,44 @@ declare class SanqianAppClient {
356
362
  * List all available agents (not just own agents)
357
363
  */
358
364
  listAvailableAgents(): Promise<_yushaw_sanqian_sdk.AgentCapability[]>;
365
+ /**
366
+ * Push a temporary context resource to Sanqian Chat
367
+ * Resources are displayed as chips in the Chat UI and included in LLM context
368
+ *
369
+ * @example
370
+ * ```typescript
371
+ * const resource = await client.pushResource({
372
+ * id: 'editor-selection',
373
+ * title: 'Selected Text',
374
+ * content: '<selection>...</selection>',
375
+ * summary: 'First 50 chars...',
376
+ * icon: '📝',
377
+ * type: 'selection',
378
+ * });
379
+ * ```
380
+ */
381
+ pushResource(resource: _yushaw_sanqian_sdk.SessionResource): Promise<_yushaw_sanqian_sdk.StoredSessionResource>;
382
+ /**
383
+ * Remove a session resource by its full ID
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * await client.removeResource('my-app:editor-selection');
388
+ * ```
389
+ */
390
+ removeResource(resourceId: string): Promise<void>;
391
+ /**
392
+ * Get all current session resources
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * const resources = client.getSessionResources();
397
+ * for (const r of resources) {
398
+ * console.log(`${r.fullId}: ${r.title}`);
399
+ * }
400
+ * ```
401
+ */
402
+ getSessionResources(): _yushaw_sanqian_sdk.StoredSessionResource[];
359
403
  /**
360
404
  * Get the underlying SDK instance
361
405
  * @internal This is for internal use by other sanqian-chat components
@@ -425,6 +469,7 @@ interface ChatUiStrings {
425
469
  embedWindow: string;
426
470
  collapseSidebar: string;
427
471
  history: string;
472
+ remove: string;
428
473
  }
429
474
  type ChatThemeMode = 'light' | 'dark' | 'auto';
430
475
  type ChatFontSize = 'small' | 'normal' | 'large' | 'extra-large';
@@ -715,6 +760,7 @@ declare class ChatPanel {
715
760
  private registeredShortcuts;
716
761
  private stateSaveTimer;
717
762
  private activeStreams;
763
+ private sessionResourceCleanup;
718
764
  constructor(config: ChatPanelConfig);
719
765
  private hostResizeHandler;
720
766
  private setupHostWindowListeners;
@@ -727,6 +773,18 @@ declare class ChatPanel {
727
773
  * Show panel
728
774
  */
729
775
  show(): void;
776
+ /**
777
+ * Focus the chat input
778
+ *
779
+ * Industry best practice (VS Code, etc.):
780
+ * 1. Focus the window (for floating mode)
781
+ * 2. Focus webContents to transfer focus to BrowserView
782
+ * 3. Send message to renderer to focus the input element
783
+ *
784
+ * Note: webContents.focus() is crucial for BrowserView - without it,
785
+ * focus stays in the main content area even if window has focus.
786
+ */
787
+ focusInput(): void;
730
788
  /**
731
789
  * Hide panel
732
790
  */
@@ -834,6 +892,11 @@ declare class ChatPanel {
834
892
  private scheduleSaveState;
835
893
  private saveState;
836
894
  private getSdk;
895
+ /**
896
+ * Setup session resource event forwarding from SDK to renderer
897
+ * Called when SDK becomes available (on connect)
898
+ */
899
+ private setupSessionResourceEvents;
837
900
  private setupIpcHandlers;
838
901
  private cleanupIpcHandlers;
839
902
  }