dsh-session-conductor 1.3.5 → 1.3.6

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/README.md CHANGED
@@ -137,22 +137,27 @@ allowBuilds:
137
137
 
138
138
  ### 真实后端预览页
139
139
 
140
- 改 UI 后免重启查看效果 —— **预览能力已移出本插件**,统一用独立插件 `dsh-useful-tools` 的 `preview` / `minihost` 工具。
140
+ 改 UI 后免重启查看效果 —— **预览能力已移出本插件**,统一用独立插件 `dsh-useful-tools`(**0.1.11**)的 `preview` / `minihost` 工具。
141
141
 
142
- **最短三行(实测可用;本仓库没有 `assets/preview.spec.mjs` 也能出页面)**:
142
+ **最短三行(实测可用;本仓库没有 `assets/preview.spec.mjs` 也能出真实界面)**:
143
143
 
144
144
  ```
145
- minihost { action: "start", plugin: "<插件根>", port: 31177 } # ① 后端模拟
146
- preview { action: "gen", plugin: "<插件根>" } # ② 前端预览页(无 spec 自动用默认垫片)
147
- preview { action: "serve", file: "<插件根>/assets/preview.html", backend: "http://127.0.0.1:31177" } # ③ 前端接后端
145
+ minihost { action: "start", plugin: "<插件根>", port: 31177, services: "<插件根>/test/api/helpers/fake-services.mjs" }
146
+ preview { action: "gen", plugin: "<插件根>" }
147
+ preview { action: "serve", file: "<插件根>/assets/preview.html", backend: "http://127.0.0.1:31177" }
148
148
  ```
149
149
 
150
- **几个容易写错的点**:
150
+ 停止:`minihost { action:"stop", port:31177 }` + `preview { action:"stop", port:31188 }`;状态:两者都用 `action:"status"`。
151
151
 
152
+ **要点**:
153
+
154
+ - **`services` 必须传**(0.1.11 新增):本插件 `apply()` 依赖 `storageDomain` 等宿主服务,不注入时 `/templates` 会 500、页面显示「storageDomain 不可用」;注入 `test/api/helpers/fake-services.mjs` 后 `/templates` 返回 200 真实数据(已实测)。
155
+ - **autoMount**(0.1.7 起):无 spec 时 mock 宿主 `__ModuleLoader__` / `require` / `ctx`(slots / locale / effect),调插件 `apply(ctx)`,把 `slots.register(def, component)` 捕获到的组件 `createRoot().render()` 到 `#root`。**限制**:组件若依赖 `useSettings` / `useSession` 这类宿主 hooks 会渲染失败(页面会显示错误与已捕获插槽清单,不白屏);要 100% 还原仍需 spec 提供 mock。locale 取插件自己 `ctx.locale.register` 的字典,键名不一致会回退成 key。
152
156
  - `gen` 默认产物是 `<插件根>/assets/preview.html`(会写进仓库)⇒ 不想入库就传 `out`(如 `out: "/tmp/sc-preview.html"`),并让 `serve` 的 `file` 指向同一路径。本仓库已把该产物加进 `.gitignore`。
153
- - `serve` 默认端口 **31188**,显式传 `port` 才用你给的。
154
- - `backend` 可指向 minihost 的端口(`/api/*` 反代到模拟后端),也可指向**真实 DSH 后端**。
157
+ - `serve` 默认端口 **31188**,显式传 `port` 才用你给的;`backend` 可指向 minihost 的端口(`/api/*` 反代到模拟后端),也可指向**真实 DSH 后端**。
155
158
  - 显式传 `spec` 但文件不存在会**直接报错**(不静默降级)—— 故意的,避免指错文件还生成一个看不懂的页面。
159
+ - **截图**(README 配图)走 **browser 插件的 `browser_screenshot`**;「自包含快照」形态不做(理由见 `dsh-useful-tools` 的 `docs/NOT-DONE.md`)。
160
+ - **隔离实例自测**(0.1.8/0.1.9):`test_env { action:"create"|"start"|"stop"|"status", plugin, pluginName }` 起独立 `DSH_HOME` + 自动端口的实例;`sync_plugin { write:true, register:true, dshHome:"<测试环境 DSH_HOME>" }` 把插件装进去(写 profile 的 `dependencies` + `dsh.profile.bundles`,并对齐 `node_modules` 副本)。
156
161
  - 停止:`minihost { action: "stop", port: 31177 }` + `preview { action: "stop", port: 31200 }`;状态:两者都用 `action: "status"`。
157
162
  - **截图**(README 配图)改走 **browser 插件的 `browser_screenshot`**;「自包含快照」形态不做(理由见 `dsh-useful-tools` 的 `docs/NOT-DONE.md`)。
158
163
 
@@ -17,10 +17,24 @@
17
17
  locale: NS,
18
18
  inject: () => ({
19
19
  onOpenSession: (sessionId) => {
20
+ // 打开会话的真实入口随宿主版本不同,按优先级尝试(**不要只认一个**):
21
+ // · workspaceNavigation.openSession(id) —— dsh-client-ui-conversation 的用法
22
+ // · ctx.uiWorkspace.openSession(id) —— dsh-client-ui-chat 的用法
23
+ // · sessions.open(id) —— 早期路径,保留兜底
24
+ // 以前只写了第三个,而宿主客户端并没有 `sessions` 这个服务 ⇒ 可选链把失败吞成
25
+ // 「点了没反应」,且不报错、难查。所以这里:多个入口都试;全失败要留下痕迹。
20
26
  try {
21
- ctx.get("sessions")?.open?.(sessionId);
22
- } catch {
23
- // 打开失败不阻塞面板
27
+ const nav = ctx.get?.("workspaceNavigation");
28
+ if (typeof nav?.openSession === "function") return nav.openSession(sessionId);
29
+ if (typeof ctx.uiWorkspace?.openSession === "function") return ctx.uiWorkspace.openSession(sessionId);
30
+ const legacy = ctx.get?.("sessions");
31
+ if (typeof legacy?.open === "function") return legacy.open(sessionId);
32
+ // eslint-disable-next-line no-console
33
+ console.warn("[session-conductor] 打不开会话:宿主客户端未暴露 workspaceNavigation/uiWorkspace/sessions 任一入口", sessionId);
34
+ } catch (error) {
35
+ // 打开失败不阻塞面板,但不再静默
36
+ // eslint-disable-next-line no-console
37
+ console.warn("[session-conductor] 打开会话失败:", error);
24
38
  }
25
39
  },
26
40
  }),
@@ -38,6 +52,6 @@
38
52
  * 本卡只保留:
39
53
  * · 分组列表(GET /api/session-conductor/group/list,只读展示)
40
54
  * · 每行「在分组下新建会话」(POST /api/session-conductor/group/new-session {workspaceId})
41
- * · 「新建会话(上次会话工作区)」(new-session 不带 workspaceId = 最近活跃会话 cwd 归属分组)
55
+ * · 「新建会话(上一次使用的工作区)」(new-session 不带 workspaceId = 最近活跃会话 cwd 归属分组)
42
56
  */
43
57
 
@@ -768,7 +768,33 @@
768
768
  }),
769
769
  (0, react_jsx_runtime.jsx)("div", {
770
770
  style: S.rowSid,
771
- title: "session id",
771
+ title: "点击复制会话 id",
772
+ // 单击复制:优先 Clipboard API,退回 execCommand(旧版宿主/非安全上下文);
773
+ // 成功后把这段文字临时换成「✅ 已复制」再复原 —— 不依赖宿主 toast(旧版未必有)。
774
+ onClick: (event) => {
775
+ event.stopPropagation();
776
+ const el = event.currentTarget;
777
+ const flash = () => {
778
+ try {
779
+ const prev = el.textContent;
780
+ el.textContent = "✅ 已复制";
781
+ setTimeout(() => { el.textContent = prev; }, 1200);
782
+ } catch { /* 提示失败不影响复制 */ }
783
+ };
784
+ try {
785
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
786
+ navigator.clipboard.writeText(session.id).then(flash, () => {});
787
+ } else {
788
+ const ta = document.createElement("textarea");
789
+ ta.value = session.id;
790
+ document.body.appendChild(ta);
791
+ ta.select();
792
+ document.execCommand("copy");
793
+ ta.remove();
794
+ flash();
795
+ }
796
+ } catch { /* 复制失败不阻塞面板 */ }
797
+ },
772
798
  children: session.id,
773
799
  }),
774
800
  ],
package/lib/client.js CHANGED
@@ -1177,7 +1177,33 @@ window.__ModuleLoader__.load({
1177
1177
  }),
1178
1178
  (0, react_jsx_runtime.jsx)("div", {
1179
1179
  style: S.rowSid,
1180
- title: "session id",
1180
+ title: "点击复制会话 id",
1181
+ // 单击复制:优先 Clipboard API,退回 execCommand(旧版宿主/非安全上下文);
1182
+ // 成功后把这段文字临时换成「✅ 已复制」再复原 —— 不依赖宿主 toast(旧版未必有)。
1183
+ onClick: (event) => {
1184
+ event.stopPropagation();
1185
+ const el = event.currentTarget;
1186
+ const flash = () => {
1187
+ try {
1188
+ const prev = el.textContent;
1189
+ el.textContent = "✅ 已复制";
1190
+ setTimeout(() => { el.textContent = prev; }, 1200);
1191
+ } catch { /* 提示失败不影响复制 */ }
1192
+ };
1193
+ try {
1194
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
1195
+ navigator.clipboard.writeText(session.id).then(flash, () => {});
1196
+ } else {
1197
+ const ta = document.createElement("textarea");
1198
+ ta.value = session.id;
1199
+ document.body.appendChild(ta);
1200
+ ta.select();
1201
+ document.execCommand("copy");
1202
+ ta.remove();
1203
+ flash();
1204
+ }
1205
+ } catch { /* 复制失败不阻塞面板 */ }
1206
+ },
1181
1207
  children: session.id,
1182
1208
  }),
1183
1209
  ],
@@ -2106,10 +2132,24 @@ window.__ModuleLoader__.load({
2106
2132
  locale: NS,
2107
2133
  inject: () => ({
2108
2134
  onOpenSession: (sessionId) => {
2135
+ // 打开会话的真实入口随宿主版本不同,按优先级尝试(**不要只认一个**):
2136
+ // · workspaceNavigation.openSession(id) —— dsh-client-ui-conversation 的用法
2137
+ // · ctx.uiWorkspace.openSession(id) —— dsh-client-ui-chat 的用法
2138
+ // · sessions.open(id) —— 早期路径,保留兜底
2139
+ // 以前只写了第三个,而宿主客户端并没有 `sessions` 这个服务 ⇒ 可选链把失败吞成
2140
+ // 「点了没反应」,且不报错、难查。所以这里:多个入口都试;全失败要留下痕迹。
2109
2141
  try {
2110
- ctx.get("sessions")?.open?.(sessionId);
2111
- } catch {
2112
- // 打开失败不阻塞面板
2142
+ const nav = ctx.get?.("workspaceNavigation");
2143
+ if (typeof nav?.openSession === "function") return nav.openSession(sessionId);
2144
+ if (typeof ctx.uiWorkspace?.openSession === "function") return ctx.uiWorkspace.openSession(sessionId);
2145
+ const legacy = ctx.get?.("sessions");
2146
+ if (typeof legacy?.open === "function") return legacy.open(sessionId);
2147
+ // eslint-disable-next-line no-console
2148
+ console.warn("[session-conductor] 打不开会话:宿主客户端未暴露 workspaceNavigation/uiWorkspace/sessions 任一入口", sessionId);
2149
+ } catch (error) {
2150
+ // 打开失败不阻塞面板,但不再静默
2151
+ // eslint-disable-next-line no-console
2152
+ console.warn("[session-conductor] 打开会话失败:", error);
2113
2153
  }
2114
2154
  },
2115
2155
  }),
@@ -2127,7 +2167,7 @@ window.__ModuleLoader__.load({
2127
2167
  * 本卡只保留:
2128
2168
  * · 分组列表(GET /api/session-conductor/group/list,只读展示)
2129
2169
  * · 每行「在分组下新建会话」(POST /api/session-conductor/group/new-session {workspaceId})
2130
- * · 「新建会话(上次会话工作区)」(new-session 不带 workspaceId = 最近活跃会话 cwd 归属分组)
2170
+ * · 「新建会话(上一次使用的工作区)」(new-session 不带 workspaceId = 最近活跃会话 cwd 归属分组)
2131
2171
  */
2132
2172
 
2133
2173
  return module.exports;
@@ -180,7 +180,15 @@ export function formatRecallBlock(turns, opts = {}) {
180
180
  kept.unshift(block);
181
181
  used += block.length;
182
182
  }
183
- return head + kept.join("");
183
+ // 有轮次因总长上限被丢掉时**如实说明**:静默丢轮次是「要 30 轮只回 5 轮」那个 bug 的根因之一,
184
+ // 调用方需要知道自己拿到的是残缺结果、以及怎么取到更早的内容。
185
+ const dropped = list.length - kept.length;
186
+ const tail = dropped > 0
187
+ ? `\n\n(⚠️ 受总长上限 ${maxTotal} 字符限制,最早的 ${dropped} 轮未包含;需要更早的内容请调小 rounds 分次取)`
188
+ : "";
189
+ // 输出末尾附上实际字符数:调用方据此判断这次取回的代价(也便于对照 maxTotalChars 的截断阈值)。
190
+ const body = head + kept.join("") + tail;
191
+ return `${body}\n\n(本次输出正文 ${body.length} 字符)`;
184
192
  }
185
193
 
186
194
  /**
@@ -291,7 +299,9 @@ export function registerMemoryRecallTool(ctx, defineTool, tools, deps = {}) {
291
299
  });
292
300
  if (!turns || turns.length === 0) return "没取到可用的记忆(日志缺失,或该会话还没有用户消息)。";
293
301
  const text = format(turns, {
294
- maxTotalChars: config?.retrieveMemoryMaxChars,
302
+ // 手动路径的总额**按请求规模算**(rounds × 单条上限),不套自动注入那条 4000 字符的紧上限:
303
+ // 调用方明确要 N 轮,就该给 N 轮;套紧上限会把最早的轮次静默丢掉(实测 rounds:30 只剩 5 轮)。
304
+ maxTotalChars: rounds * (config?.retrieveMemoryMaxCharsPerItem ?? 800),
295
305
  manual: true,
296
306
  // 跨会话时标注来源,并写明只作背景——别把别处的文字当成当前这轮的要求
297
307
  ...(fromOther ? { sourceNote: `来自另一个会话 ${targetId}` } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-session-conductor",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
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",