@yushaw/sanqian-chat 0.2.24 → 0.2.27

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.
@@ -496,6 +496,9 @@ interface LinkHandlerConfig {
496
496
 
497
497
  /** Stream event callback */
498
498
  type StreamEvent = {
499
+ /** Internal stream identifier (set by IPC adapter for precise routing) */
500
+ stream_id?: string;
501
+ } & ({
499
502
  type: 'start';
500
503
  run_id: string;
501
504
  conversationId?: string;
@@ -543,7 +546,7 @@ type StreamEvent = {
543
546
  } | {
544
547
  type: 'cancelled';
545
548
  run_id?: string;
546
- };
549
+ });
547
550
  /** Message to send */
548
551
  interface SendMessage {
549
552
  role: 'user' | 'assistant';
@@ -578,7 +581,7 @@ interface ChatAdapter {
578
581
  }): Promise<{
579
582
  cancel: () => void;
580
583
  }>;
581
- sendHitlResponse?(response: HitlResponse, runId?: string): void;
584
+ sendHitlResponse?(response: HitlResponse, runId?: string, streamId?: string): void;
582
585
  /** List available context providers */
583
586
  listResourceProviders?(): Promise<ContextProviderInfo[]>;
584
587
  /** Get resource list from a provider with search/pagination */
@@ -496,6 +496,9 @@ interface LinkHandlerConfig {
496
496
 
497
497
  /** Stream event callback */
498
498
  type StreamEvent = {
499
+ /** Internal stream identifier (set by IPC adapter for precise routing) */
500
+ stream_id?: string;
501
+ } & ({
499
502
  type: 'start';
500
503
  run_id: string;
501
504
  conversationId?: string;
@@ -543,7 +546,7 @@ type StreamEvent = {
543
546
  } | {
544
547
  type: 'cancelled';
545
548
  run_id?: string;
546
- };
549
+ });
547
550
  /** Message to send */
548
551
  interface SendMessage {
549
552
  role: 'user' | 'assistant';
@@ -578,7 +581,7 @@ interface ChatAdapter {
578
581
  }): Promise<{
579
582
  cancel: () => void;
580
583
  }>;
581
- sendHitlResponse?(response: HitlResponse, runId?: string): void;
584
+ sendHitlResponse?(response: HitlResponse, runId?: string, streamId?: string): void;
582
585
  /** List available context providers */
583
586
  listResourceProviders?(): Promise<ContextProviderInfo[]>;
584
587
  /** Get resource list from a provider with search/pagination */
@@ -581,8 +581,10 @@ function processStreamEvents(stream, onEvent, sdk, setCurrentRunId) {
581
581
  default: {
582
582
  const evt = event;
583
583
  if (evt.type === "interrupt") {
584
- currentRunId = evt.run_id || null;
585
- setCurrentRunId(evt.run_id || null);
584
+ if (evt.run_id) {
585
+ currentRunId = evt.run_id;
586
+ setCurrentRunId(evt.run_id);
587
+ }
586
588
  onEvent({
587
589
  type: "interrupt",
588
590
  interrupt_type: evt.interrupt_type || "",
@@ -542,8 +542,10 @@ function processStreamEvents(stream, onEvent, sdk, setCurrentRunId) {
542
542
  default: {
543
543
  const evt = event;
544
544
  if (evt.type === "interrupt") {
545
- currentRunId = evt.run_id || null;
546
- setCurrentRunId(evt.run_id || null);
545
+ if (evt.run_id) {
546
+ currentRunId = evt.run_id;
547
+ setCurrentRunId(evt.run_id);
548
+ }
547
549
  onEvent({
548
550
  type: "interrupt",
549
551
  interrupt_type: evt.interrupt_type || "",
@@ -715,6 +715,7 @@ declare class FloatingWindow {
715
715
  private stateSaveTimer;
716
716
  private activeStreams;
717
717
  private reconnectAcquired;
718
+ private resolveHitlRunId;
718
719
  /**
719
720
  * Get SDK instance from either getClient or getSdk
720
721
  */
@@ -920,6 +921,11 @@ declare class ChatPanel {
920
921
  private scheduleSaveState;
921
922
  private saveState;
922
923
  private getSdk;
924
+ /**
925
+ * Resolve HITL runId with deterministic binding for concurrent streams.
926
+ * Priority: explicit runId -> streamId-bound runId -> single active stream fallback.
927
+ */
928
+ private resolveHitlRunId;
923
929
  /**
924
930
  * Setup session resource event forwarding from SDK to renderer
925
931
  * Called when SDK becomes available (on connect)
@@ -715,6 +715,7 @@ declare class FloatingWindow {
715
715
  private stateSaveTimer;
716
716
  private activeStreams;
717
717
  private reconnectAcquired;
718
+ private resolveHitlRunId;
718
719
  /**
719
720
  * Get SDK instance from either getClient or getSdk
720
721
  */
@@ -920,6 +921,11 @@ declare class ChatPanel {
920
921
  private scheduleSaveState;
921
922
  private saveState;
922
923
  private getSdk;
924
+ /**
925
+ * Resolve HITL runId with deterministic binding for concurrent streams.
926
+ * Priority: explicit runId -> streamId-bound runId -> single active stream fallback.
927
+ */
928
+ private resolveHitlRunId;
923
929
  /**
924
930
  * Setup session resource event forwarding from SDK to renderer
925
931
  * Called when SDK becomes available (on connect)
@@ -366,6 +366,32 @@ var import_electron = require("electron");
366
366
  var import_fs = __toESM(require("fs"));
367
367
  var import_os = __toESM(require("os"));
368
368
  var import_path = __toESM(require("path"));
369
+
370
+ // src/main/hitl.ts
371
+ function resolveHitlRunIdFromStreams(params, activeStreams) {
372
+ if (params.runId) {
373
+ return params.runId;
374
+ }
375
+ if (params.streamId) {
376
+ const stream = activeStreams.get(params.streamId);
377
+ if (!stream || stream.cancelled) {
378
+ return null;
379
+ }
380
+ return stream.runId ?? null;
381
+ }
382
+ const activeRunIds = /* @__PURE__ */ new Set();
383
+ for (const stream of activeStreams.values()) {
384
+ if (stream.cancelled || !stream.runId) continue;
385
+ activeRunIds.add(stream.runId);
386
+ if (activeRunIds.size > 1) {
387
+ return null;
388
+ }
389
+ }
390
+ const first = activeRunIds.values().next();
391
+ return first.done ? null : first.value;
392
+ }
393
+
394
+ // src/main/FloatingWindow.ts
369
395
  var ipcHandlersRegistered = false;
370
396
  var activeInstance = null;
371
397
  var setActiveInstance = (instance) => {
@@ -399,6 +425,9 @@ var FloatingWindow = class _FloatingWindow {
399
425
  import_electron.app.whenReady().then(() => this.registerShortcut());
400
426
  }
401
427
  }
428
+ resolveHitlRunId(params) {
429
+ return resolveHitlRunIdFromStreams(params, this.activeStreams);
430
+ }
402
431
  /**
403
432
  * Get SDK instance from either getClient or getSdk
404
433
  */
@@ -735,8 +764,11 @@ var FloatingWindow = class _FloatingWindow {
735
764
  });
736
765
  import_electron.ipcMain.handle("sanqian-chat:hitlResponse", (_, params) => {
737
766
  const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
738
- if (sdk && params.runId) {
739
- sdk.sendHitlResponse(params.runId, params.response);
767
+ const runId = activeInstance?.resolveHitlRunId({ runId: params.runId, streamId: params.streamId }) ?? null;
768
+ if (sdk && runId) {
769
+ sdk.sendHitlResponse(runId, params.response);
770
+ } else if (activeInstance?.options.devMode) {
771
+ console.warn("[FloatingWindow] HITL response dropped: missing runId");
740
772
  }
741
773
  return { success: true };
742
774
  });
@@ -1685,6 +1717,13 @@ var ChatPanel = class {
1685
1717
  const client = this.config.getClient();
1686
1718
  return client;
1687
1719
  }
1720
+ /**
1721
+ * Resolve HITL runId with deterministic binding for concurrent streams.
1722
+ * Priority: explicit runId -> streamId-bound runId -> single active stream fallback.
1723
+ */
1724
+ resolveHitlRunId(params) {
1725
+ return resolveHitlRunIdFromStreams(params, this.activeStreams);
1726
+ }
1688
1727
  /**
1689
1728
  * Setup session resource event forwarding from SDK to renderer
1690
1729
  * Called when SDK becomes available (on connect)
@@ -1847,8 +1886,11 @@ var ChatPanel = class {
1847
1886
  });
1848
1887
  import_electron2.ipcMain.handle("sanqian-chat:hitlResponse", (_, params) => {
1849
1888
  const sdk = activeInstance2?.getSdk();
1850
- if (sdk && params.runId) {
1851
- sdk.sendHitlResponse(params.runId, params.response);
1889
+ const runId = activeInstance2?.resolveHitlRunId({ runId: params.runId, streamId: params.streamId }) ?? null;
1890
+ if (sdk && runId) {
1891
+ sdk.sendHitlResponse(runId, params.response);
1892
+ } else if (activeInstance2?.config.devMode) {
1893
+ console.warn("[ChatPanel] HITL response dropped: missing runId");
1852
1894
  }
1853
1895
  return { success: true };
1854
1896
  });
@@ -335,6 +335,32 @@ import { BrowserWindow, globalShortcut, screen, ipcMain, app } from "electron";
335
335
  import fs from "fs";
336
336
  import os from "os";
337
337
  import path from "path";
338
+
339
+ // src/main/hitl.ts
340
+ function resolveHitlRunIdFromStreams(params, activeStreams) {
341
+ if (params.runId) {
342
+ return params.runId;
343
+ }
344
+ if (params.streamId) {
345
+ const stream = activeStreams.get(params.streamId);
346
+ if (!stream || stream.cancelled) {
347
+ return null;
348
+ }
349
+ return stream.runId ?? null;
350
+ }
351
+ const activeRunIds = /* @__PURE__ */ new Set();
352
+ for (const stream of activeStreams.values()) {
353
+ if (stream.cancelled || !stream.runId) continue;
354
+ activeRunIds.add(stream.runId);
355
+ if (activeRunIds.size > 1) {
356
+ return null;
357
+ }
358
+ }
359
+ const first = activeRunIds.values().next();
360
+ return first.done ? null : first.value;
361
+ }
362
+
363
+ // src/main/FloatingWindow.ts
338
364
  var ipcHandlersRegistered = false;
339
365
  var activeInstance = null;
340
366
  var setActiveInstance = (instance) => {
@@ -368,6 +394,9 @@ var FloatingWindow = class _FloatingWindow {
368
394
  app.whenReady().then(() => this.registerShortcut());
369
395
  }
370
396
  }
397
+ resolveHitlRunId(params) {
398
+ return resolveHitlRunIdFromStreams(params, this.activeStreams);
399
+ }
371
400
  /**
372
401
  * Get SDK instance from either getClient or getSdk
373
402
  */
@@ -704,8 +733,11 @@ var FloatingWindow = class _FloatingWindow {
704
733
  });
705
734
  ipcMain.handle("sanqian-chat:hitlResponse", (_, params) => {
706
735
  const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
707
- if (sdk && params.runId) {
708
- sdk.sendHitlResponse(params.runId, params.response);
736
+ const runId = activeInstance?.resolveHitlRunId({ runId: params.runId, streamId: params.streamId }) ?? null;
737
+ if (sdk && runId) {
738
+ sdk.sendHitlResponse(runId, params.response);
739
+ } else if (activeInstance?.options.devMode) {
740
+ console.warn("[FloatingWindow] HITL response dropped: missing runId");
709
741
  }
710
742
  return { success: true };
711
743
  });
@@ -1660,6 +1692,13 @@ var ChatPanel = class {
1660
1692
  const client = this.config.getClient();
1661
1693
  return client;
1662
1694
  }
1695
+ /**
1696
+ * Resolve HITL runId with deterministic binding for concurrent streams.
1697
+ * Priority: explicit runId -> streamId-bound runId -> single active stream fallback.
1698
+ */
1699
+ resolveHitlRunId(params) {
1700
+ return resolveHitlRunIdFromStreams(params, this.activeStreams);
1701
+ }
1663
1702
  /**
1664
1703
  * Setup session resource event forwarding from SDK to renderer
1665
1704
  * Called when SDK becomes available (on connect)
@@ -1822,8 +1861,11 @@ var ChatPanel = class {
1822
1861
  });
1823
1862
  ipcMain2.handle("sanqian-chat:hitlResponse", (_, params) => {
1824
1863
  const sdk = activeInstance2?.getSdk();
1825
- if (sdk && params.runId) {
1826
- sdk.sendHitlResponse(params.runId, params.response);
1864
+ const runId = activeInstance2?.resolveHitlRunId({ runId: params.runId, streamId: params.streamId }) ?? null;
1865
+ if (sdk && runId) {
1866
+ sdk.sendHitlResponse(runId, params.response);
1867
+ } else if (activeInstance2?.config.devMode) {
1868
+ console.warn("[ChatPanel] HITL response dropped: missing runId");
1827
1869
  }
1828
1870
  return { success: true };
1829
1871
  });
@@ -176,6 +176,7 @@ interface SanqianChatAPI {
176
176
  sendHitlResponse(params: {
177
177
  response: HitlResponse;
178
178
  runId?: string;
179
+ streamId?: string;
179
180
  }): Promise<{
180
181
  success: boolean;
181
182
  }>;
@@ -176,6 +176,7 @@ interface SanqianChatAPI {
176
176
  sendHitlResponse(params: {
177
177
  response: HitlResponse;
178
178
  runId?: string;
179
+ streamId?: string;
179
180
  }): Promise<{
180
181
  success: boolean;
181
182
  }>;
@@ -499,6 +499,9 @@ interface LinkHandlerConfig {
499
499
 
500
500
  /** Stream event callback */
501
501
  type StreamEvent = {
502
+ /** Internal stream identifier (set by IPC adapter for precise routing) */
503
+ stream_id?: string;
504
+ } & ({
502
505
  type: 'start';
503
506
  run_id: string;
504
507
  conversationId?: string;
@@ -546,7 +549,7 @@ type StreamEvent = {
546
549
  } | {
547
550
  type: 'cancelled';
548
551
  run_id?: string;
549
- };
552
+ });
550
553
  /** Message to send */
551
554
  interface SendMessage {
552
555
  role: 'user' | 'assistant';
@@ -581,7 +584,7 @@ interface ChatAdapter {
581
584
  }): Promise<{
582
585
  cancel: () => void;
583
586
  }>;
584
- sendHitlResponse?(response: HitlResponse, runId?: string): void;
587
+ sendHitlResponse?(response: HitlResponse, runId?: string, streamId?: string): void;
585
588
  /** List available context providers */
586
589
  listResourceProviders?(): Promise<ContextProviderInfo[]>;
587
590
  /** Get resource list from a provider with search/pagination */
@@ -499,6 +499,9 @@ interface LinkHandlerConfig {
499
499
 
500
500
  /** Stream event callback */
501
501
  type StreamEvent = {
502
+ /** Internal stream identifier (set by IPC adapter for precise routing) */
503
+ stream_id?: string;
504
+ } & ({
502
505
  type: 'start';
503
506
  run_id: string;
504
507
  conversationId?: string;
@@ -546,7 +549,7 @@ type StreamEvent = {
546
549
  } | {
547
550
  type: 'cancelled';
548
551
  run_id?: string;
549
- };
552
+ });
550
553
  /** Message to send */
551
554
  interface SendMessage {
552
555
  role: 'user' | 'assistant';
@@ -581,7 +584,7 @@ interface ChatAdapter {
581
584
  }): Promise<{
582
585
  cancel: () => void;
583
586
  }>;
584
- sendHitlResponse?(response: HitlResponse, runId?: string): void;
587
+ sendHitlResponse?(response: HitlResponse, runId?: string, streamId?: string): void;
585
588
  /** List available context providers */
586
589
  listResourceProviders?(): Promise<ContextProviderInfo[]>;
587
590
  /** Get resource list from a provider with search/pagination */
@@ -122,6 +122,7 @@ function useChat(options) {
122
122
  const messagesRef = (0, import_react.useRef)(messages);
123
123
  const conversationIdRef = (0, import_react.useRef)(conversationId);
124
124
  const currentRunIdRef = (0, import_react.useRef)(null);
125
+ const pendingInterruptStreamIdRef = (0, import_react.useRef)(null);
125
126
  const currentAgentIdRef = (0, import_react.useRef)(null);
126
127
  const pendingCancelRef = (0, import_react.useRef)(false);
127
128
  const pendingCancelTimeoutRef = (0, import_react.useRef)(null);
@@ -573,6 +574,7 @@ function useChat(options) {
573
574
  setIsStreaming(false);
574
575
  setIsLoading(false);
575
576
  currentRunIdRef.current = null;
577
+ pendingInterruptStreamIdRef.current = null;
576
578
  suppressStreamRef.current = false;
577
579
  clearPendingCancel();
578
580
  break;
@@ -609,6 +611,7 @@ function useChat(options) {
609
611
  setIsLoading(false);
610
612
  setPendingInterrupt(null);
611
613
  currentRunIdRef.current = null;
614
+ pendingInterruptStreamIdRef.current = null;
612
615
  suppressStreamRef.current = false;
613
616
  clearPendingCancel();
614
617
  break;
@@ -636,13 +639,19 @@ function useChat(options) {
636
639
  setIsStreaming(false);
637
640
  setIsLoading(false);
638
641
  currentRunIdRef.current = null;
642
+ pendingInterruptStreamIdRef.current = null;
639
643
  suppressStreamRef.current = false;
640
644
  clearPendingCancel();
641
645
  break;
642
646
  }
643
647
  case "interrupt": {
644
648
  flushTypewriter();
645
- currentRunIdRef.current = event.run_id ?? null;
649
+ if (event.run_id) {
650
+ currentRunIdRef.current = event.run_id;
651
+ }
652
+ if (event.stream_id) {
653
+ pendingInterruptStreamIdRef.current = event.stream_id;
654
+ }
646
655
  const payload = event.interrupt_payload;
647
656
  if (payload) {
648
657
  setPendingInterrupt(payload);
@@ -665,6 +674,7 @@ function useChat(options) {
665
674
  }
666
675
  resetStreamBuffers();
667
676
  currentRunIdRef.current = null;
677
+ pendingInterruptStreamIdRef.current = null;
668
678
  const userMessage = {
669
679
  id: crypto.randomUUID(),
670
680
  role: "user",
@@ -784,6 +794,7 @@ function useChat(options) {
784
794
  });
785
795
  resetStreamBuffers();
786
796
  currentRunIdRef.current = null;
797
+ pendingInterruptStreamIdRef.current = null;
787
798
  if (!shouldDelayCancel) {
788
799
  clearPendingCancel();
789
800
  suppressStreamRef.current = false;
@@ -800,6 +811,7 @@ function useChat(options) {
800
811
  setIsStreaming(false);
801
812
  resetStreamBuffers();
802
813
  currentRunIdRef.current = null;
814
+ pendingInterruptStreamIdRef.current = null;
803
815
  clearPendingCancel();
804
816
  suppressStreamRef.current = false;
805
817
  }, [clearPendingCancel, resetStreamBuffers]);
@@ -837,23 +849,33 @@ function useChat(options) {
837
849
  setPendingInterrupt(null);
838
850
  resetStreamBuffers();
839
851
  currentRunIdRef.current = null;
852
+ pendingInterruptStreamIdRef.current = null;
840
853
  clearPendingCancel();
841
854
  }, [clearPendingCancel, resetStreamBuffers]);
842
855
  const sendHitlResponse = (0, import_react.useCallback)((response) => {
843
- adapter.sendHitlResponse?.(response, currentRunIdRef.current ?? void 0);
856
+ const runId = currentRunIdRef.current ?? void 0;
857
+ const streamId = pendingInterruptStreamIdRef.current ?? void 0;
858
+ if (streamId) {
859
+ adapter.sendHitlResponse?.(response, runId, streamId);
860
+ return;
861
+ }
862
+ adapter.sendHitlResponse?.(response, runId);
844
863
  }, [adapter]);
845
864
  const approveHitl = (0, import_react.useCallback)((remember = false) => {
846
865
  sendHitlResponse({ approved: true, remember });
847
866
  setPendingInterrupt(null);
867
+ pendingInterruptStreamIdRef.current = null;
848
868
  }, [sendHitlResponse]);
849
869
  const rejectHitl = (0, import_react.useCallback)((remember = false) => {
850
870
  sendHitlResponse({ approved: false, remember });
851
871
  setPendingInterrupt(null);
872
+ pendingInterruptStreamIdRef.current = null;
852
873
  setIsLoading(false);
853
874
  }, [sendHitlResponse]);
854
875
  const submitHitlInput = (0, import_react.useCallback)((response) => {
855
876
  sendHitlResponse(response);
856
877
  setPendingInterrupt(null);
878
+ pendingInterruptStreamIdRef.current = null;
857
879
  if (response.cancelled || response.timed_out) setIsLoading(false);
858
880
  }, [sendHitlResponse]);
859
881
  const removeSessionResource = (0, import_react.useCallback)((fullId) => {
@@ -5963,13 +5985,19 @@ function createIpcAdapter() {
5963
5985
  async chatStream(messages, conversationId, onEvent, options) {
5964
5986
  const streamId = crypto.randomUUID();
5965
5987
  streamCallbacks.set(streamId, (event) => {
5966
- if (event.type === "start" || event.type === "interrupt") {
5988
+ if (event.type === "start") {
5967
5989
  currentRunId = event.run_id || null;
5968
5990
  }
5991
+ if (event.type === "interrupt") {
5992
+ const interruptRunId = event.run_id;
5993
+ if (interruptRunId) {
5994
+ currentRunId = interruptRunId;
5995
+ }
5996
+ }
5969
5997
  if (event.type === "done" || event.type === "cancelled" || event.type === "error") {
5970
5998
  currentRunId = null;
5971
5999
  }
5972
- onEvent(event);
6000
+ onEvent({ ...event, stream_id: streamId });
5973
6001
  });
5974
6002
  try {
5975
6003
  await api.stream({
@@ -5991,8 +6019,12 @@ function createIpcAdapter() {
5991
6019
  throw e;
5992
6020
  }
5993
6021
  },
5994
- sendHitlResponse(response, runId) {
5995
- api.sendHitlResponse({ response, runId: runId || currentRunId || void 0 });
6022
+ sendHitlResponse(response, runId, streamId) {
6023
+ api.sendHitlResponse({
6024
+ response,
6025
+ runId: runId || currentRunId || void 0,
6026
+ streamId: streamId || void 0
6027
+ });
5996
6028
  },
5997
6029
  cleanup() {
5998
6030
  streamEventCleanup?.();
@@ -6366,8 +6398,10 @@ function processStreamEvents(stream, onEvent, sdk, setCurrentRunId) {
6366
6398
  default: {
6367
6399
  const evt = event;
6368
6400
  if (evt.type === "interrupt") {
6369
- currentRunId = evt.run_id || null;
6370
- setCurrentRunId(evt.run_id || null);
6401
+ if (evt.run_id) {
6402
+ currentRunId = evt.run_id;
6403
+ setCurrentRunId(evt.run_id);
6404
+ }
6371
6405
  onEvent({
6372
6406
  type: "interrupt",
6373
6407
  interrupt_type: evt.interrupt_type || "",
@@ -8018,6 +8052,23 @@ var defaultStrings = {
8018
8052
  approvalRequest: "Approval Request",
8019
8053
  inputRequest: "Input Request"
8020
8054
  };
8055
+ var defaultZhStrings = {
8056
+ approve: "\u6279\u51C6",
8057
+ reject: "\u62D2\u7EDD",
8058
+ submit: "\u63D0\u4EA4",
8059
+ cancel: "\u53D6\u6D88",
8060
+ rememberChoice: "\u8BB0\u4F4F\u672C\u6B21\u9009\u62E9",
8061
+ requiredField: "\u6B64\u9879\u5FC5\u586B",
8062
+ timeoutIn: "\u8D85\u65F6\u5269\u4F59",
8063
+ seconds: "\u79D2",
8064
+ executeTool: "\u6267\u884C",
8065
+ toolLabel: "\u5DE5\u5177",
8066
+ argsLabel: "\u53C2\u6570",
8067
+ defaultPrefix: "\u9ED8\u8BA4",
8068
+ enterResponse: "\u8BF7\u8F93\u5165\u4F60\u7684\u56DE\u590D...",
8069
+ approvalRequest: "\u9700\u8981\u5BA1\u6279",
8070
+ inputRequest: "\u9700\u8981\u8F93\u5165"
8071
+ };
8021
8072
  var riskColors = {
8022
8073
  low: {
8023
8074
  bg: "bg-blue-500/10",
@@ -8047,9 +8098,37 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8047
8098
  isDarkMode = false,
8048
8099
  strings = {}
8049
8100
  }) {
8050
- const t = { ...defaultStrings, ...strings };
8101
+ const mergedStrings = { ...defaultStrings, ...strings };
8051
8102
  const isApproval = interrupt.type === "approval_request";
8052
8103
  const isUserInput = interrupt.type === "user_input_request";
8104
+ const cjkRegex = /[\u3400-\u9fff]/;
8105
+ const hasCjkContent = cjkRegex.test(
8106
+ [
8107
+ interrupt.question || "",
8108
+ interrupt.context || "",
8109
+ interrupt.reason || "",
8110
+ interrupt.tool || "",
8111
+ ...interrupt.options || []
8112
+ ].join(" ")
8113
+ );
8114
+ const t = hasCjkContent ? {
8115
+ ...mergedStrings,
8116
+ approve: mergedStrings.approve === defaultStrings.approve ? defaultZhStrings.approve : mergedStrings.approve,
8117
+ reject: mergedStrings.reject === defaultStrings.reject ? defaultZhStrings.reject : mergedStrings.reject,
8118
+ submit: mergedStrings.submit === defaultStrings.submit ? defaultZhStrings.submit : mergedStrings.submit,
8119
+ cancel: mergedStrings.cancel === defaultStrings.cancel ? defaultZhStrings.cancel : mergedStrings.cancel,
8120
+ rememberChoice: mergedStrings.rememberChoice === defaultStrings.rememberChoice ? defaultZhStrings.rememberChoice : mergedStrings.rememberChoice,
8121
+ requiredField: mergedStrings.requiredField === defaultStrings.requiredField ? defaultZhStrings.requiredField : mergedStrings.requiredField,
8122
+ timeoutIn: mergedStrings.timeoutIn === defaultStrings.timeoutIn ? defaultZhStrings.timeoutIn : mergedStrings.timeoutIn,
8123
+ seconds: mergedStrings.seconds === defaultStrings.seconds ? defaultZhStrings.seconds : mergedStrings.seconds,
8124
+ executeTool: mergedStrings.executeTool === defaultStrings.executeTool ? defaultZhStrings.executeTool : mergedStrings.executeTool,
8125
+ toolLabel: mergedStrings.toolLabel === defaultStrings.toolLabel ? defaultZhStrings.toolLabel : mergedStrings.toolLabel,
8126
+ argsLabel: mergedStrings.argsLabel === defaultStrings.argsLabel ? defaultZhStrings.argsLabel : mergedStrings.argsLabel,
8127
+ defaultPrefix: mergedStrings.defaultPrefix === defaultStrings.defaultPrefix ? defaultZhStrings.defaultPrefix : mergedStrings.defaultPrefix,
8128
+ enterResponse: mergedStrings.enterResponse === defaultStrings.enterResponse ? defaultZhStrings.enterResponse : mergedStrings.enterResponse,
8129
+ approvalRequest: mergedStrings.approvalRequest === defaultStrings.approvalRequest ? defaultZhStrings.approvalRequest : mergedStrings.approvalRequest,
8130
+ inputRequest: mergedStrings.inputRequest === defaultStrings.inputRequest ? defaultZhStrings.inputRequest : mergedStrings.inputRequest
8131
+ } : mergedStrings;
8053
8132
  const [answer, setAnswer] = (0, import_react24.useState)(interrupt.default || "");
8054
8133
  const [selectedIndices, setSelectedIndices] = (0, import_react24.useState)([]);
8055
8134
  const [isComposing, setIsComposing] = (0, import_react24.useState)(false);
@@ -8157,7 +8236,38 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8157
8236
  color: isDarkMode ? "#dbeafe" : "#1d4ed8"
8158
8237
  };
8159
8238
  const cancelButtonClass = isDarkMode ? "flex-1 rounded-lg border border-zinc-500 bg-zinc-700/80 px-3 py-2 text-sm font-semibold text-zinc-100 transition-colors hover:bg-zinc-600" : "flex-1 rounded-lg border border-zinc-300 bg-zinc-100 px-3 py-2 text-sm font-semibold text-zinc-900 transition-colors hover:bg-zinc-200";
8160
- const submitButtonClass = `flex-1 rounded-lg px-3 py-2 text-sm font-semibold ${isApproval && riskLevel === "high" ? "bg-red-600 hover:bg-red-700" : "bg-blue-600 hover:bg-blue-700"} text-white shadow-sm transition-colors disabled:cursor-not-allowed disabled:opacity-45`;
8239
+ const isSubmitDisabled = isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0);
8240
+ const submitButtonClass = `flex-1 rounded-lg border px-3 py-2 text-sm font-semibold transition-colors ${isSubmitDisabled ? "cursor-not-allowed" : "hover:brightness-105"}`;
8241
+ const cancelButtonStyle = isDarkMode ? {
8242
+ background: "rgba(63, 63, 70, 0.82)",
8243
+ color: "#f4f4f5",
8244
+ borderColor: "rgba(161, 161, 170, 0.6)"
8245
+ } : {
8246
+ background: "#f4f4f5",
8247
+ color: "#18181b",
8248
+ borderColor: "rgba(113, 113, 122, 0.45)"
8249
+ };
8250
+ const submitButtonStyle = isSubmitDisabled ? isDarkMode ? {
8251
+ background: "rgba(82, 82, 91, 0.72)",
8252
+ color: "#d4d4d8",
8253
+ borderColor: "rgba(161, 161, 170, 0.35)",
8254
+ boxShadow: "none"
8255
+ } : {
8256
+ background: "rgba(228, 228, 231, 0.98)",
8257
+ color: "#52525b",
8258
+ borderColor: "rgba(161, 161, 170, 0.42)",
8259
+ boxShadow: "none"
8260
+ } : isApproval && riskLevel === "high" ? {
8261
+ background: "#dc2626",
8262
+ color: "#ffffff",
8263
+ borderColor: "rgba(220, 38, 38, 0.9)",
8264
+ boxShadow: "0 2px 8px rgba(220, 38, 38, 0.35)"
8265
+ } : {
8266
+ background: "var(--chat-accent, #2563eb)",
8267
+ color: "#ffffff",
8268
+ borderColor: "transparent",
8269
+ boxShadow: "0 2px 10px rgba(37, 99, 235, 0.3)"
8270
+ };
8161
8271
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8162
8272
  "div",
8163
8273
  {
@@ -8274,6 +8384,7 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8274
8384
  {
8275
8385
  onClick: handleCancel,
8276
8386
  className: cancelButtonClass,
8387
+ style: cancelButtonStyle,
8277
8388
  children: isApproval ? t.reject : t.cancel
8278
8389
  }
8279
8390
  ),
@@ -8281,8 +8392,9 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8281
8392
  "button",
8282
8393
  {
8283
8394
  onClick: handleSubmit,
8284
- disabled: isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0),
8395
+ disabled: isSubmitDisabled,
8285
8396
  className: submitButtonClass,
8397
+ style: submitButtonStyle,
8286
8398
  children: isApproval ? t.approve : t.submit
8287
8399
  }
8288
8400
  )
@@ -34,6 +34,7 @@ function useChat(options) {
34
34
  const messagesRef = useRef(messages);
35
35
  const conversationIdRef = useRef(conversationId);
36
36
  const currentRunIdRef = useRef(null);
37
+ const pendingInterruptStreamIdRef = useRef(null);
37
38
  const currentAgentIdRef = useRef(null);
38
39
  const pendingCancelRef = useRef(false);
39
40
  const pendingCancelTimeoutRef = useRef(null);
@@ -485,6 +486,7 @@ function useChat(options) {
485
486
  setIsStreaming(false);
486
487
  setIsLoading(false);
487
488
  currentRunIdRef.current = null;
489
+ pendingInterruptStreamIdRef.current = null;
488
490
  suppressStreamRef.current = false;
489
491
  clearPendingCancel();
490
492
  break;
@@ -521,6 +523,7 @@ function useChat(options) {
521
523
  setIsLoading(false);
522
524
  setPendingInterrupt(null);
523
525
  currentRunIdRef.current = null;
526
+ pendingInterruptStreamIdRef.current = null;
524
527
  suppressStreamRef.current = false;
525
528
  clearPendingCancel();
526
529
  break;
@@ -548,13 +551,19 @@ function useChat(options) {
548
551
  setIsStreaming(false);
549
552
  setIsLoading(false);
550
553
  currentRunIdRef.current = null;
554
+ pendingInterruptStreamIdRef.current = null;
551
555
  suppressStreamRef.current = false;
552
556
  clearPendingCancel();
553
557
  break;
554
558
  }
555
559
  case "interrupt": {
556
560
  flushTypewriter();
557
- currentRunIdRef.current = event.run_id ?? null;
561
+ if (event.run_id) {
562
+ currentRunIdRef.current = event.run_id;
563
+ }
564
+ if (event.stream_id) {
565
+ pendingInterruptStreamIdRef.current = event.stream_id;
566
+ }
558
567
  const payload = event.interrupt_payload;
559
568
  if (payload) {
560
569
  setPendingInterrupt(payload);
@@ -577,6 +586,7 @@ function useChat(options) {
577
586
  }
578
587
  resetStreamBuffers();
579
588
  currentRunIdRef.current = null;
589
+ pendingInterruptStreamIdRef.current = null;
580
590
  const userMessage = {
581
591
  id: crypto.randomUUID(),
582
592
  role: "user",
@@ -696,6 +706,7 @@ function useChat(options) {
696
706
  });
697
707
  resetStreamBuffers();
698
708
  currentRunIdRef.current = null;
709
+ pendingInterruptStreamIdRef.current = null;
699
710
  if (!shouldDelayCancel) {
700
711
  clearPendingCancel();
701
712
  suppressStreamRef.current = false;
@@ -712,6 +723,7 @@ function useChat(options) {
712
723
  setIsStreaming(false);
713
724
  resetStreamBuffers();
714
725
  currentRunIdRef.current = null;
726
+ pendingInterruptStreamIdRef.current = null;
715
727
  clearPendingCancel();
716
728
  suppressStreamRef.current = false;
717
729
  }, [clearPendingCancel, resetStreamBuffers]);
@@ -749,23 +761,33 @@ function useChat(options) {
749
761
  setPendingInterrupt(null);
750
762
  resetStreamBuffers();
751
763
  currentRunIdRef.current = null;
764
+ pendingInterruptStreamIdRef.current = null;
752
765
  clearPendingCancel();
753
766
  }, [clearPendingCancel, resetStreamBuffers]);
754
767
  const sendHitlResponse = useCallback((response) => {
755
- adapter.sendHitlResponse?.(response, currentRunIdRef.current ?? void 0);
768
+ const runId = currentRunIdRef.current ?? void 0;
769
+ const streamId = pendingInterruptStreamIdRef.current ?? void 0;
770
+ if (streamId) {
771
+ adapter.sendHitlResponse?.(response, runId, streamId);
772
+ return;
773
+ }
774
+ adapter.sendHitlResponse?.(response, runId);
756
775
  }, [adapter]);
757
776
  const approveHitl = useCallback((remember = false) => {
758
777
  sendHitlResponse({ approved: true, remember });
759
778
  setPendingInterrupt(null);
779
+ pendingInterruptStreamIdRef.current = null;
760
780
  }, [sendHitlResponse]);
761
781
  const rejectHitl = useCallback((remember = false) => {
762
782
  sendHitlResponse({ approved: false, remember });
763
783
  setPendingInterrupt(null);
784
+ pendingInterruptStreamIdRef.current = null;
764
785
  setIsLoading(false);
765
786
  }, [sendHitlResponse]);
766
787
  const submitHitlInput = useCallback((response) => {
767
788
  sendHitlResponse(response);
768
789
  setPendingInterrupt(null);
790
+ pendingInterruptStreamIdRef.current = null;
769
791
  if (response.cancelled || response.timed_out) setIsLoading(false);
770
792
  }, [sendHitlResponse]);
771
793
  const removeSessionResource = useCallback((fullId) => {
@@ -5875,13 +5897,19 @@ function createIpcAdapter() {
5875
5897
  async chatStream(messages, conversationId, onEvent, options) {
5876
5898
  const streamId = crypto.randomUUID();
5877
5899
  streamCallbacks.set(streamId, (event) => {
5878
- if (event.type === "start" || event.type === "interrupt") {
5900
+ if (event.type === "start") {
5879
5901
  currentRunId = event.run_id || null;
5880
5902
  }
5903
+ if (event.type === "interrupt") {
5904
+ const interruptRunId = event.run_id;
5905
+ if (interruptRunId) {
5906
+ currentRunId = interruptRunId;
5907
+ }
5908
+ }
5881
5909
  if (event.type === "done" || event.type === "cancelled" || event.type === "error") {
5882
5910
  currentRunId = null;
5883
5911
  }
5884
- onEvent(event);
5912
+ onEvent({ ...event, stream_id: streamId });
5885
5913
  });
5886
5914
  try {
5887
5915
  await api.stream({
@@ -5903,8 +5931,12 @@ function createIpcAdapter() {
5903
5931
  throw e;
5904
5932
  }
5905
5933
  },
5906
- sendHitlResponse(response, runId) {
5907
- api.sendHitlResponse({ response, runId: runId || currentRunId || void 0 });
5934
+ sendHitlResponse(response, runId, streamId) {
5935
+ api.sendHitlResponse({
5936
+ response,
5937
+ runId: runId || currentRunId || void 0,
5938
+ streamId: streamId || void 0
5939
+ });
5908
5940
  },
5909
5941
  cleanup() {
5910
5942
  streamEventCleanup?.();
@@ -6278,8 +6310,10 @@ function processStreamEvents(stream, onEvent, sdk, setCurrentRunId) {
6278
6310
  default: {
6279
6311
  const evt = event;
6280
6312
  if (evt.type === "interrupt") {
6281
- currentRunId = evt.run_id || null;
6282
- setCurrentRunId(evt.run_id || null);
6313
+ if (evt.run_id) {
6314
+ currentRunId = evt.run_id;
6315
+ setCurrentRunId(evt.run_id);
6316
+ }
6283
6317
  onEvent({
6284
6318
  type: "interrupt",
6285
6319
  interrupt_type: evt.interrupt_type || "",
@@ -7946,6 +7980,23 @@ var defaultStrings = {
7946
7980
  approvalRequest: "Approval Request",
7947
7981
  inputRequest: "Input Request"
7948
7982
  };
7983
+ var defaultZhStrings = {
7984
+ approve: "\u6279\u51C6",
7985
+ reject: "\u62D2\u7EDD",
7986
+ submit: "\u63D0\u4EA4",
7987
+ cancel: "\u53D6\u6D88",
7988
+ rememberChoice: "\u8BB0\u4F4F\u672C\u6B21\u9009\u62E9",
7989
+ requiredField: "\u6B64\u9879\u5FC5\u586B",
7990
+ timeoutIn: "\u8D85\u65F6\u5269\u4F59",
7991
+ seconds: "\u79D2",
7992
+ executeTool: "\u6267\u884C",
7993
+ toolLabel: "\u5DE5\u5177",
7994
+ argsLabel: "\u53C2\u6570",
7995
+ defaultPrefix: "\u9ED8\u8BA4",
7996
+ enterResponse: "\u8BF7\u8F93\u5165\u4F60\u7684\u56DE\u590D...",
7997
+ approvalRequest: "\u9700\u8981\u5BA1\u6279",
7998
+ inputRequest: "\u9700\u8981\u8F93\u5165"
7999
+ };
7949
8000
  var riskColors = {
7950
8001
  low: {
7951
8002
  bg: "bg-blue-500/10",
@@ -7975,9 +8026,37 @@ var HitlCard = memo8(function HitlCard2({
7975
8026
  isDarkMode = false,
7976
8027
  strings = {}
7977
8028
  }) {
7978
- const t = { ...defaultStrings, ...strings };
8029
+ const mergedStrings = { ...defaultStrings, ...strings };
7979
8030
  const isApproval = interrupt.type === "approval_request";
7980
8031
  const isUserInput = interrupt.type === "user_input_request";
8032
+ const cjkRegex = /[\u3400-\u9fff]/;
8033
+ const hasCjkContent = cjkRegex.test(
8034
+ [
8035
+ interrupt.question || "",
8036
+ interrupt.context || "",
8037
+ interrupt.reason || "",
8038
+ interrupt.tool || "",
8039
+ ...interrupt.options || []
8040
+ ].join(" ")
8041
+ );
8042
+ const t = hasCjkContent ? {
8043
+ ...mergedStrings,
8044
+ approve: mergedStrings.approve === defaultStrings.approve ? defaultZhStrings.approve : mergedStrings.approve,
8045
+ reject: mergedStrings.reject === defaultStrings.reject ? defaultZhStrings.reject : mergedStrings.reject,
8046
+ submit: mergedStrings.submit === defaultStrings.submit ? defaultZhStrings.submit : mergedStrings.submit,
8047
+ cancel: mergedStrings.cancel === defaultStrings.cancel ? defaultZhStrings.cancel : mergedStrings.cancel,
8048
+ rememberChoice: mergedStrings.rememberChoice === defaultStrings.rememberChoice ? defaultZhStrings.rememberChoice : mergedStrings.rememberChoice,
8049
+ requiredField: mergedStrings.requiredField === defaultStrings.requiredField ? defaultZhStrings.requiredField : mergedStrings.requiredField,
8050
+ timeoutIn: mergedStrings.timeoutIn === defaultStrings.timeoutIn ? defaultZhStrings.timeoutIn : mergedStrings.timeoutIn,
8051
+ seconds: mergedStrings.seconds === defaultStrings.seconds ? defaultZhStrings.seconds : mergedStrings.seconds,
8052
+ executeTool: mergedStrings.executeTool === defaultStrings.executeTool ? defaultZhStrings.executeTool : mergedStrings.executeTool,
8053
+ toolLabel: mergedStrings.toolLabel === defaultStrings.toolLabel ? defaultZhStrings.toolLabel : mergedStrings.toolLabel,
8054
+ argsLabel: mergedStrings.argsLabel === defaultStrings.argsLabel ? defaultZhStrings.argsLabel : mergedStrings.argsLabel,
8055
+ defaultPrefix: mergedStrings.defaultPrefix === defaultStrings.defaultPrefix ? defaultZhStrings.defaultPrefix : mergedStrings.defaultPrefix,
8056
+ enterResponse: mergedStrings.enterResponse === defaultStrings.enterResponse ? defaultZhStrings.enterResponse : mergedStrings.enterResponse,
8057
+ approvalRequest: mergedStrings.approvalRequest === defaultStrings.approvalRequest ? defaultZhStrings.approvalRequest : mergedStrings.approvalRequest,
8058
+ inputRequest: mergedStrings.inputRequest === defaultStrings.inputRequest ? defaultZhStrings.inputRequest : mergedStrings.inputRequest
8059
+ } : mergedStrings;
7981
8060
  const [answer, setAnswer] = useState15(interrupt.default || "");
7982
8061
  const [selectedIndices, setSelectedIndices] = useState15([]);
7983
8062
  const [isComposing, setIsComposing] = useState15(false);
@@ -8085,7 +8164,38 @@ var HitlCard = memo8(function HitlCard2({
8085
8164
  color: isDarkMode ? "#dbeafe" : "#1d4ed8"
8086
8165
  };
8087
8166
  const cancelButtonClass = isDarkMode ? "flex-1 rounded-lg border border-zinc-500 bg-zinc-700/80 px-3 py-2 text-sm font-semibold text-zinc-100 transition-colors hover:bg-zinc-600" : "flex-1 rounded-lg border border-zinc-300 bg-zinc-100 px-3 py-2 text-sm font-semibold text-zinc-900 transition-colors hover:bg-zinc-200";
8088
- const submitButtonClass = `flex-1 rounded-lg px-3 py-2 text-sm font-semibold ${isApproval && riskLevel === "high" ? "bg-red-600 hover:bg-red-700" : "bg-blue-600 hover:bg-blue-700"} text-white shadow-sm transition-colors disabled:cursor-not-allowed disabled:opacity-45`;
8167
+ const isSubmitDisabled = isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0);
8168
+ const submitButtonClass = `flex-1 rounded-lg border px-3 py-2 text-sm font-semibold transition-colors ${isSubmitDisabled ? "cursor-not-allowed" : "hover:brightness-105"}`;
8169
+ const cancelButtonStyle = isDarkMode ? {
8170
+ background: "rgba(63, 63, 70, 0.82)",
8171
+ color: "#f4f4f5",
8172
+ borderColor: "rgba(161, 161, 170, 0.6)"
8173
+ } : {
8174
+ background: "#f4f4f5",
8175
+ color: "#18181b",
8176
+ borderColor: "rgba(113, 113, 122, 0.45)"
8177
+ };
8178
+ const submitButtonStyle = isSubmitDisabled ? isDarkMode ? {
8179
+ background: "rgba(82, 82, 91, 0.72)",
8180
+ color: "#d4d4d8",
8181
+ borderColor: "rgba(161, 161, 170, 0.35)",
8182
+ boxShadow: "none"
8183
+ } : {
8184
+ background: "rgba(228, 228, 231, 0.98)",
8185
+ color: "#52525b",
8186
+ borderColor: "rgba(161, 161, 170, 0.42)",
8187
+ boxShadow: "none"
8188
+ } : isApproval && riskLevel === "high" ? {
8189
+ background: "#dc2626",
8190
+ color: "#ffffff",
8191
+ borderColor: "rgba(220, 38, 38, 0.9)",
8192
+ boxShadow: "0 2px 8px rgba(220, 38, 38, 0.35)"
8193
+ } : {
8194
+ background: "var(--chat-accent, #2563eb)",
8195
+ color: "#ffffff",
8196
+ borderColor: "transparent",
8197
+ boxShadow: "0 2px 10px rgba(37, 99, 235, 0.3)"
8198
+ };
8089
8199
  return /* @__PURE__ */ jsxs6(
8090
8200
  "div",
8091
8201
  {
@@ -8202,6 +8312,7 @@ var HitlCard = memo8(function HitlCard2({
8202
8312
  {
8203
8313
  onClick: handleCancel,
8204
8314
  className: cancelButtonClass,
8315
+ style: cancelButtonStyle,
8205
8316
  children: isApproval ? t.reject : t.cancel
8206
8317
  }
8207
8318
  ),
@@ -8209,8 +8320,9 @@ var HitlCard = memo8(function HitlCard2({
8209
8320
  "button",
8210
8321
  {
8211
8322
  onClick: handleSubmit,
8212
- disabled: isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0),
8323
+ disabled: isSubmitDisabled,
8213
8324
  className: submitButtonClass,
8325
+ style: submitButtonStyle,
8214
8326
  children: isApproval ? t.approve : t.submit
8215
8327
  }
8216
8328
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yushaw/sanqian-chat",
3
- "version": "0.2.24",
3
+ "version": "0.2.27",
4
4
  "description": "Floating chat window SDK for Sanqian AI Assistant",
5
5
  "main": "./dist/main/index.js",
6
6
  "types": "./dist/main/index.d.ts",