mirror of https://github.com/openclaw/openclaw.git
refactor(plugin-sdk): split runtime helper seams
This commit is contained in:
parent
470898b5e1
commit
edfaa01d1d
|
|
@ -1,3 +1,6 @@
|
|||
import type { DiscordExecApprovalConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { listDiscordAccountIds, resolveDiscordAccount } from "./accounts.js";
|
||||
import {
|
||||
createChannelApproverDmTargetResolver,
|
||||
createChannelNativeOriginTargetResolver,
|
||||
|
|
@ -6,10 +9,7 @@ import {
|
|||
doesApprovalRequestMatchChannelAccount,
|
||||
isChannelExecApprovalClientEnabledFromConfig,
|
||||
matchesApprovalRequestFilters,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
import type { DiscordExecApprovalConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { listDiscordAccountIds, resolveDiscordAccount } from "./accounts.js";
|
||||
} from "./approval-runtime.js";
|
||||
import {
|
||||
getDiscordExecApprovalApprovers,
|
||||
isDiscordExecApprovalApprover,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
export {
|
||||
isChannelExecApprovalClientEnabledFromConfig,
|
||||
matchesApprovalRequestFilters,
|
||||
getExecApprovalReplyMetadata,
|
||||
} from "openclaw/plugin-sdk/approval-client-runtime";
|
||||
export { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
export {
|
||||
createApproverRestrictedNativeApprovalCapability,
|
||||
splitChannelApprovalCapability,
|
||||
} from "openclaw/plugin-sdk/approval-delivery-runtime";
|
||||
export {
|
||||
createChannelApproverDmTargetResolver,
|
||||
createChannelNativeOriginTargetResolver,
|
||||
doesApprovalRequestMatchChannelAccount,
|
||||
} from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import { type ResolvedDiscordAccount } from "./accounts.js";
|
||||
import { type ChannelPlugin } from "./channel-api.js";
|
||||
import { discordSetupAdapter } from "./setup-core.js";
|
||||
import { discordSetupWizard } from "./channel.runtime.js";
|
||||
import { discordSetupAdapter } from "./setup-adapter.js";
|
||||
import { createDiscordPluginBase } from "./shared.js";
|
||||
|
||||
export const discordSetupPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
...createDiscordPluginBase({
|
||||
setupWizard: discordSetupWizard,
|
||||
setup: discordSetupAdapter,
|
||||
}),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ import { resolveDiscordOutboundSessionRoute } from "./outbound-session-route.js"
|
|||
import type { DiscordProbe } from "./probe.js";
|
||||
import { getDiscordRuntime } from "./runtime.js";
|
||||
import { normalizeExplicitDiscordSessionKey } from "./session-key-normalization.js";
|
||||
import { discordSetupAdapter } from "./setup-core.js";
|
||||
import { discordSetupAdapter } from "./setup-adapter.js";
|
||||
import { createDiscordPluginBase, discordConfigAdapter } from "./shared.js";
|
||||
import { collectDiscordStatusIssues } from "./status-issues.js";
|
||||
import { parseDiscordTarget } from "./target-parsing.js";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import type { ChannelDoctorLegacyConfigRule } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { resolveDiscordPreviewStreamMode } from "./preview-streaming.js";
|
||||
|
||||
function asObjectRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function hasLegacyDiscordStreamingAliases(value: unknown): boolean {
|
||||
const entry = asObjectRecord(value);
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
entry.streamMode !== undefined ||
|
||||
typeof entry.streaming === "boolean" ||
|
||||
(typeof entry.streaming === "string" &&
|
||||
entry.streaming !== resolveDiscordPreviewStreamMode(entry))
|
||||
);
|
||||
}
|
||||
|
||||
function hasLegacyDiscordAccountStreamingAliases(value: unknown): boolean {
|
||||
const accounts = asObjectRecord(value);
|
||||
if (!accounts) {
|
||||
return false;
|
||||
}
|
||||
return Object.values(accounts).some((account) => hasLegacyDiscordStreamingAliases(account));
|
||||
}
|
||||
|
||||
export const DISCORD_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "discord"],
|
||||
message:
|
||||
"channels.discord.streamMode and boolean channels.discord.streaming are legacy; use channels.discord.streaming.",
|
||||
match: hasLegacyDiscordStreamingAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "discord", "accounts"],
|
||||
message:
|
||||
"channels.discord.accounts.<id>.streamMode and boolean channels.discord.accounts.<id>.streaming are legacy; use channels.discord.accounts.<id>.streaming.",
|
||||
match: hasLegacyDiscordAccountStreamingAliases,
|
||||
},
|
||||
];
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import {
|
||||
type ChannelDoctorAdapter,
|
||||
type ChannelDoctorConfigMutation,
|
||||
type ChannelDoctorLegacyConfigRule,
|
||||
} from "openclaw/plugin-sdk/channel-contract";
|
||||
import { type OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { collectProviderDangerousNameMatchingScopes } from "openclaw/plugin-sdk/runtime";
|
||||
import { DISCORD_LEGACY_CONFIG_RULES } from "./doctor-shared.js";
|
||||
import { resolveDiscordPreviewStreamMode } from "./preview-streaming.js";
|
||||
import { isDiscordMutableAllowEntry } from "./security-audit.js";
|
||||
|
||||
type DiscordNumericIdHit = { path: string; entry: number; safe: boolean };
|
||||
|
||||
|
|
@ -26,6 +25,27 @@ function sanitizeForLog(value: string): string {
|
|||
return value.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
|
||||
}
|
||||
|
||||
function isDiscordMutableAllowEntry(raw: string): boolean {
|
||||
const text = raw.trim();
|
||||
if (!text || text === "*") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maybeMentionId = text.replace(/^<@!?/, "").replace(/>$/, "");
|
||||
if (/^\d+$/.test(maybeMentionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const prefix of ["discord:", "user:", "pk:"]) {
|
||||
if (!text.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
return text.slice(prefix.length).trim().length === 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function normalizeDiscordDmAliases(params: {
|
||||
entry: Record<string, unknown>;
|
||||
pathPrefix: string;
|
||||
|
|
@ -514,42 +534,6 @@ function collectDiscordMutableAllowlistWarnings(cfg: OpenClawConfig): string[] {
|
|||
];
|
||||
}
|
||||
|
||||
function hasLegacyDiscordStreamingAliases(value: unknown): boolean {
|
||||
const entry = asObjectRecord(value);
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
entry.streamMode !== undefined ||
|
||||
typeof entry.streaming === "boolean" ||
|
||||
(typeof entry.streaming === "string" &&
|
||||
entry.streaming !== resolveDiscordPreviewStreamMode(entry))
|
||||
);
|
||||
}
|
||||
|
||||
function hasLegacyDiscordAccountStreamingAliases(value: unknown): boolean {
|
||||
const accounts = asObjectRecord(value);
|
||||
if (!accounts) {
|
||||
return false;
|
||||
}
|
||||
return Object.values(accounts).some((account) => hasLegacyDiscordStreamingAliases(account));
|
||||
}
|
||||
|
||||
const DISCORD_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "discord"],
|
||||
message:
|
||||
"channels.discord.streamMode and boolean channels.discord.streaming are legacy; use channels.discord.streaming.",
|
||||
match: hasLegacyDiscordStreamingAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "discord", "accounts"],
|
||||
message:
|
||||
"channels.discord.accounts.<id>.streamMode and boolean channels.discord.accounts.<id>.streaming are legacy; use channels.discord.accounts.<id>.streaming.",
|
||||
match: hasLegacyDiscordAccountStreamingAliases,
|
||||
},
|
||||
];
|
||||
|
||||
export const discordDoctor: ChannelDoctorAdapter = {
|
||||
dmAllowFromMode: "topOrNested",
|
||||
groupModel: "route",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
|
||||
import { isChannelExecApprovalClientEnabledFromConfig } from "openclaw/plugin-sdk/approval-runtime";
|
||||
import { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordExecApprovalConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import { resolveDiscordAccount } from "./accounts.js";
|
||||
import {
|
||||
getExecApprovalReplyMetadata,
|
||||
isChannelExecApprovalClientEnabledFromConfig,
|
||||
resolveApprovalApprovers,
|
||||
} from "./approval-runtime.js";
|
||||
import { parseDiscordTarget } from "./target-parsing.js";
|
||||
|
||||
function normalizeDiscordApproverId(value: string): string | undefined {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Routes } from "discord-api-types/v10";
|
||||
import { getRuntimeConfigSnapshot, type OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
registerSessionBindingAdapter,
|
||||
resolveThreadBindingConversationIdFromBindingId,
|
||||
|
|
@ -9,6 +8,10 @@ import {
|
|||
type SessionBindingRecord,
|
||||
} from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { normalizeAccountId, resolveAgentIdFromSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
getRuntimeConfigSnapshot,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/runtime-config-snapshot";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createDiscordRestClient } from "../client.js";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import { createEnvPatchedAccountSetupAdapter } from "openclaw/plugin-sdk/setup-adapter-runtime";
|
||||
import type { ChannelSetupAdapter } from "openclaw/plugin-sdk/setup-runtime";
|
||||
|
||||
const channel = "discord" as const;
|
||||
|
||||
export const discordSetupAdapter: ChannelSetupAdapter = createEnvPatchedAccountSetupAdapter({
|
||||
channelKey: channel,
|
||||
defaultAccountOnlyEnvError: "DISCORD_BOT_TOKEN can only be used for the default account.",
|
||||
missingCredentialError: "Discord requires token (or --use-env).",
|
||||
hasCredentials: (input) => Boolean(input.token),
|
||||
buildPatch: (input) => (input.token ? { token: input.token } : {}),
|
||||
});
|
||||
|
|
@ -1,12 +1,7 @@
|
|||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
import type { DiscordGuildEntry } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createEnvPatchedAccountSetupAdapter } from "openclaw/plugin-sdk/setup-adapter-runtime";
|
||||
import type {
|
||||
ChannelSetupAdapter,
|
||||
ChannelSetupDmPolicy,
|
||||
ChannelSetupWizard,
|
||||
} from "openclaw/plugin-sdk/setup-runtime";
|
||||
import type { ChannelSetupDmPolicy, ChannelSetupWizard } from "openclaw/plugin-sdk/setup-runtime";
|
||||
import { createStandardChannelSetupStatus } from "openclaw/plugin-sdk/setup-runtime";
|
||||
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
import {
|
||||
|
|
@ -14,6 +9,7 @@ import {
|
|||
listDiscordSetupAccountIds,
|
||||
resolveDiscordSetupAccountConfig,
|
||||
} from "./setup-account-state.js";
|
||||
import { discordSetupAdapter } from "./setup-adapter.js";
|
||||
import {
|
||||
createAccountScopedAllowFromSection,
|
||||
createAccountScopedGroupAccessSection,
|
||||
|
|
@ -75,14 +71,6 @@ export function parseDiscordAllowFromId(value: string): string | null {
|
|||
});
|
||||
}
|
||||
|
||||
export const discordSetupAdapter: ChannelSetupAdapter = createEnvPatchedAccountSetupAdapter({
|
||||
channelKey: channel,
|
||||
defaultAccountOnlyEnvError: "DISCORD_BOT_TOKEN can only be used for the default account.",
|
||||
missingCredentialError: "Discord requires token (or --use-env).",
|
||||
hasCredentials: (input) => Boolean(input.token),
|
||||
buildPatch: (input) => (input.token ? { token: input.token } : {}),
|
||||
});
|
||||
|
||||
export function createDiscordSetupWizardBase(handlers: {
|
||||
promptAllowFrom: NonNullable<ChannelSetupDmPolicy["promptAllowFrom"]>;
|
||||
resolveAllowFromEntries: NonNullable<
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|||
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
||||
import { adaptScopedAccountAccessor } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type { ChannelDoctorAdapter } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { inspectDiscordAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listDiscordAccountIds,
|
||||
|
|
@ -11,18 +12,40 @@ import {
|
|||
} from "./accounts.js";
|
||||
import { getChatChannelMeta, type ChannelPlugin } from "./channel-api.js";
|
||||
import { DiscordChannelConfigSchema } from "./config-schema.js";
|
||||
import { discordDoctor } from "./doctor.js";
|
||||
import { createDiscordSetupWizardProxy } from "./setup-core.js";
|
||||
import { DISCORD_LEGACY_CONFIG_RULES } from "./doctor-shared.js";
|
||||
|
||||
export const DISCORD_CHANNEL = "discord" as const;
|
||||
|
||||
async function loadDiscordChannelRuntime() {
|
||||
return await import("./channel.runtime.js");
|
||||
type DiscordDoctorModule = typeof import("./doctor.js");
|
||||
|
||||
let discordDoctorModulePromise: Promise<DiscordDoctorModule> | undefined;
|
||||
|
||||
async function loadDiscordDoctorModule(): Promise<DiscordDoctorModule> {
|
||||
discordDoctorModulePromise ??= import("./doctor.js");
|
||||
return await discordDoctorModulePromise;
|
||||
}
|
||||
|
||||
export const discordSetupWizard = createDiscordSetupWizardProxy(
|
||||
async () => (await loadDiscordChannelRuntime()).discordSetupWizard,
|
||||
);
|
||||
const discordDoctor: ChannelDoctorAdapter = {
|
||||
dmAllowFromMode: "topOrNested",
|
||||
groupModel: "route",
|
||||
groupAllowFromFallbackToAllowFrom: false,
|
||||
warnOnEmptyGroupSenderAllowlist: false,
|
||||
legacyConfigRules: DISCORD_LEGACY_CONFIG_RULES,
|
||||
normalizeCompatibilityConfig: async (params) =>
|
||||
(await loadDiscordDoctorModule()).discordDoctor.normalizeCompatibilityConfig?.(params) ?? {
|
||||
config: params.cfg,
|
||||
changes: [],
|
||||
},
|
||||
collectPreviewWarnings: async (params) =>
|
||||
(await loadDiscordDoctorModule()).discordDoctor.collectPreviewWarnings?.(params) ?? [],
|
||||
collectMutableAllowlistWarnings: async (params) =>
|
||||
(await loadDiscordDoctorModule()).discordDoctor.collectMutableAllowlistWarnings?.(params) ?? [],
|
||||
repairConfig: async (params) =>
|
||||
(await loadDiscordDoctorModule()).discordDoctor.repairConfig?.(params) ?? {
|
||||
config: params.cfg,
|
||||
changes: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const discordConfigAdapter = createScopedChannelConfigAdapter<ResolvedDiscordAccount>({
|
||||
sectionKey: DISCORD_CHANNEL,
|
||||
|
|
@ -38,6 +61,7 @@ export const discordConfigAdapter = createScopedChannelConfigAdapter<ResolvedDis
|
|||
|
||||
export function createDiscordPluginBase(params: {
|
||||
setup: NonNullable<ChannelPlugin<ResolvedDiscordAccount>["setup"]>;
|
||||
setupWizard?: ChannelPlugin<ResolvedDiscordAccount>["setupWizard"];
|
||||
}): Pick<
|
||||
ChannelPlugin<ResolvedDiscordAccount>,
|
||||
| "id"
|
||||
|
|
@ -54,7 +78,7 @@ export function createDiscordPluginBase(params: {
|
|||
> {
|
||||
return {
|
||||
id: DISCORD_CHANNEL,
|
||||
setupWizard: discordSetupWizard,
|
||||
...(params.setupWizard ? { setupWizard: params.setupWizard } : {}),
|
||||
meta: { ...getChatChannelMeta(DISCORD_CHANNEL) },
|
||||
capabilities: {
|
||||
chatTypes: ["direct", "channel", "thread"],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
||||
import { feishuPlugin } from "./src/channel.js";
|
||||
import { setFeishuRuntime } from "./src/runtime.js";
|
||||
|
||||
export { feishuPlugin } from "./src/channel.js";
|
||||
export { setFeishuRuntime } from "./src/runtime.js";
|
||||
|
||||
export default defineChannelPluginEntry({
|
||||
id: "feishu",
|
||||
name: "Feishu",
|
||||
description: "Feishu/Lark channel plugin",
|
||||
plugin: feishuPlugin,
|
||||
setRuntime: setFeishuRuntime,
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { normalizeFeishuTarget } from "./targets.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import { resolveGoogleChatAccount } from "./accounts.js";
|
||||
import { isGoogleChatUserTarget, normalizeGoogleChatTarget } from "./targets.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import { normalizeMatrixApproverId } from "./exec-approvals.js";
|
||||
import { resolveMatrixAccount } from "./matrix/accounts.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import {
|
||||
createChannelApprovalCapability,
|
||||
createChannelApproverDmTargetResolver,
|
||||
createChannelNativeOriginTargetResolver,
|
||||
createApproverRestrictedNativeApprovalCapability,
|
||||
splitChannelApprovalCapability,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-delivery-runtime";
|
||||
import {
|
||||
createChannelApproverDmTargetResolver,
|
||||
createChannelNativeOriginTargetResolver,
|
||||
} from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { getMatrixApprovalAuthApprovers, matrixApprovalAuth } from "./approval-auth.js";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
buildExecApprovalPendingReplyPayload,
|
||||
getExecApprovalApproverDmNoticeText,
|
||||
resolveExecApprovalCommandDisplay,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-reply-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
createChannelNativeApprovalRuntime,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import {
|
||||
createChannelExecApprovalProfile,
|
||||
getExecApprovalReplyMetadata,
|
||||
isChannelExecApprovalClientEnabledFromConfig,
|
||||
isChannelExecApprovalTargetRecipient,
|
||||
matchesApprovalRequestFilters,
|
||||
resolveApprovalRequestChannelAccountId,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-client-runtime";
|
||||
import { resolveApprovalRequestChannelAccountId } from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import { resolveMattermostAccount } from "./mattermost/accounts.js";
|
||||
|
||||
const MATTERMOST_USER_ID_RE = /^[a-z0-9]{26}$/;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { normalizeMSTeamsMessagingTarget } from "./resolve-allowlist.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import { resolveNextcloudTalkAccount } from "./accounts.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
||||
import { signalPlugin } from "./src/channel.js";
|
||||
import { setSignalRuntime } from "./src/runtime.js";
|
||||
|
||||
export { signalPlugin } from "./src/channel.js";
|
||||
export { setSignalRuntime } from "./src/runtime.js";
|
||||
|
||||
export default defineChannelPluginEntry({
|
||||
id: "signal",
|
||||
name: "Signal",
|
||||
description: "Signal channel plugin",
|
||||
plugin: signalPlugin,
|
||||
setRuntime: setSignalRuntime,
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveSignalAccount } from "./accounts.js";
|
||||
import { normalizeSignalMessagingTarget } from "./normalize.js";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveSignalAccount } from "./accounts.js";
|
||||
|
||||
export function resolveSignalRpcContext(
|
||||
|
|
@ -7,14 +6,10 @@ export function resolveSignalRpcContext(
|
|||
) {
|
||||
const hasBaseUrl = Boolean(opts.baseUrl?.trim());
|
||||
const hasAccount = Boolean(opts.account?.trim());
|
||||
const resolvedAccount =
|
||||
accountInfo ||
|
||||
(!hasBaseUrl || !hasAccount
|
||||
? resolveSignalAccount({
|
||||
cfg: loadConfig(),
|
||||
accountId: opts.accountId,
|
||||
})
|
||||
: undefined);
|
||||
if ((!hasBaseUrl || !hasAccount) && !accountInfo) {
|
||||
throw new Error("Signal account config is required when baseUrl or account is missing");
|
||||
}
|
||||
const resolvedAccount = accountInfo;
|
||||
const baseUrl = opts.baseUrl?.trim() || resolvedAccount?.baseUrl;
|
||||
if (!baseUrl) {
|
||||
throw new Error("Signal base URL is required");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
* Signal reactions via signal-cli JSON-RPC API
|
||||
*/
|
||||
|
||||
import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveSignalAccount } from "./accounts.js";
|
||||
import { signalRpcRequest } from "./client.js";
|
||||
|
|
@ -31,6 +30,15 @@ type SignalReactionErrorMessages = {
|
|||
missingTargetAuthor: string;
|
||||
};
|
||||
|
||||
let signalConfigRuntimePromise:
|
||||
| Promise<typeof import("openclaw/plugin-sdk/config-runtime")>
|
||||
| undefined;
|
||||
|
||||
async function loadSignalConfigRuntime() {
|
||||
signalConfigRuntimePromise ??= import("openclaw/plugin-sdk/config-runtime");
|
||||
return await signalConfigRuntimePromise;
|
||||
}
|
||||
|
||||
function normalizeSignalId(raw: string): string {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
|
|
@ -77,7 +85,7 @@ async function sendReactionSignalCore(params: {
|
|||
opts: SignalReactionOpts;
|
||||
errors: SignalReactionErrorMessages;
|
||||
}): Promise<SignalReactionResult> {
|
||||
const cfg = params.opts.cfg ?? loadConfig();
|
||||
const cfg = params.opts.cfg ?? (await loadSignalConfigRuntime()).loadConfig();
|
||||
const accountInfo = resolveSignalAccount({
|
||||
cfg,
|
||||
accountId: params.opts.accountId,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,28 @@ type SignalTarget =
|
|||
| { type: "group"; groupId: string }
|
||||
| { type: "username"; username: string };
|
||||
|
||||
let signalConfigRuntimePromise:
|
||||
| Promise<typeof import("openclaw/plugin-sdk/config-runtime")>
|
||||
| undefined;
|
||||
|
||||
async function loadSignalConfigRuntime() {
|
||||
signalConfigRuntimePromise ??= import("openclaw/plugin-sdk/config-runtime");
|
||||
return await signalConfigRuntimePromise;
|
||||
}
|
||||
|
||||
async function resolveSignalRpcAccountInfo(
|
||||
opts: Pick<SignalSendOpts, "cfg" | "baseUrl" | "account" | "accountId">,
|
||||
) {
|
||||
if (opts.baseUrl?.trim() && opts.account?.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const cfg = opts.cfg ?? (await loadSignalConfigRuntime()).loadConfig();
|
||||
return resolveSignalAccount({
|
||||
cfg,
|
||||
accountId: opts.accountId,
|
||||
});
|
||||
}
|
||||
|
||||
function parseTarget(raw: string): SignalTarget {
|
||||
let value = raw.trim();
|
||||
if (!value) {
|
||||
|
|
@ -203,7 +225,8 @@ export async function sendTypingSignal(
|
|||
to: string,
|
||||
opts: SignalRpcOpts & { stop?: boolean } = {},
|
||||
): Promise<boolean> {
|
||||
const { baseUrl, account } = resolveSignalRpcContext(opts);
|
||||
const accountInfo = await resolveSignalRpcAccountInfo(opts);
|
||||
const { baseUrl, account } = resolveSignalRpcContext(opts, accountInfo);
|
||||
const targetParams = buildTargetParams(parseTarget(to), {
|
||||
recipient: true,
|
||||
group: true,
|
||||
|
|
@ -233,7 +256,8 @@ export async function sendReadReceiptSignal(
|
|||
if (!Number.isFinite(targetTimestamp) || targetTimestamp <= 0) {
|
||||
return false;
|
||||
}
|
||||
const { baseUrl, account } = resolveSignalRpcContext(opts);
|
||||
const accountInfo = await resolveSignalRpcAccountInfo(opts);
|
||||
const { baseUrl, account } = resolveSignalRpcContext(opts, accountInfo);
|
||||
const targetParams = buildTargetParams(parseTarget(to), {
|
||||
recipient: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core";
|
||||
import { slackPlugin } from "./src/channel.js";
|
||||
import { setSlackRuntime } from "./src/runtime.js";
|
||||
|
||||
export { slackPlugin } from "./src/channel.js";
|
||||
export { setSlackRuntime } from "./src/runtime.js";
|
||||
|
||||
export default defineChannelPluginEntry({
|
||||
id: "slack",
|
||||
name: "Slack",
|
||||
description: "Slack channel plugin",
|
||||
plugin: slackPlugin,
|
||||
setRuntime: setSlackRuntime,
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveSlackAccount } from "./accounts.js";
|
||||
import { normalizeSlackApproverId } from "./exec-approvals.js";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import {
|
||||
createApproverRestrictedNativeApprovalCapability,
|
||||
splitChannelApprovalCapability,
|
||||
} from "openclaw/plugin-sdk/approval-delivery-runtime";
|
||||
import {
|
||||
createChannelApproverDmTargetResolver,
|
||||
createChannelNativeOriginTargetResolver,
|
||||
createApproverRestrictedNativeApprovalCapability,
|
||||
splitChannelApprovalCapability,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { listSlackAccountIds } from "./accounts.js";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import {
|
||||
createChannelExecApprovalProfile,
|
||||
doesApprovalRequestMatchChannelAccount,
|
||||
isChannelExecApprovalTargetRecipient,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-client-runtime";
|
||||
import { doesApprovalRequestMatchChannelAccount } from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveSlackAccount } from "./accounts.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,17 @@ import { parseSlackBlocksInput } from "./blocks-input.js";
|
|||
import { buildSlackInteractiveBlocks, type SlackBlock } from "./blocks-render.js";
|
||||
import { compileSlackInteractiveReplies } from "./interactive-replies.js";
|
||||
import { SLACK_TEXT_LIMIT } from "./limits.js";
|
||||
import { sendMessageSlack, type SlackSendIdentity } from "./send.js";
|
||||
import type { SlackSendIdentity } from "./send.js";
|
||||
|
||||
const SLACK_MAX_BLOCKS = 50;
|
||||
type SlackSendFn = typeof import("./send.runtime.js").sendMessageSlack;
|
||||
|
||||
let slackSendRuntimePromise: Promise<typeof import("./send.runtime.js")> | undefined;
|
||||
|
||||
async function loadSlackSendRuntime() {
|
||||
slackSendRuntimePromise ??= import("./send.runtime.js");
|
||||
return await slackSendRuntimePromise;
|
||||
}
|
||||
|
||||
function resolveRenderedInteractiveBlocks(
|
||||
interactive?: InteractiveReply,
|
||||
|
|
@ -51,7 +59,7 @@ function resolveSlackSendIdentity(identity?: OutboundIdentity): SlackSendIdentit
|
|||
}
|
||||
|
||||
async function applySlackMessageSendingHooks(params: {
|
||||
cfg: NonNullable<NonNullable<Parameters<typeof sendMessageSlack>[2]>["cfg"]>;
|
||||
cfg: NonNullable<NonNullable<Parameters<SlackSendFn>[2]>["cfg"]>;
|
||||
to: string;
|
||||
text: string;
|
||||
threadTs?: string;
|
||||
|
|
@ -85,7 +93,7 @@ async function applySlackMessageSendingHooks(params: {
|
|||
}
|
||||
|
||||
async function sendSlackOutboundMessage(params: {
|
||||
cfg: NonNullable<NonNullable<Parameters<typeof sendMessageSlack>[2]>["cfg"]>;
|
||||
cfg: NonNullable<NonNullable<Parameters<SlackSendFn>[2]>["cfg"]>;
|
||||
to: string;
|
||||
text: string;
|
||||
mediaUrl?: string;
|
||||
|
|
@ -95,7 +103,7 @@ async function sendSlackOutboundMessage(params: {
|
|||
};
|
||||
mediaLocalRoots?: readonly string[];
|
||||
mediaReadFile?: (filePath: string) => Promise<Buffer>;
|
||||
blocks?: NonNullable<Parameters<typeof sendMessageSlack>[2]>["blocks"];
|
||||
blocks?: NonNullable<Parameters<SlackSendFn>[2]>["blocks"];
|
||||
accountId?: string | null;
|
||||
deps?: { [channelId: string]: unknown } | null;
|
||||
replyToId?: string | null;
|
||||
|
|
@ -103,7 +111,8 @@ async function sendSlackOutboundMessage(params: {
|
|||
identity?: OutboundIdentity;
|
||||
}) {
|
||||
const send =
|
||||
resolveOutboundSendDep<typeof sendMessageSlack>(params.deps, "slack") ?? sendMessageSlack;
|
||||
resolveOutboundSendDep<SlackSendFn>(params.deps, "slack") ??
|
||||
(await loadSlackSendRuntime()).sendMessageSlack;
|
||||
const threadTs =
|
||||
params.replyToId ?? (params.threadId != null ? String(params.threadId) : undefined);
|
||||
const hookResult = await applySlackMessageSendingHooks({
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/account-resolution";
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import { resolveAccount } from "./accounts.js";
|
||||
|
||||
function normalizeSynologyChatApproverId(value: string | number): string | undefined {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ExecApprovalReplyDecision } from "openclaw/plugin-sdk/approval-runtime";
|
||||
import type { ExecApprovalReplyDecision } from "openclaw/plugin-sdk/approval-reply-runtime";
|
||||
import { sanitizeTelegramCallbackData } from "./approval-callback-data.js";
|
||||
import type { TelegramInlineButtons } from "./button-types.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import {
|
||||
createApproverRestrictedNativeApprovalCapability,
|
||||
splitChannelApprovalCapability,
|
||||
} from "openclaw/plugin-sdk/approval-delivery-runtime";
|
||||
import {
|
||||
createChannelApproverDmTargetResolver,
|
||||
createChannelNativeOriginTargetResolver,
|
||||
createApproverRestrictedNativeApprovalCapability,
|
||||
splitChannelApprovalCapability,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type { ChannelApprovalCapability } from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import {
|
|||
buildExecApprovalPendingReplyPayload,
|
||||
resolveExecApprovalRequestAllowedDecisions,
|
||||
resolveExecApprovalCommandDisplay,
|
||||
type ExecApprovalRequest,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-reply-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ExecApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { normalizeMessageChannel } from "openclaw/plugin-sdk/routing";
|
||||
import { isTelegramExecApprovalClientEnabled } from "./exec-approvals.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { buildPluginApprovalPendingReplyPayload } from "openclaw/plugin-sdk/approval-runtime";
|
||||
import { buildPluginApprovalPendingReplyPayload } from "openclaw/plugin-sdk/approval-reply-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
createChannelNativeApprovalRuntime,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import {
|
||||
createChannelExecApprovalProfile,
|
||||
isChannelExecApprovalClientEnabledFromConfig,
|
||||
isChannelExecApprovalTargetRecipient,
|
||||
matchesApprovalRequestFilters,
|
||||
resolveApprovalRequestChannelAccountId,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-client-runtime";
|
||||
import { resolveApprovalRequestChannelAccountId } from "openclaw/plugin-sdk/approval-native-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { TelegramExecApprovalConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ExecApprovalRequest, PluginApprovalRequest } from "openclaw/plugin-sdk/infra-runtime";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
|
||||
import { splitChannelApprovalCapability } from "openclaw/plugin-sdk/approval-runtime";
|
||||
import { splitChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
|
||||
import { getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import type { ResolvedTelegramAccount } from "./src/accounts.js";
|
||||
import { resolveTelegramAccount } from "./src/accounts.js";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createResolvedApproverActionAuthAdapter,
|
||||
resolveApprovalApprovers,
|
||||
} from "openclaw/plugin-sdk/approval-runtime";
|
||||
} from "openclaw/plugin-sdk/approval-auth-runtime";
|
||||
import { resolveZaloAccount } from "./accounts.js";
|
||||
|
||||
function normalizeZaloApproverId(value: string | number): string | undefined {
|
||||
|
|
|
|||
16
package.json
16
package.json
|
|
@ -95,6 +95,22 @@
|
|||
"types": "./dist/plugin-sdk/approval-auth-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/approval-auth-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/approval-client-runtime": {
|
||||
"types": "./dist/plugin-sdk/approval-client-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/approval-client-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/approval-delivery-runtime": {
|
||||
"types": "./dist/plugin-sdk/approval-delivery-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/approval-delivery-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/approval-native-runtime": {
|
||||
"types": "./dist/plugin-sdk/approval-native-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/approval-native-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/approval-reply-runtime": {
|
||||
"types": "./dist/plugin-sdk/approval-reply-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/approval-reply-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/approval-runtime": {
|
||||
"types": "./dist/plugin-sdk/approval-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/approval-runtime.js"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,21 @@ export const pluginSdkDocMetadata = {
|
|||
"approval-runtime": {
|
||||
category: "runtime",
|
||||
},
|
||||
"approval-auth-runtime": {
|
||||
category: "runtime",
|
||||
},
|
||||
"approval-client-runtime": {
|
||||
category: "runtime",
|
||||
},
|
||||
"approval-delivery-runtime": {
|
||||
category: "runtime",
|
||||
},
|
||||
"approval-native-runtime": {
|
||||
category: "runtime",
|
||||
},
|
||||
"approval-reply-runtime": {
|
||||
category: "runtime",
|
||||
},
|
||||
"plugin-entry": {
|
||||
category: "core",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
"channel-setup",
|
||||
"setup-tools",
|
||||
"approval-auth-runtime",
|
||||
"approval-client-runtime",
|
||||
"approval-delivery-runtime",
|
||||
"approval-native-runtime",
|
||||
"approval-reply-runtime",
|
||||
"approval-runtime",
|
||||
"config-runtime",
|
||||
"reply-runtime",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,13 @@ type BundledChannelDiscoveryCandidate = {
|
|||
};
|
||||
};
|
||||
|
||||
const BUNDLED_CHANNEL_ENTRY_BASENAMES = [
|
||||
"channel-entry.ts",
|
||||
"channel-entry.mts",
|
||||
"channel-entry.js",
|
||||
"channel-entry.mjs",
|
||||
] as const;
|
||||
|
||||
const log = createSubsystemLogger("channels");
|
||||
|
||||
function resolveChannelPluginModuleEntry(
|
||||
|
|
@ -171,6 +178,12 @@ function resolvePreferredBundledChannelSource(
|
|||
candidate: BundledChannelDiscoveryCandidate,
|
||||
manifest: ReturnType<typeof loadPluginManifestRegistry>["plugins"][number],
|
||||
): string {
|
||||
for (const basename of BUNDLED_CHANNEL_ENTRY_BASENAMES) {
|
||||
const preferred = resolveCompiledBundledModulePath(path.resolve(candidate.rootDir, basename));
|
||||
if (fs.existsSync(preferred)) {
|
||||
return preferred;
|
||||
}
|
||||
}
|
||||
const declaredEntry = candidate.packageManifest?.extensions?.find(
|
||||
(entry): entry is string => typeof entry === "string" && entry.trim().length > 0,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,15 @@ import { applyMergePatch } from "./merge-patch.js";
|
|||
import { resolveConfigPath, resolveStateDir } from "./paths.js";
|
||||
import { isBlockedObjectKey } from "./prototype-keys.js";
|
||||
import { applyConfigOverrides } from "./runtime-overrides.js";
|
||||
import {
|
||||
clearRuntimeConfigSnapshot as clearRuntimeConfigSnapshotState,
|
||||
getRuntimeConfigSnapshot as getRuntimeConfigSnapshotState,
|
||||
getRuntimeConfigSnapshotRefreshHandler,
|
||||
getRuntimeConfigSourceSnapshot as getRuntimeConfigSourceSnapshotState,
|
||||
resetConfigRuntimeState as resetConfigRuntimeStateState,
|
||||
setRuntimeConfigSnapshot as setRuntimeConfigSnapshotState,
|
||||
setRuntimeConfigSnapshotRefreshHandler as setRuntimeConfigSnapshotRefreshHandlerState,
|
||||
} from "./runtime-snapshot.js";
|
||||
import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js";
|
||||
import {
|
||||
validateConfigObjectRawWithPlugins,
|
||||
|
|
@ -48,6 +57,15 @@ import {
|
|||
} from "./validation.js";
|
||||
import { shouldWarnOnTouchedVersion } from "./version.js";
|
||||
|
||||
export {
|
||||
clearRuntimeConfigSnapshotState as clearRuntimeConfigSnapshot,
|
||||
getRuntimeConfigSnapshotState as getRuntimeConfigSnapshot,
|
||||
getRuntimeConfigSourceSnapshotState as getRuntimeConfigSourceSnapshot,
|
||||
resetConfigRuntimeStateState as resetConfigRuntimeState,
|
||||
setRuntimeConfigSnapshotState as setRuntimeConfigSnapshot,
|
||||
setRuntimeConfigSnapshotRefreshHandlerState as setRuntimeConfigSnapshotRefreshHandler,
|
||||
};
|
||||
|
||||
// Re-export for backwards compatibility
|
||||
export { CircularIncludeError, ConfigIncludeError } from "./includes.js";
|
||||
export { MissingEnvVarError } from "./env-substitution.js";
|
||||
|
|
@ -228,15 +246,6 @@ export type ReadConfigFileSnapshotForWriteResult = {
|
|||
writeOptions: ConfigWriteOptions;
|
||||
};
|
||||
|
||||
export type RuntimeConfigSnapshotRefreshParams = {
|
||||
sourceConfig: OpenClawConfig;
|
||||
};
|
||||
|
||||
export type RuntimeConfigSnapshotRefreshHandler = {
|
||||
refresh: (params: RuntimeConfigSnapshotRefreshParams) => boolean | Promise<boolean>;
|
||||
clearOnRefreshFailure?: () => void;
|
||||
};
|
||||
|
||||
export type ConfigWriteNotification = {
|
||||
configPath: string;
|
||||
sourceConfig: OpenClawConfig;
|
||||
|
|
@ -2371,9 +2380,6 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||
const AUTO_OWNER_DISPLAY_SECRET_BY_PATH = new Map<string, string>();
|
||||
const AUTO_OWNER_DISPLAY_SECRET_PERSIST_IN_FLIGHT = new Set<string>();
|
||||
const AUTO_OWNER_DISPLAY_SECRET_PERSIST_WARNED = new Set<string>();
|
||||
let runtimeConfigSnapshot: OpenClawConfig | null = null;
|
||||
let runtimeConfigSourceSnapshot: OpenClawConfig | null = null;
|
||||
let runtimeConfigSnapshotRefreshHandler: RuntimeConfigSnapshotRefreshHandler | null = null;
|
||||
const configWriteListeners = new Set<(event: ConfigWriteNotification) => void>();
|
||||
|
||||
function notifyConfigWriteListeners(event: ConfigWriteNotification): void {
|
||||
|
|
@ -2399,31 +2405,6 @@ export function registerConfigWriteListener(
|
|||
};
|
||||
}
|
||||
|
||||
export function setRuntimeConfigSnapshot(
|
||||
config: OpenClawConfig,
|
||||
sourceConfig?: OpenClawConfig,
|
||||
): void {
|
||||
runtimeConfigSnapshot = config;
|
||||
runtimeConfigSourceSnapshot = sourceConfig ?? null;
|
||||
}
|
||||
|
||||
export function resetConfigRuntimeState(): void {
|
||||
runtimeConfigSnapshot = null;
|
||||
runtimeConfigSourceSnapshot = null;
|
||||
}
|
||||
|
||||
export function clearRuntimeConfigSnapshot(): void {
|
||||
resetConfigRuntimeState();
|
||||
}
|
||||
|
||||
export function getRuntimeConfigSnapshot(): OpenClawConfig | null {
|
||||
return runtimeConfigSnapshot;
|
||||
}
|
||||
|
||||
export function getRuntimeConfigSourceSnapshot(): OpenClawConfig | null {
|
||||
return runtimeConfigSourceSnapshot;
|
||||
}
|
||||
|
||||
function isCompatibleTopLevelRuntimeProjectionShape(params: {
|
||||
runtimeSnapshot: OpenClawConfig;
|
||||
candidate: OpenClawConfig;
|
||||
|
|
@ -2454,6 +2435,8 @@ function isCompatibleTopLevelRuntimeProjectionShape(params: {
|
|||
}
|
||||
|
||||
export function projectConfigOntoRuntimeSourceSnapshot(config: OpenClawConfig): OpenClawConfig {
|
||||
const runtimeConfigSnapshot = getRuntimeConfigSnapshotState();
|
||||
const runtimeConfigSourceSnapshot = getRuntimeConfigSourceSnapshotState();
|
||||
if (!runtimeConfigSnapshot || !runtimeConfigSourceSnapshot) {
|
||||
return config;
|
||||
}
|
||||
|
|
@ -2476,13 +2459,8 @@ export function projectConfigOntoRuntimeSourceSnapshot(config: OpenClawConfig):
|
|||
return coerceConfig(applyMergePatch(runtimeConfigSourceSnapshot, runtimePatch));
|
||||
}
|
||||
|
||||
export function setRuntimeConfigSnapshotRefreshHandler(
|
||||
refreshHandler: RuntimeConfigSnapshotRefreshHandler | null,
|
||||
): void {
|
||||
runtimeConfigSnapshotRefreshHandler = refreshHandler;
|
||||
}
|
||||
|
||||
export function loadConfig(): OpenClawConfig {
|
||||
const runtimeConfigSnapshot = getRuntimeConfigSnapshotState();
|
||||
if (runtimeConfigSnapshot) {
|
||||
return runtimeConfigSnapshot;
|
||||
}
|
||||
|
|
@ -2490,8 +2468,8 @@ export function loadConfig(): OpenClawConfig {
|
|||
// First successful load becomes the process snapshot. Long-lived runtimes
|
||||
// should swap this snapshot via explicit reload/watcher paths instead of
|
||||
// reparsing openclaw.json on hot code paths.
|
||||
setRuntimeConfigSnapshot(config);
|
||||
return runtimeConfigSnapshot ?? config;
|
||||
setRuntimeConfigSnapshotState(config);
|
||||
return getRuntimeConfigSnapshotState() ?? config;
|
||||
}
|
||||
|
||||
export function getRuntimeConfig(): OpenClawConfig {
|
||||
|
|
@ -2525,6 +2503,8 @@ export async function writeConfigFile(
|
|||
): Promise<void> {
|
||||
const io = createConfigIO();
|
||||
let nextCfg = cfg;
|
||||
const runtimeConfigSnapshot = getRuntimeConfigSnapshotState();
|
||||
const runtimeConfigSourceSnapshot = getRuntimeConfigSourceSnapshotState();
|
||||
const hadRuntimeSnapshot = Boolean(runtimeConfigSnapshot);
|
||||
const hadBothSnapshots = Boolean(runtimeConfigSnapshot && runtimeConfigSourceSnapshot);
|
||||
if (hadBothSnapshots) {
|
||||
|
|
@ -2538,20 +2518,21 @@ export async function writeConfigFile(
|
|||
unsetPaths: options.unsetPaths,
|
||||
});
|
||||
const notifyCommittedWrite = () => {
|
||||
if (!runtimeConfigSnapshot) {
|
||||
const currentRuntimeConfig = getRuntimeConfigSnapshotState();
|
||||
if (!currentRuntimeConfig) {
|
||||
return;
|
||||
}
|
||||
notifyConfigWriteListeners({
|
||||
configPath: io.configPath,
|
||||
sourceConfig: nextCfg,
|
||||
runtimeConfig: runtimeConfigSnapshot,
|
||||
runtimeConfig: currentRuntimeConfig,
|
||||
persistedHash: writeResult.persistedHash,
|
||||
writtenAtMs: Date.now(),
|
||||
});
|
||||
};
|
||||
// Keep the last-known-good runtime snapshot active until the specialized refresh path
|
||||
// succeeds, so concurrent readers do not observe unresolved SecretRefs mid-refresh.
|
||||
const refreshHandler = runtimeConfigSnapshotRefreshHandler;
|
||||
const refreshHandler = getRuntimeConfigSnapshotRefreshHandler();
|
||||
if (refreshHandler) {
|
||||
try {
|
||||
const refreshed = await refreshHandler.refresh({ sourceConfig: nextCfg });
|
||||
|
|
@ -2576,16 +2557,16 @@ export async function writeConfigFile(
|
|||
// Refresh both snapshots from disk atomically so follow-up reads get normalized config and
|
||||
// subsequent writes still get secret-preservation merge-patch (hadBothSnapshots stays true).
|
||||
const fresh = io.loadConfig();
|
||||
setRuntimeConfigSnapshot(fresh, nextCfg);
|
||||
setRuntimeConfigSnapshotState(fresh, nextCfg);
|
||||
notifyCommittedWrite();
|
||||
return;
|
||||
}
|
||||
if (hadRuntimeSnapshot) {
|
||||
const fresh = io.loadConfig();
|
||||
setRuntimeConfigSnapshot(fresh);
|
||||
setRuntimeConfigSnapshotState(fresh);
|
||||
notifyCommittedWrite();
|
||||
return;
|
||||
}
|
||||
setRuntimeConfigSnapshot(io.loadConfig());
|
||||
setRuntimeConfigSnapshotState(io.loadConfig());
|
||||
notifyCommittedWrite();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,12 @@ function buildDefaultTableModes(): Map<string, MarkdownTableMode> {
|
|||
);
|
||||
}
|
||||
|
||||
export const DEFAULT_TABLE_MODES = buildDefaultTableModes();
|
||||
let cachedDefaultTableModes: Map<string, MarkdownTableMode> | null = null;
|
||||
|
||||
function getDefaultTableModes(): Map<string, MarkdownTableMode> {
|
||||
cachedDefaultTableModes ??= buildDefaultTableModes();
|
||||
return cachedDefaultTableModes;
|
||||
}
|
||||
|
||||
const isMarkdownTableMode = (value: unknown): value is MarkdownTableMode =>
|
||||
value === "off" || value === "bullets" || value === "code" || value === "block";
|
||||
|
|
@ -62,7 +67,7 @@ export function resolveMarkdownTableMode(params: {
|
|||
accountId?: string | null;
|
||||
}): MarkdownTableMode {
|
||||
const channel = normalizeChannelId(params.channel);
|
||||
const defaultMode = channel ? (DEFAULT_TABLE_MODES.get(channel) ?? "code") : "code";
|
||||
const defaultMode = channel ? (getDefaultTableModes().get(channel) ?? "code") : "code";
|
||||
if (!channel || !params.cfg) {
|
||||
return defaultMode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import type { OpenClawConfig } from "./types.js";
|
||||
|
||||
export type RuntimeConfigSnapshotRefreshParams = {
|
||||
sourceConfig: OpenClawConfig;
|
||||
};
|
||||
|
||||
export type RuntimeConfigSnapshotRefreshHandler = {
|
||||
refresh: (params: RuntimeConfigSnapshotRefreshParams) => boolean | Promise<boolean>;
|
||||
clearOnRefreshFailure?: () => void;
|
||||
};
|
||||
|
||||
let runtimeConfigSnapshot: OpenClawConfig | null = null;
|
||||
let runtimeConfigSourceSnapshot: OpenClawConfig | null = null;
|
||||
let runtimeConfigSnapshotRefreshHandler: RuntimeConfigSnapshotRefreshHandler | null = null;
|
||||
|
||||
export function setRuntimeConfigSnapshot(
|
||||
config: OpenClawConfig,
|
||||
sourceConfig?: OpenClawConfig,
|
||||
): void {
|
||||
runtimeConfigSnapshot = config;
|
||||
runtimeConfigSourceSnapshot = sourceConfig ?? null;
|
||||
}
|
||||
|
||||
export function resetConfigRuntimeState(): void {
|
||||
runtimeConfigSnapshot = null;
|
||||
runtimeConfigSourceSnapshot = null;
|
||||
}
|
||||
|
||||
export function clearRuntimeConfigSnapshot(): void {
|
||||
resetConfigRuntimeState();
|
||||
}
|
||||
|
||||
export function getRuntimeConfigSnapshot(): OpenClawConfig | null {
|
||||
return runtimeConfigSnapshot;
|
||||
}
|
||||
|
||||
export function getRuntimeConfigSourceSnapshot(): OpenClawConfig | null {
|
||||
return runtimeConfigSourceSnapshot;
|
||||
}
|
||||
|
||||
export function setRuntimeConfigSnapshotRefreshHandler(
|
||||
refreshHandler: RuntimeConfigSnapshotRefreshHandler | null,
|
||||
): void {
|
||||
runtimeConfigSnapshotRefreshHandler = refreshHandler;
|
||||
}
|
||||
|
||||
export function getRuntimeConfigSnapshotRefreshHandler(): RuntimeConfigSnapshotRefreshHandler | null {
|
||||
return runtimeConfigSnapshotRefreshHandler;
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveStorePath } from "../config/sessions/paths.js";
|
||||
import { loadSessionStore } from "../config/sessions/store-load.js";
|
||||
import { normalizeOptionalAccountId } from "../routing/account-id.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||
import type { ExecApprovalRequest } from "./exec-approvals.js";
|
||||
import type { PluginApprovalRequest } from "./plugin-approvals.js";
|
||||
|
||||
type ApprovalRequestLike = ExecApprovalRequest | PluginApprovalRequest;
|
||||
|
||||
type ApprovalRequestSessionBinding = {
|
||||
channel?: string;
|
||||
accountId?: string;
|
||||
};
|
||||
|
||||
function normalizeOptionalString(value?: string | null): string | undefined {
|
||||
const normalized = value?.trim();
|
||||
return normalized ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizeOptionalChannel(value?: string | null): string | undefined {
|
||||
return normalizeMessageChannel(value);
|
||||
}
|
||||
|
||||
function resolvePersistedApprovalRequestSessionBinding(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
}): ApprovalRequestSessionBinding | null {
|
||||
const sessionKey = normalizeOptionalString(params.request.request.sessionKey);
|
||||
if (!sessionKey) {
|
||||
return null;
|
||||
}
|
||||
const parsed = parseAgentSessionKey(sessionKey);
|
||||
const agentId = parsed?.agentId ?? params.request.request.agentId ?? "main";
|
||||
const storePath = resolveStorePath(params.cfg.session?.store, { agentId });
|
||||
const store = loadSessionStore(storePath);
|
||||
const entry = store[sessionKey];
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
const channel = normalizeOptionalChannel(entry.origin?.provider ?? entry.lastChannel);
|
||||
const accountId = normalizeOptionalAccountId(entry.origin?.accountId ?? entry.lastAccountId);
|
||||
return channel || accountId ? { channel, accountId } : null;
|
||||
}
|
||||
|
||||
export function resolveApprovalRequestAccountId(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
channel?: string | null;
|
||||
}): string | null {
|
||||
const expectedChannel = normalizeOptionalChannel(params.channel);
|
||||
const turnSourceChannel = normalizeOptionalChannel(params.request.request.turnSourceChannel);
|
||||
if (expectedChannel && turnSourceChannel && turnSourceChannel !== expectedChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const turnSourceAccountId = normalizeOptionalAccountId(
|
||||
params.request.request.turnSourceAccountId,
|
||||
);
|
||||
if (turnSourceAccountId) {
|
||||
return turnSourceAccountId;
|
||||
}
|
||||
|
||||
const sessionBinding = resolvePersistedApprovalRequestSessionBinding(params);
|
||||
const sessionChannel = sessionBinding?.channel;
|
||||
if (expectedChannel && sessionChannel && sessionChannel !== expectedChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sessionBinding?.accountId ?? null;
|
||||
}
|
||||
|
||||
export function resolveApprovalRequestChannelAccountId(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
channel: string;
|
||||
}): string | null {
|
||||
const expectedChannel = normalizeOptionalChannel(params.channel);
|
||||
if (!expectedChannel) {
|
||||
return null;
|
||||
}
|
||||
return resolveApprovalRequestAccountId(params);
|
||||
}
|
||||
|
||||
export function doesApprovalRequestMatchChannelAccount(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
channel: string;
|
||||
accountId?: string | null;
|
||||
}): boolean {
|
||||
const expectedChannel = normalizeOptionalChannel(params.channel);
|
||||
if (!expectedChannel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const turnSourceChannel = normalizeOptionalChannel(params.request.request.turnSourceChannel);
|
||||
if (turnSourceChannel && turnSourceChannel !== expectedChannel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const turnSourceAccountId = normalizeOptionalAccountId(
|
||||
params.request.request.turnSourceAccountId,
|
||||
);
|
||||
const expectedAccountId = normalizeOptionalAccountId(params.accountId);
|
||||
if (turnSourceAccountId) {
|
||||
return !expectedAccountId || expectedAccountId === turnSourceAccountId;
|
||||
}
|
||||
|
||||
const sessionBinding = resolvePersistedApprovalRequestSessionBinding(params);
|
||||
const sessionChannel = sessionBinding?.channel;
|
||||
if (sessionChannel && sessionChannel !== expectedChannel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const boundAccountId = sessionBinding?.accountId;
|
||||
return !expectedAccountId || !boundAccountId || expectedAccountId === boundAccountId;
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
|
||||
import { normalizeOptionalAccountId } from "../routing/account-id.js";
|
||||
import { resolveStorePath } from "../config/sessions/paths.js";
|
||||
import { loadSessionStore } from "../config/sessions/store-load.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||
import { doesApprovalRequestMatchChannelAccount } from "./approval-request-account-binding.js";
|
||||
import type { ExecApprovalRequest } from "./exec-approvals.js";
|
||||
import { resolveSessionDeliveryTarget } from "./outbound/targets.js";
|
||||
import type { PluginApprovalRequest } from "./plugin-approvals.js";
|
||||
|
|
@ -14,11 +15,6 @@ export type ExecApprovalSessionTarget = {
|
|||
threadId?: number;
|
||||
};
|
||||
|
||||
type ApprovalRequestSessionBinding = {
|
||||
channel?: string;
|
||||
accountId?: string;
|
||||
};
|
||||
|
||||
type ApprovalRequestLike = ExecApprovalRequest | PluginApprovalRequest;
|
||||
type ApprovalRequestOriginTargetResolver<TTarget> = {
|
||||
cfg: OpenClawConfig;
|
||||
|
|
@ -115,28 +111,6 @@ export function resolveExecApprovalSessionTarget(params: {
|
|||
};
|
||||
}
|
||||
|
||||
function resolvePersistedApprovalRequestSessionBinding(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
}): ApprovalRequestSessionBinding | null {
|
||||
const sessionKey = normalizeOptionalString(params.request.request.sessionKey);
|
||||
if (!sessionKey) {
|
||||
return null;
|
||||
}
|
||||
const parsed = parseAgentSessionKey(sessionKey);
|
||||
const agentId = parsed?.agentId ?? params.request.request.agentId ?? "main";
|
||||
const storePath = resolveStorePath(params.cfg.session?.store, { agentId });
|
||||
const store = loadSessionStore(storePath);
|
||||
const entry = store[sessionKey];
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
channel: normalizeOptionalChannel(entry.origin?.provider ?? entry.lastChannel),
|
||||
accountId: normalizeOptionalAccountId(entry.origin?.accountId ?? entry.lastAccountId),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveApprovalRequestSessionTarget(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
|
|
@ -163,117 +137,6 @@ function resolveApprovalRequestStoredSessionTarget(params: {
|
|||
});
|
||||
}
|
||||
|
||||
// Account scoping uses the persisted same-channel binding first. The generic
|
||||
// session target only backfills legacy sessions that never stored `origin.*`.
|
||||
function resolveApprovalRequestAccountBinding(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
sessionTarget?: ExecApprovalSessionTarget | null;
|
||||
}): ApprovalRequestSessionBinding | null {
|
||||
const sessionBinding = resolvePersistedApprovalRequestSessionBinding(params);
|
||||
const channel = normalizeOptionalChannel(
|
||||
sessionBinding?.channel ?? params.sessionTarget?.channel,
|
||||
);
|
||||
const accountId = normalizeOptionalAccountId(
|
||||
sessionBinding?.accountId ?? params.sessionTarget?.accountId,
|
||||
);
|
||||
return channel || accountId ? { channel, accountId } : null;
|
||||
}
|
||||
|
||||
export function resolveApprovalRequestAccountId(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
channel?: string | null;
|
||||
}): string | null {
|
||||
const expectedChannel = normalizeOptionalChannel(params.channel);
|
||||
const turnSourceChannel = normalizeOptionalChannel(params.request.request.turnSourceChannel);
|
||||
if (expectedChannel && turnSourceChannel && turnSourceChannel !== expectedChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const turnSourceAccountId = normalizeOptionalAccountId(
|
||||
params.request.request.turnSourceAccountId,
|
||||
);
|
||||
if (turnSourceAccountId) {
|
||||
return turnSourceAccountId;
|
||||
}
|
||||
|
||||
const sessionBinding = resolveApprovalRequestAccountBinding({
|
||||
...params,
|
||||
sessionTarget: resolveApprovalRequestSessionTarget(params),
|
||||
});
|
||||
const sessionChannel = sessionBinding?.channel;
|
||||
if (expectedChannel && sessionChannel && sessionChannel !== expectedChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sessionBinding?.accountId ?? null;
|
||||
}
|
||||
|
||||
export function resolveApprovalRequestChannelAccountId(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
channel: string;
|
||||
}): string | null {
|
||||
const expectedChannel = normalizeOptionalChannel(params.channel);
|
||||
if (!expectedChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const turnSourceChannel = normalizeOptionalChannel(params.request.request.turnSourceChannel);
|
||||
if (!turnSourceChannel || turnSourceChannel === expectedChannel) {
|
||||
return resolveApprovalRequestAccountId(params);
|
||||
}
|
||||
|
||||
const sessionBinding = resolveApprovalRequestAccountBinding({
|
||||
...params,
|
||||
sessionTarget: resolveApprovalRequestStoredSessionTarget(params),
|
||||
});
|
||||
const sessionChannel = sessionBinding?.channel;
|
||||
if (sessionChannel && sessionChannel !== expectedChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sessionBinding?.accountId ?? null;
|
||||
}
|
||||
|
||||
export function doesApprovalRequestMatchChannelAccount(params: {
|
||||
cfg: OpenClawConfig;
|
||||
request: ApprovalRequestLike;
|
||||
channel: string;
|
||||
accountId?: string | null;
|
||||
}): boolean {
|
||||
const expectedChannel = normalizeOptionalChannel(params.channel);
|
||||
if (!expectedChannel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const turnSourceChannel = normalizeOptionalChannel(params.request.request.turnSourceChannel);
|
||||
if (turnSourceChannel && turnSourceChannel !== expectedChannel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const turnSourceAccountId = normalizeOptionalAccountId(
|
||||
params.request.request.turnSourceAccountId,
|
||||
);
|
||||
const expectedAccountId = normalizeOptionalAccountId(params.accountId);
|
||||
if (turnSourceAccountId) {
|
||||
return !expectedAccountId || expectedAccountId === turnSourceAccountId;
|
||||
}
|
||||
|
||||
const sessionBinding = resolveApprovalRequestAccountBinding({
|
||||
...params,
|
||||
sessionTarget: resolveApprovalRequestSessionTarget(params),
|
||||
});
|
||||
const sessionChannel = sessionBinding?.channel;
|
||||
if (sessionChannel && sessionChannel !== expectedChannel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const boundAccountId = sessionBinding?.accountId;
|
||||
return !expectedAccountId || !boundAccountId || expectedAccountId === boundAccountId;
|
||||
}
|
||||
|
||||
export function resolveApprovalRequestOriginTarget<TTarget>(
|
||||
params: ApprovalRequestOriginTargetResolver<TTarget>,
|
||||
): TTarget | null {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ function isApprovalTargetsMode(cfg: OpenClawConfig): boolean {
|
|||
return execApprovals.mode === "targets" || execApprovals.mode === "both";
|
||||
}
|
||||
|
||||
export { getExecApprovalReplyMetadata, matchesApprovalRequestFilters };
|
||||
|
||||
export function isChannelExecApprovalClientEnabledFromConfig(params: {
|
||||
enabled?: ChannelExecApprovalEnableMode;
|
||||
approverCount: number;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
export {
|
||||
createChannelExecApprovalProfile,
|
||||
getExecApprovalReplyMetadata,
|
||||
isChannelExecApprovalClientEnabledFromConfig,
|
||||
isChannelExecApprovalTargetRecipient,
|
||||
matchesApprovalRequestFilters,
|
||||
} from "./approval-client-helpers.js";
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export {
|
||||
createApproverRestrictedNativeApprovalAdapter,
|
||||
createApproverRestrictedNativeApprovalCapability,
|
||||
createChannelApprovalCapability,
|
||||
splitChannelApprovalCapability,
|
||||
} from "./approval-delivery-helpers.js";
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
export {
|
||||
createChannelApproverDmTargetResolver,
|
||||
createChannelNativeOriginTargetResolver,
|
||||
} from "./approval-native-helpers.js";
|
||||
export {
|
||||
resolveApprovalRequestOriginTarget,
|
||||
resolveApprovalRequestSessionTarget,
|
||||
resolveExecApprovalSessionTarget,
|
||||
type ExecApprovalSessionTarget,
|
||||
} from "../infra/exec-approval-session-target.js";
|
||||
export {
|
||||
doesApprovalRequestMatchChannelAccount,
|
||||
resolveApprovalRequestAccountId,
|
||||
resolveApprovalRequestChannelAccountId,
|
||||
} from "../infra/approval-request-account-binding.js";
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
export {
|
||||
buildExecApprovalPendingReplyPayload,
|
||||
getExecApprovalApproverDmNoticeText,
|
||||
getExecApprovalReplyMetadata,
|
||||
type ExecApprovalPendingReplyParams,
|
||||
type ExecApprovalReplyDecision,
|
||||
type ExecApprovalReplyMetadata,
|
||||
} from "../infra/exec-approval-reply.js";
|
||||
export { resolveExecApprovalCommandDisplay } from "../infra/exec-approval-command-display.js";
|
||||
export {
|
||||
resolveExecApprovalAllowedDecisions,
|
||||
resolveExecApprovalRequestAllowedDecisions,
|
||||
type ExecApprovalDecision,
|
||||
} from "../infra/exec-approvals.js";
|
||||
export { buildPluginApprovalPendingReplyPayload } from "./approval-renderers.js";
|
||||
|
|
@ -24,14 +24,16 @@ export {
|
|||
createChannelNativeOriginTargetResolver,
|
||||
} from "./approval-native-helpers.js";
|
||||
export {
|
||||
doesApprovalRequestMatchChannelAccount,
|
||||
resolveApprovalRequestOriginTarget,
|
||||
resolveApprovalRequestAccountId,
|
||||
resolveApprovalRequestChannelAccountId,
|
||||
resolveApprovalRequestSessionTarget,
|
||||
resolveExecApprovalSessionTarget,
|
||||
type ExecApprovalSessionTarget,
|
||||
} from "../infra/exec-approval-session-target.js";
|
||||
export {
|
||||
doesApprovalRequestMatchChannelAccount,
|
||||
resolveApprovalRequestAccountId,
|
||||
resolveApprovalRequestChannelAccountId,
|
||||
} from "../infra/approval-request-account-binding.js";
|
||||
export {
|
||||
buildPluginApprovalExpiredMessage,
|
||||
buildPluginApprovalRequestMessage,
|
||||
|
|
|
|||
|
|
@ -443,21 +443,15 @@ export function defineChannelPluginEntry<TPlugin>({
|
|||
registerCliMetadata,
|
||||
registerFull,
|
||||
}: DefineChannelPluginEntryOptions<TPlugin>): DefinedChannelPluginEntry<TPlugin> {
|
||||
let resolvedConfigSchema: ChannelEntryConfigSchema<TPlugin> | undefined;
|
||||
const getConfigSchema = (): ChannelEntryConfigSchema<TPlugin> => {
|
||||
resolvedConfigSchema ??=
|
||||
typeof configSchema === "function"
|
||||
? configSchema()
|
||||
: ((configSchema ?? emptyChannelConfigSchema()) as ChannelEntryConfigSchema<TPlugin>);
|
||||
return resolvedConfigSchema;
|
||||
};
|
||||
const resolvedConfigSchema: ChannelEntryConfigSchema<TPlugin> =
|
||||
typeof configSchema === "function"
|
||||
? configSchema()
|
||||
: ((configSchema ?? emptyChannelConfigSchema()) as ChannelEntryConfigSchema<TPlugin>);
|
||||
const entry = {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
get configSchema() {
|
||||
return getConfigSchema();
|
||||
},
|
||||
configSchema: resolvedConfigSchema,
|
||||
register(api: OpenClawPluginApi) {
|
||||
if (api.registrationMode === "cli-metadata") {
|
||||
registerCliMetadata?.(api);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ import fs from "node:fs";
|
|||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createJiti } from "jiti";
|
||||
import { loadConfig, type OpenClawConfig } from "../config/config.js";
|
||||
import JSON5 from "json5";
|
||||
import { resolveConfigPath } from "../config/paths.js";
|
||||
import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js";
|
||||
import { resolveBundledPluginPublicSurfacePath } from "../plugins/bundled-plugin-metadata.js";
|
||||
|
|
@ -136,8 +138,15 @@ function getJiti(modulePath: string) {
|
|||
|
||||
function readFacadeBoundaryConfigSafely(): OpenClawConfig {
|
||||
try {
|
||||
const config = loadConfig();
|
||||
return config && typeof config === "object" ? config : EMPTY_FACADE_BOUNDARY_CONFIG;
|
||||
const configPath = resolveConfigPath();
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return EMPTY_FACADE_BOUNDARY_CONFIG;
|
||||
}
|
||||
const raw = fs.readFileSync(configPath, "utf8");
|
||||
const parsed = JSON5.parse(raw);
|
||||
return parsed && typeof parsed === "object"
|
||||
? (parsed as OpenClawConfig)
|
||||
: EMPTY_FACADE_BOUNDARY_CONFIG;
|
||||
} catch {
|
||||
return EMPTY_FACADE_BOUNDARY_CONFIG;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
export { getRuntimeConfigSnapshot } from "../config/io.js";
|
||||
export { getRuntimeConfigSnapshot } from "../config/runtime-snapshot.js";
|
||||
export type { OpenClawConfig } from "../config/types.js";
|
||||
|
|
|
|||
Loading…
Reference in New Issue