@shgroup/dsh-serenity-hooks 1.25.3 → 1.25.5

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/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-serenity-hooks",
3
- "version": "1.25.3",
3
+ "version": "1.25.5",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):真实 DSH 工具 cc_fs/session/acc_msm/eap/neat/cce/handyman/session_rebuild/skiff_admin + 拦截缝机械约束(safe-mode/路径守卫)+ 高级设定面板(双端口网关/账号管理)+ Skiff 认知子集角色(实验性)。适配 DSH 公开版(0.1.0-rc,deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/index.js CHANGED
@@ -9338,13 +9338,17 @@ function registerGateway(ctx) {
9338
9338
  //#endregion
9339
9339
  //#region src/skiff-debug.ts
9340
9340
  /**
9341
- * skiff-debug.ts — Skiff 调试问答页(F4a',v1.25.0 实验性)
9341
+ * skiff-debug.ts — Skiff 调试问答页(F4a',v1.25.x 实验性)
9342
9342
  *
9343
9343
  * node:http 调试端口(默认关,仅监听 127.0.0.1;启停 = 人工——设置面板「Serenity」
9344
9344
  * 页 Skiff 区块开关,不随插件加载自动启动)。
9345
9345
  *
9346
- * - GET / → 问答 HTML 页(角色下拉 + 输入框 + 答案区 + 轨迹区 + WebUI 链接)
9347
- * - POST /ask → {role, question} → 走会话核心(skiff-core)→ {answer, sessionId, trajectory}
9346
+ * - GET / → 问答 HTML 页(**CCC 选择器** + 角色下拉 + 输入框 + 答案区 + 轨迹区 + WebUI 链接)
9347
+ * - POST /ask → {ccc?, role, question} → 走会话核心(skiff-core)→ {answer, sessionId, trajectory}
9348
+ *
9349
+ * **多 CCC 手工切换(v1.25.4,S142 用户)**:dsh 管理多个 CCC 时,调试页顶部 CCC
9350
+ * 下拉列出全部候选(live 会话 cwd 上溯 .serenity 去重 + 默认绑定 root 兜底),
9351
+ * 切换后角色下拉联动(各 CCC 的 skiff.roles 实时读取),提问按所选 CCC 创建 agent。
9348
9352
  *
9349
9353
  * 与 ACP stdio 协议(F4c 后续)共用同一会话核心(createSkiffAgent + askSkiff),
9350
9354
  * 协议层后加不返工。轨迹 = session.events 结构化返回(与 dsh WebUI 同源数据),
@@ -9377,10 +9381,42 @@ function sendHtml(res, html) {
9377
9381
  });
9378
9382
  res.end(html);
9379
9383
  }
9380
- /** 问答页 HTML:角色下拉 + 输入 + 答案区 + 轨迹区(JS 渲染)+ 绑定的 CCC + WebUI 链接 */
9381
- function skiffDebugPage(roles, cccRoot, webPort) {
9382
- const roleOptions = [...roles.entries()].map(([name, r]) => `<option value="${escapeHtml(name)}">${escapeHtml(name)}${r.model ? ` (${escapeHtml(r.model)})` : ""}</option>`).join("\n");
9383
- const webUrl = `http://127.0.0.1:${webPort}`;
9384
+ /**
9385
+ * 发现候选 CCC 列表(多 CCC 手工切换,v1.25.4+):
9386
+ * **dsh 工作区注册表**(workspaceRegistry.list,持久化——所有工作目录即使无 live 会话;
9387
+ * S142 用户 2026-08-29:应直接拉 dsh 工作区,且只列具体 CCC)
9388
+ * ② live 会话兜底(无 workspace 服务 / 注册表为空)
9389
+ * ③ 默认绑定 root 兜底(不在列表时放首位)
9390
+ */
9391
+ function discoverCccs(ctx, defaultRoot) {
9392
+ const roots = [];
9393
+ try {
9394
+ const registry = ctx.get?.("workspaceRegistry");
9395
+ for (const ws of registry?.list?.() ?? []) if (typeof ws.path === "string" && ws.path !== "") {
9396
+ const r = findSerenityRoot(ws.path);
9397
+ if (r && !roots.includes(r)) roots.push(r);
9398
+ }
9399
+ } catch {}
9400
+ if (roots.length === 0) try {
9401
+ const sessions = ctx.sessions;
9402
+ for (const s of sessions?.list?.() ?? []) {
9403
+ const cwd = s?.header?.cwd;
9404
+ if (typeof cwd === "string") {
9405
+ const r = findSerenityRoot(cwd);
9406
+ if (r && !roots.includes(r)) roots.push(r);
9407
+ }
9408
+ }
9409
+ } catch {}
9410
+ if (!roots.includes(defaultRoot)) roots.unshift(defaultRoot);
9411
+ return roots.map((root) => ({
9412
+ root,
9413
+ name: basename(root) || root,
9414
+ roles: [...readSkiffRoles(root).keys()]
9415
+ }));
9416
+ }
9417
+ /** 问答页 HTML:CCC 切换器 + 角色下拉 + 输入 + 答案区 + 轨迹区(JS 渲染)+ WebUI 链接 */
9418
+ function skiffDebugPage(cccs, defaultRoot, webPort) {
9419
+ const data = JSON.stringify(cccs);
9384
9420
  return `<!DOCTYPE html>
9385
9421
  <html lang="zh">
9386
9422
  <head>
@@ -9417,32 +9453,61 @@ function skiffDebugPage(roles, cccRoot, webPort) {
9417
9453
  <body>
9418
9454
  <main>
9419
9455
  <h1>Skiff Debug</h1>
9420
- <div class="sub">宁静号 trajectory 子集角色问答页(v1.25.2 实验性)— DSH agent-loop 会话核心,轨迹与 WebUI 同源</div>
9421
- <div class="ccc">CCC: ${escapeHtml(cccRoot)}</div>
9456
+ <div class="sub">宁静号 trajectory 子集角色问答页(v1.25.4 实验性)— CCC 可切换,走 DSH agent-loop 会话核心,轨迹与 WebUI 同源</div>
9457
+ <div class="ccc" id="cccBadge"></div>
9458
+ <label for="ccc">认知容器</label>
9459
+ <select id="ccc"></select>
9422
9460
  <label for="role">角色</label>
9423
- <select id="role">${roleOptions || "<option value=\"\">(未配置角色)</option>"}</select>
9461
+ <select id="role"></select>
9424
9462
  <label for="q">问题</label>
9425
9463
  <textarea id="q" placeholder="向该角色提问…"></textarea>
9426
9464
  <button id="ask">提问</button>
9427
9465
  <div id="answer" class="muted">等待提问…</div>
9428
9466
  <div id="trajectory"></div>
9429
- <p class="muted"><a href="${webUrl}" target="_blank" rel="noopener">在 dsh WebUI 查看完整会话</a>(会话列表搜索 sessionId;WebUI 有完整交互)</p>
9467
+ <p class="muted"><a href="${`http://127.0.0.1:${webPort}`}" target="_blank" rel="noopener">在 dsh WebUI 查看完整会话</a>(会话列表搜索 sessionId;WebUI 有完整交互)</p>
9430
9468
  </main>
9469
+ <script id="skiff-data" type="application/json" data-default="${escapeHtml(defaultRoot)}">${escapeHtml(data)}<\/script>
9431
9470
  <script>
9471
+ const CCCS = JSON.parse(document.getElementById('skiff-data').textContent)
9472
+ const defaultRoot = document.getElementById('skiff-data').dataset.default
9473
+ const cccSel = document.getElementById('ccc')
9474
+ const roleSel = document.getElementById('role')
9475
+ const badge = document.getElementById('cccBadge')
9432
9476
  const btn = document.getElementById('ask')
9433
9477
  const answer = document.getElementById('answer')
9434
9478
  const traj = document.getElementById('trajectory')
9435
9479
  const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))
9480
+ function currentCcc() {
9481
+ return CCCS.find((c) => c.root === cccSel.value) || (CCCS[0] || { root: '', name: '', roles: [] })
9482
+ }
9483
+ function fillCccs() {
9484
+ cccSel.innerHTML = CCCS.map((c) => {
9485
+ const n = c.roles.length
9486
+ return '<option value="' + esc(c.root) + '">' + esc(c.name) + (n ? ' (' + n + ' 角色)' : ' (无角色)') + '</option>'
9487
+ }).join('') || '<option value="">(未发现 CCC)</option>'
9488
+ if (defaultRoot) cccSel.value = CCCS.some((c) => c.root === defaultRoot) ? defaultRoot : (CCCS[0] && CCCS[0].root)
9489
+ fillRoles()
9490
+ }
9491
+ function fillRoles() {
9492
+ const c = currentCcc()
9493
+ badge.textContent = 'CCC: ' + c.root
9494
+ roleSel.innerHTML = (c.roles && c.roles.length)
9495
+ ? c.roles.map((r) => '<option value="' + esc(r) + '">' + esc(r) + '</option>').join('')
9496
+ : '<option value="">(未配置角色)</option>'
9497
+ }
9498
+ cccSel.addEventListener('change', fillRoles)
9499
+ fillCccs()
9436
9500
  btn.addEventListener('click', async () => {
9437
- const role = document.getElementById('role').value
9501
+ const c = currentCcc()
9502
+ const role = roleSel.value
9438
9503
  const q = document.getElementById('q').value.trim()
9439
- if (!role || !q) { answer.className = 'err'; answer.textContent = '请选择角色并输入问题'; return }
9504
+ if (!c.root || !role || !q) { answer.className = 'err'; answer.textContent = '请选择认知容器与角色并输入问题'; return }
9440
9505
  btn.disabled = true
9441
9506
  answer.className = 'muted'
9442
9507
  answer.textContent = '运行中…'
9443
9508
  traj.innerHTML = ''
9444
9509
  try {
9445
- const res = await fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role, question: q }) })
9510
+ const res = await fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ccc: c.root, role, question: q }) })
9446
9511
  const data = await res.json()
9447
9512
  if (!res.ok) throw new Error(data.error || ('HTTP ' + res.status))
9448
9513
  answer.className = ''
@@ -9479,12 +9544,11 @@ function escapeHtml(s) {
9479
9544
  /**
9480
9545
  * 启动调试问答服务(单实例;重复启动幂等返回既有实例)。
9481
9546
  *
9482
- * **CCC 绑定(v1.25.2 用户指出)**:服务绑定一个 CCC root(调用方 resolveSkiffRoot
9483
- * 解析:live 会话中**含 skiff.roles 的 CCC 优先**);角色配置**每次请求实时读取**
9484
- * (不缓存快照)——改 .opencode/serenity.json 后刷新页面即生效,无需重启服务。
9485
- * 页面顶部显示绑定的 CCC root,绑定可核对。
9547
+ * **CCC 绑定(v1.25.2 用户指出)**:服务绑定一个默认 CCC root(调用方 resolveSkiffRoot
9548
+ * 解析:live 会话中**含 skiff.roles 的 CCC 优先**);v1.25.4 起页面可**手工切换**到
9549
+ * 其它候选 CCC(live 会话发现的全部 CCC);角色配置**每次请求实时读取**(不缓存快照)。
9486
9550
  *
9487
- * @param root 绑定的 CCC 根(角色配置读取 + skiff agent cwd)
9551
+ * @param root 默认绑定的 CCC 根(首次加载选中;角色配置读取 + skiff agent cwd)
9488
9552
  * @param port 调试端口(仅 127.0.0.1)
9489
9553
  * @param webPort 主 WebUI 端口(WebUI 链接)
9490
9554
  */
@@ -9501,7 +9565,7 @@ async function startSkiffDebugServer(ctx, root, port, webPort) {
9501
9565
  server,
9502
9566
  port
9503
9567
  };
9504
- console.log(`[serenity-hooks] ✓ Skiff 调试问答页: http://127.0.0.1:${port}CCC: ${root},WebUI: ${webPort})`);
9568
+ console.log(`[serenity-hooks] ✓ Skiff 调试问答页: http://127.0.0.1:${port}(默认 CCC: ${root},WebUI: ${webPort})`);
9505
9569
  }
9506
9570
  function stopSkiffDebugServer() {
9507
9571
  if (!active) return;
@@ -9510,11 +9574,11 @@ function stopSkiffDebugServer() {
9510
9574
  } catch {}
9511
9575
  active = null;
9512
9576
  }
9513
- async function handle(ctx, root, webPort, req, res) {
9577
+ async function handle(ctx, defaultRoot, webPort, req, res) {
9514
9578
  try {
9515
9579
  const url = (req.url ?? "/").split("?")[0] ?? "/";
9516
9580
  if (req.method === "GET" && url === "/") {
9517
- sendHtml(res, skiffDebugPage(readSkiffRoles(root), root, webPort));
9581
+ sendHtml(res, skiffDebugPage(discoverCccs(ctx, defaultRoot), defaultRoot, webPort));
9518
9582
  return;
9519
9583
  }
9520
9584
  if (req.method === "POST" && url === "/ask") {
@@ -9527,18 +9591,19 @@ async function handle(ctx, root, webPort, req, res) {
9527
9591
  sendJson(res, 400, { error: "invalid JSON body" });
9528
9592
  return;
9529
9593
  }
9594
+ const ccc = typeof body.ccc === "string" && body.ccc !== "" ? body.ccc : defaultRoot;
9530
9595
  const roleName = typeof body.role === "string" ? body.role : "";
9531
9596
  const question = typeof body.question === "string" ? body.question : "";
9532
- const role = readSkiffRoles(root).get(roleName);
9597
+ const role = readSkiffRoles(ccc).get(roleName);
9533
9598
  if (!roleName || !role) {
9534
- sendJson(res, 400, { error: `unknown role: ${roleName}` });
9599
+ sendJson(res, 400, { error: `unknown role: ${roleName} (ccc: ${ccc})` });
9535
9600
  return;
9536
9601
  }
9537
9602
  if (!question.trim()) {
9538
9603
  sendJson(res, 400, { error: "empty question" });
9539
9604
  return;
9540
9605
  }
9541
- const result = await askSkiff(ctx, (await createSkiffAgent(ctx, root, roleName, role, readHandymanConfig(root)?.defaultModel)).agent, question);
9606
+ const result = await askSkiff(ctx, (await createSkiffAgent(ctx, ccc, roleName, role, readHandymanConfig(ccc)?.defaultModel)).agent, question);
9542
9607
  sendJson(res, 200, {
9543
9608
  answer: result.answer,
9544
9609
  sessionId: result.sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.25.3",
3
+ "version": "1.25.5",
4
4
  "description": "宁静号 ACC harness — Native Cordis 插件(DeepSeek Harness 运行时)。真实 DSH 工具注册(cc_fs/session/acc_msm 等 9 工具)+ 拦截缝机械约束(safe-mode/路径守卫/会话落盘)+ 系统提示词注入(ACC/CCE/Constraints/SKILL/Session 五块)。适配 DSH 公开版(deepseek-ai/deepseek-harness 0.1.0-rc)。",
5
5
  "license": "MIT",
6
6
  "repository": {