@yushaw/sanqian-chat 0.2.23 → 0.2.25

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,10 @@ declare class ChatPanel {
920
921
  private scheduleSaveState;
921
922
  private saveState;
922
923
  private getSdk;
924
+ /**
925
+ * Resolve HITL runId. Prefer explicit runId from renderer; fallback to latest active stream runId.
926
+ */
927
+ private resolveHitlRunId;
923
928
  /**
924
929
  * Setup session resource event forwarding from SDK to renderer
925
930
  * 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,10 @@ declare class ChatPanel {
920
921
  private scheduleSaveState;
921
922
  private saveState;
922
923
  private getSdk;
924
+ /**
925
+ * Resolve HITL runId. Prefer explicit runId from renderer; fallback to latest active stream runId.
926
+ */
927
+ private resolveHitlRunId;
923
928
  /**
924
929
  * Setup session resource event forwarding from SDK to renderer
925
930
  * Called when SDK becomes available (on connect)
@@ -399,6 +399,17 @@ var FloatingWindow = class _FloatingWindow {
399
399
  import_electron.app.whenReady().then(() => this.registerShortcut());
400
400
  }
401
401
  }
402
+ resolveHitlRunId(runId) {
403
+ if (runId) return runId;
404
+ const streams = Array.from(this.activeStreams.values());
405
+ for (let i = streams.length - 1; i >= 0; i -= 1) {
406
+ const stream = streams[i];
407
+ if (stream && stream.runId && !stream.cancelled) {
408
+ return stream.runId;
409
+ }
410
+ }
411
+ return null;
412
+ }
402
413
  /**
403
414
  * Get SDK instance from either getClient or getSdk
404
415
  */
@@ -639,7 +650,7 @@ var FloatingWindow = class _FloatingWindow {
639
650
  webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
640
651
  return;
641
652
  }
642
- const streamState = { cancelled: false, runId: null };
653
+ const streamState = { cancelled: false, runId: null, cancelSignalSent: false };
643
654
  activeInstance?.activeStreams.set(streamId, streamState);
644
655
  try {
645
656
  await sdk.ensureReady();
@@ -650,7 +661,24 @@ var FloatingWindow = class _FloatingWindow {
650
661
  { conversationId, persistHistory: true }
651
662
  );
652
663
  for await (const evt of stream) {
653
- if (streamState.cancelled) break;
664
+ const evtWithRunId = evt;
665
+ if (evtWithRunId.run_id && !streamState.runId) {
666
+ streamState.runId = evtWithRunId.run_id;
667
+ }
668
+ if (streamState.cancelled && streamState.runId && !streamState.cancelSignalSent) {
669
+ try {
670
+ sdk.cancelRun(streamState.runId);
671
+ streamState.cancelSignalSent = true;
672
+ } catch (e) {
673
+ console.warn("[FloatingWindow] Failed to cancel run:", e);
674
+ }
675
+ }
676
+ if (streamState.cancelled) {
677
+ if (streamState.cancelSignalSent) {
678
+ break;
679
+ }
680
+ continue;
681
+ }
654
682
  if (activeInstance?.options.devMode) {
655
683
  console.log("[FloatingWindow] SDK event:", evt.type, JSON.stringify(evt).slice(0, 200));
656
684
  }
@@ -702,24 +730,27 @@ var FloatingWindow = class _FloatingWindow {
702
730
  const stream = activeInstance?.activeStreams.get(params.streamId);
703
731
  if (stream) {
704
732
  stream.cancelled = true;
705
- if (stream.runId) {
733
+ if (stream.runId && !stream.cancelSignalSent) {
706
734
  const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
707
735
  if (sdk) {
708
736
  try {
709
737
  sdk.cancelRun(stream.runId);
738
+ stream.cancelSignalSent = true;
710
739
  } catch (e) {
711
740
  console.warn("[FloatingWindow] Failed to cancel run:", e);
712
741
  }
713
742
  }
714
743
  }
715
- activeInstance?.activeStreams.delete(params.streamId);
716
744
  }
717
745
  return { success: true };
718
746
  });
719
747
  import_electron.ipcMain.handle("sanqian-chat:hitlResponse", (_, params) => {
720
748
  const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
721
- if (sdk && params.runId) {
722
- sdk.sendHitlResponse(params.runId, params.response);
749
+ const runId = activeInstance?.resolveHitlRunId(params.runId) ?? null;
750
+ if (sdk && runId) {
751
+ sdk.sendHitlResponse(runId, params.response);
752
+ } else if (activeInstance?.options.devMode) {
753
+ console.warn("[FloatingWindow] HITL response dropped: missing runId");
723
754
  }
724
755
  return { success: true };
725
756
  });
@@ -1668,6 +1699,20 @@ var ChatPanel = class {
1668
1699
  const client = this.config.getClient();
1669
1700
  return client;
1670
1701
  }
1702
+ /**
1703
+ * Resolve HITL runId. Prefer explicit runId from renderer; fallback to latest active stream runId.
1704
+ */
1705
+ resolveHitlRunId(runId) {
1706
+ if (runId) return runId;
1707
+ const streams = Array.from(this.activeStreams.values());
1708
+ for (let i = streams.length - 1; i >= 0; i -= 1) {
1709
+ const stream = streams[i];
1710
+ if (stream && stream.runId && !stream.cancelled) {
1711
+ return stream.runId;
1712
+ }
1713
+ }
1714
+ return null;
1715
+ }
1671
1716
  /**
1672
1717
  * Setup session resource event forwarding from SDK to renderer
1673
1718
  * Called when SDK becomes available (on connect)
@@ -1729,7 +1774,7 @@ var ChatPanel = class {
1729
1774
  webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
1730
1775
  return;
1731
1776
  }
1732
- const streamState = { cancelled: false, runId: null };
1777
+ const streamState = { cancelled: false, runId: null, cancelSignalSent: false };
1733
1778
  activeInstance2?.activeStreams.set(streamId, streamState);
1734
1779
  try {
1735
1780
  await sdk.ensureReady();
@@ -1745,7 +1790,24 @@ var ChatPanel = class {
1745
1790
  sessionResources: sessionResources?.length ? sessionResources : void 0
1746
1791
  });
1747
1792
  for await (const evt of stream) {
1748
- if (streamState.cancelled) break;
1793
+ const evtWithRunId = evt;
1794
+ if (evtWithRunId.run_id && !streamState.runId) {
1795
+ streamState.runId = evtWithRunId.run_id;
1796
+ }
1797
+ if (streamState.cancelled && streamState.runId && !streamState.cancelSignalSent) {
1798
+ try {
1799
+ sdk.cancelRun(streamState.runId);
1800
+ streamState.cancelSignalSent = true;
1801
+ } catch (e) {
1802
+ console.warn("[ChatPanel] Failed to cancel run:", e);
1803
+ }
1804
+ }
1805
+ if (streamState.cancelled) {
1806
+ if (streamState.cancelSignalSent) {
1807
+ break;
1808
+ }
1809
+ continue;
1810
+ }
1749
1811
  if (activeInstance2?.config.devMode) {
1750
1812
  console.log("[ChatPanel] SDK event:", evt.type, JSON.stringify(evt).slice(0, 200));
1751
1813
  }
@@ -1797,24 +1859,27 @@ var ChatPanel = class {
1797
1859
  const stream = activeInstance2?.activeStreams.get(params.streamId);
1798
1860
  if (stream) {
1799
1861
  stream.cancelled = true;
1800
- if (stream.runId) {
1862
+ if (stream.runId && !stream.cancelSignalSent) {
1801
1863
  const sdk = activeInstance2?.getSdk();
1802
1864
  if (sdk) {
1803
1865
  try {
1804
1866
  sdk.cancelRun(stream.runId);
1867
+ stream.cancelSignalSent = true;
1805
1868
  } catch (e) {
1806
1869
  console.warn("[ChatPanel] Failed to cancel run:", e);
1807
1870
  }
1808
1871
  }
1809
1872
  }
1810
- activeInstance2?.activeStreams.delete(params.streamId);
1811
1873
  }
1812
1874
  return { success: true };
1813
1875
  });
1814
1876
  import_electron2.ipcMain.handle("sanqian-chat:hitlResponse", (_, params) => {
1815
1877
  const sdk = activeInstance2?.getSdk();
1816
- if (sdk && params.runId) {
1817
- sdk.sendHitlResponse(params.runId, params.response);
1878
+ const runId = activeInstance2?.resolveHitlRunId(params.runId) ?? null;
1879
+ if (sdk && runId) {
1880
+ sdk.sendHitlResponse(runId, params.response);
1881
+ } else if (activeInstance2?.config.devMode) {
1882
+ console.warn("[ChatPanel] HITL response dropped: missing runId");
1818
1883
  }
1819
1884
  return { success: true };
1820
1885
  });
@@ -368,6 +368,17 @@ var FloatingWindow = class _FloatingWindow {
368
368
  app.whenReady().then(() => this.registerShortcut());
369
369
  }
370
370
  }
371
+ resolveHitlRunId(runId) {
372
+ if (runId) return runId;
373
+ const streams = Array.from(this.activeStreams.values());
374
+ for (let i = streams.length - 1; i >= 0; i -= 1) {
375
+ const stream = streams[i];
376
+ if (stream && stream.runId && !stream.cancelled) {
377
+ return stream.runId;
378
+ }
379
+ }
380
+ return null;
381
+ }
371
382
  /**
372
383
  * Get SDK instance from either getClient or getSdk
373
384
  */
@@ -608,7 +619,7 @@ var FloatingWindow = class _FloatingWindow {
608
619
  webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
609
620
  return;
610
621
  }
611
- const streamState = { cancelled: false, runId: null };
622
+ const streamState = { cancelled: false, runId: null, cancelSignalSent: false };
612
623
  activeInstance?.activeStreams.set(streamId, streamState);
613
624
  try {
614
625
  await sdk.ensureReady();
@@ -619,7 +630,24 @@ var FloatingWindow = class _FloatingWindow {
619
630
  { conversationId, persistHistory: true }
620
631
  );
621
632
  for await (const evt of stream) {
622
- if (streamState.cancelled) break;
633
+ const evtWithRunId = evt;
634
+ if (evtWithRunId.run_id && !streamState.runId) {
635
+ streamState.runId = evtWithRunId.run_id;
636
+ }
637
+ if (streamState.cancelled && streamState.runId && !streamState.cancelSignalSent) {
638
+ try {
639
+ sdk.cancelRun(streamState.runId);
640
+ streamState.cancelSignalSent = true;
641
+ } catch (e) {
642
+ console.warn("[FloatingWindow] Failed to cancel run:", e);
643
+ }
644
+ }
645
+ if (streamState.cancelled) {
646
+ if (streamState.cancelSignalSent) {
647
+ break;
648
+ }
649
+ continue;
650
+ }
623
651
  if (activeInstance?.options.devMode) {
624
652
  console.log("[FloatingWindow] SDK event:", evt.type, JSON.stringify(evt).slice(0, 200));
625
653
  }
@@ -671,24 +699,27 @@ var FloatingWindow = class _FloatingWindow {
671
699
  const stream = activeInstance?.activeStreams.get(params.streamId);
672
700
  if (stream) {
673
701
  stream.cancelled = true;
674
- if (stream.runId) {
702
+ if (stream.runId && !stream.cancelSignalSent) {
675
703
  const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
676
704
  if (sdk) {
677
705
  try {
678
706
  sdk.cancelRun(stream.runId);
707
+ stream.cancelSignalSent = true;
679
708
  } catch (e) {
680
709
  console.warn("[FloatingWindow] Failed to cancel run:", e);
681
710
  }
682
711
  }
683
712
  }
684
- activeInstance?.activeStreams.delete(params.streamId);
685
713
  }
686
714
  return { success: true };
687
715
  });
688
716
  ipcMain.handle("sanqian-chat:hitlResponse", (_, params) => {
689
717
  const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
690
- if (sdk && params.runId) {
691
- sdk.sendHitlResponse(params.runId, params.response);
718
+ const runId = activeInstance?.resolveHitlRunId(params.runId) ?? null;
719
+ if (sdk && runId) {
720
+ sdk.sendHitlResponse(runId, params.response);
721
+ } else if (activeInstance?.options.devMode) {
722
+ console.warn("[FloatingWindow] HITL response dropped: missing runId");
692
723
  }
693
724
  return { success: true };
694
725
  });
@@ -1643,6 +1674,20 @@ var ChatPanel = class {
1643
1674
  const client = this.config.getClient();
1644
1675
  return client;
1645
1676
  }
1677
+ /**
1678
+ * Resolve HITL runId. Prefer explicit runId from renderer; fallback to latest active stream runId.
1679
+ */
1680
+ resolveHitlRunId(runId) {
1681
+ if (runId) return runId;
1682
+ const streams = Array.from(this.activeStreams.values());
1683
+ for (let i = streams.length - 1; i >= 0; i -= 1) {
1684
+ const stream = streams[i];
1685
+ if (stream && stream.runId && !stream.cancelled) {
1686
+ return stream.runId;
1687
+ }
1688
+ }
1689
+ return null;
1690
+ }
1646
1691
  /**
1647
1692
  * Setup session resource event forwarding from SDK to renderer
1648
1693
  * Called when SDK becomes available (on connect)
@@ -1704,7 +1749,7 @@ var ChatPanel = class {
1704
1749
  webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
1705
1750
  return;
1706
1751
  }
1707
- const streamState = { cancelled: false, runId: null };
1752
+ const streamState = { cancelled: false, runId: null, cancelSignalSent: false };
1708
1753
  activeInstance2?.activeStreams.set(streamId, streamState);
1709
1754
  try {
1710
1755
  await sdk.ensureReady();
@@ -1720,7 +1765,24 @@ var ChatPanel = class {
1720
1765
  sessionResources: sessionResources?.length ? sessionResources : void 0
1721
1766
  });
1722
1767
  for await (const evt of stream) {
1723
- if (streamState.cancelled) break;
1768
+ const evtWithRunId = evt;
1769
+ if (evtWithRunId.run_id && !streamState.runId) {
1770
+ streamState.runId = evtWithRunId.run_id;
1771
+ }
1772
+ if (streamState.cancelled && streamState.runId && !streamState.cancelSignalSent) {
1773
+ try {
1774
+ sdk.cancelRun(streamState.runId);
1775
+ streamState.cancelSignalSent = true;
1776
+ } catch (e) {
1777
+ console.warn("[ChatPanel] Failed to cancel run:", e);
1778
+ }
1779
+ }
1780
+ if (streamState.cancelled) {
1781
+ if (streamState.cancelSignalSent) {
1782
+ break;
1783
+ }
1784
+ continue;
1785
+ }
1724
1786
  if (activeInstance2?.config.devMode) {
1725
1787
  console.log("[ChatPanel] SDK event:", evt.type, JSON.stringify(evt).slice(0, 200));
1726
1788
  }
@@ -1772,24 +1834,27 @@ var ChatPanel = class {
1772
1834
  const stream = activeInstance2?.activeStreams.get(params.streamId);
1773
1835
  if (stream) {
1774
1836
  stream.cancelled = true;
1775
- if (stream.runId) {
1837
+ if (stream.runId && !stream.cancelSignalSent) {
1776
1838
  const sdk = activeInstance2?.getSdk();
1777
1839
  if (sdk) {
1778
1840
  try {
1779
1841
  sdk.cancelRun(stream.runId);
1842
+ stream.cancelSignalSent = true;
1780
1843
  } catch (e) {
1781
1844
  console.warn("[ChatPanel] Failed to cancel run:", e);
1782
1845
  }
1783
1846
  }
1784
1847
  }
1785
- activeInstance2?.activeStreams.delete(params.streamId);
1786
1848
  }
1787
1849
  return { success: true };
1788
1850
  });
1789
1851
  ipcMain2.handle("sanqian-chat:hitlResponse", (_, params) => {
1790
1852
  const sdk = activeInstance2?.getSdk();
1791
- if (sdk && params.runId) {
1792
- sdk.sendHitlResponse(params.runId, params.response);
1853
+ const runId = activeInstance2?.resolveHitlRunId(params.runId) ?? null;
1854
+ if (sdk && runId) {
1855
+ sdk.sendHitlResponse(runId, params.response);
1856
+ } else if (activeInstance2?.config.devMode) {
1857
+ console.warn("[ChatPanel] HITL response dropped: missing runId");
1793
1858
  }
1794
1859
  return { success: true };
1795
1860
  });
@@ -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,31 @@ 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
+ adapter.sendHitlResponse?.(
857
+ response,
858
+ currentRunIdRef.current ?? void 0,
859
+ pendingInterruptStreamIdRef.current ?? void 0
860
+ );
844
861
  }, [adapter]);
845
862
  const approveHitl = (0, import_react.useCallback)((remember = false) => {
846
863
  sendHitlResponse({ approved: true, remember });
847
864
  setPendingInterrupt(null);
865
+ pendingInterruptStreamIdRef.current = null;
848
866
  }, [sendHitlResponse]);
849
867
  const rejectHitl = (0, import_react.useCallback)((remember = false) => {
850
868
  sendHitlResponse({ approved: false, remember });
851
869
  setPendingInterrupt(null);
870
+ pendingInterruptStreamIdRef.current = null;
852
871
  setIsLoading(false);
853
872
  }, [sendHitlResponse]);
854
873
  const submitHitlInput = (0, import_react.useCallback)((response) => {
855
874
  sendHitlResponse(response);
856
875
  setPendingInterrupt(null);
876
+ pendingInterruptStreamIdRef.current = null;
857
877
  if (response.cancelled || response.timed_out) setIsLoading(false);
858
878
  }, [sendHitlResponse]);
859
879
  const removeSessionResource = (0, import_react.useCallback)((fullId) => {
@@ -5963,13 +5983,19 @@ function createIpcAdapter() {
5963
5983
  async chatStream(messages, conversationId, onEvent, options) {
5964
5984
  const streamId = crypto.randomUUID();
5965
5985
  streamCallbacks.set(streamId, (event) => {
5966
- if (event.type === "start" || event.type === "interrupt") {
5986
+ if (event.type === "start") {
5967
5987
  currentRunId = event.run_id || null;
5968
5988
  }
5989
+ if (event.type === "interrupt") {
5990
+ const interruptRunId = event.run_id;
5991
+ if (interruptRunId) {
5992
+ currentRunId = interruptRunId;
5993
+ }
5994
+ }
5969
5995
  if (event.type === "done" || event.type === "cancelled" || event.type === "error") {
5970
5996
  currentRunId = null;
5971
5997
  }
5972
- onEvent(event);
5998
+ onEvent({ ...event, stream_id: streamId });
5973
5999
  });
5974
6000
  try {
5975
6001
  await api.stream({
@@ -5991,8 +6017,12 @@ function createIpcAdapter() {
5991
6017
  throw e;
5992
6018
  }
5993
6019
  },
5994
- sendHitlResponse(response, runId) {
5995
- api.sendHitlResponse({ response, runId: runId || currentRunId || void 0 });
6020
+ sendHitlResponse(response, runId, streamId) {
6021
+ api.sendHitlResponse({
6022
+ response,
6023
+ runId: runId || currentRunId || void 0,
6024
+ streamId: streamId || void 0
6025
+ });
5996
6026
  },
5997
6027
  cleanup() {
5998
6028
  streamEventCleanup?.();
@@ -6366,8 +6396,10 @@ function processStreamEvents(stream, onEvent, sdk, setCurrentRunId) {
6366
6396
  default: {
6367
6397
  const evt = event;
6368
6398
  if (evt.type === "interrupt") {
6369
- currentRunId = evt.run_id || null;
6370
- setCurrentRunId(evt.run_id || null);
6399
+ if (evt.run_id) {
6400
+ currentRunId = evt.run_id;
6401
+ setCurrentRunId(evt.run_id);
6402
+ }
6371
6403
  onEvent({
6372
6404
  type: "interrupt",
6373
6405
  interrupt_type: evt.interrupt_type || "",
@@ -8018,6 +8050,23 @@ var defaultStrings = {
8018
8050
  approvalRequest: "Approval Request",
8019
8051
  inputRequest: "Input Request"
8020
8052
  };
8053
+ var defaultZhStrings = {
8054
+ approve: "\u6279\u51C6",
8055
+ reject: "\u62D2\u7EDD",
8056
+ submit: "\u63D0\u4EA4",
8057
+ cancel: "\u53D6\u6D88",
8058
+ rememberChoice: "\u8BB0\u4F4F\u672C\u6B21\u9009\u62E9",
8059
+ requiredField: "\u6B64\u9879\u5FC5\u586B",
8060
+ timeoutIn: "\u8D85\u65F6\u5269\u4F59",
8061
+ seconds: "\u79D2",
8062
+ executeTool: "\u6267\u884C",
8063
+ toolLabel: "\u5DE5\u5177",
8064
+ argsLabel: "\u53C2\u6570",
8065
+ defaultPrefix: "\u9ED8\u8BA4",
8066
+ enterResponse: "\u8BF7\u8F93\u5165\u4F60\u7684\u56DE\u590D...",
8067
+ approvalRequest: "\u9700\u8981\u5BA1\u6279",
8068
+ inputRequest: "\u9700\u8981\u8F93\u5165"
8069
+ };
8021
8070
  var riskColors = {
8022
8071
  low: {
8023
8072
  bg: "bg-blue-500/10",
@@ -8047,9 +8096,37 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8047
8096
  isDarkMode = false,
8048
8097
  strings = {}
8049
8098
  }) {
8050
- const t = { ...defaultStrings, ...strings };
8099
+ const mergedStrings = { ...defaultStrings, ...strings };
8051
8100
  const isApproval = interrupt.type === "approval_request";
8052
8101
  const isUserInput = interrupt.type === "user_input_request";
8102
+ const cjkRegex = /[\u3400-\u9fff]/;
8103
+ const hasCjkContent = cjkRegex.test(
8104
+ [
8105
+ interrupt.question || "",
8106
+ interrupt.context || "",
8107
+ interrupt.reason || "",
8108
+ interrupt.tool || "",
8109
+ ...interrupt.options || []
8110
+ ].join(" ")
8111
+ );
8112
+ const t = hasCjkContent ? {
8113
+ ...mergedStrings,
8114
+ approve: mergedStrings.approve === defaultStrings.approve ? defaultZhStrings.approve : mergedStrings.approve,
8115
+ reject: mergedStrings.reject === defaultStrings.reject ? defaultZhStrings.reject : mergedStrings.reject,
8116
+ submit: mergedStrings.submit === defaultStrings.submit ? defaultZhStrings.submit : mergedStrings.submit,
8117
+ cancel: mergedStrings.cancel === defaultStrings.cancel ? defaultZhStrings.cancel : mergedStrings.cancel,
8118
+ rememberChoice: mergedStrings.rememberChoice === defaultStrings.rememberChoice ? defaultZhStrings.rememberChoice : mergedStrings.rememberChoice,
8119
+ requiredField: mergedStrings.requiredField === defaultStrings.requiredField ? defaultZhStrings.requiredField : mergedStrings.requiredField,
8120
+ timeoutIn: mergedStrings.timeoutIn === defaultStrings.timeoutIn ? defaultZhStrings.timeoutIn : mergedStrings.timeoutIn,
8121
+ seconds: mergedStrings.seconds === defaultStrings.seconds ? defaultZhStrings.seconds : mergedStrings.seconds,
8122
+ executeTool: mergedStrings.executeTool === defaultStrings.executeTool ? defaultZhStrings.executeTool : mergedStrings.executeTool,
8123
+ toolLabel: mergedStrings.toolLabel === defaultStrings.toolLabel ? defaultZhStrings.toolLabel : mergedStrings.toolLabel,
8124
+ argsLabel: mergedStrings.argsLabel === defaultStrings.argsLabel ? defaultZhStrings.argsLabel : mergedStrings.argsLabel,
8125
+ defaultPrefix: mergedStrings.defaultPrefix === defaultStrings.defaultPrefix ? defaultZhStrings.defaultPrefix : mergedStrings.defaultPrefix,
8126
+ enterResponse: mergedStrings.enterResponse === defaultStrings.enterResponse ? defaultZhStrings.enterResponse : mergedStrings.enterResponse,
8127
+ approvalRequest: mergedStrings.approvalRequest === defaultStrings.approvalRequest ? defaultZhStrings.approvalRequest : mergedStrings.approvalRequest,
8128
+ inputRequest: mergedStrings.inputRequest === defaultStrings.inputRequest ? defaultZhStrings.inputRequest : mergedStrings.inputRequest
8129
+ } : mergedStrings;
8053
8130
  const [answer, setAnswer] = (0, import_react24.useState)(interrupt.default || "");
8054
8131
  const [selectedIndices, setSelectedIndices] = (0, import_react24.useState)([]);
8055
8132
  const [isComposing, setIsComposing] = (0, import_react24.useState)(false);
@@ -8130,15 +8207,70 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8130
8207
  }
8131
8208
  };
8132
8209
  const cardBg = isDarkMode ? "bg-zinc-800" : "bg-white";
8133
- const cardBorder = isDarkMode ? "border-zinc-700" : "border-zinc-200";
8210
+ const cardBorder = isDarkMode ? "border-zinc-600" : "border-zinc-300";
8134
8211
  const textPrimary = isDarkMode ? "text-zinc-100" : "text-zinc-900";
8135
8212
  const textSecondary = isDarkMode ? "text-zinc-400" : "text-zinc-500";
8136
8213
  const inputBg = isDarkMode ? "bg-zinc-900" : "bg-zinc-50";
8137
8214
  const inputBorder = isDarkMode ? "border-zinc-600" : "border-zinc-300";
8215
+ const optionControlStyle = {
8216
+ accentColor: "var(--chat-accent, #2563eb)",
8217
+ width: "1.125rem",
8218
+ height: "1.125rem",
8219
+ margin: 0,
8220
+ flexShrink: 0
8221
+ };
8222
+ const cardStyle = {
8223
+ background: isDarkMode ? "rgba(24, 24, 27, 0.98)" : "#ffffff",
8224
+ borderColor: isDarkMode ? "rgba(212, 212, 216, 0.2)" : "rgba(63, 63, 70, 0.22)",
8225
+ boxShadow: isDarkMode ? "0 12px 30px rgba(0, 0, 0, 0.4)" : "0 10px 24px rgba(15, 23, 42, 0.14)"
8226
+ };
8227
+ const questionStyle = {
8228
+ fontSize: "1rem",
8229
+ lineHeight: 1.55,
8230
+ letterSpacing: "0.01em"
8231
+ };
8232
+ const questionIconStyle = {
8233
+ background: isDarkMode ? "rgba(59, 130, 246, 0.26)" : "rgba(59, 130, 246, 0.16)",
8234
+ color: isDarkMode ? "#dbeafe" : "#1d4ed8"
8235
+ };
8236
+ 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";
8237
+ const isSubmitDisabled = isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0);
8238
+ const submitButtonClass = `flex-1 rounded-lg border px-3 py-2 text-sm font-semibold transition-colors ${isSubmitDisabled ? "cursor-not-allowed" : "hover:brightness-105"}`;
8239
+ const cancelButtonStyle = isDarkMode ? {
8240
+ background: "rgba(63, 63, 70, 0.82)",
8241
+ color: "#f4f4f5",
8242
+ borderColor: "rgba(161, 161, 170, 0.6)"
8243
+ } : {
8244
+ background: "#f4f4f5",
8245
+ color: "#18181b",
8246
+ borderColor: "rgba(113, 113, 122, 0.45)"
8247
+ };
8248
+ const submitButtonStyle = isSubmitDisabled ? isDarkMode ? {
8249
+ background: "rgba(82, 82, 91, 0.72)",
8250
+ color: "#d4d4d8",
8251
+ borderColor: "rgba(161, 161, 170, 0.35)",
8252
+ boxShadow: "none"
8253
+ } : {
8254
+ background: "rgba(228, 228, 231, 0.98)",
8255
+ color: "#52525b",
8256
+ borderColor: "rgba(161, 161, 170, 0.42)",
8257
+ boxShadow: "none"
8258
+ } : isApproval && riskLevel === "high" ? {
8259
+ background: "#dc2626",
8260
+ color: "#ffffff",
8261
+ borderColor: "rgba(220, 38, 38, 0.9)",
8262
+ boxShadow: "0 2px 8px rgba(220, 38, 38, 0.35)"
8263
+ } : {
8264
+ background: "var(--chat-accent, #2563eb)",
8265
+ color: "#ffffff",
8266
+ borderColor: "transparent",
8267
+ boxShadow: "0 2px 10px rgba(37, 99, 235, 0.3)"
8268
+ };
8138
8269
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8139
8270
  "div",
8140
8271
  {
8141
8272
  className: `rounded-xl border ${cardBorder} ${cardBg} p-3 shadow-sm`,
8273
+ style: cardStyle,
8142
8274
  role: "dialog",
8143
8275
  "aria-modal": "true",
8144
8276
  "aria-label": isApproval ? t.approvalRequest : t.inputRequest,
@@ -8153,7 +8285,7 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8153
8285
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "capitalize", children: riskLevel })
8154
8286
  ]
8155
8287
  }
8156
- ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-lg", children: "?" }) }),
8288
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "inline-flex h-7 w-7 items-center justify-center rounded-full text-base font-semibold", style: questionIconStyle, children: "?" }) }),
8157
8289
  timeLeft !== null && timeLeft > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: `text-xs ${textSecondary} whitespace-nowrap`, children: [
8158
8290
  t.timeoutIn,
8159
8291
  " ",
@@ -8162,7 +8294,7 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8162
8294
  ] })
8163
8295
  ] }),
8164
8296
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "space-y-2", children: [
8165
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: `text-sm font-medium ${textPrimary} break-words`, children: isApproval ? interrupt.reason || `${t.executeTool} ${interrupt.tool}?` : interrupt.question }),
8297
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: `text-sm font-semibold ${textPrimary} break-words`, style: questionStyle, children: isApproval ? interrupt.reason || `${t.executeTool} ${interrupt.tool}?` : interrupt.question }),
8166
8298
  isUserInput && interrupt.context && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: `text-xs ${textSecondary} rounded p-2 ${inputBg} break-words`, children: interrupt.context }),
8167
8299
  isApproval && interrupt.tool && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `text-xs ${textSecondary} rounded p-2 ${inputBg} space-y-1`, children: [
8168
8300
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
@@ -8182,26 +8314,31 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8182
8314
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { className: "text-[10px]", children: JSON.stringify(interrupt.args, null, 0) })
8183
8315
  ] })
8184
8316
  ] }),
8185
- isUserInput && interrupt.options && interrupt.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-1.5", children: interrupt.options.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8186
- "label",
8187
- {
8188
- className: `flex cursor-pointer items-center gap-2 rounded p-2 transition-colors ${selectedIndices.includes(index) ? isDarkMode ? "border-blue-500/50 bg-blue-500/20" : "border-blue-200 bg-blue-50" : `${inputBg} border-transparent hover:border-zinc-300`} border`,
8189
- children: [
8190
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
8191
- "input",
8192
- {
8193
- type: interrupt.multi_select ? "checkbox" : "radio",
8194
- name: "hitl-options",
8195
- checked: selectedIndices.includes(index),
8196
- onChange: () => handleOptionToggle(index),
8197
- className: "shrink-0"
8198
- }
8199
- ),
8200
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `text-sm ${textPrimary} break-words`, children: option })
8201
- ]
8202
- },
8203
- index
8204
- )) }),
8317
+ isUserInput && interrupt.options && interrupt.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "space-y-1.5", children: interrupt.options.map((option, index) => {
8318
+ const isSelected = selectedIndices.includes(index);
8319
+ const optionStyle = isSelected ? isDarkMode ? { borderColor: "rgba(96, 165, 250, 0.75)", background: "rgba(59, 130, 246, 0.24)" } : { borderColor: "rgba(37, 99, 235, 0.36)", background: "rgba(239, 246, 255, 0.98)" } : isDarkMode ? { borderColor: "rgba(113, 113, 122, 0.6)", background: "rgba(9, 9, 11, 0.84)" } : { borderColor: "rgba(113, 113, 122, 0.34)", background: "rgba(250, 250, 250, 0.96)" };
8320
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
8321
+ "label",
8322
+ {
8323
+ className: `flex cursor-pointer items-center gap-2 rounded p-2 transition-colors ${isSelected ? isDarkMode ? "border-blue-500/50 bg-blue-500/20" : "border-blue-200 bg-blue-50" : `${inputBg} border-transparent hover:border-zinc-300`} border`,
8324
+ style: optionStyle,
8325
+ children: [
8326
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
8327
+ "input",
8328
+ {
8329
+ type: interrupt.multi_select ? "checkbox" : "radio",
8330
+ name: "hitl-options",
8331
+ checked: isSelected,
8332
+ onChange: () => handleOptionToggle(index),
8333
+ style: optionControlStyle
8334
+ }
8335
+ ),
8336
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `text-sm ${textPrimary} break-words ${isSelected ? "font-semibold" : "font-medium"}`, children: option })
8337
+ ]
8338
+ },
8339
+ index
8340
+ );
8341
+ }) }),
8205
8342
  isUserInput && (!interrupt.options || interrupt.options.length === 0) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
8206
8343
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
8207
8344
  "input",
@@ -8244,8 +8381,8 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8244
8381
  "button",
8245
8382
  {
8246
8383
  onClick: handleCancel,
8247
- style: { color: isDarkMode ? "#f4f4f5" : "#18181b" },
8248
- className: "flex-1 rounded-lg border border-zinc-600 px-3 py-2 text-sm font-medium transition-colors hover:bg-zinc-700",
8384
+ className: cancelButtonClass,
8385
+ style: cancelButtonStyle,
8249
8386
  children: isApproval ? t.reject : t.cancel
8250
8387
  }
8251
8388
  ),
@@ -8253,8 +8390,9 @@ var HitlCard = (0, import_react24.memo)(function HitlCard2({
8253
8390
  "button",
8254
8391
  {
8255
8392
  onClick: handleSubmit,
8256
- disabled: isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0),
8257
- className: `flex-1 rounded-lg px-3 py-2 text-sm font-medium ${isApproval && riskLevel === "high" ? "bg-red-500 hover:bg-red-600" : "bg-blue-500 hover:bg-blue-600"} text-white transition-colors disabled:cursor-not-allowed disabled:opacity-40`,
8393
+ disabled: isSubmitDisabled,
8394
+ className: submitButtonClass,
8395
+ style: submitButtonStyle,
8258
8396
  children: isApproval ? t.approve : t.submit
8259
8397
  }
8260
8398
  )
@@ -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,31 @@ 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
+ adapter.sendHitlResponse?.(
769
+ response,
770
+ currentRunIdRef.current ?? void 0,
771
+ pendingInterruptStreamIdRef.current ?? void 0
772
+ );
756
773
  }, [adapter]);
757
774
  const approveHitl = useCallback((remember = false) => {
758
775
  sendHitlResponse({ approved: true, remember });
759
776
  setPendingInterrupt(null);
777
+ pendingInterruptStreamIdRef.current = null;
760
778
  }, [sendHitlResponse]);
761
779
  const rejectHitl = useCallback((remember = false) => {
762
780
  sendHitlResponse({ approved: false, remember });
763
781
  setPendingInterrupt(null);
782
+ pendingInterruptStreamIdRef.current = null;
764
783
  setIsLoading(false);
765
784
  }, [sendHitlResponse]);
766
785
  const submitHitlInput = useCallback((response) => {
767
786
  sendHitlResponse(response);
768
787
  setPendingInterrupt(null);
788
+ pendingInterruptStreamIdRef.current = null;
769
789
  if (response.cancelled || response.timed_out) setIsLoading(false);
770
790
  }, [sendHitlResponse]);
771
791
  const removeSessionResource = useCallback((fullId) => {
@@ -5875,13 +5895,19 @@ function createIpcAdapter() {
5875
5895
  async chatStream(messages, conversationId, onEvent, options) {
5876
5896
  const streamId = crypto.randomUUID();
5877
5897
  streamCallbacks.set(streamId, (event) => {
5878
- if (event.type === "start" || event.type === "interrupt") {
5898
+ if (event.type === "start") {
5879
5899
  currentRunId = event.run_id || null;
5880
5900
  }
5901
+ if (event.type === "interrupt") {
5902
+ const interruptRunId = event.run_id;
5903
+ if (interruptRunId) {
5904
+ currentRunId = interruptRunId;
5905
+ }
5906
+ }
5881
5907
  if (event.type === "done" || event.type === "cancelled" || event.type === "error") {
5882
5908
  currentRunId = null;
5883
5909
  }
5884
- onEvent(event);
5910
+ onEvent({ ...event, stream_id: streamId });
5885
5911
  });
5886
5912
  try {
5887
5913
  await api.stream({
@@ -5903,8 +5929,12 @@ function createIpcAdapter() {
5903
5929
  throw e;
5904
5930
  }
5905
5931
  },
5906
- sendHitlResponse(response, runId) {
5907
- api.sendHitlResponse({ response, runId: runId || currentRunId || void 0 });
5932
+ sendHitlResponse(response, runId, streamId) {
5933
+ api.sendHitlResponse({
5934
+ response,
5935
+ runId: runId || currentRunId || void 0,
5936
+ streamId: streamId || void 0
5937
+ });
5908
5938
  },
5909
5939
  cleanup() {
5910
5940
  streamEventCleanup?.();
@@ -6278,8 +6308,10 @@ function processStreamEvents(stream, onEvent, sdk, setCurrentRunId) {
6278
6308
  default: {
6279
6309
  const evt = event;
6280
6310
  if (evt.type === "interrupt") {
6281
- currentRunId = evt.run_id || null;
6282
- setCurrentRunId(evt.run_id || null);
6311
+ if (evt.run_id) {
6312
+ currentRunId = evt.run_id;
6313
+ setCurrentRunId(evt.run_id);
6314
+ }
6283
6315
  onEvent({
6284
6316
  type: "interrupt",
6285
6317
  interrupt_type: evt.interrupt_type || "",
@@ -7946,6 +7978,23 @@ var defaultStrings = {
7946
7978
  approvalRequest: "Approval Request",
7947
7979
  inputRequest: "Input Request"
7948
7980
  };
7981
+ var defaultZhStrings = {
7982
+ approve: "\u6279\u51C6",
7983
+ reject: "\u62D2\u7EDD",
7984
+ submit: "\u63D0\u4EA4",
7985
+ cancel: "\u53D6\u6D88",
7986
+ rememberChoice: "\u8BB0\u4F4F\u672C\u6B21\u9009\u62E9",
7987
+ requiredField: "\u6B64\u9879\u5FC5\u586B",
7988
+ timeoutIn: "\u8D85\u65F6\u5269\u4F59",
7989
+ seconds: "\u79D2",
7990
+ executeTool: "\u6267\u884C",
7991
+ toolLabel: "\u5DE5\u5177",
7992
+ argsLabel: "\u53C2\u6570",
7993
+ defaultPrefix: "\u9ED8\u8BA4",
7994
+ enterResponse: "\u8BF7\u8F93\u5165\u4F60\u7684\u56DE\u590D...",
7995
+ approvalRequest: "\u9700\u8981\u5BA1\u6279",
7996
+ inputRequest: "\u9700\u8981\u8F93\u5165"
7997
+ };
7949
7998
  var riskColors = {
7950
7999
  low: {
7951
8000
  bg: "bg-blue-500/10",
@@ -7975,9 +8024,37 @@ var HitlCard = memo8(function HitlCard2({
7975
8024
  isDarkMode = false,
7976
8025
  strings = {}
7977
8026
  }) {
7978
- const t = { ...defaultStrings, ...strings };
8027
+ const mergedStrings = { ...defaultStrings, ...strings };
7979
8028
  const isApproval = interrupt.type === "approval_request";
7980
8029
  const isUserInput = interrupt.type === "user_input_request";
8030
+ const cjkRegex = /[\u3400-\u9fff]/;
8031
+ const hasCjkContent = cjkRegex.test(
8032
+ [
8033
+ interrupt.question || "",
8034
+ interrupt.context || "",
8035
+ interrupt.reason || "",
8036
+ interrupt.tool || "",
8037
+ ...interrupt.options || []
8038
+ ].join(" ")
8039
+ );
8040
+ const t = hasCjkContent ? {
8041
+ ...mergedStrings,
8042
+ approve: mergedStrings.approve === defaultStrings.approve ? defaultZhStrings.approve : mergedStrings.approve,
8043
+ reject: mergedStrings.reject === defaultStrings.reject ? defaultZhStrings.reject : mergedStrings.reject,
8044
+ submit: mergedStrings.submit === defaultStrings.submit ? defaultZhStrings.submit : mergedStrings.submit,
8045
+ cancel: mergedStrings.cancel === defaultStrings.cancel ? defaultZhStrings.cancel : mergedStrings.cancel,
8046
+ rememberChoice: mergedStrings.rememberChoice === defaultStrings.rememberChoice ? defaultZhStrings.rememberChoice : mergedStrings.rememberChoice,
8047
+ requiredField: mergedStrings.requiredField === defaultStrings.requiredField ? defaultZhStrings.requiredField : mergedStrings.requiredField,
8048
+ timeoutIn: mergedStrings.timeoutIn === defaultStrings.timeoutIn ? defaultZhStrings.timeoutIn : mergedStrings.timeoutIn,
8049
+ seconds: mergedStrings.seconds === defaultStrings.seconds ? defaultZhStrings.seconds : mergedStrings.seconds,
8050
+ executeTool: mergedStrings.executeTool === defaultStrings.executeTool ? defaultZhStrings.executeTool : mergedStrings.executeTool,
8051
+ toolLabel: mergedStrings.toolLabel === defaultStrings.toolLabel ? defaultZhStrings.toolLabel : mergedStrings.toolLabel,
8052
+ argsLabel: mergedStrings.argsLabel === defaultStrings.argsLabel ? defaultZhStrings.argsLabel : mergedStrings.argsLabel,
8053
+ defaultPrefix: mergedStrings.defaultPrefix === defaultStrings.defaultPrefix ? defaultZhStrings.defaultPrefix : mergedStrings.defaultPrefix,
8054
+ enterResponse: mergedStrings.enterResponse === defaultStrings.enterResponse ? defaultZhStrings.enterResponse : mergedStrings.enterResponse,
8055
+ approvalRequest: mergedStrings.approvalRequest === defaultStrings.approvalRequest ? defaultZhStrings.approvalRequest : mergedStrings.approvalRequest,
8056
+ inputRequest: mergedStrings.inputRequest === defaultStrings.inputRequest ? defaultZhStrings.inputRequest : mergedStrings.inputRequest
8057
+ } : mergedStrings;
7981
8058
  const [answer, setAnswer] = useState15(interrupt.default || "");
7982
8059
  const [selectedIndices, setSelectedIndices] = useState15([]);
7983
8060
  const [isComposing, setIsComposing] = useState15(false);
@@ -8058,15 +8135,70 @@ var HitlCard = memo8(function HitlCard2({
8058
8135
  }
8059
8136
  };
8060
8137
  const cardBg = isDarkMode ? "bg-zinc-800" : "bg-white";
8061
- const cardBorder = isDarkMode ? "border-zinc-700" : "border-zinc-200";
8138
+ const cardBorder = isDarkMode ? "border-zinc-600" : "border-zinc-300";
8062
8139
  const textPrimary = isDarkMode ? "text-zinc-100" : "text-zinc-900";
8063
8140
  const textSecondary = isDarkMode ? "text-zinc-400" : "text-zinc-500";
8064
8141
  const inputBg = isDarkMode ? "bg-zinc-900" : "bg-zinc-50";
8065
8142
  const inputBorder = isDarkMode ? "border-zinc-600" : "border-zinc-300";
8143
+ const optionControlStyle = {
8144
+ accentColor: "var(--chat-accent, #2563eb)",
8145
+ width: "1.125rem",
8146
+ height: "1.125rem",
8147
+ margin: 0,
8148
+ flexShrink: 0
8149
+ };
8150
+ const cardStyle = {
8151
+ background: isDarkMode ? "rgba(24, 24, 27, 0.98)" : "#ffffff",
8152
+ borderColor: isDarkMode ? "rgba(212, 212, 216, 0.2)" : "rgba(63, 63, 70, 0.22)",
8153
+ boxShadow: isDarkMode ? "0 12px 30px rgba(0, 0, 0, 0.4)" : "0 10px 24px rgba(15, 23, 42, 0.14)"
8154
+ };
8155
+ const questionStyle = {
8156
+ fontSize: "1rem",
8157
+ lineHeight: 1.55,
8158
+ letterSpacing: "0.01em"
8159
+ };
8160
+ const questionIconStyle = {
8161
+ background: isDarkMode ? "rgba(59, 130, 246, 0.26)" : "rgba(59, 130, 246, 0.16)",
8162
+ color: isDarkMode ? "#dbeafe" : "#1d4ed8"
8163
+ };
8164
+ 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";
8165
+ const isSubmitDisabled = isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0);
8166
+ const submitButtonClass = `flex-1 rounded-lg border px-3 py-2 text-sm font-semibold transition-colors ${isSubmitDisabled ? "cursor-not-allowed" : "hover:brightness-105"}`;
8167
+ const cancelButtonStyle = isDarkMode ? {
8168
+ background: "rgba(63, 63, 70, 0.82)",
8169
+ color: "#f4f4f5",
8170
+ borderColor: "rgba(161, 161, 170, 0.6)"
8171
+ } : {
8172
+ background: "#f4f4f5",
8173
+ color: "#18181b",
8174
+ borderColor: "rgba(113, 113, 122, 0.45)"
8175
+ };
8176
+ const submitButtonStyle = isSubmitDisabled ? isDarkMode ? {
8177
+ background: "rgba(82, 82, 91, 0.72)",
8178
+ color: "#d4d4d8",
8179
+ borderColor: "rgba(161, 161, 170, 0.35)",
8180
+ boxShadow: "none"
8181
+ } : {
8182
+ background: "rgba(228, 228, 231, 0.98)",
8183
+ color: "#52525b",
8184
+ borderColor: "rgba(161, 161, 170, 0.42)",
8185
+ boxShadow: "none"
8186
+ } : isApproval && riskLevel === "high" ? {
8187
+ background: "#dc2626",
8188
+ color: "#ffffff",
8189
+ borderColor: "rgba(220, 38, 38, 0.9)",
8190
+ boxShadow: "0 2px 8px rgba(220, 38, 38, 0.35)"
8191
+ } : {
8192
+ background: "var(--chat-accent, #2563eb)",
8193
+ color: "#ffffff",
8194
+ borderColor: "transparent",
8195
+ boxShadow: "0 2px 10px rgba(37, 99, 235, 0.3)"
8196
+ };
8066
8197
  return /* @__PURE__ */ jsxs6(
8067
8198
  "div",
8068
8199
  {
8069
8200
  className: `rounded-xl border ${cardBorder} ${cardBg} p-3 shadow-sm`,
8201
+ style: cardStyle,
8070
8202
  role: "dialog",
8071
8203
  "aria-modal": "true",
8072
8204
  "aria-label": isApproval ? t.approvalRequest : t.inputRequest,
@@ -8081,7 +8213,7 @@ var HitlCard = memo8(function HitlCard2({
8081
8213
  /* @__PURE__ */ jsx12("span", { className: "capitalize", children: riskLevel })
8082
8214
  ]
8083
8215
  }
8084
- ) : /* @__PURE__ */ jsx12("span", { className: "text-lg", children: "?" }) }),
8216
+ ) : /* @__PURE__ */ jsx12("span", { className: "inline-flex h-7 w-7 items-center justify-center rounded-full text-base font-semibold", style: questionIconStyle, children: "?" }) }),
8085
8217
  timeLeft !== null && timeLeft > 0 && /* @__PURE__ */ jsxs6("span", { className: `text-xs ${textSecondary} whitespace-nowrap`, children: [
8086
8218
  t.timeoutIn,
8087
8219
  " ",
@@ -8090,7 +8222,7 @@ var HitlCard = memo8(function HitlCard2({
8090
8222
  ] })
8091
8223
  ] }),
8092
8224
  /* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
8093
- /* @__PURE__ */ jsx12("p", { className: `text-sm font-medium ${textPrimary} break-words`, children: isApproval ? interrupt.reason || `${t.executeTool} ${interrupt.tool}?` : interrupt.question }),
8225
+ /* @__PURE__ */ jsx12("p", { className: `text-sm font-semibold ${textPrimary} break-words`, style: questionStyle, children: isApproval ? interrupt.reason || `${t.executeTool} ${interrupt.tool}?` : interrupt.question }),
8094
8226
  isUserInput && interrupt.context && /* @__PURE__ */ jsx12("div", { className: `text-xs ${textSecondary} rounded p-2 ${inputBg} break-words`, children: interrupt.context }),
8095
8227
  isApproval && interrupt.tool && /* @__PURE__ */ jsxs6("div", { className: `text-xs ${textSecondary} rounded p-2 ${inputBg} space-y-1`, children: [
8096
8228
  /* @__PURE__ */ jsxs6("div", { children: [
@@ -8110,26 +8242,31 @@ var HitlCard = memo8(function HitlCard2({
8110
8242
  /* @__PURE__ */ jsx12("code", { className: "text-[10px]", children: JSON.stringify(interrupt.args, null, 0) })
8111
8243
  ] })
8112
8244
  ] }),
8113
- isUserInput && interrupt.options && interrupt.options.length > 0 && /* @__PURE__ */ jsx12("div", { className: "space-y-1.5", children: interrupt.options.map((option, index) => /* @__PURE__ */ jsxs6(
8114
- "label",
8115
- {
8116
- className: `flex cursor-pointer items-center gap-2 rounded p-2 transition-colors ${selectedIndices.includes(index) ? isDarkMode ? "border-blue-500/50 bg-blue-500/20" : "border-blue-200 bg-blue-50" : `${inputBg} border-transparent hover:border-zinc-300`} border`,
8117
- children: [
8118
- /* @__PURE__ */ jsx12(
8119
- "input",
8120
- {
8121
- type: interrupt.multi_select ? "checkbox" : "radio",
8122
- name: "hitl-options",
8123
- checked: selectedIndices.includes(index),
8124
- onChange: () => handleOptionToggle(index),
8125
- className: "shrink-0"
8126
- }
8127
- ),
8128
- /* @__PURE__ */ jsx12("span", { className: `text-sm ${textPrimary} break-words`, children: option })
8129
- ]
8130
- },
8131
- index
8132
- )) }),
8245
+ isUserInput && interrupt.options && interrupt.options.length > 0 && /* @__PURE__ */ jsx12("div", { className: "space-y-1.5", children: interrupt.options.map((option, index) => {
8246
+ const isSelected = selectedIndices.includes(index);
8247
+ const optionStyle = isSelected ? isDarkMode ? { borderColor: "rgba(96, 165, 250, 0.75)", background: "rgba(59, 130, 246, 0.24)" } : { borderColor: "rgba(37, 99, 235, 0.36)", background: "rgba(239, 246, 255, 0.98)" } : isDarkMode ? { borderColor: "rgba(113, 113, 122, 0.6)", background: "rgba(9, 9, 11, 0.84)" } : { borderColor: "rgba(113, 113, 122, 0.34)", background: "rgba(250, 250, 250, 0.96)" };
8248
+ return /* @__PURE__ */ jsxs6(
8249
+ "label",
8250
+ {
8251
+ className: `flex cursor-pointer items-center gap-2 rounded p-2 transition-colors ${isSelected ? isDarkMode ? "border-blue-500/50 bg-blue-500/20" : "border-blue-200 bg-blue-50" : `${inputBg} border-transparent hover:border-zinc-300`} border`,
8252
+ style: optionStyle,
8253
+ children: [
8254
+ /* @__PURE__ */ jsx12(
8255
+ "input",
8256
+ {
8257
+ type: interrupt.multi_select ? "checkbox" : "radio",
8258
+ name: "hitl-options",
8259
+ checked: isSelected,
8260
+ onChange: () => handleOptionToggle(index),
8261
+ style: optionControlStyle
8262
+ }
8263
+ ),
8264
+ /* @__PURE__ */ jsx12("span", { className: `text-sm ${textPrimary} break-words ${isSelected ? "font-semibold" : "font-medium"}`, children: option })
8265
+ ]
8266
+ },
8267
+ index
8268
+ );
8269
+ }) }),
8133
8270
  isUserInput && (!interrupt.options || interrupt.options.length === 0) && /* @__PURE__ */ jsxs6("div", { children: [
8134
8271
  /* @__PURE__ */ jsx12(
8135
8272
  "input",
@@ -8172,8 +8309,8 @@ var HitlCard = memo8(function HitlCard2({
8172
8309
  "button",
8173
8310
  {
8174
8311
  onClick: handleCancel,
8175
- style: { color: isDarkMode ? "#f4f4f5" : "#18181b" },
8176
- className: "flex-1 rounded-lg border border-zinc-600 px-3 py-2 text-sm font-medium transition-colors hover:bg-zinc-700",
8312
+ className: cancelButtonClass,
8313
+ style: cancelButtonStyle,
8177
8314
  children: isApproval ? t.reject : t.cancel
8178
8315
  }
8179
8316
  ),
@@ -8181,8 +8318,9 @@ var HitlCard = memo8(function HitlCard2({
8181
8318
  "button",
8182
8319
  {
8183
8320
  onClick: handleSubmit,
8184
- disabled: isUserInput && interrupt.required && !answer.trim() && (!interrupt.options || selectedIndices.length === 0),
8185
- className: `flex-1 rounded-lg px-3 py-2 text-sm font-medium ${isApproval && riskLevel === "high" ? "bg-red-500 hover:bg-red-600" : "bg-blue-500 hover:bg-blue-600"} text-white transition-colors disabled:cursor-not-allowed disabled:opacity-40`,
8321
+ disabled: isSubmitDisabled,
8322
+ className: submitButtonClass,
8323
+ style: submitButtonStyle,
8186
8324
  children: isApproval ? t.approve : t.submit
8187
8325
  }
8188
8326
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yushaw/sanqian-chat",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
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",