@shgroup/dsh-serenity-hooks 1.30.14 → 1.30.15

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.30.14",
3
+ "version": "1.30.15",
4
4
  "main": "lib/index.js",
5
5
  "description": "宁静号 ACC harness(Native Cordis 插件):给 DSH 装一个「AI 工作区」——10 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory)+ 机械约束(安全模式/工作区围墙/密钥守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.2-rc.1(deepseek-ai/deepseek-harness)。",
6
6
  "engines": {
package/lib/ccc.d.ts CHANGED
@@ -178,6 +178,15 @@ export interface WeixinRouteConfig {
178
178
  export interface SkiffRoleConfig {
179
179
  /** 角色模型(provider/model;CCC 直接指定,无白名单校验——用户拍板) */
180
180
  model?: string;
181
+ /**
182
+ * 角色类型(v1.30.15,S142 用户拍板"skiff 应该区分的类型是临时和长期"):
183
+ * - `temporary`:临时 skiff——不绑定 trajectory(无 SESSION.md 工作台),
184
+ * 提示词按创建时快照注入("注入发生一次")。
185
+ * - `persistent`:长期 skiff——绑定 trajectory(自动建/恢复 SESSION.md 工作台),
186
+ * 提示词**每轮动态重读文件**(改 `systemPromptFile` 即生效,无需重启)。
187
+ * 缺省(未写)= 隐式推断:调用方传入稳定 sessionId → persistent,否则 temporary。
188
+ */
189
+ kind?: 'temporary' | 'persistent';
181
190
  /** MSM 白名单(独立):该角色可 exec 的 MSM 名列表;缺省空 = 无 MSM 通道 */
182
191
  msms?: string[];
183
192
  /** 非 MSM 工具白名单(独立):平台/ACC 工具名列表;缺省空 = 仅 MSM 通道 */
@@ -191,7 +200,8 @@ export interface SkiffRoleConfig {
191
200
  /** 角色系统提示词(CCC 完整定义:人格/认知边界/回答风格) */
192
201
  systemPrompt?: string;
193
202
  /** 角色系统提示词文件(推荐:相对 CCC 根引用 md 文件——超长提示词在 JSON 内嵌不可读;
194
- * 优先于 systemPrompt;文件缺失/逃逸 → resolveRoleSystemPrompt 抛错) */
203
+ * 优先于 systemPrompt;文件缺失/逃逸 → resolveRoleSystemPrompt 抛错。
204
+ * persistent 型每轮重读(mtime 缓存),改文件即生效) */
195
205
  systemPromptFile?: string;
196
206
  }
197
207
  /**
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { a as findSerenityRoot, c as matchBlacklist, d as readCccName$2, f as readHandymanConfig, i as findGitRoot, l as pathInside, m as resolveInside, n as SAFE_MODE_MARKER, o as isSafeModeOn, r as classifyPath, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS, u as readBlacklist } from "./ccc-DAsSHsub.js";
3
- import { a as resolveRoleSystemPrompt, i as readSkiffRoles, l as systemPromptSource, r as isSkiffSessionId, s as roleToolWhitelist } from "./skiff-role-DlrbHPLD.js";
4
- import { A as skiffRoleFor, C as listActiveHandymen, D as splitModel, E as requireWhitelistedModel, O as writeFailedStatus, S as handymanProgressPaths, T as readProgress, _ as unregisterSkiffSession, a as startSkiffDebugServer, b as HANDYMAN_GUIDE, c as askSkiff, d as ensureWorkspacePromptSection, f as getSkiffAgent, g as skiffTrajectoryEnabled, h as skiffSessionSnapshot, k as writeProgress, l as createSkiffAgent, m as skiffSessionInfo, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffMsmGate, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as ensureSkiffSession, v as workspaceTrajectoryLine, w as newStopToken, x as buildRoundPrompt, y as waitAgentIdle } from "./skiff-debug-DYXDdzu3.js";
3
+ import { a as readSkiffRoles, d as systemPromptSource, i as isSkiffSessionId, l as roleToolWhitelist, o as resolveRoleSystemPrompt } from "./skiff-role-BYvNnwv1.js";
4
+ import { A as skiffRoleFor, C as listActiveHandymen, D as splitModel, E as requireWhitelistedModel, O as writeFailedStatus, S as handymanProgressPaths, T as readProgress, _ as unregisterSkiffSession, a as startSkiffDebugServer, b as HANDYMAN_GUIDE, c as askSkiff, d as ensureWorkspacePromptSection, f as getSkiffAgent, g as skiffTrajectoryEnabled, h as skiffSessionSnapshot, k as writeProgress, l as createSkiffAgent, m as skiffSessionInfo, n as jscSafeJsonText, o as stopSkiffDebugServer, p as skiffMsmGate, r as renderSkiffMarkdown, s as stripThink, t as discoverCccs, u as ensureSkiffSession, v as workspaceTrajectoryLine, w as newStopToken, x as buildRoundPrompt, y as waitAgentIdle } from "./skiff-debug-C5ulZKCa.js";
5
5
  import { i as hostSessions, n as hostInjected, o as hostWeb, r as hostService, s as hostWebServer, t as hostAgents } from "./access-dU_vG1q8.js";
6
6
  import { a as readWeixinCredential, c as weixinInboundDir, d as LOCALSTORE_SCOPES, f as checkLocalstoreGitCompliance, h as runLocalStore, i as matchWeixinRoute, l as weixinSessionIdFor, m as readGitTrack, n as extractWeixinText, o as readWeixinSettings, p as localstorePath, r as hasVoiceItem, s as sanitizeFileName$1, t as extractWeixinMedia } from "./weixin-route-BD_yYAZz.js";
7
7
  import { C as useSession, S as summarize, _ as resolveSessionByTitle, a as archiveSessions, b as setActiveSessionInfo, c as createSession, d as getActiveSessionInfo, f as healthCheck, g as readActiveSessionMd, h as qaCheck, i as SESSION_ACTIONS, l as extractSessionMdPathFromText, m as parseSessionContextFromEvents, n as readLastBound, o as clearActiveSessionInfo, p as listSessions, r as DEFAULT_SESSION_SCOPE, s as closeSession, t as appendBound, u as findSession, v as sessionEvents, x as showSession, y as sessionsRoot } from "./session-bound-BpTayaIS.js";
@@ -6478,7 +6478,7 @@ function registerStatusApi(ctx, opts = {}) {
6478
6478
  workspace: url.searchParams.get("workspace") ?? void 0
6479
6479
  });
6480
6480
  const root = findSerenityRoot(workspace) ?? "";
6481
- const { discoverCccs } = await import("./skiff-debug-DYXDdzu3.js").then((n) => n.i);
6481
+ const { discoverCccs } = await import("./skiff-debug-C5ulZKCa.js").then((n) => n.i);
6482
6482
  sendJson$2(res, 200, { cccs: await discoverCccs(ctx, root) });
6483
6483
  } catch (err) {
6484
6484
  sendJson$2(res, 400, { error: err.message ?? String(err) });
@@ -6675,7 +6675,7 @@ function registerStatusApi(ctx, opts = {}) {
6675
6675
  return;
6676
6676
  }
6677
6677
  const { saveWeixinRoutes } = await import("./weixin-route-BD_yYAZz.js").then((n) => n.u);
6678
- const { readSkiffRoles } = await import("./skiff-role-DlrbHPLD.js").then((n) => n.c);
6678
+ const { readSkiffRoles } = await import("./skiff-role-BYvNnwv1.js").then((n) => n.u);
6679
6679
  const roles = readSkiffRoles(root);
6680
6680
  for (const r of routes) if (!roles.has(r.role)) {
6681
6681
  sendJson$2(res, 400, { error: `unknown role: ${r.role} (not in ${root} skiff.roles)` });
@@ -9897,7 +9897,7 @@ function matchCcc(input, candidates) {
9897
9897
  }
9898
9898
  /** 组装候选列表(discoverCccs 投影 + `.serenity` 名;动态 import 保持本模块静态依赖轻量) */
9899
9899
  async function collectCandidates(ctx) {
9900
- const { discoverCccs } = await import("./skiff-debug-DYXDdzu3.js").then((n) => n.i);
9900
+ const { discoverCccs } = await import("./skiff-debug-C5ulZKCa.js").then((n) => n.i);
9901
9901
  const entries = await discoverCccs(ctx, process.cwd());
9902
9902
  const seen = /* @__PURE__ */ new Set();
9903
9903
  const out = [];
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { a as findSerenityRoot, f as readHandymanConfig } from "./ccc-DAsSHsub.js";
3
- import { a as resolveRoleSystemPrompt, i as readSkiffRoles, n as buildSkiffBasePrompt, o as roleMsmWhitelist, r as isSkiffSessionId, u as trajectorySubset } from "./skiff-role-DlrbHPLD.js";
3
+ import { a as readSkiffRoles, c as roleMsmWhitelist, f as trajectorySubset, i as isSkiffSessionId, n as buildSkiffBasePrompt, o as resolveRoleSystemPrompt, r as createRolePromptReader, s as resolveSkiffKind } from "./skiff-role-BYvNnwv1.js";
4
4
  import { i as hostSessions, r as hostService, t as hostAgents } from "./access-dU_vG1q8.js";
5
5
  import { b as setActiveSessionInfo, c as createSession, d as getActiveSessionInfo, n as readLastBound, t as appendBound } from "./session-bound-BpTayaIS.js";
6
6
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
@@ -458,15 +458,20 @@ async function createSkiffAgent(ctx, root, roleName, role, defaultModel, session
458
458
  };
459
459
  const handle = await createOrResumeAgent(ctx, id, root, model, setup, sessionId !== void 0);
460
460
  const agent = handle.agent;
461
- let cccPrompt = "";
462
- try {
463
- cccPrompt = resolveRoleSystemPrompt(root, role);
464
- } catch (err) {
465
- console.warn(`[serenity-hooks] skiff 角色 "${roleName}" 系统提示词解析失败(回退仅基础段): ${String(err?.message ?? err)}`);
466
- }
461
+ const kind = resolveSkiffKind(role, sessionId !== void 0);
462
+ const rolePrompt = kind === "persistent" ? createRolePromptReader(root, role) : (() => {
463
+ let snapshot = "";
464
+ try {
465
+ snapshot = resolveRoleSystemPrompt(root, role);
466
+ } catch (err) {
467
+ console.warn(`[serenity-hooks] skiff 角色 "${roleName}" 系统提示词解析失败(回退仅基础段): ${String(err?.message ?? err)}`);
468
+ }
469
+ return () => snapshot;
470
+ })();
471
+ rolePrompt();
467
472
  let workspaceMdPath = null;
468
473
  try {
469
- workspaceMdPath = ensureSkiffSession(root, agent, roleName, role, sessionId !== void 0);
474
+ workspaceMdPath = ensureSkiffSession(root, agent, roleName, role, kind === "persistent");
470
475
  } catch (err) {
471
476
  console.warn(`[serenity-hooks] skiff 角色 "${roleName}" 专属 SESSION 处理失败(不影响 agent 创建): ${String(err?.message ?? err)}`);
472
477
  }
@@ -474,7 +479,7 @@ async function createSkiffAgent(ctx, root, roleName, role, defaultModel, session
474
479
  agent.ctx.systemPrompt.section({
475
480
  name: "serenity-skiff",
476
481
  order: -60,
477
- text: () => [buildSkiffBasePrompt(roleName, role), cccPrompt].filter(Boolean).join("\n")
482
+ text: () => [buildSkiffBasePrompt(roleName, role), rolePrompt()].filter(Boolean).join("\n")
478
483
  });
479
484
  } catch (err) {
480
485
  console.warn(`[serenity-hooks] skiff 系统提示词注册失败: ${String(err?.message ?? err)}`);
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { m as resolveInside, p as readUtf8, s as loadSerenityConfig, t as DEFAULT_SERENITY_CONFIG_PATHS } from "./ccc-DAsSHsub.js";
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, statSync } from "node:fs";
4
4
  //#region src/skiff-role.ts
5
5
  /**
6
6
  * skiff-role.ts — Skiff(F4,v1.25.0 实验性)角色层纯逻辑(零 DSH 依赖,可独立单测)
@@ -15,9 +15,11 @@ import { existsSync } from "node:fs";
15
15
  var skiff_role_exports = /* @__PURE__ */ __exportAll({
16
16
  SKIFF_SESSION_PREFIX: () => SKIFF_SESSION_PREFIX,
17
17
  buildSkiffBasePrompt: () => buildSkiffBasePrompt,
18
+ createRolePromptReader: () => createRolePromptReader,
18
19
  isSkiffSessionId: () => isSkiffSessionId,
19
20
  readSkiffRoles: () => readSkiffRoles,
20
21
  resolveRoleSystemPrompt: () => resolveRoleSystemPrompt,
22
+ resolveSkiffKind: () => resolveSkiffKind,
21
23
  roleMsmWhitelist: () => roleMsmWhitelist,
22
24
  roleToolWhitelist: () => roleToolWhitelist,
23
25
  systemPromptSource: () => systemPromptSource,
@@ -43,6 +45,7 @@ function readSkiffRoles(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
43
45
  if (name.trim() === "") continue;
44
46
  out.set(name.trim(), {
45
47
  model: typeof role.model === "string" ? role.model : void 0,
48
+ kind: role.kind === "temporary" || role.kind === "persistent" ? role.kind : void 0,
46
49
  msms: Array.isArray(role.msms) ? role.msms.filter((m) => typeof m === "string") : void 0,
47
50
  tools: Array.isArray(role.tools) ? role.tools.filter((t) => typeof t === "string") : void 0,
48
51
  trajectory: role.trajectory && typeof role.trajectory === "object" ? {
@@ -103,6 +106,65 @@ function systemPromptSource(role) {
103
106
  if (role?.systemPrompt?.trim()) return "inline";
104
107
  return "none";
105
108
  }
109
+ function resolveSkiffKind(role, hasStableId) {
110
+ const k = role?.kind;
111
+ if (k === "temporary" || k === "persistent") return k;
112
+ return hasStableId ? "persistent" : "temporary";
113
+ }
114
+ /**
115
+ * 角色提示词**读取器**(v1.30.15,方案 A:长期型热更新)。
116
+ *
117
+ * 为什么是函数而不是字符串(R↓):旧实现 `createSkiffAgent` 在创建时把
118
+ * `resolveRoleSystemPrompt(...)` 的结果**快照**进系统提示词段闭包 → CCC 改了
119
+ * `zhaocai.md` 必须重启 dsh web 才生效(本轮实证:改完 18KB 提示词,live agent 仍用旧文本)。
120
+ * 现形态返回一个读取函数,由系统提示词段的 `text()` **每轮调用**:
121
+ * - `systemPromptFile`:按 **mtime + size** 缓存重读(命中缓存零 IO;未变不解析)
122
+ * → 改文件即生效,**无需重启**;变更时打一行 info 日志(可观测,便于确认热更生效)
123
+ * - 内嵌 `systemPrompt`:静态(JSON 配置改动仍需重启/重载配置)
124
+ * - 读取失败:返回**上次成功内容**(绝不返回空——空提示词会让角色失去人格与纪律),
125
+ * 并按标签去重告警一次
126
+ * @param root CCC 根
127
+ * @param role 角色配置
128
+ */
129
+ function createRolePromptReader(root, role) {
130
+ const file = role?.systemPromptFile?.trim();
131
+ if (!file) {
132
+ const inline = role?.systemPrompt ?? "";
133
+ return () => inline;
134
+ }
135
+ let abs = null;
136
+ try {
137
+ abs = resolveInside(root, file);
138
+ } catch (err) {
139
+ console.warn(`[serenity-hooks] ✗ skiff 角色提示词路径非法(${file}): ${String(err?.message ?? err)}`);
140
+ }
141
+ if (abs === null) return () => "";
142
+ const absPath = abs;
143
+ let cachedMtime = -1;
144
+ let cachedSize = -1;
145
+ let cachedText = "";
146
+ let warned = false;
147
+ return () => {
148
+ try {
149
+ const st = statSync(absPath);
150
+ if (st.mtimeMs === cachedMtime && st.size === cachedSize) return cachedText;
151
+ const text = readUtf8(absPath).trim();
152
+ const isReload = cachedMtime !== -1;
153
+ cachedMtime = st.mtimeMs;
154
+ cachedSize = st.size;
155
+ cachedText = text;
156
+ warned = false;
157
+ if (isReload) console.info(`[serenity-hooks] ↻ skiff 角色提示词已热重载(${file},${text.length} 字符)`);
158
+ return text;
159
+ } catch (err) {
160
+ if (!warned) {
161
+ warned = true;
162
+ console.warn(`[serenity-hooks] ✗ skiff 角色提示词读取失败(${file})——沿用上次成功内容: ${String(err?.message ?? err)}`);
163
+ }
164
+ return cachedText;
165
+ }
166
+ };
167
+ }
106
168
  /**
107
169
  * Skiff 基础提示词(dsp 只给这部分;CCC 的 systemPrompt 段由调用方拼接):
108
170
  * 身份 + 可用 MSM/工具清单 + 调用协议 + 边界声明。动态生成(清单来自角色白名单)。
@@ -125,4 +187,4 @@ function buildSkiffBasePrompt(roleName, role) {
125
187
  return lines.join("\n");
126
188
  }
127
189
  //#endregion
128
- export { resolveRoleSystemPrompt as a, skiff_role_exports as c, readSkiffRoles as i, systemPromptSource as l, buildSkiffBasePrompt as n, roleMsmWhitelist as o, isSkiffSessionId as r, roleToolWhitelist as s, SKIFF_SESSION_PREFIX as t, trajectorySubset as u };
190
+ export { readSkiffRoles as a, roleMsmWhitelist as c, systemPromptSource as d, trajectorySubset as f, isSkiffSessionId as i, roleToolWhitelist as l, buildSkiffBasePrompt as n, resolveRoleSystemPrompt as o, createRolePromptReader as r, resolveSkiffKind as s, SKIFF_SESSION_PREFIX as t, skiff_role_exports as u };
@@ -43,6 +43,30 @@ export declare function roleMsmWhitelist(role: SkiffRoleConfig | undefined): Set
43
43
  export declare function resolveRoleSystemPrompt(root: string, role: SkiffRoleConfig | undefined): string;
44
44
  /** 角色系统提示词来源(validate/list 展示用) */
45
45
  export declare function systemPromptSource(role: SkiffRoleConfig | undefined): 'file' | 'inline' | 'none';
46
+ /**
47
+ * 角色类型(v1.30.15,S142 用户拍板):
48
+ * - 显式 `role.kind` 优先(`temporary` / `persistent`)
49
+ * - 缺省 = 隐式推断:调用方给出**稳定 sessionId** → persistent;否则 temporary
50
+ * (存量配置零迁移;微信桥传固定会话 id → persistent,ACP/调试页随机 id → temporary)
51
+ */
52
+ export type SkiffKind = 'temporary' | 'persistent';
53
+ export declare function resolveSkiffKind(role: SkiffRoleConfig | undefined, hasStableId: boolean): SkiffKind;
54
+ /**
55
+ * 角色提示词**读取器**(v1.30.15,方案 A:长期型热更新)。
56
+ *
57
+ * 为什么是函数而不是字符串(R↓):旧实现 `createSkiffAgent` 在创建时把
58
+ * `resolveRoleSystemPrompt(...)` 的结果**快照**进系统提示词段闭包 → CCC 改了
59
+ * `zhaocai.md` 必须重启 dsh web 才生效(本轮实证:改完 18KB 提示词,live agent 仍用旧文本)。
60
+ * 现形态返回一个读取函数,由系统提示词段的 `text()` **每轮调用**:
61
+ * - `systemPromptFile`:按 **mtime + size** 缓存重读(命中缓存零 IO;未变不解析)
62
+ * → 改文件即生效,**无需重启**;变更时打一行 info 日志(可观测,便于确认热更生效)
63
+ * - 内嵌 `systemPrompt`:静态(JSON 配置改动仍需重启/重载配置)
64
+ * - 读取失败:返回**上次成功内容**(绝不返回空——空提示词会让角色失去人格与纪律),
65
+ * 并按标签去重告警一次
66
+ * @param root CCC 根
67
+ * @param role 角色配置
68
+ */
69
+ export declare function createRolePromptReader(root: string, role: SkiffRoleConfig | undefined): () => string;
46
70
  /**
47
71
  * Skiff 基础提示词(dsp 只给这部分;CCC 的 systemPrompt 段由调用方拼接):
48
72
  * 身份 + 可用 MSM/工具清单 + 调用协议 + 边界声明。动态生成(清单来自角色白名单)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shgroup/dsh-serenity-hooks",
3
- "version": "1.30.14",
3
+ "version": "1.30.15",
4
4
  "description": "宁静号 ACC harness(Native Cordis 插件)——给 DeepSeek Harness 装一个「AI 工作区」:10 个工具(container_fs/logbook/dashboard/container_git/msm/praxis/handyman/localstore/container_admin/autopilot-trajectory)+ 机械约束(安全模式/工作区围墙/密钥守卫/对外输出守卫)+ 工作日志与原地重建 + 网页登录入口/微信桥/子角色/对外问答页/自主巡航。适配 DSH 0.1.2-rc.1。",
5
5
  "license": "MIT",
6
6
  "repository": {