File size: 2,759 Bytes
d197cf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Configured hook helpers combine config and install records into active hooks.
import type { HookInstallRecord } from "../config/types.hooks.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { readConfigMachineState } from "../state/config-machine-state.js";
import { shouldIncludeHook } from "./config.js";
import { resolveHookKey } from "./frontmatter.js";
import type { HookPolicyEntry } from "./types.js";

/** Capture discovery and explicit selection from one config/install snapshot. */
export function resolveInternalHookSelection(config: OpenClawConfig): {
  configured: boolean;
  names: Set<string> | null;
  declaredNames: Set<string>;
} {
  const internal = config.hooks?.internal;
  const installs =
    readConfigMachineState<Record<string, HookInstallRecord>>("hooks.internal.installs");
  const names = new Set<string>();
  const declaredNames = new Set<string>();
  let open = (internal?.load?.extraDirs ?? []).some((dir) => dir.trim().length > 0);
  for (const [name, entry] of Object.entries(internal?.entries ?? {})) {
    const trimmed = name.trim();
    if (trimmed) {
      declaredNames.add(trimmed);
      if (entry?.enabled !== false) {
        names.add(trimmed);
      }
    }
  }
  for (const [installId, install] of Object.entries(installs ?? {})) {
    const hookNames = install.hooks ?? [];
    open ||= hookNames.length === 0 && Boolean(installId.trim());
    for (const name of hookNames) {
      const trimmed = name.trim();
      if (trimmed) {
        declaredNames.add(trimmed);
        names.add(trimmed);
      }
    }
  }
  const configured =
    internal?.enabled !== false &&
    (internal?.enabled === true ||
      Object.values(internal?.entries ?? {}).some((entry) => entry?.enabled !== false) ||
      open ||
      Object.keys(installs ?? {}).length > 0);
  return {
    configured,
    names:
      internal?.enabled === false
        ? new Set()
        : open || (declaredNames.size === 0 && internal?.enabled === true)
          ? null
          : names,
    declaredNames,
  };
}

/** True when an explicit selection names this entry, by hook name or resolved hook key. */
export function isHookNameSelected(
  names: Set<string> | undefined,
  entry: HookPolicyEntry,
): boolean {
  return Boolean(names?.has(entry.hook.name) || names?.has(resolveHookKey(entry.hook.name, entry)));
}

/** Shared selection and eligibility gate; importing handlers remains the loader's job. */
export function isHookLoadable(params: {
  entry: HookPolicyEntry;
  config: OpenClawConfig;
  names: Set<string> | null;
}): boolean {
  return (
    (!params.names || isHookNameSelected(params.names, params.entry)) &&
    shouldIncludeHook({ entry: params.entry, config: params.config })
  );
}