@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.
- package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
- package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
- package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
- package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
- package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
- package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
- package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
- package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
- package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
- package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
- package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
- package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
- package/dist/api.js +234 -297
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +2 -2
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +1 -1
- package/dist/subagent-hooks-api.js +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
- package/node_modules/@openclaw/fs-safe/README.md +70 -379
- package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
- package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
- package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
- package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
- package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
- package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
- package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
- package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
- package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
- package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
- package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
- package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
- package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
- package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
- package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
- package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
- package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
- package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
- package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
- package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
- package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
- package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
- package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
- package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
- package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
- package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
- package/node_modules/@openclaw/fs-safe/package.json +10 -10
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/package.json +5 -5
- package/skills/feishu-wiki/SKILL.md +1 -1
- package/dist/.setup/accounts-wRqItHug.mjs +0 -206
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
- package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
- package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
- 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.
|