mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-19 14:00:51 +00:00
fix(release): isolate bundled config docs loading
This commit is contained in:
@@ -15230,7 +15230,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Feishu",
|
||||
"help": "飞书/Lark enterprise messaging.",
|
||||
"help": "飞书/Lark enterprise messaging with doc/wiki/drive tools.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -17232,7 +17232,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Google Chat",
|
||||
"help": "Google Workspace Chat app with HTTP webhook.",
|
||||
"help": "Google Workspace Chat app via HTTP webhooks.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -22069,7 +22069,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Matrix",
|
||||
"help": "open protocol; configure a homeserver + access token.",
|
||||
"help": "open protocol; install the plugin to enable.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -26190,7 +26190,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Nostr",
|
||||
"help": "Decentralized DMs via Nostr relays (NIP-04)",
|
||||
"help": "Decentralized protocol; encrypted DMs via NIP-04.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -30798,7 +30798,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Synology Chat",
|
||||
"help": "Connect your Synology NAS Chat to OpenClaw",
|
||||
"help": "Connect your Synology NAS Chat to OpenClaw with full agent capabilities.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -34814,7 +34814,7 @@
|
||||
"network"
|
||||
],
|
||||
"label": "Tlon",
|
||||
"help": "Decentralized messaging on Urbit",
|
||||
"help": "decentralized messaging on Urbit; install the plugin to enable.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1352,7 +1352,7 @@
|
||||
{"recordType":"path","path":"channels.discord.voice.tts.provider","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.discord.voice.tts.summaryModel","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.discord.voice.tts.timeoutMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Feishu","help":"飞书/Lark enterprise messaging.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Feishu","help":"飞书/Lark enterprise messaging with doc/wiki/drive tools.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -1532,7 +1532,7 @@
|
||||
{"recordType":"path","path":"channels.feishu.webhookHost","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.webhookPath","kind":"channel","type":"string","required":true,"defaultValue":"/feishu/events","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.googlechat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Google Chat","help":"Google Workspace Chat app with HTTP webhook.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.googlechat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Google Chat","help":"Google Workspace Chat app via HTTP webhooks.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.googlechat.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.googlechat.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.googlechat.accounts.*.actions","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -1980,7 +1980,7 @@
|
||||
{"recordType":"path","path":"channels.line.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Matrix","help":"open protocol; configure a homeserver + access token.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Matrix","help":"open protocol; install the plugin to enable.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accounts.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -2362,7 +2362,7 @@
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.webhookPort","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.webhookPublicUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nostr","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Nostr","help":"Decentralized DMs via Nostr relays (NIP-04)","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nostr","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Nostr","help":"Decentralized protocol; encrypted DMs via NIP-04.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nostr.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nostr.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nostr.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -2779,7 +2779,7 @@
|
||||
{"recordType":"path","path":"channels.slack.userToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.slack.userTokenReadOnly","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["auth","channels","network","security"],"label":"Slack User Token Read Only","help":"When true, treat configured Slack user token usage as read-only helper behavior where possible. Keep enabled if you only need supplemental reads without user-context writes.","hasChildren":false}
|
||||
{"recordType":"path","path":"channels.slack.webhookPath","kind":"channel","type":"string","required":true,"defaultValue":"/slack/events","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.synology-chat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Synology Chat","help":"Connect your Synology NAS Chat to OpenClaw","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.synology-chat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Synology Chat","help":"Connect your Synology NAS Chat to OpenClaw with full agent capabilities.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.synology-chat.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.telegram","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Telegram","help":"simplest way to get started — register a bot with @BotFather and get going.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.telegram.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -3139,7 +3139,7 @@
|
||||
{"recordType":"path","path":"channels.telegram.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.telegram.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.telegram.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.tlon","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Tlon","help":"Decentralized messaging on Urbit","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.tlon","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Tlon","help":"decentralized messaging on Urbit; install the plugin to enable.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.tlon.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.tlon.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.tlon.accounts.*.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { bluebubblesPlugin } from "./src/channel.js";
|
||||
import { bluebubblesSetupPlugin } from "./src/channel.setup.js";
|
||||
|
||||
export default defineSetupPluginEntry(bluebubblesPlugin);
|
||||
export { bluebubblesSetupPlugin } from "./src/channel.setup.js";
|
||||
|
||||
export default defineSetupPluginEntry(bluebubblesSetupPlugin);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import { createAccountListHelpers, type OpenClawConfig } from "./runtime-api.js";
|
||||
import { createAccountListHelpers } from "openclaw/plugin-sdk/account-helpers";
|
||||
import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
||||
import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
|
||||
import { normalizeBlueBubblesServerUrl, type BlueBubblesAccountConfig } from "./types.js";
|
||||
|
||||
|
||||
76
extensions/bluebubbles/src/channel.setup.ts
Normal file
76
extensions/bluebubbles/src/channel.setup.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
|
||||
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
listBlueBubblesAccountIds,
|
||||
type ResolvedBlueBubblesAccount,
|
||||
resolveBlueBubblesAccount,
|
||||
resolveDefaultBlueBubblesAccountId,
|
||||
} from "./accounts.js";
|
||||
import { BlueBubblesConfigSchema } from "./config-schema.js";
|
||||
import { blueBubblesSetupAdapter } from "./setup-core.js";
|
||||
import { blueBubblesSetupWizard } from "./setup-surface.js";
|
||||
import { normalizeBlueBubblesHandle } from "./targets.js";
|
||||
|
||||
const meta = {
|
||||
id: "bluebubbles",
|
||||
label: "BlueBubbles",
|
||||
selectionLabel: "BlueBubbles (macOS app)",
|
||||
detailLabel: "BlueBubbles",
|
||||
docsPath: "/channels/bluebubbles",
|
||||
docsLabel: "bluebubbles",
|
||||
blurb: "iMessage via the BlueBubbles mac app + REST API.",
|
||||
systemImage: "bubble.left.and.text.bubble.right",
|
||||
aliases: ["bb"],
|
||||
order: 75,
|
||||
preferOver: ["imessage"],
|
||||
} as const;
|
||||
|
||||
const bluebubblesConfigAdapter = createScopedChannelConfigAdapter<ResolvedBlueBubblesAccount>({
|
||||
sectionKey: "bluebubbles",
|
||||
listAccountIds: listBlueBubblesAccountIds,
|
||||
resolveAccount: (cfg, accountId) => resolveBlueBubblesAccount({ cfg, accountId }),
|
||||
defaultAccountId: resolveDefaultBlueBubblesAccountId,
|
||||
clearBaseFields: ["serverUrl", "password", "name", "webhookPath"],
|
||||
resolveAllowFrom: (account: ResolvedBlueBubblesAccount) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatNormalizedAllowFromEntries({
|
||||
allowFrom,
|
||||
normalizeEntry: (entry) => normalizeBlueBubblesHandle(entry.replace(/^bluebubbles:/i, "")),
|
||||
}),
|
||||
});
|
||||
|
||||
export const bluebubblesSetupPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
|
||||
id: "bluebubbles",
|
||||
meta: {
|
||||
...meta,
|
||||
aliases: [...meta.aliases],
|
||||
preferOver: [...meta.preferOver],
|
||||
},
|
||||
capabilities: {
|
||||
chatTypes: ["direct", "group"],
|
||||
media: true,
|
||||
reactions: true,
|
||||
edit: true,
|
||||
unsend: true,
|
||||
reply: true,
|
||||
effects: true,
|
||||
groupManagement: true,
|
||||
},
|
||||
reload: { configPrefixes: ["channels.bluebubbles"] },
|
||||
configSchema: buildChannelConfigSchema(BlueBubblesConfigSchema),
|
||||
setupWizard: blueBubblesSetupWizard,
|
||||
config: {
|
||||
...bluebubblesConfigAdapter,
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
name: account.name,
|
||||
enabled: account.enabled,
|
||||
configured: account.configured,
|
||||
baseUrl: account.baseUrl,
|
||||
}),
|
||||
},
|
||||
setup: blueBubblesSetupAdapter,
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DEFAULT_ACCOUNT_ID, type OpenClawConfig } from "./runtime-api.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
||||
|
||||
type BlueBubblesConfigPatch = {
|
||||
serverUrl?: string;
|
||||
|
||||
@@ -3,9 +3,10 @@ import {
|
||||
buildCatchallMultiAccountChannelSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
ToolPolicySchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { z } from "zod";
|
||||
import { MarkdownConfigSchema, ToolPolicySchema } from "./runtime-api.js";
|
||||
import { buildSecretInputSchema, hasConfiguredSecretInput } from "./secret-input.js";
|
||||
|
||||
const bluebubblesActionSchema = z
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||
import { normalizeWebhookPath, type OpenClawConfig } from "./runtime-api.js";
|
||||
import { getBlueBubblesRuntime } from "./runtime.js";
|
||||
import type { BlueBubblesAccountConfig } from "./types.js";
|
||||
|
||||
export { normalizeWebhookPath };
|
||||
export {
|
||||
DEFAULT_WEBHOOK_PATH,
|
||||
normalizeWebhookPath,
|
||||
resolveWebhookPathFromConfig,
|
||||
} from "./webhook-shared.js";
|
||||
|
||||
export type BlueBubblesRuntimeEnv = {
|
||||
log?: (message: string) => void;
|
||||
@@ -29,13 +32,3 @@ export type WebhookTarget = {
|
||||
path: string;
|
||||
statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
|
||||
};
|
||||
|
||||
export const DEFAULT_WEBHOOK_PATH = "/bluebubbles-webhook";
|
||||
|
||||
export function resolveWebhookPathFromConfig(config?: BlueBubblesAccountConfig): string {
|
||||
const raw = config?.webhookPath?.trim();
|
||||
if (raw) {
|
||||
return normalizeWebhookPath(raw);
|
||||
}
|
||||
return DEFAULT_WEBHOOK_PATH;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {
|
||||
buildSecretInputSchema,
|
||||
hasConfiguredSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
normalizeSecretInputString,
|
||||
} from "./runtime-api.js";
|
||||
|
||||
} from "openclaw/plugin-sdk/secret-input-runtime";
|
||||
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input-schema";
|
||||
export {
|
||||
buildSecretInputSchema,
|
||||
hasConfiguredSecretInput,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { buildChannelSetupWizardAdapterFromSetupWizard } from "../../../src/chan
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js";
|
||||
import type { WizardPrompter } from "../../../src/wizard/prompts.js";
|
||||
import { resolveBlueBubblesAccount } from "./accounts.js";
|
||||
import { DEFAULT_WEBHOOK_PATH } from "./monitor-shared.js";
|
||||
import { DEFAULT_WEBHOOK_PATH } from "./webhook-shared.js";
|
||||
|
||||
async function createBlueBubblesConfigureAdapter() {
|
||||
const { blueBubblesSetupAdapter, blueBubblesSetupWizard } = await import("./setup-surface.js");
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
resolveDefaultBlueBubblesAccountId,
|
||||
} from "./accounts.js";
|
||||
import { applyBlueBubblesConnectionConfig } from "./config-apply.js";
|
||||
import { DEFAULT_WEBHOOK_PATH } from "./monitor-shared.js";
|
||||
import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
|
||||
import {
|
||||
blueBubblesSetupAdapter,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
} from "./setup-core.js";
|
||||
import { parseBlueBubblesAllowTarget } from "./targets.js";
|
||||
import { normalizeBlueBubblesServerUrl } from "./types.js";
|
||||
import { DEFAULT_WEBHOOK_PATH } from "./webhook-shared.js";
|
||||
|
||||
const channel = "bluebubbles" as const;
|
||||
const CONFIGURE_CUSTOM_WEBHOOK_FLAG = "__bluebubblesConfigureCustomWebhookPath";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { isAllowedParsedChatSender } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
isAllowedParsedChatSender,
|
||||
parseChatAllowTargetPrefixes,
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
type ParsedChatTarget,
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
} from "./runtime-api.js";
|
||||
} from "openclaw/plugin-sdk/imessage-core";
|
||||
|
||||
export type BlueBubblesService = "imessage" | "sms" | "auto";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DmPolicy, GroupPolicy } from "./runtime-api.js";
|
||||
import type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/setup";
|
||||
|
||||
export type { DmPolicy, GroupPolicy } from "./runtime-api.js";
|
||||
export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/setup";
|
||||
|
||||
export type BlueBubblesGroupConfig = {
|
||||
/** If true, only respond in this group when mentioned. */
|
||||
|
||||
14
extensions/bluebubbles/src/webhook-shared.ts
Normal file
14
extensions/bluebubbles/src/webhook-shared.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { normalizeWebhookPath } from "openclaw/plugin-sdk/webhook-path";
|
||||
import type { BlueBubblesAccountConfig } from "./types.js";
|
||||
|
||||
export { normalizeWebhookPath };
|
||||
|
||||
export const DEFAULT_WEBHOOK_PATH = "/bluebubbles-webhook";
|
||||
|
||||
export function resolveWebhookPathFromConfig(config?: BlueBubblesAccountConfig): string {
|
||||
const raw = config?.webhookPath?.trim();
|
||||
if (raw) {
|
||||
return normalizeWebhookPath(raw);
|
||||
}
|
||||
return DEFAULT_WEBHOOK_PATH;
|
||||
}
|
||||
@@ -8,6 +8,16 @@
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "discord",
|
||||
"label": "Discord",
|
||||
"selectionLabel": "Discord (Bot API)",
|
||||
"detailLabel": "Discord Bot",
|
||||
"docsPath": "/channels/discord",
|
||||
"docsLabel": "discord",
|
||||
"blurb": "very well supported right now.",
|
||||
"systemImage": "bubble.left.and.bubble.right"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
}
|
||||
|
||||
3
extensions/discord/src/config-schema.ts
Normal file
3
extensions/discord/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, DiscordConfigSchema } from "openclaw/plugin-sdk/discord-core";
|
||||
|
||||
export const DiscordChannelConfigSchema = buildChannelConfigSchema(DiscordConfigSchema);
|
||||
3
extensions/googlechat/src/config-schema.ts
Normal file
3
extensions/googlechat/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, GoogleChatConfigSchema } from "../runtime-api.js";
|
||||
|
||||
export const GoogleChatChannelConfigSchema = buildChannelConfigSchema(GoogleChatConfigSchema);
|
||||
@@ -8,6 +8,19 @@
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "imessage",
|
||||
"label": "iMessage",
|
||||
"selectionLabel": "iMessage (imsg)",
|
||||
"detailLabel": "iMessage",
|
||||
"docsPath": "/channels/imessage",
|
||||
"docsLabel": "imessage",
|
||||
"blurb": "this is still a work in progress.",
|
||||
"aliases": [
|
||||
"imsg"
|
||||
],
|
||||
"systemImage": "message.fill"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/imessage/src/config-schema.ts
Normal file
3
extensions/imessage/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, IMessageConfigSchema } from "openclaw/plugin-sdk/imessage-core";
|
||||
|
||||
export const IMessageChannelConfigSchema = buildChannelConfigSchema(IMessageConfigSchema);
|
||||
@@ -10,6 +10,16 @@
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "irc",
|
||||
"label": "IRC",
|
||||
"selectionLabel": "IRC (Server + Nick)",
|
||||
"detailLabel": "IRC",
|
||||
"docsPath": "/channels/irc",
|
||||
"docsLabel": "irc",
|
||||
"blurb": "classic IRC networks with DM/channel routing and pairing controls.",
|
||||
"systemImage": "network"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/line/src/config-schema.ts
Normal file
3
extensions/line/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, LineConfigSchema } from "../api.js";
|
||||
|
||||
export const LineChannelConfigSchema = buildChannelConfigSchema(LineConfigSchema);
|
||||
3
extensions/msteams/src/config-schema.ts
Normal file
3
extensions/msteams/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, MSTeamsConfigSchema } from "../runtime-api.js";
|
||||
|
||||
export const MSTeamsChannelConfigSchema = buildChannelConfigSchema(MSTeamsConfigSchema);
|
||||
@@ -8,6 +8,16 @@
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "signal",
|
||||
"label": "Signal",
|
||||
"selectionLabel": "Signal (signal-cli)",
|
||||
"detailLabel": "Signal REST",
|
||||
"docsPath": "/channels/signal",
|
||||
"docsLabel": "signal",
|
||||
"blurb": "signal-cli linked device; more setup (David Reagans: \"Hop on Discord.\").",
|
||||
"systemImage": "antenna.radiowaves.left.and.right"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/signal/src/config-schema.ts
Normal file
3
extensions/signal/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, SignalConfigSchema } from "openclaw/plugin-sdk/signal-core";
|
||||
|
||||
export const SignalChannelConfigSchema = buildChannelConfigSchema(SignalConfigSchema);
|
||||
@@ -8,6 +8,16 @@
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "slack",
|
||||
"label": "Slack",
|
||||
"selectionLabel": "Slack (Socket Mode)",
|
||||
"detailLabel": "Slack Bot",
|
||||
"docsPath": "/channels/slack",
|
||||
"docsLabel": "slack",
|
||||
"blurb": "supported (Socket Mode).",
|
||||
"systemImage": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/slack/src/config-schema.ts
Normal file
3
extensions/slack/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, SlackConfigSchema } from "openclaw/plugin-sdk/slack-core";
|
||||
|
||||
export const SlackChannelConfigSchema = buildChannelConfigSchema(SlackConfigSchema);
|
||||
4
extensions/synology-chat/src/config-schema.ts
Normal file
4
extensions/synology-chat/src/config-schema.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import { buildChannelConfigSchema } from "../api.js";
|
||||
|
||||
export const SynologyChatChannelConfigSchema = buildChannelConfigSchema(z.object({}).passthrough());
|
||||
@@ -8,6 +8,16 @@
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "telegram",
|
||||
"label": "Telegram",
|
||||
"selectionLabel": "Telegram (Bot API)",
|
||||
"detailLabel": "Telegram Bot",
|
||||
"docsPath": "/channels/telegram",
|
||||
"docsLabel": "telegram",
|
||||
"blurb": "simplest way to get started — register a bot with @BotFather and get going.",
|
||||
"systemImage": "paperplane"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/telegram/src/config-schema.ts
Normal file
3
extensions/telegram/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, TelegramConfigSchema } from "openclaw/plugin-sdk/telegram-core";
|
||||
|
||||
export const TelegramChannelConfigSchema = buildChannelConfigSchema(TelegramConfigSchema);
|
||||
@@ -12,6 +12,16 @@
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
],
|
||||
"channel": {
|
||||
"id": "twitch",
|
||||
"label": "Twitch",
|
||||
"selectionLabel": "Twitch (Chat)",
|
||||
"docsPath": "/channels/twitch",
|
||||
"blurb": "Twitch chat integration",
|
||||
"aliases": [
|
||||
"twitch-chat"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts"
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "whatsapp",
|
||||
"label": "WhatsApp",
|
||||
"selectionLabel": "WhatsApp (QR link)",
|
||||
"detailLabel": "WhatsApp Web",
|
||||
"docsPath": "/channels/whatsapp",
|
||||
"docsLabel": "whatsapp",
|
||||
"blurb": "works with your own number; recommend a separate phone + eSIM.",
|
||||
"systemImage": "message"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
extensions/whatsapp/src/config-schema.ts
Normal file
3
extensions/whatsapp/src/config-schema.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { buildChannelConfigSchema, WhatsAppConfigSchema } from "openclaw/plugin-sdk/whatsapp-core";
|
||||
|
||||
export const WhatsAppChannelConfigSchema = buildChannelConfigSchema(WhatsAppConfigSchema);
|
||||
@@ -494,6 +494,10 @@
|
||||
"types": "./dist/plugin-sdk/request-url.d.ts",
|
||||
"default": "./dist/plugin-sdk/request-url.js"
|
||||
},
|
||||
"./plugin-sdk/webhook-path": {
|
||||
"types": "./dist/plugin-sdk/webhook-path.d.ts",
|
||||
"default": "./dist/plugin-sdk/webhook-path.js"
|
||||
},
|
||||
"./plugin-sdk/runtime-store": {
|
||||
"types": "./dist/plugin-sdk/runtime-store.d.ts",
|
||||
"default": "./dist/plugin-sdk/runtime-store.js"
|
||||
@@ -522,6 +526,10 @@
|
||||
"types": "./dist/plugin-sdk/secret-input-schema.d.ts",
|
||||
"default": "./dist/plugin-sdk/secret-input-schema.js"
|
||||
},
|
||||
"./plugin-sdk/secret-input-runtime": {
|
||||
"types": "./dist/plugin-sdk/secret-input-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/secret-input-runtime.js"
|
||||
},
|
||||
"./cli-entry": "./openclaw.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -113,11 +113,13 @@
|
||||
"media-understanding",
|
||||
"google",
|
||||
"request-url",
|
||||
"webhook-path",
|
||||
"runtime-store",
|
||||
"web-media",
|
||||
"speech",
|
||||
"state-paths",
|
||||
"temp-path",
|
||||
"tool-send",
|
||||
"secret-input-schema"
|
||||
"secret-input-schema",
|
||||
"secret-input-runtime"
|
||||
]
|
||||
|
||||
56
scripts/load-channel-config-surface.ts
Normal file
56
scripts/load-channel-config-surface.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { buildChannelConfigSchema } from "../src/channels/plugins/config-schema.js";
|
||||
|
||||
function isBuiltChannelConfigSchema(
|
||||
value: unknown,
|
||||
): value is { schema: Record<string, unknown>; uiHints?: Record<string, unknown> } {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
const candidate = value as { schema?: unknown };
|
||||
return Boolean(candidate.schema && typeof candidate.schema === "object");
|
||||
}
|
||||
|
||||
function resolveConfigSchemaExport(
|
||||
imported: Record<string, unknown>,
|
||||
): { schema: Record<string, unknown>; uiHints?: Record<string, unknown> } | null {
|
||||
for (const [name, value] of Object.entries(imported)) {
|
||||
if (name.endsWith("ChannelConfigSchema") && isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, value] of Object.entries(imported)) {
|
||||
if (!name.endsWith("ConfigSchema") || name.endsWith("AccountConfigSchema")) {
|
||||
continue;
|
||||
}
|
||||
if (isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
if (value && typeof value === "object") {
|
||||
return buildChannelConfigSchema(value as never);
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of Object.values(imported)) {
|
||||
if (isBuiltChannelConfigSchema(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const modulePath = process.argv[2]?.trim();
|
||||
if (!modulePath) {
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const imported = (await import(pathToFileURL(modulePath).href)) as Record<string, unknown>;
|
||||
const resolved = resolveConfigSchemaExport(imported);
|
||||
if (!resolved) {
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
process.stdout.write(JSON.stringify(resolved));
|
||||
process.exit(0);
|
||||
@@ -1,7 +1,8 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fsSync from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { ChannelPlugin } from "../channels/plugins/index.js";
|
||||
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
|
||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
@@ -27,6 +28,20 @@ type JsonSchemaObject = JsonSchemaNode & {
|
||||
oneOf?: JsonSchemaObject[];
|
||||
};
|
||||
|
||||
type PackageChannelMetadata = {
|
||||
id: string;
|
||||
label: string;
|
||||
blurb?: string;
|
||||
};
|
||||
|
||||
type ChannelSurfaceMetadata = {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
configSchema?: Record<string, unknown>;
|
||||
configUiHints?: ConfigSchemaResponse["uiHints"];
|
||||
};
|
||||
|
||||
export type ConfigDocBaselineKind = "core" | "channel" | "plugin";
|
||||
|
||||
export type ConfigDocBaselineEntry = {
|
||||
@@ -65,6 +80,13 @@ export type ConfigDocBaselineStatefileWriteResult = {
|
||||
const GENERATED_BY = "scripts/generate-config-doc-baseline.ts" as const;
|
||||
const DEFAULT_JSON_OUTPUT = "docs/.generated/config-baseline.json";
|
||||
const DEFAULT_STATEFILE_OUTPUT = "docs/.generated/config-baseline.jsonl";
|
||||
|
||||
function logConfigDocBaselineDebug(message: string): void {
|
||||
if (process.env.OPENCLAW_CONFIG_DOC_BASELINE_DEBUG === "1") {
|
||||
console.error(`[config-doc-baseline] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRepoRoot(): string {
|
||||
const fromPackage = resolveOpenClawPackageRootSync({
|
||||
cwd: path.dirname(fileURLToPath(import.meta.url)),
|
||||
@@ -242,10 +264,10 @@ function resolveEntryKind(configPath: string): ConfigDocBaselineKind {
|
||||
return "core";
|
||||
}
|
||||
|
||||
async function resolveFirstExistingPath(candidates: string[]): Promise<string | null> {
|
||||
function resolveFirstExistingPath(candidates: string[]): string | null {
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
await fs.access(candidate);
|
||||
fsSync.accessSync(candidate);
|
||||
return candidate;
|
||||
} catch {
|
||||
// Keep scanning for other source file variants.
|
||||
@@ -254,6 +276,39 @@ async function resolveFirstExistingPath(candidates: string[]): Promise<string |
|
||||
return null;
|
||||
}
|
||||
|
||||
function loadPackageChannelMetadata(rootDir: string): PackageChannelMetadata | null {
|
||||
try {
|
||||
const packageJson = JSON.parse(
|
||||
fsSync.readFileSync(path.join(rootDir, "package.json"), "utf8"),
|
||||
) as {
|
||||
openclaw?: {
|
||||
channel?: {
|
||||
id?: unknown;
|
||||
label?: unknown;
|
||||
blurb?: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
const channel = packageJson.openclaw?.channel;
|
||||
if (!channel) {
|
||||
return null;
|
||||
}
|
||||
const id = typeof channel.id === "string" ? channel.id.trim() : "";
|
||||
const label = typeof channel.label === "string" ? channel.label.trim() : "";
|
||||
const blurb = typeof channel.blurb === "string" ? channel.blurb.trim() : "";
|
||||
if (!id || !label) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
label,
|
||||
...(blurb ? { blurb } : {}),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isChannelPlugin(value: unknown): value is ChannelPlugin {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
@@ -262,8 +317,21 @@ function isChannelPlugin(value: unknown): value is ChannelPlugin {
|
||||
return typeof candidate.id === "string" && typeof candidate.meta === "object";
|
||||
}
|
||||
|
||||
function resolveSetupChannelPlugin(value: unknown): ChannelPlugin | null {
|
||||
if (!value || typeof value !== "object") {
|
||||
return null;
|
||||
}
|
||||
const candidate = value as { plugin?: unknown };
|
||||
return isChannelPlugin(candidate.plugin) ? candidate.plugin : null;
|
||||
}
|
||||
|
||||
async function importChannelPluginModule(rootDir: string): Promise<ChannelPlugin> {
|
||||
const modulePath = await resolveFirstExistingPath([
|
||||
logConfigDocBaselineDebug(`resolve channel module ${rootDir}`);
|
||||
const modulePath = resolveFirstExistingPath([
|
||||
path.join(rootDir, "setup-entry.ts"),
|
||||
path.join(rootDir, "setup-entry.js"),
|
||||
path.join(rootDir, "setup-entry.mts"),
|
||||
path.join(rootDir, "setup-entry.mjs"),
|
||||
path.join(rootDir, "src", "channel.ts"),
|
||||
path.join(rootDir, "src", "channel.js"),
|
||||
path.join(rootDir, "src", "plugin.ts"),
|
||||
@@ -279,14 +347,23 @@ async function importChannelPluginModule(rootDir: string): Promise<ChannelPlugin
|
||||
throw new Error(`channel source not found under ${rootDir}`);
|
||||
}
|
||||
|
||||
const imported = (await import(pathToFileURL(modulePath).href)) as Record<string, unknown>;
|
||||
logConfigDocBaselineDebug(`import channel module ${modulePath}`);
|
||||
const imported = (await import(modulePath)) as Record<string, unknown>;
|
||||
logConfigDocBaselineDebug(`imported channel module ${modulePath}`);
|
||||
for (const value of Object.values(imported)) {
|
||||
if (isChannelPlugin(value)) {
|
||||
logConfigDocBaselineDebug(`resolved channel export ${modulePath}`);
|
||||
return value;
|
||||
}
|
||||
const setupPlugin = resolveSetupChannelPlugin(value);
|
||||
if (setupPlugin) {
|
||||
logConfigDocBaselineDebug(`resolved setup channel export ${modulePath}`);
|
||||
return setupPlugin;
|
||||
}
|
||||
if (typeof value === "function" && value.length === 0) {
|
||||
const resolved = value();
|
||||
if (isChannelPlugin(resolved)) {
|
||||
logConfigDocBaselineDebug(`resolved channel factory ${modulePath}`);
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
@@ -295,6 +372,91 @@ async function importChannelPluginModule(rootDir: string): Promise<ChannelPlugin
|
||||
throw new Error(`channel plugin export not found in ${modulePath}`);
|
||||
}
|
||||
|
||||
async function importChannelSurfaceMetadata(
|
||||
rootDir: string,
|
||||
repoRoot: string,
|
||||
): Promise<ChannelSurfaceMetadata | null> {
|
||||
logConfigDocBaselineDebug(`resolve channel config surface ${rootDir}`);
|
||||
const packageMetadata = loadPackageChannelMetadata(rootDir);
|
||||
if (!packageMetadata) {
|
||||
logConfigDocBaselineDebug(`missing package channel metadata ${rootDir}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const modulePath = resolveFirstExistingPath([
|
||||
path.join(rootDir, "src", "config-schema.ts"),
|
||||
path.join(rootDir, "src", "config-schema.js"),
|
||||
path.join(rootDir, "src", "config-schema.mts"),
|
||||
path.join(rootDir, "src", "config-schema.mjs"),
|
||||
]);
|
||||
if (!modulePath) {
|
||||
logConfigDocBaselineDebug(`missing channel config schema module ${rootDir}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
logConfigDocBaselineDebug(`import channel config schema ${modulePath}`);
|
||||
try {
|
||||
logConfigDocBaselineDebug(`spawn channel config schema subprocess ${modulePath}`);
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
"--import",
|
||||
"tsx",
|
||||
path.join(repoRoot, "scripts", "load-channel-config-surface.ts"),
|
||||
modulePath,
|
||||
],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
timeout: 15_000,
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
},
|
||||
);
|
||||
if (result.status !== 0 || result.error) {
|
||||
throw result.error ?? new Error(result.stderr || `child exited with status ${result.status}`);
|
||||
}
|
||||
logConfigDocBaselineDebug(`completed channel config schema subprocess ${modulePath}`);
|
||||
const configSchema = JSON.parse(result.stdout) as {
|
||||
schema: Record<string, unknown>;
|
||||
uiHints?: ConfigSchemaResponse["uiHints"];
|
||||
};
|
||||
return {
|
||||
id: packageMetadata.id,
|
||||
label: packageMetadata.label,
|
||||
description: packageMetadata.blurb,
|
||||
configSchema: configSchema.schema,
|
||||
configUiHints: configSchema.uiHints,
|
||||
};
|
||||
} catch (error) {
|
||||
logConfigDocBaselineDebug(
|
||||
`channel config schema subprocess failed for ${modulePath}: ${String(error)}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadChannelSurfaceMetadata(
|
||||
rootDir: string,
|
||||
repoRoot: string,
|
||||
): Promise<ChannelSurfaceMetadata> {
|
||||
logConfigDocBaselineDebug(`load channel surface ${rootDir}`);
|
||||
const configSurface = await importChannelSurfaceMetadata(rootDir, repoRoot);
|
||||
if (configSurface) {
|
||||
logConfigDocBaselineDebug(`resolved channel config surface ${rootDir}`);
|
||||
return configSurface;
|
||||
}
|
||||
|
||||
logConfigDocBaselineDebug(`fallback to channel plugin import ${rootDir}`);
|
||||
const plugin = await importChannelPluginModule(rootDir);
|
||||
return {
|
||||
id: plugin.id,
|
||||
label: plugin.meta.label,
|
||||
description: plugin.meta.blurb,
|
||||
configSchema: plugin.configSchema?.schema,
|
||||
configUiHints: plugin.configSchema?.uiHints,
|
||||
};
|
||||
}
|
||||
|
||||
async function loadBundledConfigSchemaResponse(): Promise<ConfigSchemaResponse> {
|
||||
const repoRoot = resolveRepoRoot();
|
||||
const env = {
|
||||
@@ -309,14 +471,26 @@ async function loadBundledConfigSchemaResponse(): Promise<ConfigSchemaResponse>
|
||||
env,
|
||||
config: {},
|
||||
});
|
||||
const channelPlugins = await Promise.all(
|
||||
manifestRegistry.plugins
|
||||
.filter((plugin) => plugin.origin === "bundled" && plugin.channels.length > 0)
|
||||
.map(async (plugin) => ({
|
||||
id: plugin.id,
|
||||
channel: await importChannelPluginModule(plugin.rootDir),
|
||||
})),
|
||||
logConfigDocBaselineDebug(`loaded ${manifestRegistry.plugins.length} bundled plugin manifests`);
|
||||
const bundledChannelPlugins = manifestRegistry.plugins.filter(
|
||||
(plugin) => plugin.origin === "bundled" && plugin.channels.length > 0,
|
||||
);
|
||||
const loadChannelsSequentiallyForDebug = process.env.OPENCLAW_CONFIG_DOC_BASELINE_DEBUG === "1";
|
||||
const channelPlugins = loadChannelsSequentiallyForDebug
|
||||
? await bundledChannelPlugins.reduce<Promise<ChannelSurfaceMetadata[]>>(
|
||||
async (promise, plugin) => {
|
||||
const loaded = await promise;
|
||||
loaded.push(await loadChannelSurfaceMetadata(plugin.rootDir, repoRoot));
|
||||
return loaded;
|
||||
},
|
||||
Promise.resolve([]),
|
||||
)
|
||||
: await Promise.all(
|
||||
bundledChannelPlugins.map(
|
||||
async (plugin) => await loadChannelSurfaceMetadata(plugin.rootDir, repoRoot),
|
||||
),
|
||||
);
|
||||
logConfigDocBaselineDebug(`imported ${channelPlugins.length} bundled channel plugins`);
|
||||
|
||||
return buildConfigSchema({
|
||||
plugins: manifestRegistry.plugins
|
||||
@@ -329,11 +503,11 @@ async function loadBundledConfigSchemaResponse(): Promise<ConfigSchemaResponse>
|
||||
configSchema: plugin.configSchema,
|
||||
})),
|
||||
channels: channelPlugins.map((entry) => ({
|
||||
id: entry.channel.id,
|
||||
label: entry.channel.meta.label,
|
||||
description: entry.channel.meta.blurb,
|
||||
configSchema: entry.channel.configSchema?.schema,
|
||||
configUiHints: entry.channel.configSchema?.uiHints,
|
||||
id: entry.id,
|
||||
label: entry.label,
|
||||
description: entry.description,
|
||||
configSchema: entry.configSchema,
|
||||
configUiHints: entry.configUiHints,
|
||||
})),
|
||||
});
|
||||
}
|
||||
@@ -344,8 +518,20 @@ export function collectConfigDocBaselineEntries(
|
||||
pathPrefix = "",
|
||||
required = false,
|
||||
entries: ConfigDocBaselineEntry[] = [],
|
||||
visited = new WeakMap<JsonSchemaObject, Set<string>>(),
|
||||
): ConfigDocBaselineEntry[] {
|
||||
const normalizedPath = normalizeBaselinePath(pathPrefix);
|
||||
const visitKey = `${normalizedPath}|${required ? "1" : "0"}`;
|
||||
const visitedPaths = visited.get(schema);
|
||||
if (visitedPaths?.has(visitKey)) {
|
||||
return entries;
|
||||
}
|
||||
if (visitedPaths) {
|
||||
visitedPaths.add(visitKey);
|
||||
} else {
|
||||
visited.set(schema, new Set([visitKey]));
|
||||
}
|
||||
|
||||
if (normalizedPath) {
|
||||
const hint = resolveUiHintMatch(uiHints, normalizedPath);
|
||||
entries.push({
|
||||
@@ -373,14 +559,21 @@ export function collectConfigDocBaselineEntries(
|
||||
continue;
|
||||
}
|
||||
const childPath = normalizedPath ? `${normalizedPath}.${key}` : key;
|
||||
collectConfigDocBaselineEntries(child, uiHints, childPath, requiredKeys.has(key), entries);
|
||||
collectConfigDocBaselineEntries(
|
||||
child,
|
||||
uiHints,
|
||||
childPath,
|
||||
requiredKeys.has(key),
|
||||
entries,
|
||||
visited,
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
|
||||
const wildcard = asSchemaObject(schema.additionalProperties);
|
||||
if (wildcard) {
|
||||
const wildcardPath = normalizedPath ? `${normalizedPath}.*` : "*";
|
||||
collectConfigDocBaselineEntries(wildcard, uiHints, wildcardPath, false, entries);
|
||||
collectConfigDocBaselineEntries(wildcard, uiHints, wildcardPath, false, entries, visited);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,13 +584,13 @@ export function collectConfigDocBaselineEntries(
|
||||
continue;
|
||||
}
|
||||
const itemPath = normalizedPath ? `${normalizedPath}.*` : "*";
|
||||
collectConfigDocBaselineEntries(child, uiHints, itemPath, false, entries);
|
||||
collectConfigDocBaselineEntries(child, uiHints, itemPath, false, entries, visited);
|
||||
}
|
||||
} else if (schema.items && typeof schema.items === "object") {
|
||||
const itemSchema = asSchemaObject(schema.items);
|
||||
if (itemSchema) {
|
||||
const itemPath = normalizedPath ? `${normalizedPath}.*` : "*";
|
||||
collectConfigDocBaselineEntries(itemSchema, uiHints, itemPath, false, entries);
|
||||
collectConfigDocBaselineEntries(itemSchema, uiHints, itemPath, false, entries, visited);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +600,7 @@ export function collectConfigDocBaselineEntries(
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
collectConfigDocBaselineEntries(child, uiHints, normalizedPath, required, entries);
|
||||
collectConfigDocBaselineEntries(child, uiHints, normalizedPath, required, entries, visited);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,14 +619,22 @@ export function dedupeConfigDocBaselineEntries(
|
||||
}
|
||||
|
||||
export async function buildConfigDocBaseline(): Promise<ConfigDocBaseline> {
|
||||
const start = Date.now();
|
||||
logConfigDocBaselineDebug("build baseline start");
|
||||
const response = await loadBundledConfigSchemaResponse();
|
||||
const schemaRoot = asSchemaObject(response.schema);
|
||||
if (!schemaRoot) {
|
||||
throw new Error("config schema root is not an object");
|
||||
}
|
||||
const collectStart = Date.now();
|
||||
logConfigDocBaselineDebug("collect baseline entries start");
|
||||
const entries = dedupeConfigDocBaselineEntries(
|
||||
collectConfigDocBaselineEntries(schemaRoot, response.uiHints),
|
||||
);
|
||||
logConfigDocBaselineDebug(
|
||||
`collect baseline entries done count=${entries.length} elapsedMs=${Date.now() - collectStart}`,
|
||||
);
|
||||
logConfigDocBaselineDebug(`build baseline done elapsedMs=${Date.now() - start}`);
|
||||
return {
|
||||
generatedBy: GENERATED_BY,
|
||||
entries,
|
||||
@@ -443,6 +644,8 @@ export async function buildConfigDocBaseline(): Promise<ConfigDocBaseline> {
|
||||
export async function renderConfigDocBaselineStatefile(
|
||||
baseline?: ConfigDocBaseline,
|
||||
): Promise<ConfigDocBaselineStatefileRender> {
|
||||
const start = Date.now();
|
||||
logConfigDocBaselineDebug("render statefile start");
|
||||
const resolvedBaseline = baseline ?? (await buildConfigDocBaseline());
|
||||
const json = `${JSON.stringify(resolvedBaseline, null, 2)}\n`;
|
||||
const metadataLine = JSON.stringify({
|
||||
@@ -456,6 +659,7 @@ export async function renderConfigDocBaselineStatefile(
|
||||
...entry,
|
||||
}),
|
||||
);
|
||||
logConfigDocBaselineDebug(`render statefile done elapsedMs=${Date.now() - start}`);
|
||||
return {
|
||||
json,
|
||||
jsonl: `${[metadataLine, ...entryLines].join("\n")}\n`,
|
||||
@@ -465,7 +669,7 @@ export async function renderConfigDocBaselineStatefile(
|
||||
|
||||
async function readIfExists(filePath: string): Promise<string | null> {
|
||||
try {
|
||||
return await fs.readFile(filePath, "utf8");
|
||||
return fsSync.readFileSync(filePath, "utf8");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -476,8 +680,8 @@ async function writeIfChanged(filePath: string, next: string): Promise<boolean>
|
||||
if (current === next) {
|
||||
return false;
|
||||
}
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, next, "utf8");
|
||||
fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fsSync.writeFileSync(filePath, next, "utf8");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -487,13 +691,23 @@ export async function writeConfigDocBaselineStatefile(params?: {
|
||||
jsonPath?: string;
|
||||
statefilePath?: string;
|
||||
}): Promise<ConfigDocBaselineStatefileWriteResult> {
|
||||
const start = Date.now();
|
||||
logConfigDocBaselineDebug("write statefile start");
|
||||
const repoRoot = params?.repoRoot ?? resolveRepoRoot();
|
||||
const jsonPath = path.resolve(repoRoot, params?.jsonPath ?? DEFAULT_JSON_OUTPUT);
|
||||
const statefilePath = path.resolve(repoRoot, params?.statefilePath ?? DEFAULT_STATEFILE_OUTPUT);
|
||||
const rendered = await renderConfigDocBaselineStatefile();
|
||||
logConfigDocBaselineDebug(`render statefile done elapsedMs=${Date.now() - start}`);
|
||||
logConfigDocBaselineDebug(`read current json start ${jsonPath}`);
|
||||
const currentJson = await readIfExists(jsonPath);
|
||||
logConfigDocBaselineDebug(`read current json done elapsedMs=${Date.now() - start}`);
|
||||
logConfigDocBaselineDebug(`read current statefile start ${statefilePath}`);
|
||||
const currentStatefile = await readIfExists(statefilePath);
|
||||
logConfigDocBaselineDebug(`read current statefile done elapsedMs=${Date.now() - start}`);
|
||||
const changed = currentJson !== rendered.json || currentStatefile !== rendered.jsonl;
|
||||
logConfigDocBaselineDebug(
|
||||
`compare statefile done changed=${changed} elapsedMs=${Date.now() - start}`,
|
||||
);
|
||||
|
||||
if (params?.check) {
|
||||
return {
|
||||
|
||||
@@ -10,3 +10,4 @@ export {
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
} from "../config/zod-schema.core.js";
|
||||
export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js";
|
||||
|
||||
@@ -12,3 +12,10 @@ export {
|
||||
resolveIMessageConfigDefaultTo,
|
||||
} from "./channel-config-helpers.js";
|
||||
export { IMessageConfigSchema } from "../config/zod-schema.providers-core.js";
|
||||
export {
|
||||
parseChatAllowTargetPrefixes,
|
||||
parseChatTargetPrefixesOrThrow,
|
||||
resolveServicePrefixedAllowTarget,
|
||||
resolveServicePrefixedTarget,
|
||||
} from "../../extensions/imessage/src/target-parsing-helpers.js";
|
||||
export type { ParsedChatTarget } from "../../extensions/imessage/src/target-parsing-helpers.js";
|
||||
|
||||
5
src/plugin-sdk/secret-input-runtime.ts
Normal file
5
src/plugin-sdk/secret-input-runtime.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
normalizeSecretInputString,
|
||||
} from "../config/types.secrets.js";
|
||||
Reference in New Issue
Block a user