@openclaw/feishu 2026.9.8 → 2026.10.1-beta.1

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 (225) hide show
  1. package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
  2. package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
  3. package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
  4. package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
  5. package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
  6. package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
  7. package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
  8. package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
  9. package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
  10. package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
  11. package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
  12. package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
  13. package/dist/api.js +234 -297
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/doctor-contract-api.js +2 -2
  16. package/dist/session-binding-contract-api.js +1 -1
  17. package/dist/setup-api.js +1 -1
  18. package/dist/setup-entry.js +1 -1
  19. package/dist/subagent-hooks-api.js +1 -1
  20. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
  21. package/node_modules/@openclaw/fs-safe/README.md +70 -379
  22. package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
  23. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
  24. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  25. package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
  26. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  27. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
  28. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
  29. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
  30. package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
  31. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
  32. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
  33. package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
  34. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
  36. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
  38. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
  39. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
  41. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
  42. package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
  43. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
  44. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
  45. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
  46. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
  47. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
  48. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
  52. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
  53. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
  54. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
  55. package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
  56. package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
  57. package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
  58. package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
  59. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
  60. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
  61. package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
  62. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
  63. package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
  64. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
  65. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
  66. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
  67. package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
  68. package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
  69. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
  70. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
  71. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
  72. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
  73. package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
  77. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
  78. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
  79. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
  81. package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
  82. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
  84. package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
  85. package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
  86. package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
  87. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
  88. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
  89. package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
  90. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
  92. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
  93. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
  94. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
  95. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
  96. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
  97. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
  98. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
  99. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
  100. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
  101. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
  102. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
  103. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
  104. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
  105. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
  106. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
  107. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
  108. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
  109. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
  110. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
  111. package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
  112. package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
  113. package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
  114. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
  115. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
  116. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
  117. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
  118. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
  119. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
  120. package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
  121. package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
  122. package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
  123. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
  124. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
  125. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
  126. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
  127. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
  128. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
  129. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
  130. package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
  131. package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
  132. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
  134. package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
  135. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
  137. package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
  139. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
  140. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
  141. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
  142. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
  143. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
  144. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
  145. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
  146. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
  147. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
  148. package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
  149. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
  150. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
  151. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
  152. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
  153. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
  154. package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
  155. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  156. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
  157. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
  158. package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
  159. package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
  160. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
  161. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
  162. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
  163. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
  164. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
  165. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
  166. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
  167. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
  168. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
  170. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
  172. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
  174. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
  175. package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
  176. package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
  177. package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
  178. package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
  179. package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
  180. package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
  181. package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
  182. package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
  183. package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
  184. package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
  185. package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
  186. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
  187. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
  188. package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
  189. package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
  190. package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
  191. package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
  192. package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
  193. package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
  194. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
  196. package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
  197. package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
  198. package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
  199. package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
  200. package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
  201. package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
  202. package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
  203. package/node_modules/@openclaw/fs-safe/package.json +10 -10
  204. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  205. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  206. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  207. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  208. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  209. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  210. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  211. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  212. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  213. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  214. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  215. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  216. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  217. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  218. package/package.json +5 -5
  219. package/skills/feishu-wiki/SKILL.md +1 -1
  220. package/dist/.setup/accounts-wRqItHug.mjs +0 -206
  221. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
  222. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
  223. package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
  224. package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
  225. package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
@@ -1,206 +0,0 @@
1
- import { s as __exportAll } from "./doctor-contract-3XC0vbIw.mjs";
2
- import { DEFAULT_ACCOUNT_ID, createAccountListHelpers, hasConfiguredAccountValue, normalizeAccountId, normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-resolution";
3
- import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
- import { coerceSecretRef } from "openclaw/plugin-sdk/provider-auth";
5
- import { canResolveEnvSecretRefInReadOnlyPath } from "openclaw/plugin-sdk/secret-ref-readonly";
6
- //#region extensions/feishu/src/accounts.ts
7
- var accounts_exports = /* @__PURE__ */ __exportAll({
8
- FeishuSecretRefUnavailableError: () => FeishuSecretRefUnavailableError,
9
- inspectFeishuCredentials: () => inspectFeishuCredentials,
10
- listEnabledFeishuAccounts: () => listEnabledFeishuAccounts,
11
- listFeishuAccountIds: () => listFeishuAccountIds,
12
- mergeFeishuAccountConfig: () => mergeFeishuAccountConfig,
13
- resolveDefaultFeishuAccountId: () => resolveDefaultFeishuAccountId,
14
- resolveDefaultFeishuAccountSelection: () => resolveDefaultFeishuAccountSelection,
15
- resolveFeishuAccount: () => resolveFeishuAccount,
16
- resolveFeishuCredentials: () => resolveFeishuCredentials,
17
- resolveFeishuRuntimeAccount: () => resolveFeishuRuntimeAccount
18
- });
19
- const { listAccountIds: listFeishuAccountIds, resolveDefaultAccountId: resolveDefaultFeishuAccountId, resolveAccountConfig: resolveMergedFeishuAccountConfig } = createAccountListHelpers("feishu", {
20
- allowUnlistedDefaultAccount: true,
21
- omitKeys: ["defaultAccount"],
22
- nestedObjectKeys: ["tools"],
23
- hasImplicitDefaultAccount: (cfg) => {
24
- const feishu = cfg.channels?.feishu;
25
- return hasConfiguredAccountValue(feishu?.appId) && hasConfiguredAccountValue(feishu?.appSecret);
26
- }
27
- });
28
- function formatSecretRefLabel(ref) {
29
- return `${ref.source}:${ref.provider}:${ref.id}`;
30
- }
31
- var FeishuSecretRefUnavailableError = class extends Error {
32
- constructor(path, ref) {
33
- super(`${path}: unresolved SecretRef "${formatSecretRefLabel(ref)}". Resolve this command against an active gateway runtime snapshot before reading it.`);
34
- this.name = "FeishuSecretRefUnavailableError";
35
- this.path = path;
36
- }
37
- };
38
- function resolveFeishuSecretLike(params) {
39
- const asString = normalizeOptionalString(params.value);
40
- if (asString) return asString;
41
- const ref = coerceSecretRef(params.value, params.cfg?.secrets?.defaults);
42
- if (!ref) return;
43
- if (params.mode === "inspect") {
44
- if (ref.source === "env" && canResolveEnvSecretRefInReadOnlyPath({
45
- cfg: params.cfg,
46
- provider: ref.provider,
47
- id: ref.id
48
- })) return normalizeOptionalString(process.env[ref.id]);
49
- return;
50
- }
51
- throw new FeishuSecretRefUnavailableError(params.path, ref);
52
- }
53
- function resolveFeishuBaseCredentials(cfg, mode, rootConfig) {
54
- const appId = resolveFeishuSecretLike({
55
- cfg: rootConfig,
56
- value: cfg?.appId,
57
- path: "channels.feishu.appId",
58
- mode
59
- });
60
- const appSecret = resolveFeishuSecretLike({
61
- cfg: rootConfig,
62
- value: cfg?.appSecret,
63
- path: "channels.feishu.appSecret",
64
- mode
65
- });
66
- if (!appId || !appSecret) return null;
67
- return {
68
- appId,
69
- appSecret,
70
- domain: cfg?.domain?.replace(/^https:/i, "https:") ?? "feishu"
71
- };
72
- }
73
- function resolveFeishuEventSecrets(cfg, mode, rootConfig) {
74
- return {
75
- encryptKey: (cfg?.connectionMode ?? "websocket") === "webhook" ? resolveFeishuSecretLike({
76
- cfg: rootConfig,
77
- value: cfg?.encryptKey,
78
- path: "channels.feishu.encryptKey",
79
- mode
80
- }) : normalizeOptionalString(cfg?.encryptKey),
81
- verificationToken: resolveFeishuSecretLike({
82
- cfg: rootConfig,
83
- value: cfg?.verificationToken,
84
- path: "channels.feishu.verificationToken",
85
- mode
86
- })
87
- };
88
- }
89
- /**
90
- * Resolve the default account selection and its source.
91
- */
92
- function resolveDefaultFeishuAccountSelection(cfg) {
93
- const preferred = normalizeOptionalAccountId((cfg.channels?.feishu)?.defaultAccount);
94
- if (preferred) return {
95
- accountId: preferred,
96
- source: "explicit-default"
97
- };
98
- const ids = listFeishuAccountIds(cfg);
99
- if (ids.includes(DEFAULT_ACCOUNT_ID)) return {
100
- accountId: DEFAULT_ACCOUNT_ID,
101
- source: "mapped-default"
102
- };
103
- return {
104
- accountId: ids[0] ?? DEFAULT_ACCOUNT_ID,
105
- source: "fallback"
106
- };
107
- }
108
- /**
109
- * Merge top-level config with account-specific config.
110
- * Account-specific fields override top-level fields.
111
- */
112
- function mergeFeishuAccountConfig(cfg, accountId) {
113
- const feishuCfg = cfg.channels?.feishu;
114
- const merged = resolveMergedFeishuAccountConfig(cfg, accountId);
115
- const topTools = feishuCfg?.tools;
116
- if (merged.tools === void 0 && topTools !== void 0) return {
117
- ...merged,
118
- tools: topTools
119
- };
120
- if (topTools?.bitable === false) return {
121
- ...merged,
122
- tools: {
123
- ...merged.tools,
124
- bitable: false
125
- }
126
- };
127
- return merged;
128
- }
129
- /**
130
- * Resolve Feishu credentials from a config.
131
- */
132
- function resolveFeishuCredentials(cfg, options) {
133
- const mode = options?.mode ?? "strict";
134
- const base = resolveFeishuBaseCredentials(cfg, mode, options?.rootConfig);
135
- if (!base) return null;
136
- const eventSecrets = resolveFeishuEventSecrets(cfg, mode, options?.rootConfig);
137
- return {
138
- ...base,
139
- ...eventSecrets
140
- };
141
- }
142
- function inspectFeishuCredentials(cfg, rootConfig) {
143
- return resolveFeishuCredentials(cfg, {
144
- mode: "inspect",
145
- rootConfig
146
- });
147
- }
148
- function buildResolvedFeishuAccount(params) {
149
- const hasExplicitAccountId = typeof params.accountId === "string" && params.accountId.trim() !== "";
150
- const defaultSelection = hasExplicitAccountId ? null : resolveDefaultFeishuAccountSelection(params.cfg);
151
- const accountId = hasExplicitAccountId ? normalizeAccountId(params.accountId) : defaultSelection?.accountId ?? DEFAULT_ACCOUNT_ID;
152
- const selectionSource = hasExplicitAccountId ? "explicit" : defaultSelection?.source ?? "fallback";
153
- const baseEnabled = (params.cfg.channels?.feishu)?.enabled !== false;
154
- const merged = mergeFeishuAccountConfig(params.cfg, accountId);
155
- const accountEnabled = merged.enabled !== false;
156
- const enabled = baseEnabled && accountEnabled;
157
- const baseCreds = resolveFeishuBaseCredentials(merged, params.baseMode, params.cfg);
158
- const eventSecrets = resolveFeishuEventSecrets(merged, params.eventSecretMode, params.cfg);
159
- const accountName = merged.name;
160
- return {
161
- accountId,
162
- selectionSource,
163
- enabled,
164
- configured: Boolean(baseCreds),
165
- name: typeof accountName === "string" ? accountName.trim() || void 0 : void 0,
166
- appId: baseCreds?.appId,
167
- appSecret: baseCreds?.appSecret,
168
- encryptKey: eventSecrets.encryptKey,
169
- verificationToken: eventSecrets.verificationToken,
170
- domain: baseCreds?.domain ?? "feishu",
171
- config: merged
172
- };
173
- }
174
- /**
175
- * Resolve a read-only Feishu account snapshot for CLI/config surfaces.
176
- * Unresolved SecretRefs are treated as unavailable instead of throwing.
177
- */
178
- function resolveFeishuAccount(params) {
179
- return buildResolvedFeishuAccount({
180
- ...params,
181
- baseMode: "inspect",
182
- eventSecretMode: "inspect"
183
- });
184
- }
185
- /**
186
- * Resolve a runtime Feishu account.
187
- * Required app credentials stay strict; event-only secrets can be required by callers.
188
- */
189
- function resolveFeishuRuntimeAccount(params, options) {
190
- return buildResolvedFeishuAccount({
191
- ...params,
192
- baseMode: "strict",
193
- eventSecretMode: options?.requireEventSecrets ? "strict" : "inspect"
194
- });
195
- }
196
- /**
197
- * List all enabled and configured accounts.
198
- */
199
- function listEnabledFeishuAccounts(cfg) {
200
- return listFeishuAccountIds(cfg).map((accountId) => resolveFeishuAccount({
201
- cfg,
202
- accountId
203
- })).filter((account) => account.enabled && account.configured);
204
- }
205
- //#endregion
206
- export { mergeFeishuAccountConfig as a, resolveFeishuRuntimeAccount as c, listFeishuAccountIds as i, inspectFeishuCredentials as n, resolveDefaultFeishuAccountId as o, listEnabledFeishuAccounts as r, resolveFeishuAccount as s, accounts_exports as t };
@@ -1,6 +0,0 @@
1
- import { type RootContext } from "./root-context.js";
2
- import type { NativeWatchBatch } from "./watch-native.js";
3
- import type { WatchSnapshot } from "./watch-scan.js";
4
- import type { WatchChange, WatchScope } from "./watch-types.js";
5
- /** Resolve native spelling aliases without treating case folding as identity. */
6
- export declare function admittedNativeChanges(root: RootContext, scopes: readonly WatchScope[], before: WatchSnapshot | undefined, after: WatchSnapshot, batch: NativeWatchBatch, signal: AbortSignal, limit: number): Promise<WatchChange[] | undefined>;
@@ -1,88 +0,0 @@
1
- import path from "node:path";
2
- import { FsSafeError } from "./errors.js";
3
- import { isNotFoundPathError } from "./path.js";
4
- import { assertRootIdentityCurrent, resolvePathInRoot } from "./root-context.js";
5
- import { createRootDirectoryObservationGuard, assertRootDirectoryObservationGuard } from "./root-directory-list.js";
6
- import { lookupRootDirectoryEntry } from "./root-directory-entry.js";
7
- import { excludedWatchPath, nativeChanges, scopedChanges } from "./watch-hints.js";
8
- /** Resolve native spelling aliases without treating case folding as identity. */
9
- export async function admittedNativeChanges(root, scopes, before, after, batch, signal, limit) {
10
- if (!nativeChanges(scopes, before, batch, limit, after))
11
- return undefined;
12
- const result = new Map();
13
- const candidates = new Map([...before?.targets ?? [], ...after.targets, ...after.directories]);
14
- const add = (change) => {
15
- if (!result.has(change.path) && result.size >= limit)
16
- return false;
17
- const prior = result.get(change.path);
18
- result.set(change.path, prior?.type === "structural" ? prior : change);
19
- return true;
20
- };
21
- for (const hint of batch.hints) {
22
- signal.throwIfAborted();
23
- const name = hint.name; // nativeChanges rejected unknown or non-literal names.
24
- let parent = hint.directory;
25
- const relative = parent ? path.join(parent, name) : name;
26
- if (excludedWatchPath(before, relative) || excludedWatchPath(after, relative))
27
- continue;
28
- let guard;
29
- let expected = after.directories.get(parent);
30
- if (!expected) {
31
- // Native recursion observes one Root handle and may report descendants of
32
- // unselected/entry-only directories. Admit a parent alias by exact identity,
33
- // not by lowercasing, and do not turn unselected descendants into events.
34
- try {
35
- const resolved = await resolvePathInRoot(root, parent ? "./" + parent : ".", { rejectSymlinks: true });
36
- guard = await createRootDirectoryObservationGuard(root, resolved.resolved);
37
- }
38
- catch (error) {
39
- await assertRootIdentityCurrent(root);
40
- if (isNotFoundPathError(error) || (error instanceof FsSafeError && ["not-found", "path-alias", "outside-workspace", "symlink", "not-file"].includes(error.code)))
41
- continue;
42
- throw error;
43
- }
44
- const admitted = [...after.directories].find(([, identity]) => identity.dev === guard.stat.dev && identity.ino === guard.stat.ino);
45
- if (!admitted)
46
- continue;
47
- [parent, expected] = admitted;
48
- }
49
- const candidate = parent ? path.join(parent, name) : name;
50
- if (excludedWatchPath(before, candidate) || excludedWatchPath(after, candidate))
51
- continue;
52
- const selected = scopedChanges(scopes, { path: candidate,
53
- type: hint.event === "change" && before?.entries.get(candidate)?.startsWith("file:") ? "content" : "structural" });
54
- if (selected.length) {
55
- for (const change of selected)
56
- if (!add(change))
57
- return undefined;
58
- continue;
59
- }
60
- if (!guard) {
61
- const resolved = await resolvePathInRoot(root, parent ? "./" + parent : ".", { rejectSymlinks: true });
62
- guard = await createRootDirectoryObservationGuard(root, resolved.resolved);
63
- }
64
- if (guard.stat.dev !== expected.dev || guard.stat.ino !== expected.ino) {
65
- throw new FsSafeError("path-mismatch", "watch hint parent changed during reconciliation");
66
- }
67
- const found = await lookupRootDirectoryEntry(root, guard, name);
68
- signal.throwIfAborted();
69
- // A missing unselected sibling is not evidence of lost selected detail.
70
- // Previously observed selected deletions remain visible in the snapshot diff;
71
- // an unseen, already-gone name has no identity proving a selected alias.
72
- if (!found)
73
- continue;
74
- for (const [relative, identity] of candidates) {
75
- if (!relative || (path.dirname(relative) === "." ? "" : path.dirname(relative)) !== parent)
76
- continue;
77
- if (identity.dev !== found.identity.dev || identity.ino !== found.identity.ino)
78
- continue;
79
- for (const change of scopedChanges(scopes, { path: relative, type: "structural" }))
80
- if (!add(change))
81
- return undefined;
82
- }
83
- await assertRootDirectoryObservationGuard(root, guard);
84
- }
85
- await assertRootIdentityCurrent(root);
86
- signal.throwIfAborted();
87
- return [...result.values()];
88
- }
@@ -1,69 +0,0 @@
1
- # Hosted mutation-policy proof
2
-
3
- The `mutation policy public behavior proof` workflow builds the exact event-head
4
- package and host addon on Node 24 Linux, macOS, and Windows. Each case executes in
5
- a fresh process, uses a private temporary fixture, and emits only a bounded,
6
- canonical receipt. POSIX workers require both real and effective non-root UIDs.
7
-
8
- Receipt schema `fs-safe-mutation-policy-proof-v2` deliberately does not describe
9
- final directory emptiness as a mutation-dispatch count. Root replacement records
10
- the unchanged contents of the original and replacement parent; denied redirect
11
- records rejection and unchanged denied/displaced parents. The existing JS mkdir
12
- counters name their exact child or next component. None counts native syscalls
13
- or excludes transient mutations that leave no final trace.
14
-
15
- ## Additional hosted cases
16
-
17
- Each POSIX write/create/copy worker, under both native-off and native-require,
18
- performs an eligible success control, an exact deeper-parent denial after an
19
- earlier parent is created, a denied redirect after preflight, and a stale-parent
20
- replacement at the public pre-mkdir authority boundary. Exact directory listings
21
- and sentinels verify that denied/current/displaced parents contain no prohibited
22
- child, target, or stage. Copy sources must retain their original bytes.
23
-
24
- Redirect injection uses the existing public `@openclaw/fs-safe/test-hooks` subpath
25
- from `dist`, not a mocked native binding. Only these isolated workers enable
26
- `NODE_ENV=test`. The exact target and single hook visit are required. The hook is
27
- after policy preflight but before parent admission; it must not be described as
28
- a post-parent-admission hook. Stale replacement instead uses the public authority
29
- callback after child-create policy admission, with an existing sentinel parent
30
- and a still-missing child. Both implementation files that enforce the following
31
- freshness check are hash-bound. No unchecked callback ordinal chooses a fault.
32
-
33
- Representative POSIX `Root.write` workers refuse authority before the first mkdir,
34
- after one parent has been created and before the next mkdir, before staging, and
35
- immediately before publication. Epoch selection uses actual fixture state. The
36
- publication refusal requires a real single-link, caller-owned mode-0600 stage
37
- containing the complete payload while the destination still contains its original
38
- sentinel. The same rejection object must escape, no callback may follow refusal,
39
- and the owned stage must be gone before fixture teardown.
40
-
41
- Windows workers exercise buffer `Root.write` through a stable final-file symlink,
42
- then refuse before stage creation for a missing destination, before publishing a
43
- complete stage to that still-missing destination, and before publishing to an
44
- existing symlink-selected destination. Missing destinations must remain absent at
45
- every authority callback and after refusal. The workers verify alias binding,
46
- destination preservation, complete single-link stages, and cleanup before fixture
47
- teardown. The default native-off and explicit
48
- `verify-content-with-lock` native-require configurations both select the existing
49
- Windows JS buffer writer. In native-require mode, the compatibility route loads
50
- the addon to publish its retained sidecar lock through `Root.create`; the payload
51
- writer remains JS. The receipt does not mislabel this as native payload
52
- publication or evidence that content-verification fallback or lock contention
53
- was exercised.
54
-
55
- ## Bounds and interpretation
56
-
57
- The receipt remains below 32 KiB, each worker has a 15-second process timeout and
58
- 4-KiB stdout limit, and the proof step has a six-minute outer timeout. Worker I/O
59
- and cleanup retain their existing deadlines. The canonical pending/failed receipt
60
- is preserved if setup, a worker, provenance validation, or emission fails.
61
- Source, built modules, the public test seam, harness helper, contract tests, and
62
- host addon are hashed and checked again after workers.
63
-
64
- These are deterministic representative observations, not an exhaustive race proof
65
- or syscall audit. Existing hash-bound internal tests remain complementary for
66
- other awaited-admission, receipt-refresh, and observation-failure interleavings.
67
- Windows root replacement and POSIX-specific pinned routes are not claimed on
68
- Windows. Hosted CI and exact artifact inspection are required before relying on
69
- new receipts; the proof supplies no performance release clearance.
@@ -1,68 +0,0 @@
1
- # Private file-store mode
2
-
3
- Private state is not a separate store family. Use `fileStore({ private: true })`
4
- when a directory holds credentials, tokens, auth profiles, or other private
5
- JSON/text state.
6
-
7
- ```ts
8
- import { fileStore } from "@openclaw/fs-safe/store";
9
-
10
- const store = fileStore({ rootDir: "/var/lib/app", private: true });
11
-
12
- await store.writeJson("state.json", state);
13
- const loaded = await store.readJsonIfExists<State>("state.json");
14
- ```
15
-
16
- ## Behavior
17
-
18
- - Writes create parent directories at `0o700` and files at `0o600` unless you
19
- pass stricter `dirMode` / `mode` options.
20
- - Async private-mode writes route through the secret-file atomic path, which refuses
21
- symlink parent components and re-asserts mode after rename. Existing directories
22
- must already have the requested mode; writes do not repair their permissions.
23
- New-directory initialization requires guarded descriptor authority and may
24
- fail closed under restrictive platform/umask combinations; see the
25
- [secret-directory policy](secret-file.md#parameters).
26
- - Locked JSON mutations prepare private directories before acquiring their
27
- sidecar and bind the lock to the admitted parent identity. Lock normalization
28
- is read-only: a deleted or replaced admitted parent is rejected, not recreated.
29
- The writer still revalidates directory admission afterward; reads do not create
30
- directories.
31
- - `readText()` and `readJson()` are strict and throw on missing files.
32
- - `readTextIfExists()` and `readJsonIfExists()` return `null` on missing files.
33
- - `write()`, `writeText()`, `writeJson()`, `writeStream()`, and `copyIn()` all
34
- keep the same root-relative `FileStore` shape.
35
-
36
- ## Sync writes
37
-
38
- Use `fileStoreSync({ private: true })` for boot paths or sync-only integration
39
- points:
40
-
41
- ```ts
42
- import { fileStoreSync } from "@openclaw/fs-safe/store";
43
-
44
- fileStoreSync({ rootDir: "/var/lib/app", private: true }).writeJson("config.json", config);
45
- ```
46
-
47
- The sync store intentionally exposes a smaller surface: path resolution,
48
- lenient reads, and atomic text/JSON writes.
49
-
50
- Sync directory modes remain repair-compatible on POSIX, but repairs are applied
51
- only through an exact-identity, no-follow directory descriptor after the store
52
- root and admitted parent name are revalidated. Root or component swaps fail
53
- without chmodding the substituted directory. Matching modes take the no-open
54
- fast path. Windows uses its existing `mkdir` mode request plus exact directory
55
- identity checks and never falls back to pathname chmod.
56
-
57
- On Linux, Node offers no portable search-only descriptor that can also be
58
- `fchmod`ed. A mismatched directory without effective read access—including one
59
- created under an owner-read-removing umask—fails closed with
60
- `permission-unverified`. Supported macOS x64/arm64 hosts additionally try
61
- `O_SEARCH` when the directory remains searchable; an inaccessible directory
62
- still fails rather than restoring the pathname race.
63
-
64
- ## See also
65
-
66
- - [`fileStore`](file-store.md) — full store API.
67
- - [Secret files](secret-file.md) — standalone credential file reads and writes.
68
- - [JSON files](json.md) — strict/lenient JSON helpers without a bound store.
@@ -1,110 +0,0 @@
1
- ---
2
- title: Test hooks
3
- description: "Internal-only injection hooks used by the fs-safe test suite. Active only when NODE_ENV=test or VITEST=true."
4
- ---
5
-
6
- # `@openclaw/fs-safe/test-hooks`
7
-
8
- Internal injection points the `fs-safe` test suite uses to deterministically reproduce open/lstat races. They are exposed as a public subpath so downstream test suites can reuse the same harness, but they are **not** part of the supported runtime API.
9
-
10
- ```ts
11
- import {
12
- getFsSafeTestHooks,
13
- __setFsSafeTestHooksForTest,
14
- type FsSafeTestHooks,
15
- } from "@openclaw/fs-safe/test-hooks";
16
- ```
17
-
18
- ## When the hooks are active
19
-
20
- Hooks are only honored when one of the following is true:
21
-
22
- - `process.env.NODE_ENV === "test"`
23
- - `process.env.VITEST === "true"`
24
-
25
- Calling `__setFsSafeTestHooksForTest(hooks)` outside of those environments throws. `getFsSafeTestHooks()` returns `undefined` when no hooks are registered, regardless of the environment.
26
-
27
- ## Shape
28
-
29
- ```ts
30
- type FsSafeTestHooks = {
31
- afterPreOpenLstat?: (filePath: string) => Promise<void> | void;
32
- beforeOpen?: (filePath: string, flags: number) => Promise<void> | void;
33
- afterOpen?: (filePath: string, handle: FileHandle) => Promise<void> | void;
34
- afterOpenedPathIdentityCheck?: (filePath: string, handle: FileHandle) => Promise<void> | void;
35
- afterRootReadPathResolution?: (filePath: string) => Promise<void> | void;
36
- beforeRootReadFinalFence?: (filePath: string, handle: FileHandle) => Promise<void> | void;
37
- afterRootReadFinalPathIdentityCheck?: (filePath: string, handle: FileHandle) => void;
38
- beforeArchiveOutputMutation?: (operation: "mkdir" | "chmod", targetPath: string) => Promise<void> | void;
39
- beforeFileStorePruneDescend?: (dirPath: string) => Promise<void> | void;
40
- beforeFileStoreSyncPrivateWrite?: (filePath: string) => void;
41
- beforeRootFallbackMutation?: (operation: "mkdir" | "move" | "remove", targetPath: string) => Promise<void> | void;
42
- beforeRootStatObservation?: (targetPath: string) => Promise<void> | void;
43
- beforeRootStatInitialObservation?: (targetPath: string) => Promise<void> | void;
44
- beforeRootListObservation?: (directoryPath: string, withFileTypes: boolean) => Promise<void> | void;
45
- afterPinnedWriteFallbackRename?: (targetPath: string) => Promise<void> | void;
46
- beforeSiblingTempWrite?: (tempPath: string) => Promise<void> | void;
47
- beforeSidecarLockSnapshotOpen?: (lockPath: string) => Promise<void> | void;
48
- beforeTrashMove?: (targetPath: string, destPath: string) => void;
49
- afterPublishTargetCreated?: (method, targetPath, identity) => Promise<void> | void;
50
- beforePublishDirectorySync?: (method, targetPath, identity) => Promise<void> | void;
51
- };
52
- ```
53
-
54
- | Hook | Fires when |
55
- |---|---|
56
- | `afterPreOpenLstat` | A pre-open `lstat` has just resolved. Use this to swap a path between validation and open. |
57
- | `beforeOpen` | The library is about to call `open(path, flags)`. Use this to inject a TOCTOU window. |
58
- | `afterOpen` | An open just succeeded. Use this to mutate state before the post-open identity check runs. |
59
- | `afterOpenedPathIdentityCheck` | A standalone local-file or absolute copy-source descriptor matches its pathname and the generic opened-path resolver is about to run. Root reads use their final admission hooks instead. |
60
- | `afterRootReadPathResolution` | A Root read path was resolved and has not yet entered local-file open admission. |
61
- | `beforeRootReadFinalFence` | A Root read descriptor passed pre-open/descriptor identity and hardlink checks and is about to enter its single final root/path/canonical-path/root admission fence. |
62
- | `afterRootReadFinalPathIdentityCheck` | The final pathname-to-descriptor comparison passed and the second root check has not run. This hook is synchronous-only. |
63
- | `beforeArchiveOutputMutation` | Archive staging is about to create a directory or apply a mode. |
64
- | `beforeFileStorePruneDescend` | File-store pruning is about to descend into a directory. |
65
- | `beforeFileStoreSyncPrivateWrite` | A synchronous private-store write is about to mutate its target. |
66
- | `beforeRootFallbackMutation` | A guarded JS root fallback is about to mkdir, move, or remove. |
67
- | `beforeRootStatObservation` | `Root.stat()` admitted its exact target and parent and is about to collect returned metadata. |
68
- | `beforeRootStatInitialObservation` | `Root.stat()` admitted its exact parent and is about to inspect the target for the first time. |
69
- | `beforeRootListObservation` | `Root.list()` admitted its exact selected directory and is about to collect names and optional metadata. |
70
- | `afterPinnedWriteFallbackRename` | A fallback rename committed and post-commit identity checks have not run yet. |
71
- | `beforeSiblingTempWrite` | A sibling temp file exists and its writer is about to run. |
72
- | `beforeSidecarLockSnapshotOpen` | A sidecar lock was inspected and is about to be opened for a bounded snapshot read. |
73
- | `beforeTrashMove` | Trash handling is about to move the target. |
74
- | `afterPublishTargetCreated` | Exclusive publication created its target and final fences have not run yet. |
75
- | `beforePublishDirectorySync` | Publication verified the target and is about to sync its parent directory. |
76
-
77
- Hooks typed `Promise<void> | void` may be sync or async and are awaited.
78
- Hooks used by synchronous code paths are typed `void` and must not return a
79
- promise.
80
-
81
- ## Usage
82
-
83
- ```ts
84
- import { afterEach, beforeEach } from "vitest";
85
- import { __setFsSafeTestHooksForTest } from "@openclaw/fs-safe/test-hooks";
86
-
87
- beforeEach(() => {
88
- __setFsSafeTestHooksForTest({
89
- beforeOpen: async (filePath) => {
90
- // swap a victim file with a symlink right before fs-safe opens it
91
- await replaceWithSymlink(filePath);
92
- },
93
- });
94
- });
95
-
96
- afterEach(() => {
97
- __setFsSafeTestHooksForTest(undefined);
98
- });
99
- ```
100
-
101
- Always clear the hooks in `afterEach` so a stuck hook does not leak across tests.
102
-
103
- ## Stability
104
-
105
- The shape can grow new optional fields between minor versions. Treat the surface as test-only and do not rely on it from production code.
106
-
107
- ## Related pages
108
-
109
- - [Testing](testing.md) — broader notes on testing against `fs-safe`.
110
- - [Security model](security-model.md) — the races these hooks help reproduce.