diff --git a/docs/.generated/config-baseline.json b/docs/.generated/config-baseline.json index f324146e90a..ec8c22e0627 100644 --- a/docs/.generated/config-baseline.json +++ b/docs/.generated/config-baseline.json @@ -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 }, { diff --git a/docs/.generated/config-baseline.jsonl b/docs/.generated/config-baseline.jsonl index 81a75844fbb..8c75f3c5177 100644 --- a/docs/.generated/config-baseline.jsonl +++ b/docs/.generated/config-baseline.jsonl @@ -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} diff --git a/extensions/bluebubbles/setup-entry.ts b/extensions/bluebubbles/setup-entry.ts index 940837c87f6..73260ef8316 100644 --- a/extensions/bluebubbles/setup-entry.ts +++ b/extensions/bluebubbles/setup-entry.ts @@ -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); diff --git a/extensions/bluebubbles/src/accounts.ts b/extensions/bluebubbles/src/accounts.ts index 0584922dfca..5c3426f8441 100644 --- a/extensions/bluebubbles/src/accounts.ts +++ b/extensions/bluebubbles/src/accounts.ts @@ -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"; diff --git a/extensions/bluebubbles/src/channel.setup.ts b/extensions/bluebubbles/src/channel.setup.ts new file mode 100644 index 00000000000..4045b4a9ef1 --- /dev/null +++ b/extensions/bluebubbles/src/channel.setup.ts @@ -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({ + 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 = { + 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, +}; diff --git a/extensions/bluebubbles/src/config-apply.ts b/extensions/bluebubbles/src/config-apply.ts index e70d718a804..ad822c5a3aa 100644 --- a/extensions/bluebubbles/src/config-apply.ts +++ b/extensions/bluebubbles/src/config-apply.ts @@ -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; diff --git a/extensions/bluebubbles/src/config-schema.ts b/extensions/bluebubbles/src/config-schema.ts index b85f6b72841..7dab48feec5 100644 --- a/extensions/bluebubbles/src/config-schema.ts +++ b/extensions/bluebubbles/src/config-schema.ts @@ -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 diff --git a/extensions/bluebubbles/src/monitor-shared.ts b/extensions/bluebubbles/src/monitor-shared.ts index 9f0776094a0..57ace2937da 100644 --- a/extensions/bluebubbles/src/monitor-shared.ts +++ b/extensions/bluebubbles/src/monitor-shared.ts @@ -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; -} diff --git a/extensions/bluebubbles/src/secret-input.ts b/extensions/bluebubbles/src/secret-input.ts index b32083456e7..b0386988c42 100644 --- a/extensions/bluebubbles/src/secret-input.ts +++ b/extensions/bluebubbles/src/secret-input.ts @@ -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, diff --git a/extensions/bluebubbles/src/setup-surface.test.ts b/extensions/bluebubbles/src/setup-surface.test.ts index 95130666e60..f731ee8469a 100644 --- a/extensions/bluebubbles/src/setup-surface.test.ts +++ b/extensions/bluebubbles/src/setup-surface.test.ts @@ -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"); diff --git a/extensions/bluebubbles/src/setup-surface.ts b/extensions/bluebubbles/src/setup-surface.ts index 823b49908c8..6b98de3acb9 100644 --- a/extensions/bluebubbles/src/setup-surface.ts +++ b/extensions/bluebubbles/src/setup-surface.ts @@ -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"; diff --git a/extensions/bluebubbles/src/targets.ts b/extensions/bluebubbles/src/targets.ts index d445c2c5f0c..605c5cecc76 100644 --- a/extensions/bluebubbles/src/targets.ts +++ b/extensions/bluebubbles/src/targets.ts @@ -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"; diff --git a/extensions/bluebubbles/src/types.ts b/extensions/bluebubbles/src/types.ts index 1b1190c703c..5c9bf2c2ca8 100644 --- a/extensions/bluebubbles/src/types.ts +++ b/extensions/bluebubbles/src/types.ts @@ -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. */ diff --git a/extensions/bluebubbles/src/webhook-shared.ts b/extensions/bluebubbles/src/webhook-shared.ts new file mode 100644 index 00000000000..ac275e7838e --- /dev/null +++ b/extensions/bluebubbles/src/webhook-shared.ts @@ -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; +} diff --git a/extensions/discord/package.json b/extensions/discord/package.json index 82770355b9e..d2e42565a22 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -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 } diff --git a/extensions/discord/src/config-schema.ts b/extensions/discord/src/config-schema.ts new file mode 100644 index 00000000000..a6866fc092d --- /dev/null +++ b/extensions/discord/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, DiscordConfigSchema } from "openclaw/plugin-sdk/discord-core"; + +export const DiscordChannelConfigSchema = buildChannelConfigSchema(DiscordConfigSchema); diff --git a/extensions/googlechat/src/config-schema.ts b/extensions/googlechat/src/config-schema.ts new file mode 100644 index 00000000000..93c43b2e25c --- /dev/null +++ b/extensions/googlechat/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, GoogleChatConfigSchema } from "../runtime-api.js"; + +export const GoogleChatChannelConfigSchema = buildChannelConfigSchema(GoogleChatConfigSchema); diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index 591deea559b..fa0c2b12787 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -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" + } } } diff --git a/extensions/imessage/src/config-schema.ts b/extensions/imessage/src/config-schema.ts new file mode 100644 index 00000000000..dc960ccdb0e --- /dev/null +++ b/extensions/imessage/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, IMessageConfigSchema } from "openclaw/plugin-sdk/imessage-core"; + +export const IMessageChannelConfigSchema = buildChannelConfigSchema(IMessageConfigSchema); diff --git a/extensions/irc/package.json b/extensions/irc/package.json index 774fa993dbd..ac861d0a90f 100644 --- a/extensions/irc/package.json +++ b/extensions/irc/package.json @@ -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" + } } } diff --git a/extensions/line/src/config-schema.ts b/extensions/line/src/config-schema.ts new file mode 100644 index 00000000000..7248ef40aa4 --- /dev/null +++ b/extensions/line/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, LineConfigSchema } from "../api.js"; + +export const LineChannelConfigSchema = buildChannelConfigSchema(LineConfigSchema); diff --git a/extensions/msteams/src/config-schema.ts b/extensions/msteams/src/config-schema.ts new file mode 100644 index 00000000000..b0c7bc18fd9 --- /dev/null +++ b/extensions/msteams/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, MSTeamsConfigSchema } from "../runtime-api.js"; + +export const MSTeamsChannelConfigSchema = buildChannelConfigSchema(MSTeamsConfigSchema); diff --git a/extensions/signal/package.json b/extensions/signal/package.json index f63128914c9..f6d4d6c9a1d 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -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" + } } } diff --git a/extensions/signal/src/config-schema.ts b/extensions/signal/src/config-schema.ts new file mode 100644 index 00000000000..a4f2d054ffd --- /dev/null +++ b/extensions/signal/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, SignalConfigSchema } from "openclaw/plugin-sdk/signal-core"; + +export const SignalChannelConfigSchema = buildChannelConfigSchema(SignalConfigSchema); diff --git a/extensions/slack/package.json b/extensions/slack/package.json index 51439a37170..8ed415b4122 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -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" + } } } diff --git a/extensions/slack/src/config-schema.ts b/extensions/slack/src/config-schema.ts new file mode 100644 index 00000000000..d5f28cf7905 --- /dev/null +++ b/extensions/slack/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, SlackConfigSchema } from "openclaw/plugin-sdk/slack-core"; + +export const SlackChannelConfigSchema = buildChannelConfigSchema(SlackConfigSchema); diff --git a/extensions/synology-chat/src/config-schema.ts b/extensions/synology-chat/src/config-schema.ts new file mode 100644 index 00000000000..cfdc3fb7a81 --- /dev/null +++ b/extensions/synology-chat/src/config-schema.ts @@ -0,0 +1,4 @@ +import { z } from "zod"; +import { buildChannelConfigSchema } from "../api.js"; + +export const SynologyChatChannelConfigSchema = buildChannelConfigSchema(z.object({}).passthrough()); diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index deed30477a9..29c0dd9290b 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -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" + } } } diff --git a/extensions/telegram/src/config-schema.ts b/extensions/telegram/src/config-schema.ts new file mode 100644 index 00000000000..ec32270c2f2 --- /dev/null +++ b/extensions/telegram/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, TelegramConfigSchema } from "openclaw/plugin-sdk/telegram-core"; + +export const TelegramChannelConfigSchema = buildChannelConfigSchema(TelegramConfigSchema); diff --git a/extensions/twitch/package.json b/extensions/twitch/package.json index bc730150b5e..6288b6fa2bb 100644 --- a/extensions/twitch/package.json +++ b/extensions/twitch/package.json @@ -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" + ] + } } } diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index 356b2e3894b..3a2be87dca9 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -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" + } } } diff --git a/extensions/whatsapp/src/config-schema.ts b/extensions/whatsapp/src/config-schema.ts new file mode 100644 index 00000000000..23f7de4058f --- /dev/null +++ b/extensions/whatsapp/src/config-schema.ts @@ -0,0 +1,3 @@ +import { buildChannelConfigSchema, WhatsAppConfigSchema } from "openclaw/plugin-sdk/whatsapp-core"; + +export const WhatsAppChannelConfigSchema = buildChannelConfigSchema(WhatsAppConfigSchema); diff --git a/package.json b/package.json index e3978f388a1..5270222db8a 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index cb0911af1e9..61460faf315 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -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" ] diff --git a/scripts/load-channel-config-surface.ts b/scripts/load-channel-config-surface.ts new file mode 100644 index 00000000000..2dfb3e60d83 --- /dev/null +++ b/scripts/load-channel-config-surface.ts @@ -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; uiHints?: Record } { + 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, +): { schema: Record; uiHints?: Record } | 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; +const resolved = resolveConfigSchemaExport(imported); +if (!resolved) { + process.exit(3); +} + +process.stdout.write(JSON.stringify(resolved)); +process.exit(0); diff --git a/src/config/doc-baseline.ts b/src/config/doc-baseline.ts index 396634cb088..57fe4792b0b 100644 --- a/src/config/doc-baseline.ts +++ b/src/config/doc-baseline.ts @@ -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; + 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 { +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 { - 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; + logConfigDocBaselineDebug(`import channel module ${modulePath}`); + const imported = (await import(modulePath)) as Record; + 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 { + 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; + 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 { + 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 { const repoRoot = resolveRepoRoot(); const env = { @@ -309,14 +471,26 @@ async function loadBundledConfigSchemaResponse(): Promise 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>( + 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 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>(), ): 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 { + 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 { export async function renderConfigDocBaselineStatefile( baseline?: ConfigDocBaseline, ): Promise { + 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 { 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 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 { + 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 { diff --git a/src/plugin-sdk/channel-config-schema.ts b/src/plugin-sdk/channel-config-schema.ts index ac24cec0d27..0dcc9d1861c 100644 --- a/src/plugin-sdk/channel-config-schema.ts +++ b/src/plugin-sdk/channel-config-schema.ts @@ -10,3 +10,4 @@ export { GroupPolicySchema, MarkdownConfigSchema, } from "../config/zod-schema.core.js"; +export { ToolPolicySchema } from "../config/zod-schema.agent-runtime.js"; diff --git a/src/plugin-sdk/imessage-core.ts b/src/plugin-sdk/imessage-core.ts index ac93a67f307..961a3cf62ed 100644 --- a/src/plugin-sdk/imessage-core.ts +++ b/src/plugin-sdk/imessage-core.ts @@ -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"; diff --git a/src/plugin-sdk/secret-input-runtime.ts b/src/plugin-sdk/secret-input-runtime.ts new file mode 100644 index 00000000000..f0dff88987d --- /dev/null +++ b/src/plugin-sdk/secret-input-runtime.ts @@ -0,0 +1,5 @@ +export { + hasConfiguredSecretInput, + normalizeResolvedSecretInputString, + normalizeSecretInputString, +} from "../config/types.secrets.js";