Files
openclaw/src/channels/reply-prefix.ts
Vincent Koc a976cc2e95 Slack: add opt-in interactive reply directives (#44607)
* Reply: add Slack interactive directive parser

* Reply: wire Slack directives into normalization

* Reply: cover Slack directive parsing

* Reply: test Slack directive normalization

* Slack: hint interactive reply directives

* Config: add Slack interactive reply capability type

* Config: validate Slack interactive reply capability

* Reply: gate Slack directives behind capability

* Slack: gate interactive reply hints by capability

* Tests: cover Slack interactive reply capability gating

* Changelog: note opt-in Slack interactive replies

* Slack: fix interactive reply review findings

* Slack: harden interactive reply routing and limits

* Slack: harden interactive reply trust and validation
2026-03-13 14:08:04 -07:00

81 lines
2.5 KiB
TypeScript

import { resolveEffectiveMessagesConfig, resolveIdentityName } from "../agents/identity.js";
import {
extractShortModelName,
type ResponsePrefixContext,
} from "../auto-reply/reply/response-prefix-template.js";
import type { GetReplyOptions } from "../auto-reply/types.js";
import type { OpenClawConfig } from "../config/config.js";
import { isSlackInteractiveRepliesEnabled } from "../slack/interactive-replies.js";
type ModelSelectionContext = Parameters<NonNullable<GetReplyOptions["onModelSelected"]>>[0];
export type ReplyPrefixContextBundle = {
prefixContext: ResponsePrefixContext;
responsePrefix?: string;
enableSlackInteractiveReplies?: boolean;
responsePrefixContextProvider: () => ResponsePrefixContext;
onModelSelected: (ctx: ModelSelectionContext) => void;
};
export type ReplyPrefixOptions = Pick<
ReplyPrefixContextBundle,
| "responsePrefix"
| "enableSlackInteractiveReplies"
| "responsePrefixContextProvider"
| "onModelSelected"
>;
export function createReplyPrefixContext(params: {
cfg: OpenClawConfig;
agentId: string;
channel?: string;
accountId?: string;
}): ReplyPrefixContextBundle {
const { cfg, agentId } = params;
const prefixContext: ResponsePrefixContext = {
identityName: resolveIdentityName(cfg, agentId),
};
const onModelSelected = (ctx: ModelSelectionContext) => {
// Mutate the object directly instead of reassigning to ensure closures see updates.
prefixContext.provider = ctx.provider;
prefixContext.model = extractShortModelName(ctx.model);
prefixContext.modelFull = `${ctx.provider}/${ctx.model}`;
prefixContext.thinkingLevel = ctx.thinkLevel ?? "off";
};
return {
prefixContext,
responsePrefix: resolveEffectiveMessagesConfig(cfg, agentId, {
channel: params.channel,
accountId: params.accountId,
}).responsePrefix,
enableSlackInteractiveReplies:
params.channel === "slack"
? isSlackInteractiveRepliesEnabled({ cfg, accountId: params.accountId })
: undefined,
responsePrefixContextProvider: () => prefixContext,
onModelSelected,
};
}
export function createReplyPrefixOptions(params: {
cfg: OpenClawConfig;
agentId: string;
channel?: string;
accountId?: string;
}): ReplyPrefixOptions {
const {
responsePrefix,
enableSlackInteractiveReplies,
responsePrefixContextProvider,
onModelSelected,
} = createReplyPrefixContext(params);
return {
responsePrefix,
enableSlackInteractiveReplies,
responsePrefixContextProvider,
onModelSelected,
};
}