dsh-session-conductor 1.3.6 → 1.3.7
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/lib/apply.js
CHANGED
|
@@ -318,6 +318,13 @@ export async function apply(ctx, config = {}) {
|
|
|
318
318
|
res.setHeader("Cache-Control", "no-store");
|
|
319
319
|
if (res.socket?.setNoDelay) res.socket.setNoDelay(true);
|
|
320
320
|
res.write(JSON.stringify({ ok: true, stream: true }) + "\n"); // 头行(前端跳过非会话行)
|
|
321
|
+
// 立即把头行推出去:冷缓存时后面要逐份解压上百份日志(实测 41~226 秒),
|
|
322
|
+
// 若头行压在缓冲里,浏览器这侧会一直「没有响应」,看起来就像接口挂了。
|
|
323
|
+
// flushHeaders/flush 老版本不一定有,取不到就跳过(写操作本身已尽量及时)。
|
|
324
|
+
try {
|
|
325
|
+
if (typeof res.flushHeaders === "function") res.flushHeaders();
|
|
326
|
+
if (typeof res.flush === "function") res.flush();
|
|
327
|
+
} catch { /* 不支持 flush 的宿主不影响主流程 */ }
|
|
321
328
|
const onItem = (item) => { try { res.write(JSON.stringify(item) + "\n"); } catch { /* 连接已断 */ } };
|
|
322
329
|
await buildSessionListCached(ctx, { onItem, serial: true });
|
|
323
330
|
try { res.end(); } catch { /* 连接已断 */ }
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const [groups, setGroups] = react.useState(null); // workspaces[](group/list);null = host 无分组 API
|
|
6
6
|
const [collapsed, setCollapsed] = react.useState(() => new Set()); // 折叠的分组(含 "__ungrouped__")
|
|
7
7
|
const [error, setError] = react.useState(null);
|
|
8
|
+
const [retryIn, setRetryIn] = react.useState(null); // 自动重试倒计时(秒);null = 没有待重试的定时器
|
|
8
9
|
const [busy, setBusy] = react.useState(null);
|
|
9
10
|
// —— 搜索 + 批量/按条件删除 ——
|
|
10
11
|
const [query, setQuery] = react.useState(""); // 列表过滤关键词
|
|
@@ -31,9 +32,26 @@
|
|
|
31
32
|
next.splice(i, 0, item);
|
|
32
33
|
return next;
|
|
33
34
|
};
|
|
35
|
+
// 列表拉取失败自动重试:DSH 刚重启时插件路由/宿主服务未必已就绪,首次请求会直接
|
|
36
|
+
// 网络失败(Failed to fetch),冷缓存首次构建也很慢 —— 靠用户手动点「重试」既费事又
|
|
37
|
+
// 容易误以为插件坏了。这里按退避自动重试,界面照常给出失败原因 + 下次重试倒计时,
|
|
38
|
+
// 成功一次即清零。数组长度 = 最多自动重试几次。
|
|
39
|
+
const LIST_RETRY_DELAYS = [3000, 8000, 20000, 45000];
|
|
40
|
+
const listRetryRef = react.useRef(0);
|
|
41
|
+
const listTimerRef = react.useRef(null);
|
|
42
|
+
const refreshRef = react.useRef(null);
|
|
43
|
+
// 卸载时清掉待重试的定时器,避免组件已销毁还在拉数据(也会让「已关闭面板」继续打后端)
|
|
44
|
+
react.useEffect(() => () => {
|
|
45
|
+
if (listTimerRef.current) clearTimeout(listTimerRef.current);
|
|
46
|
+
listTimerRef.current = null;
|
|
47
|
+
}, []);
|
|
34
48
|
const refresh = react.useCallback(async () => {
|
|
49
|
+
if (listTimerRef.current) { clearTimeout(listTimerRef.current); listTimerRef.current = null; }
|
|
50
|
+
setRetryIn(null);
|
|
35
51
|
try {
|
|
36
|
-
|
|
52
|
+
// 流式列表**不设超时**(第三个参数 0):冷缓存要逐份解压上百份日志,本机实测
|
|
53
|
+
// 41~226 秒;套 30 秒上限会把「慢但正常」的请求掐成「读取会话列表失败」。
|
|
54
|
+
const listPromise = window.__scFetch("/api/session-conductor/list", { headers: { Accept: "application/x-ndjson" } }, 0);
|
|
37
55
|
const groupPromise = window.__scFetch("/api/session-conductor/group/list")
|
|
38
56
|
.then((r) => (r.ok ? r.json() : { ok: false })).catch(() => ({ ok: false }));
|
|
39
57
|
const listRes = await listPromise;
|
|
@@ -73,10 +91,18 @@
|
|
|
73
91
|
const groupRes = await groupPromise;
|
|
74
92
|
setGroups(groupRes?.ok === true ? groupRes.workspaces ?? [] : null);
|
|
75
93
|
setError(null);
|
|
94
|
+
listRetryRef.current = 0; // 成功一次即清零,下次失败从最短退避重来
|
|
76
95
|
} catch (cause) {
|
|
77
96
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
97
|
+
const delay = LIST_RETRY_DELAYS[listRetryRef.current];
|
|
98
|
+
if (delay !== void 0) {
|
|
99
|
+
listRetryRef.current += 1;
|
|
100
|
+
setRetryIn(Math.round(delay / 1000));
|
|
101
|
+
listTimerRef.current = setTimeout(() => { refreshRef.current?.(); }, delay);
|
|
102
|
+
}
|
|
78
103
|
}
|
|
79
104
|
}, []);
|
|
105
|
+
refreshRef.current = refresh;
|
|
80
106
|
|
|
81
107
|
// 启动即自动加载(插件挂载就预取一次,不依赖面板打开);打开面板时再刷新一次保证最新
|
|
82
108
|
react.useEffect(() => {
|
|
@@ -1215,6 +1241,7 @@
|
|
|
1215
1241
|
style: S.error,
|
|
1216
1242
|
children: [
|
|
1217
1243
|
t("list.error", { message: error }),
|
|
1244
|
+
retryIn !== null && t("list.autoRetry", { n: retryIn }),
|
|
1218
1245
|
" ",
|
|
1219
1246
|
(0, react_jsx_runtime.jsx)("button", {
|
|
1220
1247
|
style: { ...S.actionBtn(false), marginLeft: 6 },
|
|
@@ -15,10 +15,16 @@
|
|
|
15
15
|
|
|
16
16
|
// 全局 fetch 超时兜底:DSH API 挂起(重负载/路由未注册)时界面不再无限等待。
|
|
17
17
|
// 与 fetch 同签名(url, opts, timeoutMs),默认 30s;分片统一走 __scFetch。
|
|
18
|
+
// timeoutMs 传 0(或负数)= **不设超时**:只适合「本来就慢、但会持续吐数据」的流式接口
|
|
19
|
+
// (会话列表冷缓存要逐份解压上百份日志,实测 41~226 秒;套 30 秒上限必然被中途掐断,
|
|
20
|
+
// 表现为面板刚重启就报「读取会话列表失败」,等缓存建好后重试又正常)。
|
|
21
|
+
// 这类接口的正确判据不是「多久没回完」,而是「有没有在吐数据」,由调用方自己盯流。
|
|
18
22
|
const FETCH_TIMEOUT_MS = 30000;
|
|
19
23
|
window.__scFetch = function (url, opts, timeoutMs) {
|
|
24
|
+
const budget = timeoutMs === void 0 ? FETCH_TIMEOUT_MS : timeoutMs;
|
|
25
|
+
if (!(budget > 0)) return fetch(url, opts || {});
|
|
20
26
|
const controller = new AbortController();
|
|
21
|
-
const timer = setTimeout(() => controller.abort(),
|
|
27
|
+
const timer = setTimeout(() => controller.abort(), budget);
|
|
22
28
|
const next = opts ? Object.assign({}, opts, { signal: controller.signal }) : { signal: controller.signal };
|
|
23
29
|
return fetch(url, next).finally(() => clearTimeout(timer));
|
|
24
30
|
};
|
package/lib/client.js
CHANGED
|
@@ -92,10 +92,16 @@ window.__ModuleLoader__.load({
|
|
|
92
92
|
|
|
93
93
|
// 全局 fetch 超时兜底:DSH API 挂起(重负载/路由未注册)时界面不再无限等待。
|
|
94
94
|
// 与 fetch 同签名(url, opts, timeoutMs),默认 30s;分片统一走 __scFetch。
|
|
95
|
+
// timeoutMs 传 0(或负数)= **不设超时**:只适合「本来就慢、但会持续吐数据」的流式接口
|
|
96
|
+
// (会话列表冷缓存要逐份解压上百份日志,实测 41~226 秒;套 30 秒上限必然被中途掐断,
|
|
97
|
+
// 表现为面板刚重启就报「读取会话列表失败」,等缓存建好后重试又正常)。
|
|
98
|
+
// 这类接口的正确判据不是「多久没回完」,而是「有没有在吐数据」,由调用方自己盯流。
|
|
95
99
|
const FETCH_TIMEOUT_MS = 30000;
|
|
96
100
|
window.__scFetch = function (url, opts, timeoutMs) {
|
|
101
|
+
const budget = timeoutMs === void 0 ? FETCH_TIMEOUT_MS : timeoutMs;
|
|
102
|
+
if (!(budget > 0)) return fetch(url, opts || {});
|
|
97
103
|
const controller = new AbortController();
|
|
98
|
-
const timer = setTimeout(() => controller.abort(),
|
|
104
|
+
const timer = setTimeout(() => controller.abort(), budget);
|
|
99
105
|
const next = opts ? Object.assign({}, opts, { signal: controller.signal }) : { signal: controller.signal };
|
|
100
106
|
return fetch(url, next).finally(() => clearTimeout(timer));
|
|
101
107
|
};
|
|
@@ -414,6 +420,7 @@ window.__ModuleLoader__.load({
|
|
|
414
420
|
const [groups, setGroups] = react.useState(null); // workspaces[](group/list);null = host 无分组 API
|
|
415
421
|
const [collapsed, setCollapsed] = react.useState(() => new Set()); // 折叠的分组(含 "__ungrouped__")
|
|
416
422
|
const [error, setError] = react.useState(null);
|
|
423
|
+
const [retryIn, setRetryIn] = react.useState(null); // 自动重试倒计时(秒);null = 没有待重试的定时器
|
|
417
424
|
const [busy, setBusy] = react.useState(null);
|
|
418
425
|
// —— 搜索 + 批量/按条件删除 ——
|
|
419
426
|
const [query, setQuery] = react.useState(""); // 列表过滤关键词
|
|
@@ -440,9 +447,26 @@ window.__ModuleLoader__.load({
|
|
|
440
447
|
next.splice(i, 0, item);
|
|
441
448
|
return next;
|
|
442
449
|
};
|
|
450
|
+
// 列表拉取失败自动重试:DSH 刚重启时插件路由/宿主服务未必已就绪,首次请求会直接
|
|
451
|
+
// 网络失败(Failed to fetch),冷缓存首次构建也很慢 —— 靠用户手动点「重试」既费事又
|
|
452
|
+
// 容易误以为插件坏了。这里按退避自动重试,界面照常给出失败原因 + 下次重试倒计时,
|
|
453
|
+
// 成功一次即清零。数组长度 = 最多自动重试几次。
|
|
454
|
+
const LIST_RETRY_DELAYS = [3000, 8000, 20000, 45000];
|
|
455
|
+
const listRetryRef = react.useRef(0);
|
|
456
|
+
const listTimerRef = react.useRef(null);
|
|
457
|
+
const refreshRef = react.useRef(null);
|
|
458
|
+
// 卸载时清掉待重试的定时器,避免组件已销毁还在拉数据(也会让「已关闭面板」继续打后端)
|
|
459
|
+
react.useEffect(() => () => {
|
|
460
|
+
if (listTimerRef.current) clearTimeout(listTimerRef.current);
|
|
461
|
+
listTimerRef.current = null;
|
|
462
|
+
}, []);
|
|
443
463
|
const refresh = react.useCallback(async () => {
|
|
464
|
+
if (listTimerRef.current) { clearTimeout(listTimerRef.current); listTimerRef.current = null; }
|
|
465
|
+
setRetryIn(null);
|
|
444
466
|
try {
|
|
445
|
-
|
|
467
|
+
// 流式列表**不设超时**(第三个参数 0):冷缓存要逐份解压上百份日志,本机实测
|
|
468
|
+
// 41~226 秒;套 30 秒上限会把「慢但正常」的请求掐成「读取会话列表失败」。
|
|
469
|
+
const listPromise = window.__scFetch("/api/session-conductor/list", { headers: { Accept: "application/x-ndjson" } }, 0);
|
|
446
470
|
const groupPromise = window.__scFetch("/api/session-conductor/group/list")
|
|
447
471
|
.then((r) => (r.ok ? r.json() : { ok: false })).catch(() => ({ ok: false }));
|
|
448
472
|
const listRes = await listPromise;
|
|
@@ -482,10 +506,18 @@ window.__ModuleLoader__.load({
|
|
|
482
506
|
const groupRes = await groupPromise;
|
|
483
507
|
setGroups(groupRes?.ok === true ? groupRes.workspaces ?? [] : null);
|
|
484
508
|
setError(null);
|
|
509
|
+
listRetryRef.current = 0; // 成功一次即清零,下次失败从最短退避重来
|
|
485
510
|
} catch (cause) {
|
|
486
511
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
512
|
+
const delay = LIST_RETRY_DELAYS[listRetryRef.current];
|
|
513
|
+
if (delay !== void 0) {
|
|
514
|
+
listRetryRef.current += 1;
|
|
515
|
+
setRetryIn(Math.round(delay / 1000));
|
|
516
|
+
listTimerRef.current = setTimeout(() => { refreshRef.current?.(); }, delay);
|
|
517
|
+
}
|
|
487
518
|
}
|
|
488
519
|
}, []);
|
|
520
|
+
refreshRef.current = refresh;
|
|
489
521
|
|
|
490
522
|
// 启动即自动加载(插件挂载就预取一次,不依赖面板打开);打开面板时再刷新一次保证最新
|
|
491
523
|
react.useEffect(() => {
|
|
@@ -1624,6 +1656,7 @@ window.__ModuleLoader__.load({
|
|
|
1624
1656
|
style: S.error,
|
|
1625
1657
|
children: [
|
|
1626
1658
|
t("list.error", { message: error }),
|
|
1659
|
+
retryIn !== null && t("list.autoRetry", { n: retryIn }),
|
|
1627
1660
|
" ",
|
|
1628
1661
|
(0, react_jsx_runtime.jsx)("button", {
|
|
1629
1662
|
style: { ...S.actionBtn(false), marginLeft: 6 },
|
package/lib/i18n/en.json
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"list.activeEmpty": "No active sessions (live/running)",
|
|
19
19
|
"list.loading": "Loading…",
|
|
20
20
|
"list.error": "Failed to load sessions: {message}",
|
|
21
|
+
"list.autoRetry": " (retrying automatically in {n}s)",
|
|
21
22
|
"group.ungrouped": "Ungrouped",
|
|
22
23
|
"badge.archived": "Archived",
|
|
23
24
|
"badge.running": "Running",
|
package/lib/i18n/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-conductor",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
4
4
|
"description": "DSH session management enhancement: archive/restore sessions, batch & conditional delete, AI auto-rename, interrupted-session auto-resume, release idle sessions, workspace grouping, undo last message, repair damaged session logs, value analysis, full-text search, per-member model switching (list_models / set_member_model), and plan/closing template injection (system-prompt + context).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|