File size: 2,446 Bytes
46252cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/**
 * Per-session activation gate. A plugin declares whether it is session-scoped (the default); the
 * operator then activates it for all sessions (`['*']`) or an explicit set. A global plugin
 * (`sessionScoped === false`, e.g. a metrics logger) ignores this and is always active.
 *
 * A non-session-attributed event (no `sessionId`) is never gated — the plugin chose to register that
 * hook, and there is no number to scope it to.
 */
export function isPluginActiveForSession(
  sessionScoped: boolean,
  activeSessions: string[],
  sessionId: string | undefined,
): boolean {
  if (!sessionScoped) return true;
  if (sessionId === undefined) return true;
  if (activeSessions.includes('*')) return true;
  return activeSessions.includes(sessionId);
}

const isPlainObject = (v: unknown): v is Record<string, unknown> =>
  typeof v === 'object' && v !== null && !Array.isArray(v);

/**
 * Recursively merge `override` over `base`: nested plain objects merge key-by-key so an override that
 * sets only some keys inherits the rest from the base (incl. nested secrets); arrays and scalars are
 * replaced wholesale. Returns fresh objects; never mutates the inputs.
 */
function deepMerge(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown> {
  const out: Record<string, unknown> = { ...base };
  for (const [key, value] of Object.entries(override)) {
    const baseValue = out[key];
    out[key] = isPlainObject(baseValue) && isPlainObject(value) ? deepMerge(baseValue, value) : value;
  }
  return out;
}

/**
 * Resolve the config a plugin sees for a given session: the per-session override (if any) deep-merged
 * over the base ('*') config, so an override only changes the keys it specifies (at any depth) and
 * inherits the rest from the base — a sparse override can't drop a base key (e.g. a nested secret) it
 * didn't touch. A global plugin and a non-session-attributed event (no sessionId) get the base
 * unchanged. Returns a fresh object on merge; never mutates the inputs.
 */
export function resolvePluginConfig(
  base: Record<string, unknown>,
  sessionConfig: Record<string, Record<string, unknown>> | undefined,
  sessionId: string | undefined,
  sessionScoped: boolean,
): Record<string, unknown> {
  if (!sessionScoped || sessionId === undefined || !sessionConfig) return base;
  const override = sessionConfig[sessionId];
  return override ? deepMerge(base, override) : base;
}