@shgroup/opencode-serenity-plugin 0.2.0

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 (151) hide show
  1. package/README.md +199 -0
  2. package/bin/opencode-serenity-plugin.js +316 -0
  3. package/dist/activation.d.ts +40 -0
  4. package/dist/activation.d.ts.map +1 -0
  5. package/dist/activation.js +133 -0
  6. package/dist/activation.js.map +1 -0
  7. package/dist/bash-override.d.ts +10 -0
  8. package/dist/bash-override.d.ts.map +1 -0
  9. package/dist/bash-override.js +24 -0
  10. package/dist/bash-override.js.map +1 -0
  11. package/dist/bash-toggle.d.ts +17 -0
  12. package/dist/bash-toggle.d.ts.map +1 -0
  13. package/dist/bash-toggle.js +42 -0
  14. package/dist/bash-toggle.js.map +1 -0
  15. package/dist/config-schema.d.ts +300 -0
  16. package/dist/config-schema.d.ts.map +1 -0
  17. package/dist/config-schema.js +185 -0
  18. package/dist/config-schema.js.map +1 -0
  19. package/dist/errors.d.ts +90 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +151 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/fs/file-system-tool.d.ts +25 -0
  24. package/dist/fs/file-system-tool.d.ts.map +1 -0
  25. package/dist/fs/file-system-tool.js +318 -0
  26. package/dist/fs/file-system-tool.js.map +1 -0
  27. package/dist/fs/resolve-path.d.ts +53 -0
  28. package/dist/fs/resolve-path.d.ts.map +1 -0
  29. package/dist/fs/resolve-path.js +100 -0
  30. package/dist/fs/resolve-path.js.map +1 -0
  31. package/dist/hooks/compacting.d.ts +23 -0
  32. package/dist/hooks/compacting.d.ts.map +1 -0
  33. package/dist/hooks/compacting.js +90 -0
  34. package/dist/hooks/compacting.js.map +1 -0
  35. package/dist/hooks/permission-auto-reply.d.ts +91 -0
  36. package/dist/hooks/permission-auto-reply.d.ts.map +1 -0
  37. package/dist/hooks/permission-auto-reply.js +158 -0
  38. package/dist/hooks/permission-auto-reply.js.map +1 -0
  39. package/dist/hooks/permission-guards.d.ts +41 -0
  40. package/dist/hooks/permission-guards.d.ts.map +1 -0
  41. package/dist/hooks/permission-guards.js +153 -0
  42. package/dist/hooks/permission-guards.js.map +1 -0
  43. package/dist/hooks/shell-env.d.ts +20 -0
  44. package/dist/hooks/shell-env.d.ts.map +1 -0
  45. package/dist/hooks/shell-env.js +38 -0
  46. package/dist/hooks/shell-env.js.map +1 -0
  47. package/dist/hooks/util.d.ts +81 -0
  48. package/dist/hooks/util.d.ts.map +1 -0
  49. package/dist/hooks/util.js +172 -0
  50. package/dist/hooks/util.js.map +1 -0
  51. package/dist/index.d.ts +26 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +63 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/init/init-wizard.d.ts +39 -0
  56. package/dist/init/init-wizard.d.ts.map +1 -0
  57. package/dist/init/init-wizard.js +297 -0
  58. package/dist/init/init-wizard.js.map +1 -0
  59. package/dist/install.d.ts +117 -0
  60. package/dist/install.d.ts.map +1 -0
  61. package/dist/install.js +255 -0
  62. package/dist/install.js.map +1 -0
  63. package/dist/msm-schema.d.ts +76 -0
  64. package/dist/msm-schema.d.ts.map +1 -0
  65. package/dist/msm-schema.js +207 -0
  66. package/dist/msm-schema.js.map +1 -0
  67. package/dist/msm.d.ts +25 -0
  68. package/dist/msm.d.ts.map +1 -0
  69. package/dist/msm.js +317 -0
  70. package/dist/msm.js.map +1 -0
  71. package/dist/session/lib.d.ts +33 -0
  72. package/dist/session/lib.d.ts.map +1 -0
  73. package/dist/session/lib.js +475 -0
  74. package/dist/session/lib.js.map +1 -0
  75. package/dist/session/session-tool.d.ts +17 -0
  76. package/dist/session/session-tool.d.ts.map +1 -0
  77. package/dist/session/session-tool.js +109 -0
  78. package/dist/session/session-tool.js.map +1 -0
  79. package/dist/skills/install-skill.d.ts +36 -0
  80. package/dist/skills/install-skill.d.ts.map +1 -0
  81. package/dist/skills/install-skill.js +91 -0
  82. package/dist/skills/install-skill.js.map +1 -0
  83. package/dist/skills/template-loader.d.ts +79 -0
  84. package/dist/skills/template-loader.d.ts.map +1 -0
  85. package/dist/skills/template-loader.js +170 -0
  86. package/dist/skills/template-loader.js.map +1 -0
  87. package/dist/state.d.ts +35 -0
  88. package/dist/state.d.ts.map +1 -0
  89. package/dist/state.js +62 -0
  90. package/dist/state.js.map +1 -0
  91. package/dist/tui.d.ts +61 -0
  92. package/dist/tui.d.ts.map +1 -0
  93. package/dist/tui.js +279 -0
  94. package/dist/tui.js.map +1 -0
  95. package/dist/types/index.d.ts +30 -0
  96. package/dist/types/index.d.ts.map +1 -0
  97. package/dist/types/index.js +17 -0
  98. package/dist/types/index.js.map +1 -0
  99. package/dist/util/config-patch.d.ts +58 -0
  100. package/dist/util/config-patch.d.ts.map +1 -0
  101. package/dist/util/config-patch.js +117 -0
  102. package/dist/util/config-patch.js.map +1 -0
  103. package/dist/util/git.d.ts +29 -0
  104. package/dist/util/git.d.ts.map +1 -0
  105. package/dist/util/git.js +74 -0
  106. package/dist/util/git.js.map +1 -0
  107. package/dist/util/init-check.d.ts +22 -0
  108. package/dist/util/init-check.d.ts.map +1 -0
  109. package/dist/util/init-check.js +76 -0
  110. package/dist/util/init-check.js.map +1 -0
  111. package/dist/util/init.d.ts +54 -0
  112. package/dist/util/init.d.ts.map +1 -0
  113. package/dist/util/init.js +87 -0
  114. package/dist/util/init.js.map +1 -0
  115. package/dist/util/log.d.ts +25 -0
  116. package/dist/util/log.d.ts.map +1 -0
  117. package/dist/util/log.js +28 -0
  118. package/dist/util/log.js.map +1 -0
  119. package/dist/util/msm-call.d.ts +48 -0
  120. package/dist/util/msm-call.d.ts.map +1 -0
  121. package/dist/util/msm-call.js +86 -0
  122. package/dist/util/msm-call.js.map +1 -0
  123. package/dist/util/msm-exec-runtime.d.ts +123 -0
  124. package/dist/util/msm-exec-runtime.d.ts.map +1 -0
  125. package/dist/util/msm-exec-runtime.js +532 -0
  126. package/dist/util/msm-exec-runtime.js.map +1 -0
  127. package/dist/util/path.d.ts +10 -0
  128. package/dist/util/path.d.ts.map +1 -0
  129. package/dist/util/path.js +21 -0
  130. package/dist/util/path.js.map +1 -0
  131. package/dist/util/ready-state.d.ts +43 -0
  132. package/dist/util/ready-state.d.ts.map +1 -0
  133. package/dist/util/ready-state.js +104 -0
  134. package/dist/util/ready-state.js.map +1 -0
  135. package/dist/util/serenity-file.d.ts +30 -0
  136. package/dist/util/serenity-file.d.ts.map +1 -0
  137. package/dist/util/serenity-file.js +69 -0
  138. package/dist/util/serenity-file.js.map +1 -0
  139. package/dist/util/tui-install.d.ts +61 -0
  140. package/dist/util/tui-install.d.ts.map +1 -0
  141. package/dist/util/tui-install.js +94 -0
  142. package/dist/util/tui-install.js.map +1 -0
  143. package/docs/architecture-v0.md +294 -0
  144. package/docs/contract-v0.md +417 -0
  145. package/docs/plugin-self-contained-msm-v1.md +182 -0
  146. package/docs/refactor-direction-v1.11.md +78 -0
  147. package/docs/requirements-v0-scope.md +104 -0
  148. package/docs/requirements-v0-summary.md +108 -0
  149. package/docs/rr7-init-design.md +304 -0
  150. package/docs/v0.1-candidates.md +132 -0
  151. package/package.json +54 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * resolve-path.ts — 宁静号文件系统基础设施
3
+ *
4
+ * 核心能力:
5
+ * - 向上遍历寻找 .serenity 文件(实例根检测)
6
+ * - 基于实例根做路径解析
7
+ * - 路径安全校验(确保在根内)
8
+ *
9
+ * 供 file-system-tool.ts 消费,也可被其他模块复用。
10
+ */
11
+ /**
12
+ * findSerenityRoot — 从 cwd 开始向上遍历,寻找包含 .serenity 文件的目录
13
+ *
14
+ * @param cwd 起始目录(通常是 opencode 的 input.directory)
15
+ * @returns 包含 .serenity 的目录绝对路径
16
+ * @throws Error 如果未找到宁静号根
17
+ */
18
+ export declare function findSerenityRoot(cwd: string): string;
19
+ /**
20
+ * findSerenityRootSafe — 不抛错版本
21
+ * @returns 根路径或 null(未找到时)
22
+ */
23
+ export declare function findSerenityRootSafe(cwd: string): string | null;
24
+ /**
25
+ * readSerenityInstanceName — 从 .serenity 文件读取实例名
26
+ * @returns 实例名(如 "home-serenity", "tg-serenity")或 null
27
+ */
28
+ export declare function readSerenityInstanceName(root: string): string | null;
29
+ /**
30
+ * resolveRootPath — 将 path 基于宁静号根解析为绝对路径
31
+ *
32
+ * @param root 实例根路径
33
+ * @param path 相对路径(如 "AGENT_SESSIONS/")或绝对路径
34
+ * @returns 解析后的绝对路径
35
+ */
36
+ export declare function resolveRootPath(root: string, path: string): string;
37
+ /**
38
+ * isPathInsideSerenity — 判断路径是否在宁静号根内
39
+ *
40
+ * @param root 实例根路径
41
+ * @param target 要检查的路径(绝对路径)
42
+ * @returns true 当 target 在 root 目录内
43
+ */
44
+ export declare function isPathInsideSerenity(root: string, target: string): boolean;
45
+ /**
46
+ * serenityPathRelative — 获取 target 相对于宁静号根的路径
47
+ *
48
+ * @param root 实例根路径
49
+ * @param target 绝对路径
50
+ * @returns 相对路径(如 ".opencode/skills/eap/SKILL.md")
51
+ */
52
+ export declare function serenityPathRelative(root: string, target: string): string;
53
+ //# sourceMappingURL=resolve-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-path.d.ts","sourceRoot":"","sources":["../../src/fs/resolve-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBpD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQpE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAG1E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMzE"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * resolve-path.ts — 宁静号文件系统基础设施
3
+ *
4
+ * 核心能力:
5
+ * - 向上遍历寻找 .serenity 文件(实例根检测)
6
+ * - 基于实例根做路径解析
7
+ * - 路径安全校验(确保在根内)
8
+ *
9
+ * 供 file-system-tool.ts 消费,也可被其他模块复用。
10
+ */
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+ import { resolve, relative, dirname, normalize } from 'node:path';
13
+ /**
14
+ * findSerenityRoot — 从 cwd 开始向上遍历,寻找包含 .serenity 文件的目录
15
+ *
16
+ * @param cwd 起始目录(通常是 opencode 的 input.directory)
17
+ * @returns 包含 .serenity 的目录绝对路径
18
+ * @throws Error 如果未找到宁静号根
19
+ */
20
+ export function findSerenityRoot(cwd) {
21
+ let current = resolve(cwd);
22
+ // 向上遍历到根
23
+ while (true) {
24
+ const marker = resolve(current, '.serenity');
25
+ if (existsSync(marker)) {
26
+ return current;
27
+ }
28
+ // 到达根目录
29
+ const parent = dirname(current);
30
+ if (parent === current) {
31
+ throw new Error(`No serenity instance found: no .serenity file found when walking up from "${cwd}"`);
32
+ }
33
+ current = parent;
34
+ }
35
+ }
36
+ /**
37
+ * findSerenityRootSafe — 不抛错版本
38
+ * @returns 根路径或 null(未找到时)
39
+ */
40
+ export function findSerenityRootSafe(cwd) {
41
+ try {
42
+ return findSerenityRoot(cwd);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ /**
49
+ * readSerenityInstanceName — 从 .serenity 文件读取实例名
50
+ * @returns 实例名(如 "home-serenity", "tg-serenity")或 null
51
+ */
52
+ export function readSerenityInstanceName(root) {
53
+ const marker = resolve(root, '.serenity');
54
+ try {
55
+ const content = readFileSync(marker, 'utf8').trim();
56
+ return content || null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ /**
63
+ * resolveRootPath — 将 path 基于宁静号根解析为绝对路径
64
+ *
65
+ * @param root 实例根路径
66
+ * @param path 相对路径(如 "AGENT_SESSIONS/")或绝对路径
67
+ * @returns 解析后的绝对路径
68
+ */
69
+ export function resolveRootPath(root, path) {
70
+ if (path.startsWith('/')) {
71
+ return normalize(path);
72
+ }
73
+ return normalize(resolve(root, path));
74
+ }
75
+ /**
76
+ * isPathInsideSerenity — 判断路径是否在宁静号根内
77
+ *
78
+ * @param root 实例根路径
79
+ * @param target 要检查的路径(绝对路径)
80
+ * @returns true 当 target 在 root 目录内
81
+ */
82
+ export function isPathInsideSerenity(root, target) {
83
+ const rel = relative(root, target);
84
+ return !rel.startsWith('..');
85
+ }
86
+ /**
87
+ * serenityPathRelative — 获取 target 相对于宁静号根的路径
88
+ *
89
+ * @param root 实例根路径
90
+ * @param target 绝对路径
91
+ * @returns 相对路径(如 ".opencode/skills/eap/SKILL.md")
92
+ */
93
+ export function serenityPathRelative(root, target) {
94
+ const rel = relative(root, target);
95
+ if (rel.startsWith('..')) {
96
+ throw new Error(`Path "${target}" is outside serenity root "${root}"`);
97
+ }
98
+ return rel;
99
+ }
100
+ //# sourceMappingURL=resolve-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-path.js","sourceRoot":"","sources":["../../src/fs/resolve-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAE3B,SAAS;IACT,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC7C,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,QAAQ;QACR,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,6EAA6E,GAAG,GAAG,CACpF,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,IAAI,CAAC;QACH,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACpD,OAAO,OAAO,IAAI,IAAI,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,IAAY;IACxD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,MAAc;IAC/D,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,MAAc;IAC/D,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,+BAA+B,IAAI,GAAG,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Compacting / System Transform Hook 工厂
3
+ *
4
+ * 包含:
5
+ * 1. experimental.chat.system.transform — 注入 SKILL.md 全文到 system prompt
6
+ * 2. experimental.session.compacting — 压缩时注入"serenity 关键状态" context
7
+ *
8
+ * 设计:
9
+ * - system.transform 唯一职责:把 state.skillContent 全文 push 到 system prompt
10
+ * - 同一 session 内 system.transform 可能被多次触发(每次重建 system prompt),
11
+ * 通过检查 output.system 是否已包含 skillContent 实现 idempotent dedup(无状态)
12
+ * - compacting 保留:避免 serenity 关键状态被压缩丢失
13
+ */
14
+ import type { Hooks } from '@opencode-ai/plugin';
15
+ import { type HookConfig } from './util.js';
16
+ /** 工厂:返回 compacting / system transform / tool definition 相关的 hooks 集合
17
+ *
18
+ * v1.12: 改用 safeCreateHook(factory pattern)
19
+ * - safeHook(旧):禁用时返回 undefined(hook 不注册)
20
+ * - safeCreateHook(新):禁用时返回 no-op(hook 注册但不做事)— host 期望 hook 存在
21
+ */
22
+ export declare function createCompactingHooks(config?: HookConfig): Partial<Hooks>;
23
+ //# sourceMappingURL=compacting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compacting.d.ts","sourceRoot":"","sources":["../../src/hooks/compacting.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AA8E5D;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAsBzE"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Compacting / System Transform Hook 工厂
3
+ *
4
+ * 包含:
5
+ * 1. experimental.chat.system.transform — 注入 SKILL.md 全文到 system prompt
6
+ * 2. experimental.session.compacting — 压缩时注入"serenity 关键状态" context
7
+ *
8
+ * 设计:
9
+ * - system.transform 唯一职责:把 state.skillContent 全文 push 到 system prompt
10
+ * - 同一 session 内 system.transform 可能被多次触发(每次重建 system prompt),
11
+ * 通过检查 output.system 是否已包含 skillContent 实现 idempotent dedup(无状态)
12
+ * - compacting 保留:避免 serenity 关键状态被压缩丢失
13
+ */
14
+ import { getState, ensureReady } from '../state.js';
15
+ import { safeCreateHook } from './util.js';
16
+ const systemTransformImpl = async (_input, output) => {
17
+ try {
18
+ await ensureReady();
19
+ }
20
+ catch {
21
+ return;
22
+ }
23
+ const state = getState();
24
+ if (!state.skillContent)
25
+ return; // SKILL.md 读失败或缺失 → 跳过
26
+ // idempotent dedup:检查 output.system 是否已包含 skillContent
27
+ if (output.system.includes(state.skillContent))
28
+ return;
29
+ output.system.push(state.skillContent);
30
+ };
31
+ const sessionCompactingImpl = async (_input, output) => {
32
+ try {
33
+ await ensureReady();
34
+ }
35
+ catch {
36
+ return;
37
+ }
38
+ const state = getState();
39
+ output.context.push(`[serenity-state] cwdRoot=${state.cwdRoot}; instanceName=${state.instanceName}; skillPath=${state.skillPath}`);
40
+ };
41
+ /**
42
+ * tool.definition — 为 task tool 注入 serenity 精简上下文,
43
+ * 让 LLM 在创建 subagent 时自动把 context 传入 prompt 参数。
44
+ *
45
+ * 只劫持 toolID === 'task',其他 tool 透传。
46
+ */
47
+ const toolDefinitionImpl = async (input, output) => {
48
+ // 只处理 task tool(subagent 创建)
49
+ if (input.toolID !== 'task')
50
+ return;
51
+ try {
52
+ await ensureReady();
53
+ }
54
+ catch {
55
+ return;
56
+ }
57
+ const state = getState();
58
+ if (!state.activated || !state.instanceName)
59
+ return;
60
+ const context = [
61
+ `=== Serenity System Context ===`,
62
+ `Instance: ${state.instanceName}`,
63
+ `Root: ${state.cwdRoot}`,
64
+ ``,
65
+ `Available serenity tools for subagents:`,
66
+ ` - msm_list — discover registered MSM tools with descriptions`,
67
+ ` - msm_exec — execute an MSM by name with string array args`,
68
+ ` - file_system — safe file operations within serenity root`,
69
+ ` - session_tool — session lifecycle management`,
70
+ ``,
71
+ `IMPORTANT: Include this serenity context in the 'prompt'`,
72
+ `parameter so the subagent can use serenity tools.`,
73
+ `=== End Serenity Context ===`,
74
+ ].join('\n');
75
+ output.description = context + '\n\n' + output.description;
76
+ };
77
+ /** 工厂:返回 compacting / system transform / tool definition 相关的 hooks 集合
78
+ *
79
+ * v1.12: 改用 safeCreateHook(factory pattern)
80
+ * - safeHook(旧):禁用时返回 undefined(hook 不注册)
81
+ * - safeCreateHook(新):禁用时返回 no-op(hook 注册但不做事)— host 期望 hook 存在
82
+ */
83
+ export function createCompactingHooks(config) {
84
+ const hooks = {};
85
+ hooks['experimental.chat.system.transform'] = safeCreateHook('experimental.chat.system.transform', () => systemTransformImpl, config);
86
+ hooks['experimental.session.compacting'] = safeCreateHook('experimental.session.compacting', () => sessionCompactingImpl, config);
87
+ hooks['tool.definition'] = safeCreateHook('tool.definition', () => toolDefinitionImpl, config);
88
+ return hooks;
89
+ }
90
+ //# sourceMappingURL=compacting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compacting.js","sourceRoot":"","sources":["../../src/hooks/compacting.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,cAAc,EAAmB,MAAM,WAAW,CAAC;AAE5D,MAAM,mBAAmB,GAA6D,KAAK,EACzF,MAAM,EACN,MAAM,EACN,EAAE;IACF,IAAI,CAAC;QACH,MAAM,WAAW,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK,CAAC,YAAY;QAAE,OAAO,CAAE,uBAAuB;IAEzD,uDAAuD;IACvD,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC;QAAE,OAAO;IAEvD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;AACzC,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAA0D,KAAK,EACxF,MAAM,EACN,MAAM,EACN,EAAE;IACF,IAAI,CAAC;QACH,MAAM,WAAW,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,CAAC,OAAO,CAAC,IAAI,CACjB,4BAA4B,KAAK,CAAC,OAAO,kBAAkB,KAAK,CAAC,YAAY,eAAe,KAAK,CAAC,SAAS,EAAE,CAC9G,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,kBAAkB,GAA0C,KAAK,EACrE,KAAK,EACL,MAAM,EACN,EAAE;IACF,6BAA6B;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO;IAEpC,IAAI,CAAC;QACH,MAAM,WAAW,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,YAAY;QAAE,OAAO;IAEpD,MAAM,OAAO,GAAG;QACd,iCAAiC;QACjC,aAAa,KAAK,CAAC,YAAY,EAAE;QACjC,SAAS,KAAK,CAAC,OAAO,EAAE;QACxB,EAAE;QACF,yCAAyC;QACzC,gEAAgE;QAChE,8DAA8D;QAC9D,6DAA6D;QAC7D,iDAAiD;QACjD,EAAE;QACF,0DAA0D;QAC1D,mDAAmD;QACnD,8BAA8B;KAC/B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,CAAC,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC;AAC7D,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAmB;IACvD,MAAM,KAAK,GAAmB,EAAE,CAAC;IAEjC,KAAK,CAAC,oCAAoC,CAAC,GAAG,cAAc,CAC1D,oCAAoC,EACpC,GAAG,EAAE,CAAC,mBAAmB,EACzB,MAAM,CACP,CAAC;IAEF,KAAK,CAAC,iCAAiC,CAAC,GAAG,cAAc,CACvD,iCAAiC,EACjC,GAAG,EAAE,CAAC,qBAAqB,EAC3B,MAAM,CACP,CAAC;IAEF,KAAK,CAAC,iBAAiB,CAAC,GAAG,cAAc,CACvC,iBAAiB,EACjB,GAAG,EAAE,CAAC,kBAAkB,EACxB,MAAM,CACP,CAAC;IAEF,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Permission Auto-Reply Hook(v1.3-v2 → v1.3-v3 → v1.3-v4 演进)
3
+ *
4
+ * 监听 opencode permission 事件,**无条件 reply `"always"`**(trust opencode
5
+ * own `always` list 作为单一真相源)。
6
+ *
7
+ * v1.3-v4 决策(基于真实 payload 观测):
8
+ * - opencode 1.16+ 在推送 `permission.asked` 事件时,**`always` 字段已包含
9
+ * opencode 自己判定 "始终放行" 的 pattern 列表**(per user opencode.json 配置)
10
+ * - plugin 应**直接 reply "always"**,不需做自己的 pattern/cwdRoot check
11
+ * - 这就是用户 "装好 agent 就能放开权限" 的最简实现:opencode 自己的
12
+ * always 列表 = 单一真相源;plugin 不应重复做权限判定
13
+ * - 如果用户想要 "cwdRoot 外不弹窗" — 应该改 opencode.json#permission,
14
+ * 不应由 plugin 拦
15
+ *
16
+ * 演进历史:
17
+ * - v1.3-v2:只听 `permission.updated` 事件,事件名错(plugin 收到 0 事件)→ 不工作
18
+ * - v1.3-v3:根因修复 — 加听 `permission.asked` / `permission.v2.asked` + 解析
19
+ * v1/v2 两种 props schema + 改用 v2 client permission reply API
20
+ * - v1.3-v4:简化 — 删 "patterns 都在 cwdRoot 内" 的硬约束,改 trust
21
+ * opencode `always` 列表(v1.3-v3 已收到事件 + 已能 reply,只是多做了
22
+ * 无意义的 pattern check)
23
+ *
24
+ * 事件名与 props schema 矩阵:
25
+ * | 事件名 | type 字段 | pattern 字段 | reply API |
26
+ * |-------------------------|-----------------|-------------------------|----------------------------------------------------|
27
+ * | `permission.updated` | `type` | `pattern` (string\|[]) | v2 `client.permission.reply({requestID, reply})` |
28
+ * | `permission.asked` | `permission` | `patterns` (string[]) | 同上(v2 事件 v1 props 兼容) |
29
+ * | `permission.v2.asked` | `permission` | `patterns` (string[]) | 同上(v2 事件 v2 props) |
30
+ *
31
+ * 行为细节:
32
+ * - 仅在 plugin 激活时生效(state.activated)
33
+ * - v2 client 初始化失败 / 缺失 → 静默让 opencode 走原始 ask(不阻断)
34
+ * - reply 失败 → 静默让 opencode 走原始 ask
35
+ * - reply 成功 → opencode 自身把 pattern 加到 "always" 列表(单 session 永久放行)
36
+ *
37
+ * 与 RR5(路径守卫)的协同:
38
+ * - RR5: tool.execute.before 防御 cwdRoot 外 throw(plugin 层 hard stop)
39
+ * - 本 hook: 对 opencode 弹窗一律 reply "always"(trust opencode 配置)
40
+ * - 两者无冲突:RR5 拦的是"已经到达 plugin 的危险调用",本 hook 处理
41
+ * "opencode 想弹窗询问 user 之前"的阶段
42
+ */
43
+ /** v2 client 简化类型(仅本 hook 用到的部分) */
44
+ type V2Client = {
45
+ permission: {
46
+ reply: (params: {
47
+ requestID: string;
48
+ directory?: string;
49
+ workspace?: string;
50
+ reply?: 'once' | 'always' | 'reject';
51
+ message?: string;
52
+ }) => Promise<unknown>;
53
+ respond: (params: {
54
+ requestID: string;
55
+ reply?: 'once' | 'always' | 'reject';
56
+ message?: string;
57
+ }) => Promise<unknown>;
58
+ };
59
+ session: {
60
+ permission: {
61
+ reply: (params: {
62
+ sessionID: string;
63
+ requestID: string;
64
+ reply?: 'once' | 'always' | 'reject';
65
+ message?: string;
66
+ }) => Promise<unknown>;
67
+ };
68
+ };
69
+ };
70
+ export interface PermissionAutoReplyDeps {
71
+ /** plugin input.serverUrl(URL 对象)— 用于 v2 createOpencodeClient({baseUrl}) */
72
+ getServerUrl: () => URL | null;
73
+ /** 工厂注入点:测试可注入 mock createOpencodeClient(避免真实 HTTP) */
74
+ createV2Client?: (config: {
75
+ baseUrl: string;
76
+ }) => V2Client;
77
+ }
78
+ /** Event union 简化版 */
79
+ type EventLike = {
80
+ type: string;
81
+ properties?: unknown;
82
+ };
83
+ /**
84
+ * event hook 处理器
85
+ * 收到 opencode event,识别 permission 事件并自动 reply
86
+ */
87
+ export declare function createPermissionAutoReplyHandler(deps: PermissionAutoReplyDeps): (input: {
88
+ event: EventLike;
89
+ }) => Promise<void>;
90
+ export {};
91
+ //# sourceMappingURL=permission-auto-reply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-auto-reply.d.ts","sourceRoot":"","sources":["../../src/hooks/permission-auto-reply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAMH,oCAAoC;AACpC,KAAK,QAAQ,GAAG;IACd,UAAU,EAAE;QACV,KAAK,EAAE,CAAC,MAAM,EAAE;YACd,SAAS,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;YACrC,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;QACvB,OAAO,EAAE,CAAC,MAAM,EAAE;YAChB,SAAS,EAAE,MAAM,CAAC;YAClB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;YACrC,OAAO,CAAC,EAAE,MAAM,CAAC;SAClB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KACxB,CAAC;IACF,OAAO,EAAE;QACP,UAAU,EAAE;YACV,KAAK,EAAE,CAAC,MAAM,EAAE;gBACd,SAAS,EAAE,MAAM,CAAC;gBAClB,SAAS,EAAE,MAAM,CAAC;gBAClB,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;gBACrC,OAAO,CAAC,EAAE,MAAM,CAAC;aAClB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;SACxB,CAAC;KACH,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,YAAY,EAAE,MAAM,GAAG,GAAG,IAAI,CAAC;IAC/B,uDAAuD;IACvD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,QAAQ,CAAC;CAC5D;AAqBD,sBAAsB;AACtB,KAAK,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AASxD;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,uBAAuB,GAC5B,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAsGhD"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Permission Auto-Reply Hook(v1.3-v2 → v1.3-v3 → v1.3-v4 演进)
3
+ *
4
+ * 监听 opencode permission 事件,**无条件 reply `"always"`**(trust opencode
5
+ * own `always` list 作为单一真相源)。
6
+ *
7
+ * v1.3-v4 决策(基于真实 payload 观测):
8
+ * - opencode 1.16+ 在推送 `permission.asked` 事件时,**`always` 字段已包含
9
+ * opencode 自己判定 "始终放行" 的 pattern 列表**(per user opencode.json 配置)
10
+ * - plugin 应**直接 reply "always"**,不需做自己的 pattern/cwdRoot check
11
+ * - 这就是用户 "装好 agent 就能放开权限" 的最简实现:opencode 自己的
12
+ * always 列表 = 单一真相源;plugin 不应重复做权限判定
13
+ * - 如果用户想要 "cwdRoot 外不弹窗" — 应该改 opencode.json#permission,
14
+ * 不应由 plugin 拦
15
+ *
16
+ * 演进历史:
17
+ * - v1.3-v2:只听 `permission.updated` 事件,事件名错(plugin 收到 0 事件)→ 不工作
18
+ * - v1.3-v3:根因修复 — 加听 `permission.asked` / `permission.v2.asked` + 解析
19
+ * v1/v2 两种 props schema + 改用 v2 client permission reply API
20
+ * - v1.3-v4:简化 — 删 "patterns 都在 cwdRoot 内" 的硬约束,改 trust
21
+ * opencode `always` 列表(v1.3-v3 已收到事件 + 已能 reply,只是多做了
22
+ * 无意义的 pattern check)
23
+ *
24
+ * 事件名与 props schema 矩阵:
25
+ * | 事件名 | type 字段 | pattern 字段 | reply API |
26
+ * |-------------------------|-----------------|-------------------------|----------------------------------------------------|
27
+ * | `permission.updated` | `type` | `pattern` (string\|[]) | v2 `client.permission.reply({requestID, reply})` |
28
+ * | `permission.asked` | `permission` | `patterns` (string[]) | 同上(v2 事件 v1 props 兼容) |
29
+ * | `permission.v2.asked` | `permission` | `patterns` (string[]) | 同上(v2 事件 v2 props) |
30
+ *
31
+ * 行为细节:
32
+ * - 仅在 plugin 激活时生效(state.activated)
33
+ * - v2 client 初始化失败 / 缺失 → 静默让 opencode 走原始 ask(不阻断)
34
+ * - reply 失败 → 静默让 opencode 走原始 ask
35
+ * - reply 成功 → opencode 自身把 pattern 加到 "always" 列表(单 session 永久放行)
36
+ *
37
+ * 与 RR5(路径守卫)的协同:
38
+ * - RR5: tool.execute.before 防御 cwdRoot 外 throw(plugin 层 hard stop)
39
+ * - 本 hook: 对 opencode 弹窗一律 reply "always"(trust opencode 配置)
40
+ * - 两者无冲突:RR5 拦的是"已经到达 plugin 的危险调用",本 hook 处理
41
+ * "opencode 想弹窗询问 user 之前"的阶段
42
+ */
43
+ import { createOpencodeClient } from '@opencode-ai/sdk/v2';
44
+ import { log } from '../util/log.js';
45
+ import { getState } from '../state.js';
46
+ /** 三种要监听的事件名 */
47
+ const WATCHED_EVENT_TYPES = new Set([
48
+ 'permission.updated', // v1 事件
49
+ 'permission.asked', // v2 事件(v1 兼容)
50
+ 'permission.v2.asked', // v2 事件(v2 风格)
51
+ ]);
52
+ /**
53
+ * event hook 处理器
54
+ * 收到 opencode event,识别 permission 事件并自动 reply
55
+ */
56
+ export function createPermissionAutoReplyHandler(deps) {
57
+ // 缓存 v2 client(lazy 初始化 + 单例)
58
+ let v2ClientCache = null;
59
+ let v2ClientInitFailed = false;
60
+ function getV2Client() {
61
+ if (v2ClientCache)
62
+ return v2ClientCache;
63
+ if (v2ClientInitFailed)
64
+ return null;
65
+ const serverUrl = deps.getServerUrl();
66
+ if (!serverUrl) {
67
+ log.warn('perm-reply', 'serverUrl unavailable; v2 client not initialized');
68
+ v2ClientInitFailed = true;
69
+ return null;
70
+ }
71
+ try {
72
+ const factory = deps.createV2Client ?? ((config) => createOpencodeClient(config));
73
+ v2ClientCache = factory({ baseUrl: serverUrl.toString() });
74
+ log.debug('perm-reply', 'v2 client initialized', { baseUrl: serverUrl.toString() });
75
+ return v2ClientCache;
76
+ }
77
+ catch (err) {
78
+ log.warn('perm-reply', 'v2 client init failed; auto-reply disabled', { err: String(err) });
79
+ v2ClientInitFailed = true;
80
+ return null;
81
+ }
82
+ }
83
+ return async (input) => {
84
+ const event = input.event;
85
+ if (!event || !WATCHED_EVENT_TYPES.has(event.type))
86
+ return;
87
+ const state = getState();
88
+ if (!state.activated)
89
+ return;
90
+ // 解析事件 props(v1 vs v2 两种 schema)
91
+ const isV2Event = event.type === 'permission.asked' || event.type === 'permission.v2.asked';
92
+ let toolName;
93
+ let patterns;
94
+ let requestId;
95
+ let alwaysList = [];
96
+ if (isV2Event) {
97
+ const props = event.properties;
98
+ if (!props || !props.id || !props.sessionID) {
99
+ log.warn('perm-reply', 'v2 event missing id/sessionID', { type: event.type });
100
+ return;
101
+ }
102
+ toolName = props.permission;
103
+ patterns = props.patterns;
104
+ requestId = props.id;
105
+ alwaysList = props.always ?? [];
106
+ }
107
+ else {
108
+ // v1 event
109
+ const props = event.properties;
110
+ if (!props || !props.id) {
111
+ log.warn('perm-reply', 'v1 event missing id', { type: event.type });
112
+ return;
113
+ }
114
+ toolName = props.type;
115
+ const pattern = props.pattern;
116
+ patterns = Array.isArray(pattern) ? pattern : pattern ? [pattern] : [];
117
+ requestId = props.id;
118
+ }
119
+ // v1.3-v4 决策(基于 /tmp/serenity-plugin.log 真实 payload):
120
+ // opencode 1.16+ 推送 `always: ["*"]` 时 = opencode 自己已知道 "始终放行" 列表匹配
121
+ // → plugin 应**直接 reply "always"**,不需做 pattern check
122
+ // 这是用户"装好 agent 就能放开权限"的最简实现:opencode 自己的 always 列表是单一真相源
123
+ // 如果用户想要"cwdRoot 外不弹窗"——应该让 opencode 的 permission 配置接管(而不是 plugin 拦)
124
+ log.info('perm-reply', 'replying always (v1.3-v4: trust opencode always list)', {
125
+ eventType: event.type,
126
+ tool: toolName,
127
+ patterns,
128
+ always: alwaysList,
129
+ requestID: requestId,
130
+ });
131
+ // 用 v2 client reply "always"
132
+ const client = getV2Client();
133
+ if (!client)
134
+ return;
135
+ try {
136
+ // v2 SDK 实际 API:client.permission.reply({requestID, reply}) — 无需 sessionID
137
+ // (v2 Permission class 旧 `respond({sessionID, permissionID, response})` 已 deprecated)
138
+ await client.permission.reply({
139
+ requestID: requestId,
140
+ reply: 'always',
141
+ });
142
+ log.info('perm-reply', 'auto-replied always', {
143
+ eventType: event.type,
144
+ tool: toolName,
145
+ patterns,
146
+ requestID: requestId,
147
+ });
148
+ }
149
+ catch (err) {
150
+ log.warn('perm-reply', 'reply failed; falling back to opencode ask', {
151
+ eventType: event.type,
152
+ requestID: requestId,
153
+ err: String(err),
154
+ });
155
+ }
156
+ };
157
+ }
158
+ //# sourceMappingURL=permission-auto-reply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-auto-reply.js","sourceRoot":"","sources":["../../src/hooks/permission-auto-reply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Permission Guards Hook 工厂
3
+ *
4
+ * 包含:tool.execute.before hook
5
+ * 职责(RR5 + v1.6 补全):
6
+ * 1. read / edit / write 工具的 path 字段强制在 cwdRoot 内(RR5 hard block;v1.6 加 edit/write)
7
+ * 2. symlink 防御(v1.6 扩展 v1-1 msm-schema 的 realpath 逻辑到 tool.execute.before)
8
+ *
9
+ * 注意:bash 禁令于 2026-06-08 移除(旧 RR3),改用运行时开关:
10
+ * TUI slash command /serenity-bash-on|off|status 控制,通过文件 IPC 通信,
11
+ * 在 tool.execute.before 中做静默拒绝。默认 bash 启用。
12
+ *
13
+ * 设计:v1.6 RR5 补全 = plugin **接管**权限管理(不再依赖 opencode.json 静态 allow)
14
+ * - 主仓 opencode.json 已复原 read/edit = "ask"(commit fe19b5e)
15
+ * - LLM 在 cwdRoot 内时:v1.3-v2 auto-reply 仍处理 "ask" 弹窗
16
+ * - LLM 在 cwdRoot 外时:plugin hard block 早于 opencode 弹窗生效
17
+ *
18
+ * L3 验证:单 hook 抛错会中断整条 Effect 链(plugin/index.ts:286-299),
19
+ * 所以抛错已被 safeHook 包装为 silent(util.ts)
20
+ */
21
+ import type { Hooks } from '@opencode-ai/plugin';
22
+ import { type HookConfig } from './util.js';
23
+ type ToolArgs = Record<string, unknown>;
24
+ /**
25
+ * v1.6 字段名提取:
26
+ * - 显式列表:command, path, filePath, file, cwd(v0.1-3 已有)
27
+ * - 启发式后缀:*path* / *file* / *dir* / *Path* / *File* / *Dir*(v1.6 加)
28
+ * 覆盖:targetPath / sourceFilePath / outputDir / newPath / etc.
29
+ */
30
+ export declare function extractPathsFromArgs(args: ToolArgs): string[];
31
+ /** 工厂:返回 permission guards 相关的 hooks 集合
32
+ *
33
+ * v1.6 关键修正:permission-guards **不**走 safeHook。
34
+ * 原因:safeHook 的"silent 策略"会吞掉 throw,导致 RR5 hard block 失效。
35
+ * L3 验证:单 hook 抛错中断整条 Effect 链(plugin/index.ts:286-299)——
36
+ * 这正是 RR5 hard block 想要的行为,所以应当让它**传播**给 opencode。
37
+ * 其他 hook(compacting / shell-env)保留 safeHook(throw 不应硬中断)。
38
+ */
39
+ export declare function createPermissionGuards(config?: HookConfig): Partial<Hooks>;
40
+ export {};
41
+ //# sourceMappingURL=permission-guards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-guards.d.ts","sourceRoot":"","sources":["../../src/hooks/permission-guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAKjD,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAI3D,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExC;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,CAuB7D;AAuFD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,CAO1E"}