@yushaw/sanqian-chat 0.2.25 → 0.2.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.d.mts +2 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/main/index.d.mts +4 -2
- package/dist/main/index.d.ts +4 -2
- package/dist/main/index.js +167 -51
- package/dist/main/index.mjs +167 -51
- package/dist/preload/factories.d.mts +2 -1
- package/dist/preload/factories.d.ts +2 -1
- package/dist/renderer/index.d.mts +3 -2
- package/dist/renderer/index.d.ts +3 -2
- package/dist/renderer/index.js +132 -133
- package/dist/renderer/index.mjs +132 -133
- package/package.json +1 -1
package/dist/core/index.d.mts
CHANGED
|
@@ -74,7 +74,8 @@ interface ChatUiConfigSerializable {
|
|
|
74
74
|
/** Font size scale: small (13px), normal (14px), large (16px), extra-large (18px) */
|
|
75
75
|
fontSize?: ChatFontSize;
|
|
76
76
|
accentColor?: string;
|
|
77
|
-
|
|
77
|
+
/** Supports Locale plus BCP-47 variants (e.g. zh-CN, en-US) */
|
|
78
|
+
locale?: Locale | string;
|
|
78
79
|
strings?: Partial<ChatUiStrings>;
|
|
79
80
|
alwaysOnTop?: boolean;
|
|
80
81
|
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -74,7 +74,8 @@ interface ChatUiConfigSerializable {
|
|
|
74
74
|
/** Font size scale: small (13px), normal (14px), large (16px), extra-large (18px) */
|
|
75
75
|
fontSize?: ChatFontSize;
|
|
76
76
|
accentColor?: string;
|
|
77
|
-
|
|
77
|
+
/** Supports Locale plus BCP-47 variants (e.g. zh-CN, en-US) */
|
|
78
|
+
locale?: Locale | string;
|
|
78
79
|
strings?: Partial<ChatUiStrings>;
|
|
79
80
|
alwaysOnTop?: boolean;
|
|
80
81
|
}
|
package/dist/main/index.d.mts
CHANGED
|
@@ -507,7 +507,8 @@ interface ChatUiConfigSerializable {
|
|
|
507
507
|
/** Font size scale: small (13px), normal (14px), large (16px), extra-large (18px) */
|
|
508
508
|
fontSize?: ChatFontSize;
|
|
509
509
|
accentColor?: string;
|
|
510
|
-
|
|
510
|
+
/** Supports Locale plus BCP-47 variants (e.g. zh-CN, en-US) */
|
|
511
|
+
locale?: Locale | string;
|
|
511
512
|
strings?: Partial<ChatUiStrings>;
|
|
512
513
|
alwaysOnTop?: boolean;
|
|
513
514
|
}
|
|
@@ -922,7 +923,8 @@ declare class ChatPanel {
|
|
|
922
923
|
private saveState;
|
|
923
924
|
private getSdk;
|
|
924
925
|
/**
|
|
925
|
-
* Resolve HITL runId
|
|
926
|
+
* Resolve HITL runId with deterministic binding for concurrent streams.
|
|
927
|
+
* Priority: explicit runId -> streamId-bound runId -> single active stream fallback.
|
|
926
928
|
*/
|
|
927
929
|
private resolveHitlRunId;
|
|
928
930
|
/**
|
package/dist/main/index.d.ts
CHANGED
|
@@ -507,7 +507,8 @@ interface ChatUiConfigSerializable {
|
|
|
507
507
|
/** Font size scale: small (13px), normal (14px), large (16px), extra-large (18px) */
|
|
508
508
|
fontSize?: ChatFontSize;
|
|
509
509
|
accentColor?: string;
|
|
510
|
-
|
|
510
|
+
/** Supports Locale plus BCP-47 variants (e.g. zh-CN, en-US) */
|
|
511
|
+
locale?: Locale | string;
|
|
511
512
|
strings?: Partial<ChatUiStrings>;
|
|
512
513
|
alwaysOnTop?: boolean;
|
|
513
514
|
}
|
|
@@ -922,7 +923,8 @@ declare class ChatPanel {
|
|
|
922
923
|
private saveState;
|
|
923
924
|
private getSdk;
|
|
924
925
|
/**
|
|
925
|
-
* Resolve HITL runId
|
|
926
|
+
* Resolve HITL runId with deterministic binding for concurrent streams.
|
|
927
|
+
* Priority: explicit runId -> streamId-bound runId -> single active stream fallback.
|
|
926
928
|
*/
|
|
927
929
|
private resolveHitlRunId;
|
|
928
930
|
/**
|
package/dist/main/index.js
CHANGED
|
@@ -366,6 +366,65 @@ 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
|
+
function resolveOwnedHitlRunIdFromStreams(params, activeStreams, senderWebContentsId) {
|
|
394
|
+
if (params.streamId) {
|
|
395
|
+
const stream = activeStreams.get(params.streamId);
|
|
396
|
+
if (!stream || stream.cancelled) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
if (stream.ownerWebContentsId !== senderWebContentsId) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
return stream.runId ?? null;
|
|
403
|
+
}
|
|
404
|
+
if (params.runId) {
|
|
405
|
+
for (const stream of activeStreams.values()) {
|
|
406
|
+
if (stream.cancelled || !stream.runId) continue;
|
|
407
|
+
if (stream.ownerWebContentsId !== senderWebContentsId) continue;
|
|
408
|
+
if (stream.runId === params.runId) {
|
|
409
|
+
return params.runId;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const activeRunIds = /* @__PURE__ */ new Set();
|
|
415
|
+
for (const stream of activeStreams.values()) {
|
|
416
|
+
if (stream.cancelled || !stream.runId) continue;
|
|
417
|
+
if (stream.ownerWebContentsId !== senderWebContentsId) continue;
|
|
418
|
+
activeRunIds.add(stream.runId);
|
|
419
|
+
if (activeRunIds.size > 1) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const first = activeRunIds.values().next();
|
|
424
|
+
return first.done ? null : first.value;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/main/FloatingWindow.ts
|
|
369
428
|
var ipcHandlersRegistered = false;
|
|
370
429
|
var activeInstance = null;
|
|
371
430
|
var setActiveInstance = (instance) => {
|
|
@@ -399,16 +458,11 @@ var FloatingWindow = class _FloatingWindow {
|
|
|
399
458
|
import_electron.app.whenReady().then(() => this.registerShortcut());
|
|
400
459
|
}
|
|
401
460
|
}
|
|
402
|
-
resolveHitlRunId(
|
|
403
|
-
if (
|
|
404
|
-
|
|
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
|
-
}
|
|
461
|
+
resolveHitlRunId(params, senderWebContentsId) {
|
|
462
|
+
if (typeof senderWebContentsId === "number") {
|
|
463
|
+
return resolveOwnedHitlRunIdFromStreams(params, this.activeStreams, senderWebContentsId);
|
|
410
464
|
}
|
|
411
|
-
return
|
|
465
|
+
return resolveHitlRunIdFromStreams(params, this.activeStreams);
|
|
412
466
|
}
|
|
413
467
|
/**
|
|
414
468
|
* Get SDK instance from either getClient or getSdk
|
|
@@ -650,7 +704,13 @@ var FloatingWindow = class _FloatingWindow {
|
|
|
650
704
|
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
|
|
651
705
|
return;
|
|
652
706
|
}
|
|
653
|
-
const streamState = {
|
|
707
|
+
const streamState = {
|
|
708
|
+
cancelled: false,
|
|
709
|
+
runId: null,
|
|
710
|
+
cancelSignalSent: false,
|
|
711
|
+
pendingHitlResponse: null,
|
|
712
|
+
ownerWebContentsId: webContents.id
|
|
713
|
+
};
|
|
654
714
|
activeInstance?.activeStreams.set(streamId, streamState);
|
|
655
715
|
try {
|
|
656
716
|
await sdk.ensureReady();
|
|
@@ -665,6 +725,14 @@ var FloatingWindow = class _FloatingWindow {
|
|
|
665
725
|
if (evtWithRunId.run_id && !streamState.runId) {
|
|
666
726
|
streamState.runId = evtWithRunId.run_id;
|
|
667
727
|
}
|
|
728
|
+
if (streamState.pendingHitlResponse && streamState.runId) {
|
|
729
|
+
try {
|
|
730
|
+
sdk.sendHitlResponse(streamState.runId, streamState.pendingHitlResponse);
|
|
731
|
+
streamState.pendingHitlResponse = null;
|
|
732
|
+
} catch (e) {
|
|
733
|
+
console.warn("[FloatingWindow] Failed to flush queued HITL response:", e);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
668
736
|
if (streamState.cancelled && streamState.runId && !streamState.cancelSignalSent) {
|
|
669
737
|
try {
|
|
670
738
|
sdk.cancelRun(streamState.runId);
|
|
@@ -726,31 +794,50 @@ var FloatingWindow = class _FloatingWindow {
|
|
|
726
794
|
activeInstance?.activeStreams.delete(streamId);
|
|
727
795
|
}
|
|
728
796
|
});
|
|
729
|
-
import_electron.ipcMain.handle("sanqian-chat:cancelStream", (
|
|
797
|
+
import_electron.ipcMain.handle("sanqian-chat:cancelStream", (event, params) => {
|
|
730
798
|
const stream = activeInstance?.activeStreams.get(params.streamId);
|
|
731
|
-
if (stream) {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
799
|
+
if (!stream) {
|
|
800
|
+
return { success: false, error: "stream_not_found" };
|
|
801
|
+
}
|
|
802
|
+
if (stream.ownerWebContentsId !== event.sender.id) {
|
|
803
|
+
console.warn("[FloatingWindow] Rejecting cancelStream from non-owner sender");
|
|
804
|
+
return { success: false, error: "stream_not_owned_by_sender" };
|
|
805
|
+
}
|
|
806
|
+
stream.cancelled = true;
|
|
807
|
+
if (stream.runId && !stream.cancelSignalSent) {
|
|
808
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
809
|
+
if (sdk) {
|
|
810
|
+
try {
|
|
811
|
+
sdk.cancelRun(stream.runId);
|
|
812
|
+
stream.cancelSignalSent = true;
|
|
813
|
+
} catch (e) {
|
|
814
|
+
console.warn("[FloatingWindow] Failed to cancel run:", e);
|
|
742
815
|
}
|
|
743
816
|
}
|
|
744
817
|
}
|
|
745
818
|
return { success: true };
|
|
746
819
|
});
|
|
747
|
-
import_electron.ipcMain.handle("sanqian-chat:hitlResponse", (
|
|
820
|
+
import_electron.ipcMain.handle("sanqian-chat:hitlResponse", (event, params) => {
|
|
748
821
|
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
749
|
-
const
|
|
822
|
+
const senderWebContentsId = event.sender.id;
|
|
823
|
+
const runId = activeInstance?.resolveHitlRunId(
|
|
824
|
+
{ runId: params.runId, streamId: params.streamId },
|
|
825
|
+
senderWebContentsId
|
|
826
|
+
) ?? null;
|
|
750
827
|
if (sdk && runId) {
|
|
751
828
|
sdk.sendHitlResponse(runId, params.response);
|
|
829
|
+
} else if (params.streamId) {
|
|
830
|
+
const stream = activeInstance?.activeStreams.get(params.streamId);
|
|
831
|
+
if (stream && !stream.cancelled && stream.ownerWebContentsId === senderWebContentsId) {
|
|
832
|
+
stream.pendingHitlResponse = params.response;
|
|
833
|
+
if (activeInstance?.options.devMode) {
|
|
834
|
+
console.warn("[FloatingWindow] Queued HITL response while waiting for runId");
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
console.warn("[FloatingWindow] HITL response dropped: stream not found/cancelled/not-owned");
|
|
838
|
+
}
|
|
752
839
|
} else if (activeInstance?.options.devMode) {
|
|
753
|
-
console.warn("[FloatingWindow] HITL response dropped: missing runId");
|
|
840
|
+
console.warn("[FloatingWindow] HITL response dropped: missing or unauthorized runId");
|
|
754
841
|
}
|
|
755
842
|
return { success: true };
|
|
756
843
|
});
|
|
@@ -1700,18 +1787,14 @@ var ChatPanel = class {
|
|
|
1700
1787
|
return client;
|
|
1701
1788
|
}
|
|
1702
1789
|
/**
|
|
1703
|
-
* Resolve HITL runId
|
|
1790
|
+
* Resolve HITL runId with deterministic binding for concurrent streams.
|
|
1791
|
+
* Priority: explicit runId -> streamId-bound runId -> single active stream fallback.
|
|
1704
1792
|
*/
|
|
1705
|
-
resolveHitlRunId(
|
|
1706
|
-
if (
|
|
1707
|
-
|
|
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
|
-
}
|
|
1793
|
+
resolveHitlRunId(params, senderWebContentsId) {
|
|
1794
|
+
if (typeof senderWebContentsId === "number") {
|
|
1795
|
+
return resolveOwnedHitlRunIdFromStreams(params, this.activeStreams, senderWebContentsId);
|
|
1713
1796
|
}
|
|
1714
|
-
return
|
|
1797
|
+
return resolveHitlRunIdFromStreams(params, this.activeStreams);
|
|
1715
1798
|
}
|
|
1716
1799
|
/**
|
|
1717
1800
|
* Setup session resource event forwarding from SDK to renderer
|
|
@@ -1774,7 +1857,13 @@ var ChatPanel = class {
|
|
|
1774
1857
|
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
|
|
1775
1858
|
return;
|
|
1776
1859
|
}
|
|
1777
|
-
const streamState = {
|
|
1860
|
+
const streamState = {
|
|
1861
|
+
cancelled: false,
|
|
1862
|
+
runId: null,
|
|
1863
|
+
cancelSignalSent: false,
|
|
1864
|
+
pendingHitlResponse: null,
|
|
1865
|
+
ownerWebContentsId: webContents.id
|
|
1866
|
+
};
|
|
1778
1867
|
activeInstance2?.activeStreams.set(streamId, streamState);
|
|
1779
1868
|
try {
|
|
1780
1869
|
await sdk.ensureReady();
|
|
@@ -1794,6 +1883,14 @@ var ChatPanel = class {
|
|
|
1794
1883
|
if (evtWithRunId.run_id && !streamState.runId) {
|
|
1795
1884
|
streamState.runId = evtWithRunId.run_id;
|
|
1796
1885
|
}
|
|
1886
|
+
if (streamState.pendingHitlResponse && streamState.runId) {
|
|
1887
|
+
try {
|
|
1888
|
+
sdk.sendHitlResponse(streamState.runId, streamState.pendingHitlResponse);
|
|
1889
|
+
streamState.pendingHitlResponse = null;
|
|
1890
|
+
} catch (e) {
|
|
1891
|
+
console.warn("[ChatPanel] Failed to flush queued HITL response:", e);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1797
1894
|
if (streamState.cancelled && streamState.runId && !streamState.cancelSignalSent) {
|
|
1798
1895
|
try {
|
|
1799
1896
|
sdk.cancelRun(streamState.runId);
|
|
@@ -1855,31 +1952,50 @@ var ChatPanel = class {
|
|
|
1855
1952
|
activeInstance2?.activeStreams.delete(streamId);
|
|
1856
1953
|
}
|
|
1857
1954
|
});
|
|
1858
|
-
import_electron2.ipcMain.handle("sanqian-chat:cancelStream", (
|
|
1955
|
+
import_electron2.ipcMain.handle("sanqian-chat:cancelStream", (event, params) => {
|
|
1859
1956
|
const stream = activeInstance2?.activeStreams.get(params.streamId);
|
|
1860
|
-
if (stream) {
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1957
|
+
if (!stream) {
|
|
1958
|
+
return { success: false, error: "stream_not_found" };
|
|
1959
|
+
}
|
|
1960
|
+
if (stream.ownerWebContentsId !== event.sender.id) {
|
|
1961
|
+
console.warn("[ChatPanel] Rejecting cancelStream from non-owner sender");
|
|
1962
|
+
return { success: false, error: "stream_not_owned_by_sender" };
|
|
1963
|
+
}
|
|
1964
|
+
stream.cancelled = true;
|
|
1965
|
+
if (stream.runId && !stream.cancelSignalSent) {
|
|
1966
|
+
const sdk = activeInstance2?.getSdk();
|
|
1967
|
+
if (sdk) {
|
|
1968
|
+
try {
|
|
1969
|
+
sdk.cancelRun(stream.runId);
|
|
1970
|
+
stream.cancelSignalSent = true;
|
|
1971
|
+
} catch (e) {
|
|
1972
|
+
console.warn("[ChatPanel] Failed to cancel run:", e);
|
|
1871
1973
|
}
|
|
1872
1974
|
}
|
|
1873
1975
|
}
|
|
1874
1976
|
return { success: true };
|
|
1875
1977
|
});
|
|
1876
|
-
import_electron2.ipcMain.handle("sanqian-chat:hitlResponse", (
|
|
1978
|
+
import_electron2.ipcMain.handle("sanqian-chat:hitlResponse", (event, params) => {
|
|
1877
1979
|
const sdk = activeInstance2?.getSdk();
|
|
1878
|
-
const
|
|
1980
|
+
const senderWebContentsId = event.sender.id;
|
|
1981
|
+
const runId = activeInstance2?.resolveHitlRunId(
|
|
1982
|
+
{ runId: params.runId, streamId: params.streamId },
|
|
1983
|
+
senderWebContentsId
|
|
1984
|
+
) ?? null;
|
|
1879
1985
|
if (sdk && runId) {
|
|
1880
1986
|
sdk.sendHitlResponse(runId, params.response);
|
|
1987
|
+
} else if (params.streamId) {
|
|
1988
|
+
const stream = activeInstance2?.activeStreams.get(params.streamId);
|
|
1989
|
+
if (stream && !stream.cancelled && stream.ownerWebContentsId === senderWebContentsId) {
|
|
1990
|
+
stream.pendingHitlResponse = params.response;
|
|
1991
|
+
if (activeInstance2?.config.devMode) {
|
|
1992
|
+
console.warn("[ChatPanel] Queued HITL response while waiting for runId");
|
|
1993
|
+
}
|
|
1994
|
+
} else {
|
|
1995
|
+
console.warn("[ChatPanel] HITL response dropped: stream not found/cancelled/not-owned");
|
|
1996
|
+
}
|
|
1881
1997
|
} else if (activeInstance2?.config.devMode) {
|
|
1882
|
-
console.warn("[ChatPanel] HITL response dropped: missing runId");
|
|
1998
|
+
console.warn("[ChatPanel] HITL response dropped: missing or unauthorized runId");
|
|
1883
1999
|
}
|
|
1884
2000
|
return { success: true };
|
|
1885
2001
|
});
|
package/dist/main/index.mjs
CHANGED
|
@@ -335,6 +335,65 @@ 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
|
+
function resolveOwnedHitlRunIdFromStreams(params, activeStreams, senderWebContentsId) {
|
|
363
|
+
if (params.streamId) {
|
|
364
|
+
const stream = activeStreams.get(params.streamId);
|
|
365
|
+
if (!stream || stream.cancelled) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
if (stream.ownerWebContentsId !== senderWebContentsId) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
return stream.runId ?? null;
|
|
372
|
+
}
|
|
373
|
+
if (params.runId) {
|
|
374
|
+
for (const stream of activeStreams.values()) {
|
|
375
|
+
if (stream.cancelled || !stream.runId) continue;
|
|
376
|
+
if (stream.ownerWebContentsId !== senderWebContentsId) continue;
|
|
377
|
+
if (stream.runId === params.runId) {
|
|
378
|
+
return params.runId;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
const activeRunIds = /* @__PURE__ */ new Set();
|
|
384
|
+
for (const stream of activeStreams.values()) {
|
|
385
|
+
if (stream.cancelled || !stream.runId) continue;
|
|
386
|
+
if (stream.ownerWebContentsId !== senderWebContentsId) continue;
|
|
387
|
+
activeRunIds.add(stream.runId);
|
|
388
|
+
if (activeRunIds.size > 1) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const first = activeRunIds.values().next();
|
|
393
|
+
return first.done ? null : first.value;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/main/FloatingWindow.ts
|
|
338
397
|
var ipcHandlersRegistered = false;
|
|
339
398
|
var activeInstance = null;
|
|
340
399
|
var setActiveInstance = (instance) => {
|
|
@@ -368,16 +427,11 @@ var FloatingWindow = class _FloatingWindow {
|
|
|
368
427
|
app.whenReady().then(() => this.registerShortcut());
|
|
369
428
|
}
|
|
370
429
|
}
|
|
371
|
-
resolveHitlRunId(
|
|
372
|
-
if (
|
|
373
|
-
|
|
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
|
-
}
|
|
430
|
+
resolveHitlRunId(params, senderWebContentsId) {
|
|
431
|
+
if (typeof senderWebContentsId === "number") {
|
|
432
|
+
return resolveOwnedHitlRunIdFromStreams(params, this.activeStreams, senderWebContentsId);
|
|
379
433
|
}
|
|
380
|
-
return
|
|
434
|
+
return resolveHitlRunIdFromStreams(params, this.activeStreams);
|
|
381
435
|
}
|
|
382
436
|
/**
|
|
383
437
|
* Get SDK instance from either getClient or getSdk
|
|
@@ -619,7 +673,13 @@ var FloatingWindow = class _FloatingWindow {
|
|
|
619
673
|
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
|
|
620
674
|
return;
|
|
621
675
|
}
|
|
622
|
-
const streamState = {
|
|
676
|
+
const streamState = {
|
|
677
|
+
cancelled: false,
|
|
678
|
+
runId: null,
|
|
679
|
+
cancelSignalSent: false,
|
|
680
|
+
pendingHitlResponse: null,
|
|
681
|
+
ownerWebContentsId: webContents.id
|
|
682
|
+
};
|
|
623
683
|
activeInstance?.activeStreams.set(streamId, streamState);
|
|
624
684
|
try {
|
|
625
685
|
await sdk.ensureReady();
|
|
@@ -634,6 +694,14 @@ var FloatingWindow = class _FloatingWindow {
|
|
|
634
694
|
if (evtWithRunId.run_id && !streamState.runId) {
|
|
635
695
|
streamState.runId = evtWithRunId.run_id;
|
|
636
696
|
}
|
|
697
|
+
if (streamState.pendingHitlResponse && streamState.runId) {
|
|
698
|
+
try {
|
|
699
|
+
sdk.sendHitlResponse(streamState.runId, streamState.pendingHitlResponse);
|
|
700
|
+
streamState.pendingHitlResponse = null;
|
|
701
|
+
} catch (e) {
|
|
702
|
+
console.warn("[FloatingWindow] Failed to flush queued HITL response:", e);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
637
705
|
if (streamState.cancelled && streamState.runId && !streamState.cancelSignalSent) {
|
|
638
706
|
try {
|
|
639
707
|
sdk.cancelRun(streamState.runId);
|
|
@@ -695,31 +763,50 @@ var FloatingWindow = class _FloatingWindow {
|
|
|
695
763
|
activeInstance?.activeStreams.delete(streamId);
|
|
696
764
|
}
|
|
697
765
|
});
|
|
698
|
-
ipcMain.handle("sanqian-chat:cancelStream", (
|
|
766
|
+
ipcMain.handle("sanqian-chat:cancelStream", (event, params) => {
|
|
699
767
|
const stream = activeInstance?.activeStreams.get(params.streamId);
|
|
700
|
-
if (stream) {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
768
|
+
if (!stream) {
|
|
769
|
+
return { success: false, error: "stream_not_found" };
|
|
770
|
+
}
|
|
771
|
+
if (stream.ownerWebContentsId !== event.sender.id) {
|
|
772
|
+
console.warn("[FloatingWindow] Rejecting cancelStream from non-owner sender");
|
|
773
|
+
return { success: false, error: "stream_not_owned_by_sender" };
|
|
774
|
+
}
|
|
775
|
+
stream.cancelled = true;
|
|
776
|
+
if (stream.runId && !stream.cancelSignalSent) {
|
|
777
|
+
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
778
|
+
if (sdk) {
|
|
779
|
+
try {
|
|
780
|
+
sdk.cancelRun(stream.runId);
|
|
781
|
+
stream.cancelSignalSent = true;
|
|
782
|
+
} catch (e) {
|
|
783
|
+
console.warn("[FloatingWindow] Failed to cancel run:", e);
|
|
711
784
|
}
|
|
712
785
|
}
|
|
713
786
|
}
|
|
714
787
|
return { success: true };
|
|
715
788
|
});
|
|
716
|
-
ipcMain.handle("sanqian-chat:hitlResponse", (
|
|
789
|
+
ipcMain.handle("sanqian-chat:hitlResponse", (event, params) => {
|
|
717
790
|
const sdk = activeInstance ? _FloatingWindow.getSdkFromOptions(activeInstance.options) : null;
|
|
718
|
-
const
|
|
791
|
+
const senderWebContentsId = event.sender.id;
|
|
792
|
+
const runId = activeInstance?.resolveHitlRunId(
|
|
793
|
+
{ runId: params.runId, streamId: params.streamId },
|
|
794
|
+
senderWebContentsId
|
|
795
|
+
) ?? null;
|
|
719
796
|
if (sdk && runId) {
|
|
720
797
|
sdk.sendHitlResponse(runId, params.response);
|
|
798
|
+
} else if (params.streamId) {
|
|
799
|
+
const stream = activeInstance?.activeStreams.get(params.streamId);
|
|
800
|
+
if (stream && !stream.cancelled && stream.ownerWebContentsId === senderWebContentsId) {
|
|
801
|
+
stream.pendingHitlResponse = params.response;
|
|
802
|
+
if (activeInstance?.options.devMode) {
|
|
803
|
+
console.warn("[FloatingWindow] Queued HITL response while waiting for runId");
|
|
804
|
+
}
|
|
805
|
+
} else {
|
|
806
|
+
console.warn("[FloatingWindow] HITL response dropped: stream not found/cancelled/not-owned");
|
|
807
|
+
}
|
|
721
808
|
} else if (activeInstance?.options.devMode) {
|
|
722
|
-
console.warn("[FloatingWindow] HITL response dropped: missing runId");
|
|
809
|
+
console.warn("[FloatingWindow] HITL response dropped: missing or unauthorized runId");
|
|
723
810
|
}
|
|
724
811
|
return { success: true };
|
|
725
812
|
});
|
|
@@ -1675,18 +1762,14 @@ var ChatPanel = class {
|
|
|
1675
1762
|
return client;
|
|
1676
1763
|
}
|
|
1677
1764
|
/**
|
|
1678
|
-
* Resolve HITL runId
|
|
1765
|
+
* Resolve HITL runId with deterministic binding for concurrent streams.
|
|
1766
|
+
* Priority: explicit runId -> streamId-bound runId -> single active stream fallback.
|
|
1679
1767
|
*/
|
|
1680
|
-
resolveHitlRunId(
|
|
1681
|
-
if (
|
|
1682
|
-
|
|
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
|
-
}
|
|
1768
|
+
resolveHitlRunId(params, senderWebContentsId) {
|
|
1769
|
+
if (typeof senderWebContentsId === "number") {
|
|
1770
|
+
return resolveOwnedHitlRunIdFromStreams(params, this.activeStreams, senderWebContentsId);
|
|
1688
1771
|
}
|
|
1689
|
-
return
|
|
1772
|
+
return resolveHitlRunIdFromStreams(params, this.activeStreams);
|
|
1690
1773
|
}
|
|
1691
1774
|
/**
|
|
1692
1775
|
* Setup session resource event forwarding from SDK to renderer
|
|
@@ -1749,7 +1832,13 @@ var ChatPanel = class {
|
|
|
1749
1832
|
webContents.send("sanqian-chat:streamEvent", { streamId, event: { type: "error", error: "SDK or agent not ready" } });
|
|
1750
1833
|
return;
|
|
1751
1834
|
}
|
|
1752
|
-
const streamState = {
|
|
1835
|
+
const streamState = {
|
|
1836
|
+
cancelled: false,
|
|
1837
|
+
runId: null,
|
|
1838
|
+
cancelSignalSent: false,
|
|
1839
|
+
pendingHitlResponse: null,
|
|
1840
|
+
ownerWebContentsId: webContents.id
|
|
1841
|
+
};
|
|
1753
1842
|
activeInstance2?.activeStreams.set(streamId, streamState);
|
|
1754
1843
|
try {
|
|
1755
1844
|
await sdk.ensureReady();
|
|
@@ -1769,6 +1858,14 @@ var ChatPanel = class {
|
|
|
1769
1858
|
if (evtWithRunId.run_id && !streamState.runId) {
|
|
1770
1859
|
streamState.runId = evtWithRunId.run_id;
|
|
1771
1860
|
}
|
|
1861
|
+
if (streamState.pendingHitlResponse && streamState.runId) {
|
|
1862
|
+
try {
|
|
1863
|
+
sdk.sendHitlResponse(streamState.runId, streamState.pendingHitlResponse);
|
|
1864
|
+
streamState.pendingHitlResponse = null;
|
|
1865
|
+
} catch (e) {
|
|
1866
|
+
console.warn("[ChatPanel] Failed to flush queued HITL response:", e);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1772
1869
|
if (streamState.cancelled && streamState.runId && !streamState.cancelSignalSent) {
|
|
1773
1870
|
try {
|
|
1774
1871
|
sdk.cancelRun(streamState.runId);
|
|
@@ -1830,31 +1927,50 @@ var ChatPanel = class {
|
|
|
1830
1927
|
activeInstance2?.activeStreams.delete(streamId);
|
|
1831
1928
|
}
|
|
1832
1929
|
});
|
|
1833
|
-
ipcMain2.handle("sanqian-chat:cancelStream", (
|
|
1930
|
+
ipcMain2.handle("sanqian-chat:cancelStream", (event, params) => {
|
|
1834
1931
|
const stream = activeInstance2?.activeStreams.get(params.streamId);
|
|
1835
|
-
if (stream) {
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1932
|
+
if (!stream) {
|
|
1933
|
+
return { success: false, error: "stream_not_found" };
|
|
1934
|
+
}
|
|
1935
|
+
if (stream.ownerWebContentsId !== event.sender.id) {
|
|
1936
|
+
console.warn("[ChatPanel] Rejecting cancelStream from non-owner sender");
|
|
1937
|
+
return { success: false, error: "stream_not_owned_by_sender" };
|
|
1938
|
+
}
|
|
1939
|
+
stream.cancelled = true;
|
|
1940
|
+
if (stream.runId && !stream.cancelSignalSent) {
|
|
1941
|
+
const sdk = activeInstance2?.getSdk();
|
|
1942
|
+
if (sdk) {
|
|
1943
|
+
try {
|
|
1944
|
+
sdk.cancelRun(stream.runId);
|
|
1945
|
+
stream.cancelSignalSent = true;
|
|
1946
|
+
} catch (e) {
|
|
1947
|
+
console.warn("[ChatPanel] Failed to cancel run:", e);
|
|
1846
1948
|
}
|
|
1847
1949
|
}
|
|
1848
1950
|
}
|
|
1849
1951
|
return { success: true };
|
|
1850
1952
|
});
|
|
1851
|
-
ipcMain2.handle("sanqian-chat:hitlResponse", (
|
|
1953
|
+
ipcMain2.handle("sanqian-chat:hitlResponse", (event, params) => {
|
|
1852
1954
|
const sdk = activeInstance2?.getSdk();
|
|
1853
|
-
const
|
|
1955
|
+
const senderWebContentsId = event.sender.id;
|
|
1956
|
+
const runId = activeInstance2?.resolveHitlRunId(
|
|
1957
|
+
{ runId: params.runId, streamId: params.streamId },
|
|
1958
|
+
senderWebContentsId
|
|
1959
|
+
) ?? null;
|
|
1854
1960
|
if (sdk && runId) {
|
|
1855
1961
|
sdk.sendHitlResponse(runId, params.response);
|
|
1962
|
+
} else if (params.streamId) {
|
|
1963
|
+
const stream = activeInstance2?.activeStreams.get(params.streamId);
|
|
1964
|
+
if (stream && !stream.cancelled && stream.ownerWebContentsId === senderWebContentsId) {
|
|
1965
|
+
stream.pendingHitlResponse = params.response;
|
|
1966
|
+
if (activeInstance2?.config.devMode) {
|
|
1967
|
+
console.warn("[ChatPanel] Queued HITL response while waiting for runId");
|
|
1968
|
+
}
|
|
1969
|
+
} else {
|
|
1970
|
+
console.warn("[ChatPanel] HITL response dropped: stream not found/cancelled/not-owned");
|
|
1971
|
+
}
|
|
1856
1972
|
} else if (activeInstance2?.config.devMode) {
|
|
1857
|
-
console.warn("[ChatPanel] HITL response dropped: missing runId");
|
|
1973
|
+
console.warn("[ChatPanel] HITL response dropped: missing or unauthorized runId");
|
|
1858
1974
|
}
|
|
1859
1975
|
return { success: true };
|
|
1860
1976
|
});
|