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
- const listPromise = window.__scFetch("/api/session-conductor/list", { headers: { Accept: "application/x-ndjson" } });
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(), timeoutMs || FETCH_TIMEOUT_MS);
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(), timeoutMs || FETCH_TIMEOUT_MS);
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
- const listPromise = window.__scFetch("/api/session-conductor/list", { headers: { Accept: "application/x-ndjson" } });
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
@@ -18,6 +18,7 @@
18
18
  "list.activeEmpty": "当前没有活跃会话(live/running)",
19
19
  "list.loading": "读取中…",
20
20
  "list.error": "读取会话列表失败:{message}",
21
+ "list.autoRetry": "({n} 秒后自动重试)",
21
22
  "group.ungrouped": "未分组",
22
23
  "badge.archived": "已归档",
23
24
  "badge.running": "运行中",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-session-conductor",
3
- "version": "1.3.6",
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",