@shgroup/dsh-serenity-hooks 1.26.14 → 1.26.16

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 (58) hide show
  1. package/dsh.plugin.json +1 -1
  2. package/lib/acp-core.d.ts +100 -0
  3. package/lib/acp-http.d.ts +29 -0
  4. package/lib/api.d.ts +47 -0
  5. package/lib/autotrajectory.d.ts +186 -0
  6. package/lib/ccc-CfDrlfA7.js +141 -0
  7. package/lib/ccc.d.ts +154 -0
  8. package/lib/config-ops.d.ts +166 -0
  9. package/lib/constants.d.ts +8 -0
  10. package/lib/fs-ops.d.ts +36 -0
  11. package/lib/gateway-auth.d.ts +89 -0
  12. package/lib/gateway-proxy.d.ts +39 -0
  13. package/lib/gateway.d.ts +80 -0
  14. package/lib/git-ops.d.ts +28 -0
  15. package/lib/handyman-ops.d.ts +68 -0
  16. package/lib/handyman-preset-inherit.d.ts +34 -0
  17. package/lib/index.d.ts +68 -0
  18. package/lib/index.js +2172 -1992
  19. package/lib/invariant.d.ts +29 -0
  20. package/lib/json.d.ts +9 -0
  21. package/lib/kit-ops.d.ts +23 -0
  22. package/lib/localstore-ops.d.ts +85 -0
  23. package/lib/msm-ops.d.ts +78 -0
  24. package/lib/output-guard-seam.d.ts +21 -0
  25. package/lib/output-guard.d.ts +68 -0
  26. package/lib/rebuild.d.ts +118 -0
  27. package/lib/seams/bootstrap.d.ts +83 -0
  28. package/lib/seams/compact.d.ts +31 -0
  29. package/lib/seams/context.d.ts +48 -0
  30. package/lib/seams/env.d.ts +16 -0
  31. package/lib/seams/guards.d.ts +72 -0
  32. package/lib/seams/keeper.d.ts +61 -0
  33. package/lib/seams/opencode-skills.d.ts +14 -0
  34. package/lib/seams/system-prompt.d.ts +113 -0
  35. package/lib/session-ops.d.ts +127 -0
  36. package/lib/settings-section.d.ts +113 -0
  37. package/lib/skiff-core.d.ts +78 -0
  38. package/lib/skiff-debug.d.ts +86 -0
  39. package/lib/skiff-registry.d.ts +25 -0
  40. package/lib/skiff-role.d.ts +50 -0
  41. package/lib/skills/opencode-scan.d.ts +27 -0
  42. package/lib/skills-discovery.d.ts +25 -0
  43. package/lib/status.d.ts +38 -0
  44. package/lib/tools/autotrajectory-exp.d.ts +24 -0
  45. package/lib/tools/cc-fs.d.ts +7 -0
  46. package/lib/tools/cce.d.ts +9 -0
  47. package/lib/tools/eap.d.ts +7 -0
  48. package/lib/tools/git.d.ts +4 -0
  49. package/lib/tools/handyman.d.ts +43 -0
  50. package/lib/tools/kit.d.ts +4 -0
  51. package/lib/tools/localstore.d.ts +8 -0
  52. package/lib/tools/msm.d.ts +4 -0
  53. package/lib/tools/neat.d.ts +5 -0
  54. package/lib/tools/rebuild.d.ts +19 -0
  55. package/lib/tools/session.d.ts +78 -0
  56. package/lib/tools/skiff-admin.d.ts +25 -0
  57. package/lib/totp.d.ts +40 -0
  58. package/package.json +6 -4
@@ -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[]>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * gateway.ts — F1 双端口网关(v1.21)+ 安全加固(v1.22.4)——**装配层**
3
+ *
4
+ * 需求(S142 用户拍板):dsh web 维持 127.0.0.1 主端口现状不变,**额外监听一个端口**;
5
+ * 额外端口需网页登录(账号+密码),登录后即原生 Web UI 使用;适应任何部署情况。
6
+ *
7
+ * 实现(零改 DSH):
8
+ * - 插件自起第二 node:http server(host/port 走 localstore 配置,默认 0.0.0.0:3081)
9
+ * - 未登录 → 返回内嵌极简登录页(user + pass + submit)
10
+ * - POST /serenity/login → 验证账号(localstore accounts,scrypt hash)→ Set-Cookie(HttpOnly)
11
+ * - 已登录 → 反向代理到 http://127.0.0.1:${ctx.webServer.port}
12
+ * (Host 头改写成 127.0.0.1:<主端口> → 过信任栅栏;WS upgrade 转发握手 + pipe socket)
13
+ * - token 内存表 + HttpOnly cookie(重启即失效——用户决策"重启重新登录")
14
+ *
15
+ * v1.22.4 安全加固(S142 公网审计,用户原则:不体验影响直接修/体验影响改方案/不限制 IP):
16
+ * - S5:token 带过期时间(滑动 24h)+ POST /serenity/logout 主动吊销
17
+ * - S3:登录 POST 与配置写操作校验 Origin(同源/loopback 主端口)+ 双提交 CSRF cookie
18
+ * - S2:账号维度失败锁定(5 次失败 → 15 分钟,指数退避)——不按 IP(用户要求随时随地访问)
19
+ * - S1:可选 TOTP 第二因素(账号绑定后登录需 6 位码;Authenticator 兼容,RFC 6238)
20
+ * - S7:cookieSecure 配置项(反代 TLS 时开启;默认关保持明文 HTTP 可用)
21
+ * - S9:登录失败审计日志(时间/IP/账号/原因,console.warn)
22
+ *
23
+ * v1.22.8 熵点治理(S142):认证域纯逻辑拆到 gateway-auth.ts(会话/锁定/CSRF/Origin/
24
+ * cookie/登录页),代理辅助纯逻辑拆到 gateway-proxy.ts(polyfill/headers/workspace 过滤);
25
+ * 本文件只保留 HTTP 装配(startGateway/registerGateway/readBody)——re-export 两模块
26
+ * 保持既有 import 面兼容(tests/gateway.test.ts 直接 import 两模块导出)。
27
+ */
28
+ import type { Context } from 'cordis';
29
+ import { type Server } from 'node:http';
30
+ export { RANDOM_UUID_POLYFILL, injectPolyfillHtml, filterWorkspaceList, workspaceAllowed, workspaceDenyResponse, buildProxyHeaders, } from './gateway-proxy.js';
31
+ export { loginPageHtml, verifyGatewayLogin, SESSION_TTL_MS, issueToken, revokeToken, validateToken, FAIL_LOCK_THRESHOLD, FAIL_LOCK_BASE_MS, FAIL_LOCK_MAX_MS, getFailState, resetFailState, isAccountLocked, recordLoginFailure, accountLockRemaining, newCsrfToken, isCsrfValid, csrfFromRequest, safeEqual, originAllowed, cookieValue, } from './gateway-auth.js';
32
+ declare module 'cordis' {
33
+ interface Events {
34
+ /** /serenity/config PUT(账号/监听/白名单变化)→ 强制重建 gateway(lastSig=null) */
35
+ 'serenity/config-updated'(): void;
36
+ /** DSH settings 简单配置变化(开关/阈值)→ 重新 sync(sig 判断,无实质变化不重建) */
37
+ 'serenity/settings-changed'(): void;
38
+ }
39
+ }
40
+ export interface GatewayConfig {
41
+ host: string;
42
+ port: number;
43
+ /** 登录态 cookie 名 */
44
+ cookieName: string;
45
+ /** 主端口(ctx.webServer.port) */
46
+ mainPort: number;
47
+ /** 登录失败后的重定向延迟(秒) */
48
+ loginDelayMs: number;
49
+ /** 外部可访问的工作区路径前缀白名单(v1.22;空 = 全部允许) */
50
+ allowWorkspaces?: string[];
51
+ /** Cookie Secure 属性(v1.22.4;反代 TLS 时开启;明文 HTTP 下必须关,否则 cookie 不落) */
52
+ cookieSecure?: boolean;
53
+ /** 允许外部新建工作区(v1.22.4;false = workspace.create 一律 403 RPC error) */
54
+ allowWorkspaceCreate?: boolean;
55
+ /** 启用 Authenticator 第二因素(v1.22.4;false = TOTP 完全禁用) */
56
+ totpEnabled?: boolean;
57
+ }
58
+ /**
59
+ * 启动第二监听器。返回 { server, dispose }——dispose 关 server(**不清 token**:
60
+ * token 模块级,进程重启自然清空;热重建清 token → 已登录用户 WS 断 + 重连 cookie 无效)。
61
+ * @param config - 监听/代理配置。
62
+ * @param getAccounts - 运行时读取账号列表(plugin 全局文件;gateway.enabled 开关在调用方判)。
63
+ */
64
+ export declare function startGateway(config: GatewayConfig, getAccounts: () => readonly {
65
+ id: string;
66
+ user: string;
67
+ passHash: string;
68
+ totpSecret?: string;
69
+ }[]): {
70
+ server: Server;
71
+ dispose: () => void;
72
+ };
73
+ /** 注册 gateway(index.ts apply 调用)。
74
+ * 归属原则(v1.22):gateway 是 plugin 全局能力——enabled 开关读 DSH settings
75
+ * (readSimpleSettings().gatewayEnabled),host/port/accounts 读 plugin 全局文件
76
+ * (readAdvancedSettings(),~/.dsh/serenity-hooks.json)。**不依赖任何具体 CCC**。
77
+ * 幂等 sync:仅当 配置/端口/开关 变化时重建监听器。
78
+ * 事件驱动:config-updated(/serenity/config PUT 后 emit)+ DSH settings 变化(onChange emit)触发重新 sync。
79
+ */
80
+ export declare function registerGateway(ctx: Context): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * git-ops.ts — cc_git 纯操作层(零 DSH 依赖)
3
+ *
4
+ * 行为对齐 osp(opencode-serenity-plugin/src/git/cc-git-tool.ts)——osp 是 ACC 工具 spec:
5
+ * - status:JSON {clean, files:[{status,file}], summary}
6
+ * - commit:git add -A + commit -m;clean 返回 '(nothing to commit — working tree clean)'
7
+ * - push:无 remote 报错;非快进 → [REJECTED] + 操作建议(绝不自动 force)
8
+ * - pull:git fetch + merge --ff-only;up-to-date / [REJECTED] 建议
9
+ * - log:--oneline [-n <count>]
10
+ * - diff:git diff [--cached] [<ref>] [-- <path>]
11
+ * 保留 dsp 增强:localstore git 合规联动(S134:deny 且 .gitignore 未覆盖 → status 提示 / commit 拒绝)。
12
+ */
13
+ import type { JsonValue } from './json.js';
14
+ export type GitAction = 'status' | 'commit' | 'push' | 'log' | 'pull' | 'diff';
15
+ export declare const GIT_ACTIONS: readonly GitAction[];
16
+ export interface GitArgs {
17
+ action: GitAction;
18
+ message?: string;
19
+ /** log 条数(对齐 osp 参数 n,默认 10,max 100) */
20
+ count?: number;
21
+ /** diff: 显示暂存区变更(--cached) */
22
+ staged?: boolean;
23
+ /** diff: 对比 ref(如 HEAD~1 / main / origin/main) */
24
+ ref?: string;
25
+ /** diff: 限定路径 */
26
+ path?: string;
27
+ }
28
+ export declare function runGit(root: string, args: GitArgs): JsonValue;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * handyman-ops.ts — handyman(杂工)纯逻辑层(零 DSH 依赖,可独立单测)
3
+ *
4
+ * v1.24.0:loop(牛马)→ handyman(杂工)重命名。语义对齐 osp loop:
5
+ * 进度文件(handyman-<label>.md/.json)、续跑、轮次 prompt 结构、stop token。
6
+ * 不兼容旧 loop- 进度文件(用户拍板:仅新 handyman- 前缀)。
7
+ */
8
+ export interface HandymanProgress {
9
+ round: number;
10
+ done: boolean;
11
+ label: string;
12
+ model: string;
13
+ updated: string;
14
+ lastResponse: string;
15
+ /** 对齐 osp writeFailedStatus:failed 时 done=true + status=failed + errorCode */
16
+ status?: 'running' | 'done' | 'failed';
17
+ errorCode?: string;
18
+ errorMessage?: string;
19
+ }
20
+ /** label 脱敏(Windows 审计问题 17):非法字符 → '-',去尾点/空格,限长(按码点截断,修复代理对切散 U+FFFD) */
21
+ export declare function sanitizeLabel(label: string): string;
22
+ export declare function handymanProgressPaths(root: string, label: string): {
23
+ md: string;
24
+ json: string;
25
+ };
26
+ /** 读取进度(续跑);无文件返回 round 0 */
27
+ export declare function readProgress(root: string, label: string): HandymanProgress | null;
28
+ export declare function writeProgress(root: string, label: string, p: HandymanProgress): void;
29
+ /** 失败状态落盘(对齐 osp writeFailedStatus:done=true / status=failed / errorCode) */
30
+ export declare function writeFailedStatus(root: string, label: string, info: {
31
+ errorCode: string;
32
+ errorMessage?: string;
33
+ }): void;
34
+ export declare function newStopToken(): string;
35
+ /** 解析 model 字符串(provider/model)→ {provider, model};无 / 视为 model-only */
36
+ export declare function splitModel(model: string): {
37
+ provider: string | undefined;
38
+ model: string;
39
+ };
40
+ /** 校验模型在白名单内;不在 → 抛错(用户拍板:只能使用 CCC 配置的模型) */
41
+ export declare function requireWhitelistedModel(model: string, models: string[]): void;
42
+ /** 轮次 prompt(对齐老 loop 结构:回顾进度 → 自由工作 → 汇报;S134 EAP 化:固定详尽) */
43
+ export declare function buildRoundPrompt(opts: {
44
+ root: string;
45
+ session?: string;
46
+ label: string;
47
+ round: number;
48
+ stopToken: string;
49
+ progress?: HandymanProgress | null;
50
+ task?: string;
51
+ }): string;
52
+ /**
53
+ * handyman 规模化使用指引(guide 子命令输出;S134 继承 + v1.24.0 更新):
54
+ * 使用 handyman 前必须先加载 eap 设计方案;并行策略(jobs 编排);提示词规范(详尽固定 EAP);
55
+ * 阅读/文字编写类 handyman 内部也加载 eap。
56
+ */
57
+ export declare const HANDYMAN_GUIDE = "# handyman \u2014 Scale-Up Usage Guide (guide)\n\n## \u26A0\uFE0F Before using: load eap and design the plan\nBefore calling handyman, load eap (acc-eap skill) and design the \"scale-up handyman plan\" based on the EAP framework.\n\n### 1. Task decomposition (E\u2191 Explicit)\n- Split large tasks into explicit subtasks: each subtask defines goal / input / boundaries (what to do, what not to do) / acceptance criteria\n- Make dependencies explicit: dependent tasks run serially, independent ones can run in parallel\n\n### 2. Prompt design (handyman's task parameter)\n- task must be detailed, fixed, and EAP-compliant: clear goal, drawn boundaries, decidable acceptance criteria\n- Anti-example \"handle this file\" \u2014 ambiguous; good example \"read <path>, extract all rows of the\u300C\u5173\u952E\u51B3\u7B56\u300Dtable,\n output a JSON array (fields id/conclusion/evidence), do not modify the original file\"\n- Reading/curating or text-writing work (extracting from files, summarizing, writing docs, generating text, etc.):\n the handyman-internal agent is also required to load eap and organize output per the EAP standard\n\n### 3. Model whitelist (CCC-configured, mandatory)\n- handyman only uses models listed in .opencode/serenity.json \"handyman.models\" \u2014 never arbitrary models\n- Recursive subagents inside a handyman inherit the handyman's model automatically (DSH native)\n- Keep the subagent tool instance free of a fixed agentOptions, or model inheritance breaks\n\n### 4. Parallel strategy (jobs orchestration, workflow capability)\n- Independent subtasks can run in parallel via handyman(jobs=[...]): each job gets its own label + task + stop token + progress file\n- Concurrency safety guaranteed: unique sessionId (handyman-<label>-<uuid>), progress files isolated per label\n (AGENT_SESSIONS/handyman-<label>.json) \u2014 same label resumes, different labels never interfere\n- Parallel cap: handyman.maxParallel (default 10 \u2014 cheap models are cheap)\n- Aggregation: after each parallel job produces progress, the main agent merges (or spawns one aggregation handyman)\n- For programmable pipeline/phase orchestration at scale, use the platform's workflow tool instead\n\n## Completion criteria (osp loop standard)\n- The only completion condition = the handyman-internal agent echoes this round's random verification code (stop token); dialogue round cap (default 100, osp fail-safe, forced stop beyond the cap, resumable)\n- Automatic restart on abnormal agent stop (\u2264100 restarts, anti-infinite-loop)\n\n## Waiting UI\n- The WebUI session-header Serenity detail card shows running handymen's progress (label / round / last response), one line per parallel job, ~3s refresh\n";
58
+ /** handyman 运行状态(进度文件摘要;WebUI 等待界面数据源) */
59
+ export interface HandymanRunInfo {
60
+ label: string;
61
+ round: number;
62
+ done: boolean;
63
+ model: string;
64
+ updated: string;
65
+ lastResponse: string;
66
+ }
67
+ /** 列出 AGENT_SESSIONS/handyman-*.json 的全部进度(按 updated 倒序;坏文件跳过) */
68
+ export declare function listActiveHandymen(root: string): HandymanRunInfo[];
@@ -0,0 +1,34 @@
1
+ /**
2
+ * handyman-preset-inherit.ts — handyman worker 的 preset 继承 + 工具面收窄(纯函数,可单测)
3
+ *
4
+ * v1.24.0:loop → handyman 重命名。对齐 subagent 先例(applyChildComposition →
5
+ * agentPresets.composeFrom):handyman worker 在创建窗口内 join 发起方 agent 的 standing
6
+ * preset 组合,从而获得 preset 层工具(read/write/edit 等)。
7
+ *
8
+ * **工具面收窄(用户拍板)**:worker 内部**不含 handyman 工具本身**——递归编排归主
9
+ * agent,worker 内部只走 subagent(模型继承 worker,同样低能)。实现:setup 钩子
10
+ * composeFrom 后对子 scope `tools.restrict({ deny: ['handyman'] })`(同 scope 注册的
11
+ * 规则,最近 scope 胜出——worker 看不到 handyman,主 agent 不受影响)。
12
+ *
13
+ * agentPresets 是可选服务(ctx.get 读取):
14
+ * - 无该服务 / 父未 join preset → 返回空(worker 落全局工具层 + deny handyman);
15
+ * - 有且父已 join → 返回 preset id(写入 session meta 供重建)+ setup 钩子
16
+ * (在 agent 未发布前执行 composeFrom + restrict,失败由创建事务回滚)。
17
+ *
18
+ * 仅依赖 cordis 的 Context 类型(type-only,运行时擦除)——保持可被 vitest
19
+ * 直接加载(与 ccc.ts / handyman-ops.ts 同级的纯模块约定)。
20
+ */
21
+ import type { Context } from 'cordis';
22
+ /** 从父 agent ctx 解析 preset 继承的创建期组合。 */
23
+ export interface HandymanPresetInheritance {
24
+ /** 父 agent 的 preset id(写入子 session meta.agentPreset,供持久化重建);无则不写。 */
25
+ readonly agentPreset?: string;
26
+ /** 创建 setup 钩子:子 scope join 父的 standing preset + deny handyman 工具;无 preset 继承时仅 deny。 */
27
+ readonly setup?: (childCtx: Context) => void;
28
+ }
29
+ /**
30
+ * 解析 handyman worker 从父 agent 继承的 preset,并组装创建 setup 钩子。
31
+ * @param parentCtx - 发起 handyman 的 agent 的 scope ctx;无(headless/非 agent 上下文)时为 undefined。
32
+ * @returns 继承结果:meta 用的 agentPreset 与创建 setup 钩子。
33
+ */
34
+ export declare function handymanPresetInheritance(parentCtx: Context | undefined): HandymanPresetInheritance;
package/lib/index.d.ts ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * dsh-serenity-hooks — 宁静号 ACC harness(Native Cordis 插件,DSH 运行时)
3
+ *
4
+ * 形态(DSH 官方 native hook 约定):name / inject / Config / apply,无 default export。
5
+ *
6
+ * 能力:
7
+ * 1. 真实 DSH 工具注册:cc_fs(文件系统 15 子命令,含 reveal)、session(会话全周期 7 子命令)
8
+ * 2. 拦截缝机械约束:tools/pre-execute + ctx.tools.guard(安全模式/黑名单/路径守卫)
9
+ *
10
+ * 加载:~/.dsh/config.yaml 加 insert 行(免改 DSH 源码),插件包装入 DSH node_modules。
11
+ *
12
+ * 配置:进程级 Config(cordis.yml 提供)+ 运行时读取 CCC 的 .opencode/serenity.json(规范位置,.dsh 回退)。
13
+ */
14
+ import type { Context } from 'cordis';
15
+ import z from '@deepseek-ai/schemastery';
16
+ export declare const name = "dsh-serenity-hooks";
17
+ /** 主动调用的服务;其余(agent 事件)随 harness 装配必然存在 */
18
+ export declare const inject: string[];
19
+ /** 插件配置(cordis.yml 提供;进程级) */
20
+ export interface Config {
21
+ /** CCC 配置相对路径(运行时从根读取);缺省 .opencode/serenity.json(规范)+ .dsh/serenity.json(回退) */
22
+ serenityConfigPaths?: string[];
23
+ /** 注册真实工具 */
24
+ tools?: boolean;
25
+ /** 注册拦截缝守卫 */
26
+ guards?: boolean;
27
+ /** 注册 session-keeper DCP 提醒 */
28
+ keeper?: boolean;
29
+ /** keeper 缺省阈值 */
30
+ keeperThreshold?: number;
31
+ /** 注册 ACC 上下文注入(session-start + prompt-submit) */
32
+ context?: boolean;
33
+ /** 注册压缩保留(compaction/end 后重注入 ACC 身份) */
34
+ compactRetention?: boolean;
35
+ /** 注册 HTTP 状态接口(WebUI 停靠栏) */
36
+ api?: boolean;
37
+ /** 身份注入时并入 CCC 入口 skill 内容(0 = 不注入) */
38
+ entrySkillMaxChars?: number;
39
+ /** 注册 shell.env(DSH_SERENITY_* 环境事实) */
40
+ env?: boolean;
41
+ /** 兼容 opencode skill 标准(provider:.opencode/skills 扫描注册) */
42
+ opencodeSkills?: boolean;
43
+ /** F1 双端口网关(简单配置;entry 默认值,运行时经 DSH settings) */
44
+ gateway?: {
45
+ enabled?: boolean;
46
+ };
47
+ /** F2 超限重建(entry 默认值,运行时经 DSH settings) */
48
+ rebuild?: {
49
+ enabled?: boolean;
50
+ thresholdRatio?: number;
51
+ };
52
+ /** F3 会话命名(entry 默认值,运行时经 DSH settings) */
53
+ naming?: {
54
+ enabled?: boolean;
55
+ };
56
+ /** F4 Skiff(实验性):调试服务启停(entry 默认值,运行时经 DSH settings) */
57
+ skiff?: {
58
+ enabled?: boolean;
59
+ debugPort?: number;
60
+ };
61
+ /** F4c ACP(实验性):HTTP JSON-RPC 端点启停(entry 默认值,运行时经 DSH settings) */
62
+ acp?: {
63
+ enabled?: boolean;
64
+ httpPort?: number;
65
+ };
66
+ }
67
+ export declare const Config: z<Config>;
68
+ export declare function apply(ctx: Context, config: Config): void;