Files
openclaw/src/plugins/hook-agent-context.ts
Kaspre 69a0c925b8 fix(codex): cover side-question native hooks (#82559)
* fix(codex): cover side-question native hooks

* fix(codex): enforce native approvals for app-server requests

* fix(codex): preserve approval fallback after native relay noop

* fix(codex): satisfy approval relay json typing

* fix(codex): run approval relay in report mode

* fix(codex): keep relay pre-tool decisions deny-only

* fix(codex): remove dead relay approval branch

* fix(codex): dedupe app-server relay approvals

* fix(codex): fail closed on native relay rewrites

* fix(codex): preserve side-question provider context

* fix(codex): route side-question replies to origin

* fix(codex): preserve native hook channel context

* test(codex): align native relay rewrite assertion

* fix(codex): align side-question hook config

* fix(codex): route side-question approvals safely

* test(codex): fix side-question hook typing

* fix(codex): preserve side-question hook policy context

* fix(codex): close native hook relay review gaps

* fix(codex): keep dynamic tool hook channel context

* fix(codex): preserve native finalize hook channel context

* fix(codex): scope dynamic tool result hooks by channel

* fix(codex): drop stale deadcode allowlist entry

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 12:02:17 +01:00

83 lines
2.6 KiB
TypeScript

import { parseRawSessionConversationRef } from "../sessions/session-key-utils.js";
import { normalizeOptionalString } from "../shared/string-coerce.js";
import type { PluginHookAgentContext } from "./hook-types.js";
const TARGET_PREFIXES = new Set(["channel", "chat", "direct", "dm", "group", "thread", "user"]);
function normalizeKey(value: string | undefined): string {
return (value ?? "").trim().toLowerCase();
}
function stripConversationPrefix(
value: string | undefined,
...providers: Array<string | undefined>
): string | undefined {
const text = normalizeOptionalString(value);
if (!text) {
return undefined;
}
const separatorIndex = text.indexOf(":");
if (separatorIndex === -1) {
return text;
}
const prefix = normalizeKey(text.slice(0, separatorIndex));
const suffix = normalizeOptionalString(text.slice(separatorIndex + 1));
if (!suffix) {
return text;
}
if (
TARGET_PREFIXES.has(prefix) ||
providers.some((provider) => prefix === normalizeKey(provider))
) {
return suffix;
}
return text;
}
export function resolveAgentHookChannelId(params: {
sessionKey?: string | null;
messageChannel?: string | null;
messageProvider?: string | null;
currentChannelId?: string | null;
messageTo?: string | null;
}): string | undefined {
const provider = normalizeOptionalString(params.messageProvider);
const messageChannel = normalizeOptionalString(params.messageChannel);
const parsed = parseRawSessionConversationRef(params.sessionKey);
if (parsed?.rawId) {
return parsed.rawId;
}
const metadataChannel =
stripConversationPrefix(params.currentChannelId ?? undefined, provider, messageChannel) ??
stripConversationPrefix(params.messageTo ?? undefined, provider, messageChannel);
if (metadataChannel && normalizeKey(metadataChannel) !== normalizeKey(provider)) {
return metadataChannel;
}
const strippedMessageChannel = stripConversationPrefix(
params.messageChannel ?? undefined,
provider,
messageChannel,
);
if (strippedMessageChannel && normalizeKey(strippedMessageChannel) !== normalizeKey(provider)) {
return strippedMessageChannel;
}
return messageChannel ?? provider;
}
export function buildAgentHookContextChannelFields(params: {
sessionKey?: string | null;
messageChannel?: string | null;
messageProvider?: string | null;
currentChannelId?: string | null;
messageTo?: string | null;
}): Pick<PluginHookAgentContext, "channelId" | "messageProvider"> {
return {
messageProvider: normalizeOptionalString(params.messageProvider),
channelId: resolveAgentHookChannelId(params),
};
}