@shgroup/dsh-serenity-hooks 1.26.15 → 1.26.17

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.
Files changed (61) hide show
  1. package/dsh.plugin.json +1 -1
  2. package/experiments/autotrajectory/SKILL.md +14 -8
  3. package/experiments/autotrajectory/scripts/autotrajectory-exp.ts +23 -6
  4. package/lib/acp-core.d.ts +100 -0
  5. package/lib/acp-http.d.ts +29 -0
  6. package/lib/api.d.ts +47 -0
  7. package/lib/autotrajectory.d.ts +193 -0
  8. package/lib/ccc-CfDrlfA7.js +141 -0
  9. package/lib/ccc.d.ts +162 -0
  10. package/lib/client.js +5 -0
  11. package/lib/config-ops.d.ts +166 -0
  12. package/lib/constants.d.ts +8 -0
  13. package/lib/fs-ops.d.ts +36 -0
  14. package/lib/gateway-auth.d.ts +89 -0
  15. package/lib/gateway-proxy.d.ts +39 -0
  16. package/lib/gateway.d.ts +80 -0
  17. package/lib/git-ops.d.ts +28 -0
  18. package/lib/handyman-ops.d.ts +68 -0
  19. package/lib/handyman-preset-inherit.d.ts +34 -0
  20. package/lib/index.d.ts +68 -0
  21. package/lib/index.js +10 -8
  22. package/lib/invariant.d.ts +29 -0
  23. package/lib/json.d.ts +9 -0
  24. package/lib/kit-ops.d.ts +23 -0
  25. package/lib/localstore-ops.d.ts +85 -0
  26. package/lib/msm-ops.d.ts +78 -0
  27. package/lib/output-guard-seam.d.ts +21 -0
  28. package/lib/output-guard.d.ts +68 -0
  29. package/lib/rebuild.d.ts +118 -0
  30. package/lib/seams/bootstrap.d.ts +83 -0
  31. package/lib/seams/compact.d.ts +31 -0
  32. package/lib/seams/context.d.ts +48 -0
  33. package/lib/seams/env.d.ts +16 -0
  34. package/lib/seams/guards.d.ts +72 -0
  35. package/lib/seams/keeper.d.ts +61 -0
  36. package/lib/seams/opencode-skills.d.ts +14 -0
  37. package/lib/seams/system-prompt.d.ts +113 -0
  38. package/lib/session-ops.d.ts +127 -0
  39. package/lib/settings-section.d.ts +113 -0
  40. package/lib/skiff-core.d.ts +78 -0
  41. package/lib/skiff-debug.d.ts +86 -0
  42. package/lib/skiff-registry.d.ts +25 -0
  43. package/lib/skiff-role.d.ts +50 -0
  44. package/lib/skills/opencode-scan.d.ts +27 -0
  45. package/lib/skills-discovery.d.ts +25 -0
  46. package/lib/status.d.ts +38 -0
  47. package/lib/tools/autotrajectory-exp.d.ts +24 -0
  48. package/lib/tools/cc-fs.d.ts +7 -0
  49. package/lib/tools/cce.d.ts +9 -0
  50. package/lib/tools/eap.d.ts +7 -0
  51. package/lib/tools/git.d.ts +4 -0
  52. package/lib/tools/handyman.d.ts +43 -0
  53. package/lib/tools/kit.d.ts +4 -0
  54. package/lib/tools/localstore.d.ts +8 -0
  55. package/lib/tools/msm.d.ts +4 -0
  56. package/lib/tools/neat.d.ts +5 -0
  57. package/lib/tools/rebuild.d.ts +19 -0
  58. package/lib/tools/session.d.ts +78 -0
  59. package/lib/tools/skiff-admin.d.ts +25 -0
  60. package/lib/totp.d.ts +40 -0
  61. package/package.json +6 -4
@@ -0,0 +1,141 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ //#region src/ccc.ts
4
+ /**
5
+ * ccc.ts — CCC 纯逻辑层(零 DSH 依赖,可独立单测)
6
+ *
7
+ * 职责:CCC 根检测(P1)、git 检测(P2)、serenity.json 配置读取(.opencode 规范位置,.dsh 回退)、
8
+ * 路径守卫(P3 语义)、安全模式黑名单匹配。
9
+ *
10
+ * 由 tools/ 与 seams/ 复用;逻辑移植自 dsh-serenity-plugin v0.1-v0.2
11
+ * runner(本项目自有代码,非 opencode-serenity-plugin 源码)。
12
+ */
13
+ function findSerenityRoot(cwd) {
14
+ let current = resolve(cwd);
15
+ while (true) {
16
+ const marker = resolve(current, ".serenity");
17
+ if (existsSync(marker) && statSync(marker).isFile()) return current;
18
+ const parent = dirname(current);
19
+ if (parent === current) return null;
20
+ current = parent;
21
+ }
22
+ }
23
+ function findGitRoot(cwd) {
24
+ let current = resolve(cwd);
25
+ while (true) {
26
+ if (existsSync(resolve(current, ".git"))) return current;
27
+ const parent = dirname(current);
28
+ if (parent === current) return null;
29
+ current = parent;
30
+ }
31
+ }
32
+ /**
33
+ * 前缀判定:abs 是否位于 rootAbs 之内。
34
+ * caseInsensitive(Windows 盘符/路径大小写不敏感)由调用方按平台传入;
35
+ * 边界必须是路径分隔符(`\` 或 `/` 任一)——不依赖平台 sep,跨平台语义一致:
36
+ * - 跨盘符绝对路径(root 在 D:\、target 在 C:\)前缀不匹配 → outside
37
+ * - 兄弟目录前缀陷阱(home vs home2)边界非分隔符 → outside
38
+ * (旧实现用 path.relative().startsWith('..')——跨盘时 relative 返回绝对路径原文,
39
+ * 不以 `..` 开头 → 漏判放行,见 Windows 兼容审计问题 1。)
40
+ */
41
+ function pathInside(rootAbs, abs, caseInsensitive = process.platform === "win32") {
42
+ const r = caseInsensitive ? rootAbs.toLowerCase() : rootAbs;
43
+ const a = caseInsensitive ? abs.toLowerCase() : abs;
44
+ if (a === r) return true;
45
+ if (!a.startsWith(r)) return false;
46
+ const next = a[r.length];
47
+ return next === "\\" || next === "/";
48
+ }
49
+ function classifyPath(p, root) {
50
+ const rootAbs = resolve(root);
51
+ const abs = resolve(p);
52
+ const ci = process.platform === "win32";
53
+ if (ci ? abs.toLowerCase() === rootAbs.toLowerCase() : abs === rootAbs) return "same";
54
+ return pathInside(rootAbs, abs, ci) ? "inside" : "outside";
55
+ }
56
+ function resolveInside(root, p) {
57
+ const abs = resolve(root, p);
58
+ if (classifyPath(abs, root) === "outside") throw new Error(`Path escape blocked: "${p}" resolves outside "${root}"`);
59
+ return abs;
60
+ }
61
+ /**
62
+ * 解析 handyman 配置(v1.24.0:取代 loop.defaultModel;不兼容旧 loop 配置——用户拍板)。
63
+ * @returns 白名单 + 缺省模型 + 上限;models 未配置返回 null(工具应报错要求配置)
64
+ */
65
+ function readHandymanConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
66
+ const cfg = loadSerenityConfig(root, paths);
67
+ const models = cfg.handyman?.models;
68
+ if (!Array.isArray(models) || models.length === 0) return null;
69
+ const clean = models.map((m) => m.trim()).filter(Boolean);
70
+ if (clean.length === 0) return null;
71
+ const defaultModel = cfg.handyman?.defaultModel?.trim();
72
+ return {
73
+ models: clean,
74
+ defaultModel: defaultModel !== void 0 && clean.includes(defaultModel) ? defaultModel : clean[0],
75
+ maxRounds: cfg.handyman?.maxRounds ?? 100,
76
+ maxParallel: cfg.handyman?.maxParallel ?? 10
77
+ };
78
+ }
79
+ /**
80
+ * ACC 依赖的 CCC 配置路径(S134 修正:兼容历史——历史在 .opencode,规范位置 = .opencode;
81
+ * .dsh 仅作 dsh 运行时回退,不优先)。
82
+ */
83
+ const DEFAULT_SERENITY_CONFIG_PATHS = [".opencode/serenity.json", ".dsh/serenity.json"];
84
+ /**
85
+ * 读取 UTF-8 文件并剥离 BOM(Windows 审计问题 16):PowerShell/Windows 编辑器
86
+ * 写出的 BOM(\uFEFF)会让 JSON.parse 抛错(配置静默变空)或 frontmatter 检测失败(技能被丢弃)。
87
+ */
88
+ function readUtf8(path) {
89
+ return readFileSync(path, "utf-8").replace(/^\uFEFF/, "");
90
+ }
91
+ function loadSerenityConfig(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
92
+ for (const candidate of paths) {
93
+ const p = resolve(root, candidate);
94
+ if (!existsSync(p)) continue;
95
+ try {
96
+ return JSON.parse(readUtf8(p));
97
+ } catch {
98
+ return {};
99
+ }
100
+ }
101
+ return {};
102
+ }
103
+ const SAFE_MODE_MARKER = ".serenity-safe-on";
104
+ function isSafeModeOn(root) {
105
+ return existsSync(resolve(root, SAFE_MODE_MARKER));
106
+ }
107
+ /**
108
+ * 读取黑名单(对齐 osp readBlacklist):支持两种条目格式——
109
+ * string:".secrets/" 或 "regex:^..."(前缀 / 正则)
110
+ * object:{ "pattern": ".secrets/", "message": "自定义提示" }
111
+ * dsp v1.17.4 前只支持 string(对象会被 String() 成 "[object Object]" → 规则失效,不拦截)。
112
+ */
113
+ function readBlacklist(root, paths = DEFAULT_SERENITY_CONFIG_PATHS) {
114
+ const rules = loadSerenityConfig(root, paths).safeMode?.blacklist;
115
+ if (!Array.isArray(rules)) return [];
116
+ const out = [];
117
+ for (const item of rules) if (typeof item === "string") {
118
+ if (item) out.push({ pattern: item });
119
+ } else if (item && typeof item === "object" && typeof item.pattern === "string") {
120
+ const obj = item;
121
+ const p = obj.pattern;
122
+ if (p) out.push({
123
+ pattern: p,
124
+ message: typeof obj.message === "string" ? obj.message : void 0
125
+ });
126
+ }
127
+ return out;
128
+ }
129
+ /** 匹配黑名单规则;命中返回条目,未命中返回 null。前缀匹配 / regex: 前缀(对齐 osp)。Windows:规则反斜杠归一化为正斜杠与 rel 对齐 */
130
+ function matchBlacklist(relPath, rules) {
131
+ for (const rule of rules) if (rule.pattern.startsWith("regex:")) try {
132
+ if (new RegExp(rule.pattern.slice(6)).test(relPath)) return rule;
133
+ } catch {}
134
+ else {
135
+ const pat = rule.pattern.split("\\").join("/");
136
+ if (relPath.startsWith(pat)) return rule;
137
+ }
138
+ return null;
139
+ }
140
+ //#endregion
141
+ export { findSerenityRoot as a, matchBlacklist as c, readHandymanConfig as d, readUtf8 as f, findGitRoot as i, pathInside as l, SAFE_MODE_MARKER as n, isSafeModeOn as o, resolveInside as p, classifyPath as r, loadSerenityConfig as s, DEFAULT_SERENITY_CONFIG_PATHS as t, readBlacklist as u };
package/lib/ccc.d.ts ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * ccc.ts — CCC 纯逻辑层(零 DSH 依赖,可独立单测)
3
+ *
4
+ * 职责:CCC 根检测(P1)、git 检测(P2)、serenity.json 配置读取(.opencode 规范位置,.dsh 回退)、
5
+ * 路径守卫(P3 语义)、安全模式黑名单匹配。
6
+ *
7
+ * 由 tools/ 与 seams/ 复用;逻辑移植自 dsh-serenity-plugin v0.1-v0.2
8
+ * runner(本项目自有代码,非 opencode-serenity-plugin 源码)。
9
+ */
10
+ export declare function findSerenityRoot(cwd: string): string | null;
11
+ export declare function findGitRoot(cwd: string): string | null;
12
+ export type PathClass = 'inside' | 'outside' | 'same';
13
+ /**
14
+ * 前缀判定:abs 是否位于 rootAbs 之内。
15
+ * caseInsensitive(Windows 盘符/路径大小写不敏感)由调用方按平台传入;
16
+ * 边界必须是路径分隔符(`\` 或 `/` 任一)——不依赖平台 sep,跨平台语义一致:
17
+ * - 跨盘符绝对路径(root 在 D:\、target 在 C:\)前缀不匹配 → outside
18
+ * - 兄弟目录前缀陷阱(home vs home2)边界非分隔符 → outside
19
+ * (旧实现用 path.relative().startsWith('..')——跨盘时 relative 返回绝对路径原文,
20
+ * 不以 `..` 开头 → 漏判放行,见 Windows 兼容审计问题 1。)
21
+ */
22
+ export declare function pathInside(rootAbs: string, abs: string, caseInsensitive?: boolean): boolean;
23
+ export declare function classifyPath(p: string, root: string): PathClass;
24
+ export declare function resolveInside(root: string, p: string): string;
25
+ export interface SerenityConfig {
26
+ /** handyman(杂工)配置:模型白名单(v1.24.0 取代 loop) */
27
+ handyman?: {
28
+ /** 可用模型白名单(provider/model 列表)——handyman 只能使用其中模型(用户拍板:CCC 配置控制) */
29
+ models?: string[];
30
+ /** 缺省模型(必须 ∈ models) */
31
+ defaultModel?: string;
32
+ /** 对话轮次上限(保险阀,缺省 100) */
33
+ maxRounds?: number;
34
+ /** jobs 编排并行上限(缺省 10——便宜模型便宜) */
35
+ maxParallel?: number;
36
+ };
37
+ sessionKeeper?: {
38
+ threshold?: number;
39
+ };
40
+ safeMode?: {
41
+ blacklist?: string[];
42
+ };
43
+ /** localstore git 提交策略(S134 重设计):allow 可提交 / deny 禁提交(缺省 deny) */
44
+ localstore?: {
45
+ gitTrack?: 'allow' | 'deny';
46
+ };
47
+ hooks?: {
48
+ enabled?: boolean;
49
+ injectAccContext?: boolean;
50
+ enforceSafeMode?: boolean;
51
+ sessionKeeper?: boolean;
52
+ /** 重启自动恢复最近激活的宁静号会话(session-start 时,根会话且无自身标记 → 回退最近 use 的标记) */
53
+ autoRestoreSession?: boolean;
54
+ };
55
+ /** Skiff(F4,v1.25.0 实验性)认知子集角色配置——角色归 CCC 定义(S142 用户拍板) */
56
+ skiff?: {
57
+ /** 角色定义(名 → 角色配置);缺省/空 = Skiff 未启用(零影响) */
58
+ roles?: Record<string, SkiffRoleConfig>;
59
+ };
60
+ /**
61
+ * 自主轨迹(v1.26.12 实验提案,specs self-sustaining-trajectory-hypothesis):
62
+ * CCC 定义的一条自主 trajectory——时钟唤起 + 先验偏见(自生+随机)+ 前台运行。
63
+ * 未配置或 enabled=false → 机制完全不启动(零资源占用,零影响现有功能)。
64
+ */
65
+ autotrajectory?: AutoTrajectorySettings;
66
+ }
67
+ /**
68
+ * 自主轨迹配置(CCC 定义;S142 用户拍板 v0.5):
69
+ * - 偏见内容提供者 = CCC 根目录下脚本(biasProvider,缺省 autotrajectory-bias.ts)——
70
+ * tool 直接运行取 stdout 作为偏见内容;脚本缺失 → 报错要求实现(不再经 mech-registry 注册 MSM)
71
+ * - 会话标志 = 目录名后缀 `--auto`(验证用方便):AGENT_SESSIONS/<date>--<desc>--auto/
72
+ * - **session 必填**(用户拍板:自动唤起不默认任何会话——CCC 日常有多条 trajectory 在跑,
73
+ * 未配置明确目标绝不唤起);唤起窗口避开北京时间 8~18 点(用量峰谷省钱——用户拍板)
74
+ */
75
+ export interface AutoTrajectorySettings {
76
+ /** 总开关(缺省 false——默认关,未开零资源占用) */
77
+ enabled?: boolean;
78
+ /** 无人类活动 N 小时后自动唤起(缺省 12) */
79
+ intervalHours?: number;
80
+ /** 偏见内容提供者脚本(相对 CCC 根;缺省 autotrajectory-bias.ts——缺失报错要求实现) */
81
+ biasProvider?: string;
82
+ /**
83
+ * 轨迹焦点(topPrompt,v1.26.17,用户"确保自动轨迹质量"):**CCC 定义 autotrajectory 时
84
+ * 自己填写**的顶层提示词——本轨迹的核心目标/纪律/质量要求。**每次唤起最先注入**(位于
85
+ * 身份锚定之前,影响力最大),作为稳定焦点锚定 trajectory,防止多轮自主唤起中焦点丢失
86
+ * (腐化)。区别于偏见内容(每轮随机探索方向):焦点=稳定锚,偏见=随机探索,两者互补。
87
+ * 实验经验:trajectory 在多轮中腐化严重(焦点丢失)——topPrompt 即为此而设。
88
+ */
89
+ topPrompt?: string;
90
+ /** **必填**:目标会话(S### / 目录名)——未配置绝不唤起(CCC 日常多轨迹并行,不默认) */
91
+ session?: string;
92
+ /** 避开唤起的高峰时段(北京时间,[start, end) 内不唤起;缺省 {8, 18}——用量峰谷省钱) */
93
+ avoidWakeHours?: {
94
+ start?: number;
95
+ end?: number;
96
+ };
97
+ }
98
+ /**
99
+ * Skiff 角色配置(CCC 定义:全知全能轨迹的一个子集):
100
+ * 能力面 = tools(非 MSM 工具白名单)+ msms(MSM 白名单)双白名单,白名单外全隐藏;
101
+ * 轨迹纪律面 = trajectory 子集(session/keeper/rebuild 参与项,默认全关)。
102
+ * 实验性质:未配置任何角色时 Skiff 完全零影响(无监听、无 agent 创建)。
103
+ */
104
+ export interface SkiffRoleConfig {
105
+ /** 角色模型(provider/model;CCC 直接指定,无白名单校验——用户拍板) */
106
+ model?: string;
107
+ /** MSM 白名单(独立):该角色可 exec 的 MSM 名列表;缺省空 = 无 MSM 通道 */
108
+ msms?: string[];
109
+ /** 非 MSM 工具白名单(独立):平台/ACC 工具名列表;缺省空 = 仅 MSM 通道 */
110
+ tools?: string[];
111
+ /** 轨迹纪律子集(缺省全 false = 完全独立,不参与 keeper/session/rebuild) */
112
+ trajectory?: {
113
+ session?: boolean;
114
+ keeper?: boolean;
115
+ rebuild?: boolean;
116
+ };
117
+ /** 角色系统提示词(CCC 完整定义:人格/认知边界/回答风格) */
118
+ systemPrompt?: string;
119
+ /** 角色系统提示词文件(推荐:相对 CCC 根引用 md 文件——超长提示词在 JSON 内嵌不可读;
120
+ * 优先于 systemPrompt;文件缺失/逃逸 → resolveRoleSystemPrompt 抛错) */
121
+ systemPromptFile?: string;
122
+ }
123
+ /**
124
+ * 解析 handyman 配置(v1.24.0:取代 loop.defaultModel;不兼容旧 loop 配置——用户拍板)。
125
+ * @returns 白名单 + 缺省模型 + 上限;models 未配置返回 null(工具应报错要求配置)
126
+ */
127
+ export declare function readHandymanConfig(root: string, paths?: string[]): {
128
+ models: string[];
129
+ defaultModel: string;
130
+ maxRounds: number;
131
+ maxParallel: number;
132
+ } | null;
133
+ /**
134
+ * ACC 依赖的 CCC 配置路径(S134 修正:兼容历史——历史在 .opencode,规范位置 = .opencode;
135
+ * .dsh 仅作 dsh 运行时回退,不优先)。
136
+ */
137
+ export declare const DEFAULT_SERENITY_CONFIG_PATHS: string[];
138
+ /**
139
+ * 读取 UTF-8 文件并剥离 BOM(Windows 审计问题 16):PowerShell/Windows 编辑器
140
+ * 写出的 BOM(\uFEFF)会让 JSON.parse 抛错(配置静默变空)或 frontmatter 检测失败(技能被丢弃)。
141
+ */
142
+ export declare function readUtf8(path: string): string;
143
+ export declare function loadSerenityConfig(root: string, paths?: string[]): SerenityConfig;
144
+ export declare const SAFE_MODE_MARKER = ".serenity-safe-on";
145
+ export declare function isSafeModeOn(root: string): boolean;
146
+ /** 黑名单条目(对齐 osp BlacklistEntry):pattern + 可选自定义拦截提示 message */
147
+ export interface BlacklistRule {
148
+ pattern: string;
149
+ message?: string;
150
+ }
151
+ /**
152
+ * 读取黑名单(对齐 osp readBlacklist):支持两种条目格式——
153
+ * string:".secrets/" 或 "regex:^..."(前缀 / 正则)
154
+ * object:{ "pattern": ".secrets/", "message": "自定义提示" }
155
+ * dsp v1.17.4 前只支持 string(对象会被 String() 成 "[object Object]" → 规则失效,不拦截)。
156
+ */
157
+ export declare function readBlacklist(root: string, paths?: string[]): BlacklistRule[];
158
+ /** 匹配黑名单规则;命中返回条目,未命中返回 null。前缀匹配 / regex: 前缀(对齐 osp)。Windows:规则反斜杠归一化为正斜杠与 rel 对齐 */
159
+ export declare function matchBlacklist(relPath: string, rules: BlacklistRule[]): BlacklistRule | null;
160
+ /** 写类工具名(safe-mode 下禁止) */
161
+ export declare const WRITE_TOOLS: Set<string>;
162
+ export declare function isWriteTool(toolName: string): boolean;
package/lib/client.js CHANGED
@@ -4445,6 +4445,11 @@ Instruction-following style:
4445
4445
  desc: status.biasProvider,
4446
4446
  control: null
4447
4447
  }) }),
4448
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4449
+ title: "轨迹焦点 (topPrompt)",
4450
+ desc: status.topPrompt ? status.topPrompt : "未定义——CCC 定义 autotrajectory 时应填写本轨迹核心焦点(防多轮唤起焦点丢失)",
4451
+ control: null
4452
+ }) }),
4448
4453
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCard, {
4449
4454
  title: "立即唤起",
4450
4455
  desc: wakeResult ?? "调试:手动触发一次唤起(跳过窗口/间隔,仍校验配置与偏见脚本)",
@@ -0,0 +1,166 @@
1
+ /**
2
+ * config-ops.ts — plugin 全局配置读写(结构化,`~/.dsh/serenity-hooks.json`)
3
+ *
4
+ * 归属原则(S142 用户拍板,v1.22):**plugin 是全局的,CCC 是具体的**——
5
+ * 账号密码/gateway 监听配置是 plugin 级能力,归 plugin 全局文件;
6
+ * CCC 的 localstore.json 只管 CCC 自己的凭据/配置。v1.21.x 曾把
7
+ * `serenityAdvanced` 存进 CCC localstore(归属错误 + 与 DSH settings 开关割裂),
8
+ * 本版本迁移到 plugin 全局文件(migrateLegacyLocalstore 一次性迁移)。
9
+ *
10
+ * 文件:$DSH_HOME/serenity-hooks.json(缺省 ~/.dsh/serenity-hooks.json;
11
+ * env SERENITY_HOOKS_CONFIG 可覆盖——测试/部署注入)。
12
+ * 权限:0600(含账号密码 hash,敏感)。
13
+ *
14
+ * 安全:密码仅存 scrypt hash(node:crypto 内置,零依赖);wire 层永不返回 hash
15
+ * (GET 只回 user/id,设置面板"密码"字段提交空串 = 不修改)。
16
+ */
17
+ /** 高级设定节名(localstore.json 顶层) */
18
+ export declare const ADVANCED_SECTION = "serenityAdvanced";
19
+ /** 一个外部访问账号(密码只存 hash,永不落 wire) */
20
+ export interface GatewayAccount {
21
+ /** 稳定键(UI CRUD 定位) */
22
+ id: string;
23
+ /** 登录用户名 */
24
+ user: string;
25
+ /** scrypt hash(salt:hex) */
26
+ passHash: string;
27
+ /** TOTP 第二因素 secret(v1.22.4;可选;base32 无填充。缺省/空 = 该账号仅密码登录) */
28
+ totpSecret?: string;
29
+ }
30
+ /** F1 双端口网关配置 */
31
+ export interface GatewaySettings {
32
+ enabled: boolean;
33
+ host: string;
34
+ port: number;
35
+ accounts: GatewayAccount[];
36
+ /** 外部可访问的工作区路径前缀白名单(v1.22;空数组 = 全部允许) */
37
+ workspaces: string[];
38
+ /** Cookie Secure 属性(v1.22.4;反代 TLS 时开启,明文 HTTP 下必须关) */
39
+ cookieSecure: boolean;
40
+ /** 是否允许外部(gateway)新建工作区(v1.22.4;false = workspace.create 一律 403) */
41
+ allowWorkspaceCreate: boolean;
42
+ /** 是否启用 Authenticator 第二因素(v1.22.4;false = TOTP 完全禁用——登录不要求、绑定入口隐藏) */
43
+ totpEnabled: boolean;
44
+ }
45
+ /** F2 超限重建配置 */
46
+ export interface RebuildSettings {
47
+ enabled: boolean;
48
+ /** contextPressure 触发比例(0~1) */
49
+ thresholdRatio: number;
50
+ }
51
+ /** F3 会话命名配置 */
52
+ export interface NamingSettings {
53
+ enabled: boolean;
54
+ }
55
+ /** 彩蛋功能:persona 模式(v1.23.1,S142 用户需求)
56
+ * 配置后替换 ACC 系统提示词中"输出约束/指令遵循约束"部分(EAP 块 + MSM 原则段);
57
+ * 未配置(mode 空)→ 完全默认行为,零影响。 */
58
+ export interface PersonaSettings {
59
+ /** 彩蛋模式名(显示用;空 = 彩蛋关闭) */
60
+ mode: string;
61
+ /** 用户替换文本(替代 EAP 块 + MSM 原则段的原文) */
62
+ overrideText: string;
63
+ }
64
+ /** F4d 建议问答页(v1.26.1,S142 用户:按认知容器暴露问答页供他人验证):
65
+ * 与 ACP HTTP 共用 3100 端口;key 首次启用自动生成(plugin 全局文件固定),无 key 不工作。
66
+ * v1.26.2:按容器权限控制——allowed 白名单(容器名);空 = 全部开放(向后兼容 v1.26.1 全局开放) */
67
+ export interface PublicAskSettings {
68
+ /** 访问 key(空 = 未生成;首次启用时 ensurePublicAskKey 自动生成随机 key 写回固定) */
69
+ key: string;
70
+ /** 开放容器白名单(CCC 目录名,如 home-serenity);空数组 = 全部容器开放 */
71
+ allowed: string[];
72
+ }
73
+ /** 高级设定全量(localstore.json 持久化形态;passHash 含 hash) */
74
+ export interface AdvancedSettings {
75
+ gateway: GatewaySettings;
76
+ rebuild: RebuildSettings;
77
+ naming: NamingSettings;
78
+ persona: PersonaSettings;
79
+ publicAsk: PublicAskSettings;
80
+ }
81
+ /** 默认值(工厂——每次返回新对象,防止调用方意外共享引用) */
82
+ export declare function defaultAdvancedSettings(): AdvancedSettings;
83
+ /** 生成 scrypt hash(格式 `salt:hex`);salt 16 字节随机 */
84
+ export declare function hashPassword(password: string): string;
85
+ /** 校验密码与存储 hash(timing-safe) */
86
+ export declare function verifyPassword(password: string, stored: string): boolean;
87
+ /** 全局配置文件路径:env SERENITY_HOOKS_CONFIG 覆盖(测试注入)→ $DSH_HOME → ~/.dsh */
88
+ export declare function globalConfigPath(): string;
89
+ /** 读取全局配置(文件缺失/坏 JSON → 默认值) */
90
+ export declare function readAdvancedSettings(): AdvancedSettings;
91
+ /** 写入全局配置(整体替换) */
92
+ export declare function writeAdvancedSettings(settings: AdvancedSettings): void;
93
+ /**
94
+ * 部分更新:传入 Partial,深合并到现有值。
95
+ * accounts 传入数组 → 整体替换;accounts 未传 → 保留现有。
96
+ */
97
+ export declare function updateAdvancedSettings(patch: Partial<AdvancedSettings>): AdvancedSettings;
98
+ /**
99
+ * 确保问答页访问 key 存在(首次启用自动生成 + 写回固定,用户拍板"配置时生成"):
100
+ * 读取全局配置 publicAsk.key;为空 → 生成 32 字节随机 hex(64 字符)→ 写回持久化。
101
+ * @returns 当前固定 key(已生成则返回既有值——**幂等**,不会覆盖用户手改的 key)
102
+ */
103
+ export declare function ensurePublicAskKey(): string;
104
+ /** 校验问答页访问 key(timing-safe;key 未生成 → 恒 false——没有 key 不工作) */
105
+ export declare function verifyPublicAskKey(provided: string | undefined): boolean;
106
+ /**
107
+ * 轮换问答页访问 key(v1.26.5,S142 用户:公网开放 + 不好说要换 key 呢):
108
+ * 生成新 32 字节随机 hex(64 字符)→ 覆盖写回(**强制替换**——旧 key 立即失效)。
109
+ * @returns 新 key(面板展示给管理员,重新分享给使用者)
110
+ */
111
+ export declare function rotatePublicAskKey(): string;
112
+ /** 失败锁定阈值(连续失败次数) */
113
+ export declare const PUBLIC_ASK_FAIL_THRESHOLD = 5;
114
+ /** 首次锁定基础时长(指数退避底数) */
115
+ export declare const PUBLIC_ASK_LOCK_BASE_MS: number;
116
+ /** 锁定上限 */
117
+ export declare const PUBLIC_ASK_LOCK_MAX_MS: number;
118
+ /** 重置 IP 失败状态(测试/管理员解封) */
119
+ export declare function resetPublicAskIpFail(ip: string): void;
120
+ /** IP 当前是否锁定(到期自动解锁) */
121
+ export declare function isPublicAskIpLocked(ip: string): boolean;
122
+ /** 记录一次 key 校验失败;达到阈值 → 锁定(指数退避)。返回锁定剩余毫秒(0 = 未锁定) */
123
+ export declare function recordPublicAskFail(ip: string): number;
124
+ /**
125
+ * 一次性迁移(v1.21.x → v1.22):旧版把 `serenityAdvanced` 存在 CCC localstore.json;
126
+ * 新版归 plugin 全局文件。全局文件已存在 → 跳过(幂等);localstore 无旧节 → 跳过。
127
+ * @param root - CCC 根(localstore.json 所在目录)
128
+ * @returns true = 已迁移(旧节保留在 localstore 供回滚,读取方以全局文件为准)
129
+ */
130
+ export declare function migrateLegacyLocalstore(root: string | null): boolean;
131
+ /** 账号的 wire 形态:只有 id/user + hasPassword/hasTotp(无 hash/secret) */
132
+ export interface GatewayAccountWire {
133
+ id: string;
134
+ user: string;
135
+ hasPassword: boolean;
136
+ /** 该账号是否已绑定 TOTP(v1.22.4) */
137
+ hasTotp: boolean;
138
+ }
139
+ /** 设定 wire 形态(GET /serenity/config 返回;rebuild/naming 同持久化,gateway 去 hash) */
140
+ export interface AdvancedSettingsWire {
141
+ gateway: {
142
+ enabled: boolean;
143
+ host: string;
144
+ port: number;
145
+ accounts: GatewayAccountWire[];
146
+ workspaces: string[];
147
+ cookieSecure: boolean;
148
+ allowWorkspaceCreate: boolean;
149
+ totpEnabled: boolean;
150
+ };
151
+ rebuild: RebuildSettings;
152
+ naming: NamingSettings;
153
+ persona: PersonaSettings;
154
+ publicAsk: {
155
+ /** 开放容器白名单(v1.26.2:容器名数组;空 = 全部开放) */
156
+ allowed: string[];
157
+ };
158
+ }
159
+ /** 持久化 → wire(剥离 passHash/totpSecret;只留布尔) */
160
+ export declare function toWire(settings: AdvancedSettings): AdvancedSettingsWire;
161
+ /**
162
+ * wire → 持久化(面板 PUT 用):
163
+ * accounts 元素可选带 `pass`:非空 → 重新 hash;空/缺省 → 保留现有 hash(按 id 匹配)。
164
+ * 新账号(id 不在现有)必须带非空 pass,否则抛错(无法生成 hash)。
165
+ */
166
+ export declare function applyWirePatch(wire: Partial<AdvancedSettingsWire>): AdvancedSettings;
@@ -0,0 +1,8 @@
1
+ /** 常量(纯模块,零 DSH 依赖) */
2
+ /** 插件 ID */
3
+ export declare const PLUGIN_ID = "dsh-serenity-hooks";
4
+ /**
5
+ * ACC 版本:自动从 package.json 读取(单一真相源,消除与 CHANGELOG 的漂移)。
6
+ * 发布时只需改 package.json 的 version。
7
+ */
8
+ export declare const ACC_VERSION: string;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * fs-ops.ts — cc_fs 纯操作层(零 DSH 依赖,可独立单测)
3
+ *
4
+ * 行为对齐 osp(opencode-serenity-plugin/src/fs/file-system-tool.ts)——osp 是 ACC 工具 spec:
5
+ * - rm:目录需 recursive 才删除;非空目录无 recursive → [SKIP];保护 .serenity 与 CCC 根;dry-run 预览
6
+ * - cp:目录需 recursive;dst 已存在报错;自动建父目录
7
+ * - mv:dst 已存在报错;自动建父目录
8
+ * - list/tree/exists/info/find:输出结构与 osp 一致(JSON 元数据 / 嵌套树 / glob+fuzzy 搜索)
9
+ * - touch:存在更新 mtime、不存在创建(自动建父目录);append:自动建父目录 + 返回字节数
10
+ * 保留 dsp 增强:win32 reveal 用 spawn+unref(explorer GUI 进程退出码不可靠,fire-and-forget)。
11
+ */
12
+ import type { JsonValue } from './json.js';
13
+ export type CcFsAction = 'root' | 'resolve' | 'exists' | 'list' | 'tree' | 'relative' | 'mkdir' | 'rm' | 'mv' | 'cp' | 'touch' | 'append' | 'reveal' | 'info' | 'find';
14
+ export declare const CC_FS_ACTIONS: readonly CcFsAction[];
15
+ export interface CcFsArgs {
16
+ action: CcFsAction;
17
+ path?: string;
18
+ paths?: string[];
19
+ src?: string;
20
+ dst?: string;
21
+ content?: string;
22
+ pattern?: string;
23
+ depth?: number;
24
+ dryRun?: boolean;
25
+ /** 对齐 osp:rm 删目录 / cp 复制目录需 recursive */
26
+ recursive?: boolean;
27
+ /** 对齐 osp:tree files-only / dirs-only(互斥) */
28
+ filesOnly?: boolean;
29
+ dirsOnly?: boolean;
30
+ /** 对齐 osp:find 返回绝对路径 */
31
+ absolute?: boolean;
32
+ /** 对齐 osp:find 最大递归深度(缺省不限) */
33
+ maxDepth?: number;
34
+ }
35
+ export type CcFsResult = JsonValue;
36
+ export declare function runCcFs(root: string, args: CcFsArgs): CcFsResult;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * gateway-auth.ts — F1 双端口网关:认证域纯逻辑(v1.22.4 安全加固)
3
+ *
4
+ * 从 gateway.ts 拆分(S142 熵点治理,v1.22.8):认证纯逻辑独立成模块——
5
+ * 登录验证(scrypt)/ 会话 token(滑动 TTL + 吊销)/ 失败锁定(账号维度指数退避)/
6
+ * CSRF(双提交 + Origin 校验)/ cookie 解析 / 登录页 HTML。
7
+ * gateway.ts 保留 HTTP 装配(startGateway/registerGateway)并从本模块 import。
8
+ *
9
+ * 纯函数设计(可单测):无 socket/无 HTTP 依赖,全部导出可注入测试。
10
+ */
11
+ import type { IncomingMessage } from 'node:http';
12
+ export declare function verifyGatewayLogin(accounts: readonly {
13
+ user: string;
14
+ passHash: string;
15
+ }[], user: string, password: string): boolean;
16
+ /** 会话滑动过期时长(毫秒):24h 无活动 → 失效(用户重新登录) */
17
+ export declare const SESSION_TTL_MS: number;
18
+ export interface GatewaySession {
19
+ token: string;
20
+ /** 最后活动时间(滑动续期:每次校验通过刷新) */
21
+ lastActiveAt: number;
22
+ /** 绑定账号(审计/登出定位) */
23
+ user: string;
24
+ }
25
+ /** 颁发会话 token(Hex 32 字节) */
26
+ export declare function issueToken(user: string): string;
27
+ /** 主动吊销会话(登出);返回是否确实存在 */
28
+ export declare function revokeToken(token: string): boolean;
29
+ /**
30
+ * 校验 token 有效性(滑动过期:有效则刷新 lastActiveAt)。
31
+ * @returns 有效会话或 undefined
32
+ */
33
+ export declare function validateToken(token: string | undefined): GatewaySession | undefined;
34
+ /** 失败锁定阈值:连续失败 N 次进入锁定 */
35
+ export declare const FAIL_LOCK_THRESHOLD = 5;
36
+ /** 首次锁定时长(毫秒) */
37
+ export declare const FAIL_LOCK_BASE_MS: number;
38
+ /** 锁定时长指数退避上限 */
39
+ export declare const FAIL_LOCK_MAX_MS: number;
40
+ export interface FailState {
41
+ /** 连续失败计数(成功登录后清零) */
42
+ count: number;
43
+ /** 当前锁定截止时间(0 = 未锁定) */
44
+ lockedUntil: number;
45
+ /** 已连续锁定次数(退避指数) */
46
+ lockRound: number;
47
+ }
48
+ /** 获取账号失败状态(纯逻辑,供测试注入) */
49
+ export declare function getFailState(user: string): FailState;
50
+ /** 重置失败状态(登录成功/管理员解封) */
51
+ export declare function resetFailState(user: string): void;
52
+ /** 账号当前是否锁定(含锁定到期自动解锁——到期后读取即解锁) */
53
+ export declare function isAccountLocked(user: string): boolean;
54
+ /** 记录一次失败;达到阈值 → 锁定(指数退避)。返回锁定剩余毫秒(0 = 未锁定) */
55
+ export declare function recordLoginFailure(user: string): number;
56
+ /** 剩余锁定毫秒(0 = 未锁定) */
57
+ export declare function accountLockRemaining(user: string): number;
58
+ /** CSRF token 有效期(毫秒):10 分钟窗口(扫码/多标签场景留足时间) */
59
+ export declare const CSRF_TTL_MS: number;
60
+ /** CSRF token 集合上限(防内存膨胀;超限清理最旧) */
61
+ export declare const CSRF_MAX_TOKENS = 50;
62
+ /** 生成 CSRF token(随机 32B hex)并入集合 */
63
+ export declare function newCsrfToken(): string;
64
+ /** token 是否有效(在集合且未过期;过期自动清理) */
65
+ export declare function isCsrfValid(token: string): boolean;
66
+ /** 从请求头取 CSRF token(X-CSRF-Token 或表单字段) */
67
+ export declare function csrfFromRequest(req: IncomingMessage, body: URLSearchParams): string | null;
68
+ /** 常量时间比较(token 校验用) */
69
+ export declare function safeEqual(a: string, b: string): boolean;
70
+ /**
71
+ * Origin 校验(S3):跨站 POST 被浏览器强制带 Origin(同源 GET/导航除外)。
72
+ * 允许:① 同源(Origin.host === 网关自身 host)② 主端口 loopback(127.0.0.1:mainPort)。
73
+ * 无 Origin(curl/非浏览器直连)→ 仅当携带有效 CSRF 双提交时放行(脚本无 cookie 无法伪造)。
74
+ * @returns 是否通过
75
+ */
76
+ export declare function originAllowed(originHeader: string | undefined, reqHost: string, mainPort: number): boolean;
77
+ /** 从 Cookie 头解析指定 cookie 值(纯逻辑,可单测) */
78
+ export declare function cookieValue(header: string | undefined, name: string): string | undefined;
79
+ /**
80
+ * 登录页:内嵌 HTML 无外部资源——适配任何部署。
81
+ * 移动端关键点:
82
+ * - viewport meta(防止移动浏览器按 980px 视口缩放)
83
+ * - 触控目标 ≥ 44px(Apple HIG);输入字号 ≥ 16px(iOS 聚焦不自动放大)
84
+ * - env(safe-area-inset-*) 适配刘海屏/底部手势条
85
+ * - color-scheme: dark + 明暗自适应
86
+ * @param extra - 错误提示文案
87
+ * @param csrf - CSRF token(隐藏字段随表单双提交;无 = 旧调用方兼容)
88
+ */
89
+ export declare function loginPageHtml(extra: string, csrf?: string): string;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * gateway-proxy.ts — F1 双端口网关:代理辅助纯逻辑(v1.22.1-1.22.3 稳定性修复)
3
+ *
4
+ * 从 gateway.ts 拆分(S142 熵点治理,v1.22.8):
5
+ * - crypto.randomUUID polyfill(非安全上下文修复,v1.22.0)
6
+ * - 反代请求头构造(信任栅栏修复:Host + Origin 同步改写 loopback,v1.22.1)
7
+ * - workspace 白名单过滤(响应 items 前缀过滤 + create 校验 + 403 RPC 响应,v1.22)
8
+ * - HTML polyfill 注入(幂等 marker)
9
+ * 纯函数设计(可单测);gateway.ts 装配层 import 使用。
10
+ */
11
+ /**
12
+ * 经第二端口 http://LAN-IP:3081 访问 = 非安全上下文 → DSH client 的
13
+ * `crypto.randomUUID()`(ui-conversation/service.ts 等)抛错,provider 目录加载失败。
14
+ * 用 `crypto.getRandomValues` 实现(与 DSH 官方 random-uuid.ts 同算法),
15
+ * 零改 DSH——gateway 反代 HTML 时注入。
16
+ */
17
+ export declare const RANDOM_UUID_POLYFILL = "<script>\n(function () {\n if (typeof crypto === 'undefined' || typeof crypto.randomUUID === 'function') return\n try {\n crypto.randomUUID = function () {\n var bytes = crypto.getRandomValues(new Uint8Array(16))\n bytes[6] = (bytes[6] & 0x0f) | 0x40\n bytes[8] = (bytes[8] & 0x3f) | 0x80\n var hex = Array.from(bytes, function (b) { return b.toString(16).padStart(2, '0') }).join('')\n return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20)\n }\n } catch (e) { /* getRandomValues \u4E5F\u4E0D\u53EF\u7528\u5219\u653E\u5F03 */ }\n})()\n</script>";
18
+ /**
19
+ * workspace.list 响应过滤(v1.22 白名单):
20
+ * DSH client→server RPC 全部走 HTTP JSON(`POST /api/workspace.list`,WS 仅下行推送)。
21
+ * 白名单(workspaces 路径前缀)非空 → 只保留匹配前缀的 items;
22
+ * 空 = 全部允许(默认,向后兼容)。
23
+ */
24
+ export declare function filterWorkspaceList(body: string, allowPrefixes: readonly string[]): string;
25
+ /**
26
+ * 校验 workspace.create 请求路径是否在白名单内(v1.22):
27
+ * 白名单非空且路径不匹配 → 拒绝(由调用方构造 403 RPC 响应)。
28
+ */
29
+ export declare function workspaceAllowed(allowPrefixes: readonly string[], path: string | undefined): boolean;
30
+ /** 构造 workspace.create 拒绝的 JSON RPC 响应体(code=forbidden) */
31
+ export declare function workspaceDenyResponse(rpcId: string): string;
32
+ /** 在 HTML 的 </head> 前注入 polyfill(幂等:含 marker 则跳过) */
33
+ export declare function injectPolyfillHtml(html: string): string;
34
+ /**
35
+ * 反代请求头构造(v1.22.1 信任栅栏修复,纯逻辑可测):
36
+ * DSH isTrustedApiRequest 要求 Origin.host === Host.host——Host 改写为 loopback 后
37
+ * Origin 必须同步改写(浏览器 POST 必带 Origin,透传外部地址 → 403)。
38
+ */
39
+ export declare function buildProxyHeaders(reqHeaders: Record<string, string | string[] | undefined>, mainPort: number, bodyOverride?: string): Record<string, string | number | string[]>;