dsh-multi-chat 0.6.6 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -12
- package/plugin/dsh-client-ui-multi-wall/lib/client.js +19 -17
- package/plugin/dsh-client-ui-multi-wall/lib/index.js +60 -25
- package/plugin/dsh-client-ui-multi-wall/lib/types/client/locales.d.ts +4 -0
- package/plugin/dsh-client-ui-multi-wall/lib/types/client/wall-injected.d.ts +7 -3
- package/plugin/dsh-client-ui-multi-wall/lib/types/index.d.ts +14 -0
- package/plugin/dsh-client-ui-multi-wall/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-multi-chat",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "DSH
|
|
3
|
+
"version": "0.6.8",
|
|
4
|
+
"description": "DSH 多对话:在官�?DeepSeek Harness Web 界面里以多窗口网格并排运行、监控多个对话实例,一屏盯住所�?Agent 的实时进度;内置带口令认证的局域网网关,手�?平板也能看。npx dsh-multi-chat install|start|stop|gateway",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "plugin/dsh-client-ui-multi-wall/lib/index.js",
|
|
@@ -20,16 +20,6 @@
|
|
|
20
20
|
"bundle": {
|
|
21
21
|
"patch": "./cordis.patch.yml"
|
|
22
22
|
},
|
|
23
|
-
"client": {
|
|
24
|
-
"platform": "web",
|
|
25
|
-
"inject": [
|
|
26
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
27
|
-
"@deepseek-ai/dsh-client-locale",
|
|
28
|
-
"@deepseek-ai/dsh-client-ui-layout",
|
|
29
|
-
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
30
|
-
"@deepseek-ai/dsh-client-ui-primitives"
|
|
31
|
-
]
|
|
32
|
-
},
|
|
33
23
|
"marketplace": {
|
|
34
24
|
"profiles": ["web"],
|
|
35
25
|
"requiresRestart": true,
|
|
@@ -21,6 +21,7 @@ window.__ModuleLoader__.load({
|
|
|
21
21
|
"scan.from": "起始端口",
|
|
22
22
|
"scan.to": "结束端口",
|
|
23
23
|
"create": "新建窗口",
|
|
24
|
+
"create.pending": "启动中…",
|
|
24
25
|
"create.done": "已创建窗口 :{port}",
|
|
25
26
|
"create.failed": "创建失败:{error}",
|
|
26
27
|
"create.unknown": "未知原因",
|
|
@@ -67,6 +68,7 @@ window.__ModuleLoader__.load({
|
|
|
67
68
|
"scan.from": "Start port",
|
|
68
69
|
"scan.to": "End port",
|
|
69
70
|
"create": "New window",
|
|
71
|
+
"create.pending": "Starting…",
|
|
70
72
|
"create.done": "Created window :{port}",
|
|
71
73
|
"create.failed": "Create failed: {error}",
|
|
72
74
|
"create.unknown": "unknown reason",
|
|
@@ -242,27 +244,27 @@ window.__ModuleLoader__.load({
|
|
|
242
244
|
document.head.appendChild(tag);
|
|
243
245
|
}
|
|
244
246
|
var WallView_module_css_default = {
|
|
247
|
+
"empty": "ouhzTG_empty",
|
|
245
248
|
"paneActions": "ouhzTG_paneActions",
|
|
246
|
-
"
|
|
249
|
+
"paneHead": "ouhzTG_paneHead",
|
|
247
250
|
"wall": "ouhzTG_wall",
|
|
248
|
-
"action": "ouhzTG_action",
|
|
249
|
-
"danger": "ouhzTG_danger",
|
|
250
|
-
"paneBody": "ouhzTG_paneBody",
|
|
251
|
-
"title": "ouhzTG_title",
|
|
252
|
-
"hint": "ouhzTG_hint",
|
|
253
|
-
"empty": "ouhzTG_empty",
|
|
254
|
-
"zoomed": "ouhzTG_zoomed",
|
|
255
251
|
"field": "ouhzTG_field",
|
|
256
|
-
"paneHead": "ouhzTG_paneHead",
|
|
257
252
|
"status": "ouhzTG_status",
|
|
258
|
-
"
|
|
259
|
-
"grid": "ouhzTG_grid",
|
|
253
|
+
"confirm": "ouhzTG_confirm",
|
|
260
254
|
"dot": "ouhzTG_dot",
|
|
255
|
+
"title": "ouhzTG_title",
|
|
256
|
+
"linkText": "ouhzTG_linkText",
|
|
257
|
+
"danger": "ouhzTG_danger",
|
|
258
|
+
"paneBody": "ouhzTG_paneBody",
|
|
259
|
+
"controls": "ouhzTG_controls",
|
|
260
|
+
"action": "ouhzTG_action",
|
|
261
261
|
"pane": "ouhzTG_pane",
|
|
262
|
-
"
|
|
262
|
+
"grid": "ouhzTG_grid",
|
|
263
|
+
"hint": "ouhzTG_hint",
|
|
263
264
|
"toolbar": "ouhzTG_toolbar",
|
|
264
|
-
"
|
|
265
|
-
"
|
|
265
|
+
"linkBar": "ouhzTG_linkBar",
|
|
266
|
+
"paneTitle": "ouhzTG_paneTitle",
|
|
267
|
+
"zoomed": "ouhzTG_zoomed"
|
|
266
268
|
};
|
|
267
269
|
//#endregion
|
|
268
270
|
//#region src/client/WallView.tsx
|
|
@@ -552,7 +554,7 @@ window.__ModuleLoader__.load({
|
|
|
552
554
|
onClick: () => {
|
|
553
555
|
handleCreate();
|
|
554
556
|
},
|
|
555
|
-
children: t("create")
|
|
557
|
+
children: creating ? t("create.pending") : t("create")
|
|
556
558
|
}),
|
|
557
559
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
558
560
|
open: colsMenuOpen,
|
|
@@ -681,8 +683,8 @@ window.__ModuleLoader__.load({
|
|
|
681
683
|
document.head.appendChild(tag);
|
|
682
684
|
}
|
|
683
685
|
var WallToggle_module_css_default = {
|
|
684
|
-
"
|
|
685
|
-
"
|
|
686
|
+
"label": "bmjS6q_label",
|
|
687
|
+
"row": "bmjS6q_row"
|
|
686
688
|
};
|
|
687
689
|
//#endregion
|
|
688
690
|
//#region src/client/WallToggle.tsx
|
|
@@ -692,43 +692,74 @@ function resolveLauncher() {
|
|
|
692
692
|
};
|
|
693
693
|
}
|
|
694
694
|
/**
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
695
|
+
* Collect every distinct local TCP port that is listening, in ONE command
|
|
696
|
+
* (not one `netstat`/`lsof` per candidate, which the old free-port scan ran
|
|
697
|
+
* sequentially and could take many seconds on slow Windows boxes). Windows
|
|
698
|
+
* parses `netstat`; POSIX parses `lsof` `(LISTEN)` lines.
|
|
699
|
+
* @returns the set of busy ports.
|
|
698
700
|
*/
|
|
699
|
-
async function
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
701
|
+
async function listeningPorts() {
|
|
702
|
+
const set = /* @__PURE__ */ new Set();
|
|
703
|
+
if (process.platform === "win32") {
|
|
704
|
+
const stdout = await execStdout("netstat", [
|
|
705
|
+
"-ano",
|
|
706
|
+
"-p",
|
|
707
|
+
"tcp"
|
|
708
|
+
]);
|
|
709
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
710
|
+
const m = /^\s*TCP\s+([0-9.]+|\*|\[::\]):(\d+)\s+\S+\s+LISTENING\s+(\d+)\s*$/.exec(line);
|
|
711
|
+
if (m !== null) set.add(Number(m[2]));
|
|
712
|
+
}
|
|
713
|
+
return set;
|
|
714
|
+
}
|
|
715
|
+
const stdout = await execStdout("lsof", [
|
|
716
|
+
"-nP",
|
|
717
|
+
"-iTCP",
|
|
718
|
+
"-sTCP:LISTEN"
|
|
719
|
+
]);
|
|
720
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
721
|
+
const m = /:(\d+)\s+\(LISTEN\)\s*$/.exec(line.trim());
|
|
722
|
+
if (m !== null) set.add(Number(m[1]));
|
|
704
723
|
}
|
|
724
|
+
return set;
|
|
705
725
|
}
|
|
706
726
|
/**
|
|
707
727
|
* Pick the first free port in [lo, hi] that is neither the serving port nor
|
|
708
|
-
* already listening.
|
|
728
|
+
* already listening. The busy set is resolved once (a single command), then
|
|
729
|
+
* scanned in memory.
|
|
709
730
|
* @param lo - first port of the range.
|
|
710
731
|
* @param hi - last port of the range.
|
|
711
732
|
* @param selfPort - the port serving this wall (never chosen).
|
|
712
733
|
* @returns a free port, or undefined when the range is exhausted.
|
|
713
734
|
*/
|
|
714
735
|
async function pickFreePort(lo, hi, selfPort) {
|
|
736
|
+
const busy = await listeningPorts();
|
|
715
737
|
for (let port = lo; port <= hi; port++) {
|
|
716
738
|
if (port === selfPort) continue;
|
|
717
|
-
if (
|
|
739
|
+
if (busy.has(port)) continue;
|
|
718
740
|
return port;
|
|
719
741
|
}
|
|
720
742
|
}
|
|
721
743
|
/**
|
|
722
|
-
* Spawn a new `dsh web` instance on a port and
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
744
|
+
* Spawn a new `dsh web` instance on a port and decide quickly whether it is
|
|
745
|
+
* viable. Detached so it outlives this process. This is intentionally NOT a
|
|
746
|
+
* full readiness gate: the wall polls liveness itself, so the create response
|
|
747
|
+
* returns fast and a still-booting instance simply shows a pane that lights up
|
|
748
|
+
* when the server finishes. A short bounded wait still catches immediate
|
|
749
|
+
* spawn failures (bad bin, ENOENT) and very fast boots.
|
|
750
|
+
*
|
|
751
|
+
* On a genuine launch failure the child is killed so no orphan lingers; on a
|
|
752
|
+
* slow-but-viable start the deadline returns ok:true and the child keeps
|
|
753
|
+
* booting in the background (the pane already mounted, liveness confirms when
|
|
754
|
+
* alive). The child's stderr is captured and quoted into every failure so a
|
|
755
|
+
* crash or a bad bin surfaces a concrete reason instead of a bare timeout.
|
|
726
756
|
* @param launcher - how to spawn the dsh CLI.
|
|
727
757
|
* @param port - the port for the new instance.
|
|
728
|
-
* @param timeoutMs - how long to wait
|
|
758
|
+
* @param timeoutMs - how long to wait before handing back ok:true.
|
|
759
|
+
* @param pollMs - readiness probe interval while waiting.
|
|
729
760
|
* @returns ok plus the port, or ok:false with a reason.
|
|
730
761
|
*/
|
|
731
|
-
async function startInstance(launcher, port, timeoutMs =
|
|
762
|
+
async function startInstance(launcher, port, timeoutMs = 3e3, pollMs = 300) {
|
|
732
763
|
const child = spawn(launcher.file, [...launcher.args, String(port)], {
|
|
733
764
|
detached: true,
|
|
734
765
|
stdio: [
|
|
@@ -755,11 +786,16 @@ async function startInstance(launcher, port, timeoutMs = 2e4) {
|
|
|
755
786
|
};
|
|
756
787
|
const deadline = Date.now() + timeoutMs;
|
|
757
788
|
for (;;) {
|
|
758
|
-
if (spawnFailure.error !== null)
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
789
|
+
if (spawnFailure.error !== null) {
|
|
790
|
+
try {
|
|
791
|
+
child.kill();
|
|
792
|
+
} catch {}
|
|
793
|
+
return {
|
|
794
|
+
ok: false,
|
|
795
|
+
port,
|
|
796
|
+
error: `new instance failed to start: ${spawnFailure.error.message}`
|
|
797
|
+
};
|
|
798
|
+
}
|
|
763
799
|
if (child.exitCode !== null) return {
|
|
764
800
|
ok: false,
|
|
765
801
|
port,
|
|
@@ -770,11 +806,10 @@ async function startInstance(launcher, port, timeoutMs = 2e4) {
|
|
|
770
806
|
port
|
|
771
807
|
};
|
|
772
808
|
if (Date.now() > deadline) return {
|
|
773
|
-
ok:
|
|
774
|
-
port
|
|
775
|
-
error: `instance did not become ready in time${detail()}`
|
|
809
|
+
ok: true,
|
|
810
|
+
port
|
|
776
811
|
};
|
|
777
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
812
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
778
813
|
}
|
|
779
814
|
}
|
|
780
815
|
/**
|
|
@@ -10,6 +10,7 @@ export declare const zh: {
|
|
|
10
10
|
'scan.from': string;
|
|
11
11
|
'scan.to': string;
|
|
12
12
|
create: string;
|
|
13
|
+
'create.pending': string;
|
|
13
14
|
'create.done': string;
|
|
14
15
|
'create.failed': string;
|
|
15
16
|
'create.unknown': string;
|
|
@@ -24,6 +25,7 @@ export declare const zh: {
|
|
|
24
25
|
'link.hint': string;
|
|
25
26
|
'link.reachable': string;
|
|
26
27
|
'link.unreachable': string;
|
|
28
|
+
'link.token': string;
|
|
27
29
|
columns: string;
|
|
28
30
|
'columns.auto': string;
|
|
29
31
|
refresh: string;
|
|
@@ -57,6 +59,7 @@ export declare const en: {
|
|
|
57
59
|
'scan.from': string;
|
|
58
60
|
'scan.to': string;
|
|
59
61
|
create: string;
|
|
62
|
+
'create.pending': string;
|
|
60
63
|
'create.done': string;
|
|
61
64
|
'create.failed': string;
|
|
62
65
|
'create.unknown': string;
|
|
@@ -71,6 +74,7 @@ export declare const en: {
|
|
|
71
74
|
'link.hint': string;
|
|
72
75
|
'link.reachable': string;
|
|
73
76
|
'link.unreachable': string;
|
|
77
|
+
'link.token': string;
|
|
74
78
|
columns: string;
|
|
75
79
|
'columns.auto': string;
|
|
76
80
|
refresh: string;
|
|
@@ -24,11 +24,15 @@ export interface CreateRow {
|
|
|
24
24
|
export interface LinkRow {
|
|
25
25
|
port: number;
|
|
26
26
|
host: string;
|
|
27
|
-
/** LAN URLs (`http://<lan-ip>:<port>/`), possibly empty. */
|
|
27
|
+
/** LAN URLs (`http://<lan-ip>:<gateway-port>/`), possibly empty. */
|
|
28
28
|
lan: string[];
|
|
29
|
-
/** Whether the instance is reachable off-loopback (
|
|
29
|
+
/** Whether the instance is reachable off-loopback (gateway running). */
|
|
30
30
|
reachable: boolean;
|
|
31
|
-
/**
|
|
31
|
+
/** The inline gateway's listen port (when started). */
|
|
32
|
+
gatewayPort?: number;
|
|
33
|
+
/** The login token (generated or configured). */
|
|
34
|
+
token?: string;
|
|
35
|
+
/** Human hint when not reachable. */
|
|
32
36
|
hint?: string;
|
|
33
37
|
}
|
|
34
38
|
/** The inject face delivered to the wall view registration. */
|
|
@@ -26,6 +26,16 @@ export interface MultiWallConfig {
|
|
|
26
26
|
* answers `{ lan: [publicUrl + '/'], reachable: true }`.
|
|
27
27
|
*/
|
|
28
28
|
publicUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Gateway listen port for the inline phone-access gateway. `0` (default)
|
|
31
|
+
* means `targetPort + 5000`.
|
|
32
|
+
*/
|
|
33
|
+
gatewayPort?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Optional fixed login token for the inline gateway. Empty (default) means
|
|
36
|
+
* a random token is generated per gateway start (returned by /multi/api/link).
|
|
37
|
+
*/
|
|
38
|
+
gatewayToken?: string;
|
|
29
39
|
}
|
|
30
40
|
/** Schema-validated config (the Loader resolves defaults for absent keys). */
|
|
31
41
|
export declare const Config: z<Schemastery.ObjectS<{
|
|
@@ -33,11 +43,15 @@ export declare const Config: z<Schemastery.ObjectS<{
|
|
|
33
43
|
scanTo: z<number, number>;
|
|
34
44
|
ports: z<number[], number[]>;
|
|
35
45
|
publicUrl: z<string, string>;
|
|
46
|
+
gatewayPort: z<number, number>;
|
|
47
|
+
gatewayToken: z<string, string>;
|
|
36
48
|
}>, Schemastery.ObjectT<{
|
|
37
49
|
scanFrom: z<number, number>;
|
|
38
50
|
scanTo: z<number, number>;
|
|
39
51
|
ports: z<number[], number[]>;
|
|
40
52
|
publicUrl: z<string, string>;
|
|
53
|
+
gatewayPort: z<number, number>;
|
|
54
|
+
gatewayToken: z<string, string>;
|
|
41
55
|
}>>;
|
|
42
56
|
/** One stop result row from /multi/api/stop. */
|
|
43
57
|
export interface StopRow {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-multi-wall",
|
|
3
3
|
"description": "Multi-window wall surface: a sidebar footer action that opens a full-screen wall of every running DSH instance (one pane per port), inside the official web GUI. Additive slots only — no existing row or interaction logic is changed.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|