@cored-im/openclaw-plugin 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -358,7 +358,7 @@ var base = (0, import_core.createChannelPluginBase)({
358
358
  setup: {
359
359
  validateInput: ({ input }) => {
360
360
  const missing = [];
361
- if (!input.appToken) missing.push("--appToken (App ID)");
361
+ if (!input.appToken) missing.push("--app-token (App ID)");
362
362
  if (!input.token) missing.push("--token (App Secret)");
363
363
  if (!input.url) missing.push("--url (Backend URL)");
364
364
  if (missing.length > 0) {
@@ -366,7 +366,7 @@ var base = (0, import_core.createChannelPluginBase)({
366
366
  `Missing required flags: ${missing.join(", ")}`,
367
367
  "",
368
368
  "Either provide all flags:",
369
- ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
369
+ ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
370
370
  "",
371
371
  "Or use the interactive wizard:",
372
372
  " openclaw channels add"
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/channel.ts","../src/config.ts","../src/core/cored-client.ts","../src/messaging/outbound.ts","../src/targets.ts","../src/messaging/inbound.ts"],"sourcesContent":["// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport { defineChannelPluginEntry } from \"openclaw/plugin-sdk/core\";\nimport { coredPlugin } from \"./channel.js\";\nimport { listEnabledAccountConfigs, validateAccountConfig } from \"./config.js\";\nimport {\n createClient,\n destroyAllClients,\n clientCount,\n} from \"./core/cored-client.js\";\nimport type { OpenClawPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { processInboundMessage } from \"./messaging/inbound.js\";\nimport { makeDeliver, setTyping, clearTyping, readMessage } from \"./messaging/outbound.js\";\nimport type { CoredAccountConfig, CoredMessageEvent } from \"./types.js\";\n\nexport default defineChannelPluginEntry({\n id: \"cored\",\n name: \"Cored\",\n description: \"Connect OpenClaw with Cored\",\n plugin: coredPlugin,\n registerFull(api) {\n api.registerService({\n id: \"cored-sdk\",\n start: async () => {\n if (clientCount() > 0) return;\n\n const accounts = listEnabledAccountConfigs(api.config);\n if (accounts.length === 0) {\n api.logger?.warn?.(\"[cored] no enabled account config found — service idle\");\n return;\n }\n\n for (const account of accounts) {\n const errors = validateAccountConfig(account);\n if (errors.length > 0) {\n api.logger?.warn?.(\n `[cored] skipping account=${account.accountId}: ${errors.map((e) => e.message).join(\"; \")}`,\n );\n continue;\n }\n\n try {\n await startAccount(api, account);\n api.logger?.info?.(\n `[cored] account=${account.accountId} connected (appId=${account.appId})`,\n );\n } catch (err) {\n api.logger?.error?.(\n `[cored] account=${account.accountId} failed to start: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n api.logger?.info?.(`[cored] service started with ${clientCount()} account(s)`);\n },\n stop: async () => {\n await destroyAllClients();\n api.logger?.info?.(\"[cored] service stopped — all clients disconnected\");\n },\n });\n\n api.logger?.info?.(\"[cored] plugin registered\");\n },\n});\n\n/**\n * Start a single account — create client, subscribe to inbound events.\n */\nasync function startAccount(\n api: OpenClawPluginApi,\n account: CoredAccountConfig,\n): Promise<void> {\n const deliver = makeDeliver(account.accountId, (msg) => api.logger?.warn?.(msg));\n\n await createClient({\n config: account,\n log: (msg: string) => api.logger?.debug?.(msg),\n onMessage: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => {\n // Fire-and-forget: process inbound with typing indicator\n handleInbound(api, accountConfig, event, deliver).catch((err) => {\n api.logger?.error?.(\n `[cored] unhandled inbound error for account=${accountConfig.accountId}: ${err}`,\n );\n });\n },\n });\n}\n\n/**\n * Handle a single inbound message with typing indicator lifecycle.\n */\nasync function handleInbound(\n api: OpenClawPluginApi,\n account: CoredAccountConfig,\n event: CoredMessageEvent,\n deliver: (chatId: string, text: string) => Promise<void>,\n): Promise<void> {\n const chatId = event.message?.chatId;\n const messageId = event.message?.messageId;\n\n // Set typing indicator and mark message as read before processing\n if (chatId) {\n setTyping(chatId, account.accountId).catch(() => {});\n }\n if (messageId) {\n readMessage(messageId, account.accountId).catch(() => {});\n }\n\n try {\n await processInboundMessage(api, account, event, { deliver });\n } finally {\n // Clear typing after dispatch completes\n if (chatId) {\n clearTyping(chatId, account.accountId).catch(() => {});\n }\n }\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createChatChannelPlugin,\n createChannelPluginBase,\n} from \"openclaw/plugin-sdk/core\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { listAccountIds, resolveAccountConfig } from \"./config.js\";\nimport { sendText } from \"./messaging/outbound.js\";\nimport { parseTarget } from \"./targets.js\";\nimport type { CoredAccountConfig } from \"./types.js\";\n\nexport const base = createChannelPluginBase<CoredAccountConfig>({\n id: \"cored\",\n\n meta: {\n id: \"cored\",\n label: \"Cored\",\n selectionLabel: \"Cored\",\n docsPath: \"/channels/cored\",\n blurb: \"Connect OpenClaw to Cored\",\n aliases: [\"co\"],\n },\n\n capabilities: {\n chatTypes: [\"direct\", \"group\"],\n },\n\n config: {\n listAccountIds: (cfg: OpenClawConfig) => listAccountIds(cfg),\n resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>\n resolveAccountConfig(cfg, accountId ?? undefined),\n inspectAccount(cfg: OpenClawConfig, accountId?: string | null) {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n const hasConfig = Boolean(\n resolved.appId && resolved.appSecret && resolved.backendUrl,\n );\n return {\n enabled: resolved.enabled,\n configured: hasConfig,\n tokenStatus: hasConfig ? \"available\" : \"missing\",\n };\n },\n },\n\n setup: {\n validateInput: ({ input }) => {\n const missing: string[] = [];\n if (!input.appToken) missing.push(\"--appToken (App ID)\");\n if (!input.token) missing.push(\"--token (App Secret)\");\n if (!input.url) missing.push(\"--url (Backend URL)\");\n if (missing.length > 0) {\n return [\n `Missing required flags: ${missing.join(\", \")}`,\n \"\",\n \"Either provide all flags:\",\n ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,\n \"\",\n \"Or use the interactive wizard:\",\n \" openclaw channels add\",\n ].join(\"\\n\");\n }\n return null;\n },\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const updated = structuredClone(cfg) as Record<string, unknown>;\n if (!updated.channels) updated.channels = {};\n const ch = updated.channels as Record<string, Record<string, unknown>>;\n if (!ch[\"cored\"]) ch[\"cored\"] = {};\n const section = ch[\"cored\"];\n\n // Map ChannelSetupInput keys to our config shape:\n // appToken → appId, token → appSecret, url → backendUrl\n const appId = input.appToken;\n const appSecret = input.token;\n const backendUrl = input.url;\n\n if (accountId && accountId !== \"default\") {\n // Multi-account: write under accounts.<accountId>\n if (!section.accounts) section.accounts = {};\n const accounts = section.accounts as Record<string, Record<string, unknown>>;\n if (!accounts[accountId]) accounts[accountId] = {};\n const account = accounts[accountId];\n if (appId) account.appId = appId;\n if (appSecret) account.appSecret = appSecret;\n if (backendUrl) account.backendUrl = backendUrl;\n } else {\n // Single-account: write at top level\n if (appId) section.appId = appId;\n if (appSecret) section.appSecret = appSecret;\n if (backendUrl) section.backendUrl = backendUrl;\n }\n\n return updated as OpenClawConfig;\n },\n },\n\n setupWizard: {\n channel: \"cored\",\n status: {\n configuredLabel: \"Connected\",\n unconfiguredLabel: \"Not configured\",\n resolveConfigured: ({ cfg }) => {\n const ids = listAccountIds(cfg);\n return ids.some((id) => {\n const resolved = resolveAccountConfig(cfg, id);\n return Boolean(resolved.appId && resolved.appSecret && resolved.backendUrl);\n });\n },\n },\n credentials: [\n {\n inputKey: \"appToken\",\n providerHint: \"cored\",\n credentialLabel: \"App ID\",\n preferredEnvVar: \"CORED_APP_ID\",\n envPrompt: \"Use CORED_APP_ID from environment?\",\n keepPrompt: \"Keep current App ID?\",\n inputPrompt: \"Enter your Cored App ID:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appId),\n hasConfiguredValue: Boolean(resolved.appId),\n };\n },\n },\n {\n inputKey: \"token\",\n providerHint: \"cored\",\n credentialLabel: \"App Secret\",\n preferredEnvVar: \"CORED_APP_SECRET\",\n envPrompt: \"Use CORED_APP_SECRET from environment?\",\n keepPrompt: \"Keep current App Secret?\",\n inputPrompt: \"Enter your Cored App Secret:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appSecret),\n hasConfiguredValue: Boolean(resolved.appSecret),\n };\n },\n },\n {\n inputKey: \"url\",\n providerHint: \"cored\",\n credentialLabel: \"Backend URL\",\n preferredEnvVar: \"CORED_BACKEND_URL\",\n envPrompt: \"Use CORED_BACKEND_URL from environment?\",\n keepPrompt: \"Keep current Backend URL?\",\n inputPrompt: \"Enter your Cored backend server URL:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.backendUrl),\n hasConfiguredValue: Boolean(resolved.backendUrl),\n };\n },\n },\n ],\n },\n});\n\n// Cast needed: createChannelPluginBase returns Partial<config> but\n// createChatChannelPlugin requires config to be defined. We always\n// provide config above so the cast is safe.\nexport const coredPlugin = createChatChannelPlugin<CoredAccountConfig>({\n base: base as Parameters<typeof createChatChannelPlugin<CoredAccountConfig>>[0][\"base\"],\n\n // DM security: who can message the bot\n security: {\n dm: {\n channelKey: \"cored\",\n resolvePolicy: () => undefined,\n resolveAllowFrom: () => [],\n defaultPolicy: \"allowlist\",\n },\n },\n\n // Threading: how replies are delivered\n threading: { topLevelReplyToMode: \"reply\" },\n\n // Outbound: send messages to the platform\n outbound: {\n attachedResults: {\n channel: \"cored\",\n sendText: async (ctx) => {\n const target = parseTarget(ctx.to);\n if (!target) {\n throw new Error(`[cored] invalid send target: ${ctx.to}`);\n }\n const result = await sendText(\n target.id,\n ctx.text,\n ctx.accountId ?? undefined,\n ctx.replyToId ?? undefined,\n );\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n return { messageId: result.messageId ?? \"\" };\n },\n },\n base: {\n deliveryMode: \"direct\",\n resolveTarget: ({ to }) => {\n if (!to) return { ok: false as const, error: new Error(\"[cored] --to is required\") };\n const target = parseTarget(to);\n if (!target) {\n return {\n ok: false as const,\n error: new Error(\n `Cored requires --to <user:ID|chat:ID>, got: ${JSON.stringify(to)}`,\n ),\n };\n }\n return { ok: true as const, to: `${target.kind}:${target.id}` };\n },\n },\n },\n});\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { CoredChannelConfig, CoredAccountConfig } from \"./types.js\";\n\nconst DEFAULTS = {\n enableEncryption: true,\n requestTimeout: 30_000,\n} as const;\n\nconst ENV_PREFIX = \"CORED_\";\n\nfunction getChannelConfig(cfg: unknown): CoredChannelConfig | undefined {\n const root = cfg as Record<string, unknown> | undefined;\n return root?.channels\n ? ((root.channels as Record<string, unknown>).cored as\n | CoredChannelConfig\n | undefined)\n : undefined;\n}\n\nfunction readEnvConfig(): Partial<CoredAccountConfig> {\n const env = process.env;\n const result: Partial<CoredAccountConfig> = {};\n\n if (env[`${ENV_PREFIX}APP_ID`]) result.appId = env[`${ENV_PREFIX}APP_ID`];\n if (env[`${ENV_PREFIX}APP_SECRET`])\n result.appSecret = env[`${ENV_PREFIX}APP_SECRET`];\n if (env[`${ENV_PREFIX}BACKEND_URL`])\n result.backendUrl = env[`${ENV_PREFIX}BACKEND_URL`];\n if (env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== undefined)\n result.enableEncryption =\n env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== \"false\";\n if (env[`${ENV_PREFIX}REQUEST_TIMEOUT`])\n result.requestTimeout = Number(env[`${ENV_PREFIX}REQUEST_TIMEOUT`]);\n\n return result;\n}\n\nexport function listAccountIds(cfg: unknown): string[] {\n const ch = getChannelConfig(cfg);\n if (!ch) {\n // Check env vars as fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n }\n if (ch.accounts) return Object.keys(ch.accounts);\n if (ch.appId) return [\"default\"];\n // env var fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n}\n\nexport function resolveAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const ch = getChannelConfig(cfg);\n const id = accountId ?? \"default\";\n const envConfig = readEnvConfig();\n\n const raw = ch?.accounts?.[id] ?? ch;\n\n return {\n accountId: id,\n appId: raw?.appId ?? envConfig.appId ?? \"\",\n appSecret: raw?.appSecret ?? envConfig.appSecret ?? \"\",\n backendUrl: raw?.backendUrl ?? envConfig.backendUrl ?? \"\",\n enabled: raw?.enabled ?? true,\n enableEncryption:\n raw?.enableEncryption ?? envConfig.enableEncryption ?? DEFAULTS.enableEncryption,\n requestTimeout:\n raw?.requestTimeout ?? envConfig.requestTimeout ?? DEFAULTS.requestTimeout,\n };\n}\n\nexport interface ConfigValidationError {\n field: string;\n message: string;\n}\n\nexport function validateAccountConfig(\n config: CoredAccountConfig,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (!config.appId) {\n errors.push({\n field: \"appId\",\n message: `Account \"${config.accountId}\": appId is required. Set it in channels.cored.appId or CORED_APP_ID env var.`,\n });\n }\n\n if (!config.appSecret) {\n errors.push({\n field: \"appSecret\",\n message: `Account \"${config.accountId}\": appSecret is required. Set it in channels.cored.appSecret or CORED_APP_SECRET env var.`,\n });\n }\n\n if (!config.backendUrl) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl is required. Set it in channels.cored.backendUrl or CORED_BACKEND_URL env var.`,\n });\n } else if (\n !config.backendUrl.startsWith(\"http://\") &&\n !config.backendUrl.startsWith(\"https://\")\n ) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl must start with http:// or https:// (got \"${config.backendUrl}\").`,\n });\n }\n\n if (\n typeof config.requestTimeout !== \"number\" ||\n !Number.isFinite(config.requestTimeout) ||\n config.requestTimeout <= 0\n ) {\n errors.push({\n field: \"requestTimeout\",\n message: `Account \"${config.accountId}\": requestTimeout must be a positive number in milliseconds (got ${config.requestTimeout}).`,\n });\n }\n\n return errors;\n}\n\nexport function resolveAndValidateAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const config = resolveAccountConfig(cfg, accountId);\n const errors = validateAccountConfig(config);\n\n if (errors.length > 0) {\n const messages = errors.map((e) => ` - ${e.message}`).join(\"\\n\");\n throw new Error(\n `[cored] Invalid config for account \"${config.accountId}\":\\n${messages}`,\n );\n }\n\n return config;\n}\n\nexport function listEnabledAccountConfigs(cfg: unknown): CoredAccountConfig[] {\n const ids = listAccountIds(cfg);\n return ids\n .map((id) => resolveAccountConfig(cfg, id))\n .filter((account) => account.enabled);\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cored client manager — wraps the @cored-im/sdk\n * and provides per-account lifecycle management.\n *\n * This module bridges the SDK's snake_case API with the plugin's camelCase\n * conventions and manages client instances by account ID.\n */\n\nimport { CoredClient, LoggerLevel, ApiError } from \"@cored-im/sdk\";\nimport type { Logger } from \"@cored-im/sdk\";\nimport type { CoredAccountConfig, ConnectionState, CoredMessageEvent } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Auth error detection\n// ---------------------------------------------------------------------------\n\n/** Cored auth failure error code (鉴权失败). */\nconst AUTH_ERROR_CODE = 40000006;\n\n/**\n * Check whether an error is a Cored auth/token failure.\n * Returns true for ApiError with code 40000006, which indicates\n * the token has expired or is otherwise invalid.\n */\nexport function isAuthError(err: unknown): boolean {\n return err instanceof ApiError && err.code === AUTH_ERROR_CODE;\n}\n\n// ---------------------------------------------------------------------------\n// Constants — the new SDK doesn't re-export message type enums from its\n// top-level barrel, so we define the constant locally. The value matches\n// the SDK's MessageType_TEXT = 'text' in message_enum.ts.\n// ---------------------------------------------------------------------------\n\nexport const MessageType_TEXT = \"text\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The SDK client instance returned by CoredClient.create().\n *\n * The new @cored-im/sdk uses:\n * - client.Im.v1.Message.sendMessage(req) — snake_case request fields\n * - client.Im.v1.Message.Event.onMessageReceive(handler) — sync, void return\n * - client.Im.v1.Chat.createTyping(req) — snake_case request fields\n * - client.preheat() / client.close() — camelCase lifecycle methods\n */\nexport type SdkClient = CoredClient;\n\n/**\n * Local send-message request shape matching the SDK's SendMessageReq.\n * Only text is used for now; add specific fields (image, card, etc.)\n * as outbound capabilities grow.\n */\nexport interface SendMessageReq {\n chat_id?: string;\n message_type?: string;\n message_content?: {\n text?: { content?: string };\n };\n reply_message_id?: string;\n}\n\n/**\n * Raw event shape from the SDK's onMessageReceive handler.\n *\n * The new SDK delivers typed events with shape:\n * { header: EventHeader, body: { message?: Message } }\n *\n * Message fields are snake_case. sender_id is a UserId object:\n * { user_id?, union_user_id?, open_user_id? }\n */\nexport interface SdkMessageEvent {\n header?: {\n event_id?: string;\n event_type?: string;\n event_created_at?: string;\n };\n body?: {\n message?: {\n message_id?: string;\n message_type?: string;\n message_status?: string;\n message_content?: unknown;\n message_created_at?: string | number;\n chat_id?: string;\n chat_seq_id?: string | number;\n sender_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n } | string;\n // These may appear in group chats\n chat_type?: string;\n mention_user_list?: Array<{\n user_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n };\n user_name?: string;\n }>;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Client state\n// ---------------------------------------------------------------------------\n\nexport interface ManagedClient {\n client: SdkClient;\n config: CoredAccountConfig;\n /** The raw handler reference, needed for offMessageReceive. */\n eventHandler?: (event: SdkMessageEvent) => void;\n /** Diagnostic connection state. Updated on create/destroy. */\n connectionState: ConnectionState;\n}\n\nconst clients = new Map<string, ManagedClient>();\n\n// ---------------------------------------------------------------------------\n// Logger adapter — bridge plugin's simple log callback to SDK's Logger interface\n// ---------------------------------------------------------------------------\n\nfunction makeLoggerAdapter(\n log?: (msg: string, ctx?: Record<string, unknown>) => void,\n): Logger {\n const emit = (level: string) => (msg: string, ...args: unknown[]) => {\n log?.(`[${level}] ${msg}${args.length ? \" \" + JSON.stringify(args) : \"\"}`);\n };\n return {\n debug: emit(\"debug\"),\n info: emit(\"info\"),\n warn: emit(\"warn\"),\n error: emit(\"error\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport interface CreateClientOptions {\n config: CoredAccountConfig;\n onMessage?: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => void;\n log?: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\n/**\n * Create and connect a Cored SDK client for the given account.\n * Stores it in the client map for later retrieval.\n */\nexport async function createClient(opts: CreateClientOptions): Promise<ManagedClient> {\n const { config, onMessage, log } = opts;\n const accountId = config.accountId;\n\n // Tear down existing client for this account if any\n if (clients.has(accountId)) {\n await destroyClient(accountId);\n }\n\n const sdkClient = await CoredClient.create(\n config.backendUrl,\n config.appId,\n config.appSecret,\n {\n enableEncryption: config.enableEncryption,\n requestTimeout: config.requestTimeout,\n logger: log ? makeLoggerAdapter(log) : undefined,\n logLevel: log ? LoggerLevel.Debug : LoggerLevel.Info,\n },\n );\n\n // Preheat warms the token and verifies connectivity\n await sdkClient.preheat();\n\n const managed: ManagedClient = { client: sdkClient, config, connectionState: \"connected\" };\n\n // Subscribe to incoming messages if handler provided\n if (onMessage) {\n const handler = (sdkEvent: SdkMessageEvent) => {\n const normalized = normalizeSdkEvent(sdkEvent);\n if (normalized) {\n onMessage(normalized, config);\n }\n };\n\n // New SDK: onMessageReceive is synchronous, returns void.\n // Store handler reference for offMessageReceive on teardown.\n sdkClient.Im.v1.Message.Event.onMessageReceive(\n handler as Parameters<typeof sdkClient.Im.v1.Message.Event.onMessageReceive>[0],\n );\n managed.eventHandler = handler;\n }\n\n clients.set(accountId, managed);\n return managed;\n}\n\n/**\n * Destroy and disconnect a client by account ID.\n */\nexport async function destroyClient(accountId: string): Promise<void> {\n const managed = clients.get(accountId);\n if (!managed) return;\n\n managed.connectionState = \"disconnecting\";\n\n // Unsubscribe from events using offMessageReceive\n if (managed.eventHandler) {\n managed.client.Im.v1.Message.Event.offMessageReceive(\n managed.eventHandler as Parameters<typeof managed.client.Im.v1.Message.Event.offMessageReceive>[0],\n );\n }\n try {\n await managed.client.close();\n } catch {\n // Best-effort close\n }\n clients.delete(accountId);\n}\n\n/**\n * Destroy all managed clients.\n */\nexport async function destroyAllClients(): Promise<void> {\n const ids = [...clients.keys()];\n await Promise.allSettled(ids.map((id) => destroyClient(id)));\n}\n\n/**\n * Get a managed client by account ID. Falls back to the first available client.\n */\nexport function getClient(accountId?: string): ManagedClient | undefined {\n if (accountId && clients.has(accountId)) return clients.get(accountId);\n if (clients.size > 0) return clients.values().next().value as ManagedClient;\n return undefined;\n}\n\n/**\n * Number of currently connected clients.\n */\nexport function clientCount(): number {\n return clients.size;\n}\n\n/**\n * Get diagnostic state for all managed clients.\n */\nexport function getClientStates(): Array<{ accountId: string; connectionState: ConnectionState }> {\n return [...clients.entries()].map(([id, m]) => ({\n accountId: id,\n connectionState: m.connectionState,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Event normalization — SDK snake_case event -> plugin camelCase\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a snake_case SDK event to the plugin's CoredMessageEvent format.\n * Returns null if the event is malformed.\n *\n * The new SDK delivers events with lowercase field names:\n * { header, body: { message: { message_id, sender_id: UserId, ... } } }\n *\n * sender_id is now a UserId object { user_id, union_user_id, open_user_id }\n * in the new SDK, but we keep backward compat with string for safety.\n */\nexport function normalizeSdkEvent(sdk: SdkMessageEvent): CoredMessageEvent | null {\n const msg = sdk.body?.message;\n if (!msg) return null;\n\n // Extract user ID from sender_id — may be UserId object or legacy string\n const senderId = msg.sender_id;\n let userId = \"\";\n if (typeof senderId === \"string\") {\n userId = senderId;\n } else if (senderId && typeof senderId === \"object\") {\n userId =\n senderId.user_id ??\n senderId.open_user_id ??\n senderId.union_user_id ??\n \"\";\n }\n\n // Extract mention user IDs from the new SDK's text mention format\n const mentionUsers = (msg.mention_user_list ?? []).map((u) => ({\n userId: u.user_id?.user_id ?? u.user_id?.open_user_id ?? u.user_id?.union_user_id ?? \"\",\n }));\n\n // message_created_at may be Int64 (string) in the new SDK\n const createdAt =\n typeof msg.message_created_at === \"string\"\n ? parseInt(msg.message_created_at, 10) || Date.now()\n : msg.message_created_at ?? Date.now();\n\n return {\n message: {\n messageId: msg.message_id ?? \"\",\n messageType: msg.message_type ?? \"\",\n messageContent: msg.message_content,\n chatId: msg.chat_id ?? \"\",\n chatType: msg.chat_type ?? \"direct\",\n sender: {\n userId,\n },\n createdAt,\n mentionUserList: mentionUsers,\n },\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Outbound message delivery — send text/typing/read to Cored chats.\n *\n * Pipeline: normalize target -> validate account/client -> send -> map errors\n *\n * Text-only for now; media/card/file delivery is follow-up (task 08).\n */\n\nimport {\n getClient,\n isAuthError,\n MessageType_TEXT,\n type ManagedClient,\n type SendMessageReq,\n} from \"../core/cored-client.js\";\n\n// ---------------------------------------------------------------------------\n// Send text\n// ---------------------------------------------------------------------------\n\nexport interface SendTextResult {\n ok: boolean;\n messageId?: string;\n error?: Error;\n provider?: string;\n}\n\n/**\n * Send a text message to a Cored chat.\n *\n * On auth error (code 40000006), forces a token refresh via preheat() and\n * retries once. This handles the SDK's token-refresh edge case without\n * requiring a gateway restart.\n */\nexport async function sendText(\n chatId: string,\n text: string,\n accountId?: string,\n replyMessageId?: string,\n logWarn?: (msg: string) => void,\n): Promise<SendTextResult> {\n const managed = getClient(accountId);\n if (!managed) {\n return {\n ok: false,\n error: new Error(\n `[cored] no connected client for account=${accountId ?? \"default\"}`,\n ),\n };\n }\n\n const req: SendMessageReq = {\n chat_id: chatId,\n message_type: MessageType_TEXT,\n message_content: {\n text: { content: text },\n },\n reply_message_id: replyMessageId,\n };\n\n try {\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (err) {\n if (!isAuthError(err)) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed for chat=${chatId}: ${err instanceof Error ? err.message : String(err)}`,\n ),\n };\n }\n\n // Auth token expired despite SDK auto-refresh — force refresh and retry once.\n // This avoids requiring a gateway restart when the SDK's background token\n // refresh hits an edge case (time sync drift, swallowed fetch failure, etc.).\n logWarn?.(\n `[cored] auth error on send (chat=${chatId}, account=${accountId ?? \"default\"}) — refreshing token and retrying`,\n );\n\n try {\n await managed.client.preheat();\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n logWarn?.(\n `[cored] auth retry succeeded (chat=${chatId}, account=${accountId ?? \"default\"})`,\n );\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (retryErr) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed after auth retry for chat=${chatId}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,\n ),\n };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Set typing indicator in a chat. Cored typing lasts ~5s.\n */\nexport async function setTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.createTyping({ chat_id: chatId });\n } catch {\n // Typing is best-effort — don't fail the message flow\n }\n}\n\n/**\n * Clear typing indicator.\n */\nexport async function clearTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.deleteTyping({ chat_id: chatId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Read receipt\n// ---------------------------------------------------------------------------\n\n/**\n * Mark a message as read.\n */\nexport async function readMessage(\n messageId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Message.readMessage({ message_id: messageId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery callback for inbound dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deliver function scoped to an account, suitable for passing\n * to processInboundMessage's InboundDispatchOptions.\n */\nexport function makeDeliver(\n accountId?: string,\n logWarn?: (msg: string) => void,\n): (chatId: string, text: string) => Promise<void> {\n return async (chatId: string, text: string) => {\n const result = await sendText(chatId, text, accountId, undefined, logWarn);\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Target parsing & validation for the --to argument.\n *\n * Accepted formats:\n * \"chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" }\n * \"user:83870344313569283\" -> { kind: \"user\", id: \"83870344313569283\" }\n * \"cored:chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (prefix stripped)\n * \"oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (bare ID defaults to chat)\n *\n * Returns null for empty, whitespace-only, or malformed targets (e.g. \"user:\" with no ID).\n */\n\nimport type { ParsedTarget } from \"./types.js\";\n\n/**\n * Parse a raw --to string into a structured target.\n * Returns null if the input is missing, empty, or has no usable ID.\n */\nexport function parseTarget(to?: string): ParsedTarget | null {\n const raw = String(to ?? \"\").trim();\n if (!raw) return null;\n\n // Strip optional \"cored:\" channel prefix (case-insensitive)\n const stripped = raw.replace(/^cored:/i, \"\");\n\n if (stripped.startsWith(\"user:\")) {\n const id = stripped.slice(\"user:\".length).trim();\n return id ? { kind: \"user\", id } : null;\n }\n\n if (stripped.startsWith(\"chat:\")) {\n const id = stripped.slice(\"chat:\".length).trim();\n return id ? { kind: \"chat\", id } : null;\n }\n\n // Bare ID — default to chat (Cored SDK uses ChatId for sending)\n return stripped ? { kind: \"chat\", id: stripped } : null;\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Inbound message processing pipeline for Cored IM.\n *\n * Pipeline: parse -> gate -> dedup -> build context -> dispatch\n *\n * Codes against the CoredClient interface contract (task 99) —\n * the client is not imported here; this module receives parsed events.\n */\n\nimport type {\n CoredAccountConfig,\n CoredMessage,\n CoredMessageEvent,\n} from \"../types.js\";\n\n// Plugin API surface used by this module.\n// At runtime the full OpenClawPluginApi is provided by the gateway;\n// we only declare the subset we access so the module stays decoupled\n// and testable without importing the full SDK.\nexport interface InboundPluginApi {\n config: Record<string, unknown>;\n runtime?: {\n channel?: {\n reply?: {\n dispatchReplyWithBufferedBlockDispatcher?: (...args: any[]) => any;\n };\n session?: {\n recordInboundSession?: (...args: any[]) => any;\n resolveStorePath?: (...args: any[]) => string;\n };\n routing?: {\n resolveAgentRoute?: (...args: any[]) => unknown;\n };\n };\n };\n logger?: {\n debug?: (msg: string) => void;\n info?: (msg: string) => void;\n warn?: (msg: string) => void;\n error?: (msg: string) => void;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Parse — extract usable text body from incoming message\n// ---------------------------------------------------------------------------\n\nexport interface ParsedInboundMessage {\n messageId: string;\n chatId: string;\n chatType: \"direct\" | \"group\";\n senderId: string;\n body: string;\n timestamp: number;\n mentionUserIds: string[];\n}\n\n/**\n * Normalize a raw Cored message event into a structured inbound message.\n * Returns `null` if the event cannot be parsed into a usable message.\n *\n * Only text messages are supported in this phase — media/card/file are\n * follow-up work (task 08).\n */\nexport function parseMessageEvent(\n event: CoredMessageEvent,\n): ParsedInboundMessage | null {\n const msg = event?.message;\n if (!msg || !msg.messageId || !msg.chatId) return null;\n\n const body = extractTextBody(msg);\n if (body === null) return null;\n\n const chatType = msg.chatType === \"group\" ? \"group\" : \"direct\";\n\n const senderId =\n msg.sender?.userId || msg.sender?.openUserId || msg.sender?.unionUserId;\n if (!senderId) return null;\n\n const mentionUserIds = (msg.mentionUserList ?? [])\n .map((u) => u.userId || u.openUserId || u.unionUserId || \"\")\n .filter(Boolean);\n\n return {\n messageId: msg.messageId,\n chatId: msg.chatId,\n chatType,\n senderId,\n body,\n timestamp: msg.createdAt ?? Date.now(),\n mentionUserIds,\n };\n}\n\n/**\n * Extract plain-text content from a Cored message.\n * Returns `null` for non-text or empty messages.\n */\nfunction extractTextBody(msg: CoredMessage): string | null {\n if (msg.messageType !== \"text\") return null;\n\n const content = msg.messageContent;\n if (typeof content === \"string\") {\n // Content may be JSON-encoded string (Cored convention)\n try {\n const parsed = JSON.parse(content);\n // Cored format: { text: { content: \"Hello\" } }\n if (parsed?.text && typeof parsed.text === \"object\" && typeof parsed.text.content === \"string\")\n return parsed.text.content.trim() || null;\n if (typeof parsed?.text === \"string\") return parsed.text.trim() || null;\n if (typeof parsed?.content === \"string\")\n return parsed.content.trim() || null;\n // Fall through to raw string\n } catch {\n // Not JSON — use raw string\n }\n return content.trim() || null;\n }\n\n if (content && typeof content === \"object\") {\n const obj = content as Record<string, unknown>;\n // Cored format: { text: { content: \"Hello\" } }\n if (obj.text && typeof obj.text === \"object\") {\n const textObj = obj.text as Record<string, unknown>;\n if (typeof textObj.content === \"string\") return textObj.content.trim() || null;\n }\n if (typeof obj.text === \"string\") return obj.text.trim() || null;\n if (typeof obj.content === \"string\") return obj.content.trim() || null;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Gate — filter out messages the plugin should not process\n// ---------------------------------------------------------------------------\n\nexport interface GateResult {\n pass: boolean;\n reason?: string;\n}\n\n/**\n * Determine whether a parsed inbound message should be processed.\n * Checks in order: self-message, whitelist, group mention requirement.\n */\nexport function checkMessageGate(\n msg: ParsedInboundMessage,\n account: CoredAccountConfig,\n): GateResult {\n return { pass: true };\n}\n\n// ---------------------------------------------------------------------------\n// Dedup — skip already-processed message IDs\n// ---------------------------------------------------------------------------\n\nconst DEDUP_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst DEDUP_CLEANUP_INTERVAL_MS = 60 * 1000; // clean every 1 minute\nconst DEDUP_MAX_SIZE = 10_000;\n\n/** In-memory set of recently processed message IDs with TTL. */\nconst processedMessages = new Map<string, number>();\nlet lastCleanup = Date.now();\n\n/**\n * Returns `true` if the message has already been processed (duplicate).\n * Records the message ID on first encounter.\n */\nexport function isDuplicate(messageId: string): boolean {\n cleanupIfNeeded();\n\n if (processedMessages.has(messageId)) return true;\n\n processedMessages.set(messageId, Date.now());\n return false;\n}\n\nfunction cleanupIfNeeded(): void {\n const now = Date.now();\n if (now - lastCleanup < DEDUP_CLEANUP_INTERVAL_MS) return;\n lastCleanup = now;\n\n const expiry = now - DEDUP_TTL_MS;\n for (const [id, ts] of processedMessages) {\n if (ts < expiry) processedMessages.delete(id);\n }\n\n // Hard cap to prevent unbounded growth\n if (processedMessages.size > DEDUP_MAX_SIZE) {\n const excess = processedMessages.size - DEDUP_MAX_SIZE;\n const iter = processedMessages.keys();\n for (let i = 0; i < excess; i++) {\n const key = iter.next().value;\n if (key !== undefined) processedMessages.delete(key);\n }\n }\n}\n\n/** Reset dedup state — exposed for testing only. */\nexport function _resetDedup(): void {\n processedMessages.clear();\n lastCleanup = Date.now();\n}\n\n// ---------------------------------------------------------------------------\n// Build context & dispatch\n// ---------------------------------------------------------------------------\n\nexport interface InboundContext {\n Body: string;\n From: string;\n To: string;\n SessionKey: string;\n AccountId: string;\n ChatType: \"direct\" | \"group\";\n Provider: \"cored\";\n Surface: \"cored\";\n MessageSid: string;\n Timestamp: number;\n CommandAuthorized: boolean;\n _cored: {\n accountId: string;\n isGroup: boolean;\n senderId: string;\n chatId: string;\n };\n}\n\n/**\n * Build the OpenClaw context payload from a parsed inbound message.\n */\nexport function buildContext(\n msg: ParsedInboundMessage,\n account: CoredAccountConfig,\n): InboundContext {\n const isGroup = msg.chatType === \"group\";\n\n // Session key: group chats key on chatId, DMs key on sender\n const sessionKey = isGroup\n ? `cored:chat:${msg.chatId}`\n : `cored:user:${msg.senderId}`;\n\n return {\n Body: msg.body,\n From: isGroup ? `cored:chat:${msg.chatId}` : `cored:user:${msg.senderId}`,\n To: `cored:bot:${account.appId}`,\n SessionKey: sessionKey,\n AccountId: account.accountId,\n ChatType: isGroup ? \"group\" : \"direct\",\n Provider: \"cored\",\n Surface: \"cored\",\n MessageSid: msg.messageId,\n Timestamp: msg.timestamp,\n CommandAuthorized: true,\n _cored: {\n accountId: account.accountId,\n isGroup,\n senderId: msg.senderId,\n chatId: msg.chatId,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Options for the deliver callback wired by the caller (e.g. the service\n * start handler in index.ts once task 06 connects the CoredClient).\n */\nexport interface InboundDispatchOptions {\n /** Send a reply back to the Cored chat. */\n deliver: (chatId: string, text: string) => Promise<void>;\n}\n\n/**\n * Process an inbound Cored message event end-to-end:\n * parse -> gate -> dedup -> build context -> dispatch.\n *\n * Returns `true` if the message was dispatched, `false` if filtered.\n */\nexport async function processInboundMessage(\n api: InboundPluginApi,\n account: CoredAccountConfig,\n event: CoredMessageEvent,\n opts: InboundDispatchOptions,\n): Promise<boolean> {\n const logger = api.logger;\n\n // Helper for safe logging\n const log = {\n debug: (msg: string) => { logger?.debug?.(msg); },\n info: (msg: string) => { logger?.info?.(msg); },\n warn: (msg: string) => { logger?.warn?.(msg); },\n error: (msg: string) => { logger?.error?.(msg); },\n };\n\n // 1. Parse\n const parsed = parseMessageEvent(event);\n if (!parsed) {\n log.debug(\n `[cored] ignoring unparseable event (messageId=${event?.message?.messageId ?? \"unknown\"} messageType=${event?.message?.messageType ?? \"undefined\"})`,\n );\n return false;\n }\n\n // 2. Gate\n const gate = checkMessageGate(parsed, account);\n if (!gate.pass) {\n log.debug(\n `[cored] gated message=${parsed.messageId} reason=${gate.reason} chat=${parsed.chatId}`,\n );\n return false;\n }\n\n // 3. Dedup\n if (isDuplicate(parsed.messageId)) {\n log.debug(\n `[cored] duplicate message=${parsed.messageId} chat=${parsed.chatId}`,\n );\n return false;\n }\n\n // 4. Build context\n const ctx = buildContext(parsed, account);\n\n log.info(\n `[cored] dispatching message=${parsed.messageId} chat=${parsed.chatId} sender=${parsed.senderId} type=${parsed.chatType}`,\n );\n\n // 5. Dispatch\n const runtime = api.runtime;\n if (!runtime?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) {\n log.warn(\"[cored] runtime.channel.reply not available — cannot dispatch\");\n return false;\n }\n\n // Record session for context continuity\n const cfgSession = (api.config as Record<string, unknown>)?.session as Record<string, unknown> | undefined;\n const storePath =\n runtime.channel.session?.resolveStorePath?.(\n cfgSession?.store,\n { agentId: \"main\" },\n ) ?? \"\";\n\n await runtime.channel.session?.recordInboundSession?.({\n storePath,\n sessionKey: ctx.SessionKey,\n ctx,\n updateLastRoute:\n ctx.ChatType === \"direct\"\n ? {\n sessionKey: ctx.SessionKey,\n channel: \"cored\",\n to: parsed.chatId,\n accountId: account.accountId,\n }\n : undefined,\n });\n\n // Dispatch reply with buffered block dispatcher\n log.debug(\n `[cored] dispatch starting for message=${parsed.messageId} session=${ctx.SessionKey}`,\n );\n\n await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx,\n cfg: api.config,\n dispatcherOptions: {\n deliver: async (payload: { text?: string }) => {\n log.info(\n `[cored] deliver callback called for message=${parsed.messageId} hasText=${!!payload.text} textLen=${payload.text?.length ?? 0}`,\n );\n if (payload.text) {\n await opts.deliver(parsed.chatId, payload.text);\n log.info(\n `[cored] deliver completed for message=${parsed.messageId} chat=${parsed.chatId}`,\n );\n }\n },\n onError: (err: unknown, info?: { kind?: string }) => {\n log.error(\n `[cored] ${info?.kind ?? \"reply\"} error for message=${parsed.messageId}: ${err}`,\n );\n },\n },\n });\n\n log.info(\n `[cored] dispatch finished for message=${parsed.messageId}`,\n );\n\n return true;\n}\n"],"mappings":"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","names":["import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/channel.ts","../src/config.ts","../src/core/cored-client.ts","../src/messaging/outbound.ts","../src/targets.ts","../src/messaging/inbound.ts"],"sourcesContent":["// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport { defineChannelPluginEntry } from \"openclaw/plugin-sdk/core\";\nimport { coredPlugin } from \"./channel.js\";\nimport { listEnabledAccountConfigs, validateAccountConfig } from \"./config.js\";\nimport {\n createClient,\n destroyAllClients,\n clientCount,\n} from \"./core/cored-client.js\";\nimport type { OpenClawPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { processInboundMessage } from \"./messaging/inbound.js\";\nimport { makeDeliver, setTyping, clearTyping, readMessage } from \"./messaging/outbound.js\";\nimport type { CoredAccountConfig, CoredMessageEvent } from \"./types.js\";\n\nexport default defineChannelPluginEntry({\n id: \"cored\",\n name: \"Cored\",\n description: \"Connect OpenClaw with Cored\",\n plugin: coredPlugin,\n registerFull(api) {\n api.registerService({\n id: \"cored-sdk\",\n start: async () => {\n if (clientCount() > 0) return;\n\n const accounts = listEnabledAccountConfigs(api.config);\n if (accounts.length === 0) {\n api.logger?.warn?.(\"[cored] no enabled account config found — service idle\");\n return;\n }\n\n for (const account of accounts) {\n const errors = validateAccountConfig(account);\n if (errors.length > 0) {\n api.logger?.warn?.(\n `[cored] skipping account=${account.accountId}: ${errors.map((e) => e.message).join(\"; \")}`,\n );\n continue;\n }\n\n try {\n await startAccount(api, account);\n api.logger?.info?.(\n `[cored] account=${account.accountId} connected (appId=${account.appId})`,\n );\n } catch (err) {\n api.logger?.error?.(\n `[cored] account=${account.accountId} failed to start: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n api.logger?.info?.(`[cored] service started with ${clientCount()} account(s)`);\n },\n stop: async () => {\n await destroyAllClients();\n api.logger?.info?.(\"[cored] service stopped — all clients disconnected\");\n },\n });\n\n api.logger?.info?.(\"[cored] plugin registered\");\n },\n});\n\n/**\n * Start a single account — create client, subscribe to inbound events.\n */\nasync function startAccount(\n api: OpenClawPluginApi,\n account: CoredAccountConfig,\n): Promise<void> {\n const deliver = makeDeliver(account.accountId, (msg) => api.logger?.warn?.(msg));\n\n await createClient({\n config: account,\n log: (msg: string) => api.logger?.debug?.(msg),\n onMessage: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => {\n // Fire-and-forget: process inbound with typing indicator\n handleInbound(api, accountConfig, event, deliver).catch((err) => {\n api.logger?.error?.(\n `[cored] unhandled inbound error for account=${accountConfig.accountId}: ${err}`,\n );\n });\n },\n });\n}\n\n/**\n * Handle a single inbound message with typing indicator lifecycle.\n */\nasync function handleInbound(\n api: OpenClawPluginApi,\n account: CoredAccountConfig,\n event: CoredMessageEvent,\n deliver: (chatId: string, text: string) => Promise<void>,\n): Promise<void> {\n const chatId = event.message?.chatId;\n const messageId = event.message?.messageId;\n\n // Set typing indicator and mark message as read before processing\n if (chatId) {\n setTyping(chatId, account.accountId).catch(() => {});\n }\n if (messageId) {\n readMessage(messageId, account.accountId).catch(() => {});\n }\n\n try {\n await processInboundMessage(api, account, event, { deliver });\n } finally {\n // Clear typing after dispatch completes\n if (chatId) {\n clearTyping(chatId, account.accountId).catch(() => {});\n }\n }\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createChatChannelPlugin,\n createChannelPluginBase,\n} from \"openclaw/plugin-sdk/core\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { listAccountIds, resolveAccountConfig } from \"./config.js\";\nimport { sendText } from \"./messaging/outbound.js\";\nimport { parseTarget } from \"./targets.js\";\nimport type { CoredAccountConfig } from \"./types.js\";\n\nexport const base = createChannelPluginBase<CoredAccountConfig>({\n id: \"cored\",\n\n meta: {\n id: \"cored\",\n label: \"Cored\",\n selectionLabel: \"Cored\",\n docsPath: \"/channels/cored\",\n blurb: \"Connect OpenClaw to Cored\",\n aliases: [\"co\"],\n },\n\n capabilities: {\n chatTypes: [\"direct\", \"group\"],\n },\n\n config: {\n listAccountIds: (cfg: OpenClawConfig) => listAccountIds(cfg),\n resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>\n resolveAccountConfig(cfg, accountId ?? undefined),\n inspectAccount(cfg: OpenClawConfig, accountId?: string | null) {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n const hasConfig = Boolean(\n resolved.appId && resolved.appSecret && resolved.backendUrl,\n );\n return {\n enabled: resolved.enabled,\n configured: hasConfig,\n tokenStatus: hasConfig ? \"available\" : \"missing\",\n };\n },\n },\n\n setup: {\n validateInput: ({ input }) => {\n const missing: string[] = [];\n if (!input.appToken) missing.push(\"--app-token (App ID)\");\n if (!input.token) missing.push(\"--token (App Secret)\");\n if (!input.url) missing.push(\"--url (Backend URL)\");\n if (missing.length > 0) {\n return [\n `Missing required flags: ${missing.join(\", \")}`,\n \"\",\n \"Either provide all flags:\",\n ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,\n \"\",\n \"Or use the interactive wizard:\",\n \" openclaw channels add\",\n ].join(\"\\n\");\n }\n return null;\n },\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const updated = structuredClone(cfg) as Record<string, unknown>;\n if (!updated.channels) updated.channels = {};\n const ch = updated.channels as Record<string, Record<string, unknown>>;\n if (!ch[\"cored\"]) ch[\"cored\"] = {};\n const section = ch[\"cored\"];\n\n // Map ChannelSetupInput keys to our config shape:\n // appToken → appId, token → appSecret, url → backendUrl\n const appId = input.appToken;\n const appSecret = input.token;\n const backendUrl = input.url;\n\n if (accountId && accountId !== \"default\") {\n // Multi-account: write under accounts.<accountId>\n if (!section.accounts) section.accounts = {};\n const accounts = section.accounts as Record<string, Record<string, unknown>>;\n if (!accounts[accountId]) accounts[accountId] = {};\n const account = accounts[accountId];\n if (appId) account.appId = appId;\n if (appSecret) account.appSecret = appSecret;\n if (backendUrl) account.backendUrl = backendUrl;\n } else {\n // Single-account: write at top level\n if (appId) section.appId = appId;\n if (appSecret) section.appSecret = appSecret;\n if (backendUrl) section.backendUrl = backendUrl;\n }\n\n return updated as OpenClawConfig;\n },\n },\n\n setupWizard: {\n channel: \"cored\",\n status: {\n configuredLabel: \"Connected\",\n unconfiguredLabel: \"Not configured\",\n resolveConfigured: ({ cfg }) => {\n const ids = listAccountIds(cfg);\n return ids.some((id) => {\n const resolved = resolveAccountConfig(cfg, id);\n return Boolean(resolved.appId && resolved.appSecret && resolved.backendUrl);\n });\n },\n },\n credentials: [\n {\n inputKey: \"appToken\",\n providerHint: \"cored\",\n credentialLabel: \"App ID\",\n preferredEnvVar: \"CORED_APP_ID\",\n envPrompt: \"Use CORED_APP_ID from environment?\",\n keepPrompt: \"Keep current App ID?\",\n inputPrompt: \"Enter your Cored App ID:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appId),\n hasConfiguredValue: Boolean(resolved.appId),\n };\n },\n },\n {\n inputKey: \"token\",\n providerHint: \"cored\",\n credentialLabel: \"App Secret\",\n preferredEnvVar: \"CORED_APP_SECRET\",\n envPrompt: \"Use CORED_APP_SECRET from environment?\",\n keepPrompt: \"Keep current App Secret?\",\n inputPrompt: \"Enter your Cored App Secret:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appSecret),\n hasConfiguredValue: Boolean(resolved.appSecret),\n };\n },\n },\n {\n inputKey: \"url\",\n providerHint: \"cored\",\n credentialLabel: \"Backend URL\",\n preferredEnvVar: \"CORED_BACKEND_URL\",\n envPrompt: \"Use CORED_BACKEND_URL from environment?\",\n keepPrompt: \"Keep current Backend URL?\",\n inputPrompt: \"Enter your Cored backend server URL:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.backendUrl),\n hasConfiguredValue: Boolean(resolved.backendUrl),\n };\n },\n },\n ],\n },\n});\n\n// Cast needed: createChannelPluginBase returns Partial<config> but\n// createChatChannelPlugin requires config to be defined. We always\n// provide config above so the cast is safe.\nexport const coredPlugin = createChatChannelPlugin<CoredAccountConfig>({\n base: base as Parameters<typeof createChatChannelPlugin<CoredAccountConfig>>[0][\"base\"],\n\n // DM security: who can message the bot\n security: {\n dm: {\n channelKey: \"cored\",\n resolvePolicy: () => undefined,\n resolveAllowFrom: () => [],\n defaultPolicy: \"allowlist\",\n },\n },\n\n // Threading: how replies are delivered\n threading: { topLevelReplyToMode: \"reply\" },\n\n // Outbound: send messages to the platform\n outbound: {\n attachedResults: {\n channel: \"cored\",\n sendText: async (ctx) => {\n const target = parseTarget(ctx.to);\n if (!target) {\n throw new Error(`[cored] invalid send target: ${ctx.to}`);\n }\n const result = await sendText(\n target.id,\n ctx.text,\n ctx.accountId ?? undefined,\n ctx.replyToId ?? undefined,\n );\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n return { messageId: result.messageId ?? \"\" };\n },\n },\n base: {\n deliveryMode: \"direct\",\n resolveTarget: ({ to }) => {\n if (!to) return { ok: false as const, error: new Error(\"[cored] --to is required\") };\n const target = parseTarget(to);\n if (!target) {\n return {\n ok: false as const,\n error: new Error(\n `Cored requires --to <user:ID|chat:ID>, got: ${JSON.stringify(to)}`,\n ),\n };\n }\n return { ok: true as const, to: `${target.kind}:${target.id}` };\n },\n },\n },\n});\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { CoredChannelConfig, CoredAccountConfig } from \"./types.js\";\n\nconst DEFAULTS = {\n enableEncryption: true,\n requestTimeout: 30_000,\n} as const;\n\nconst ENV_PREFIX = \"CORED_\";\n\nfunction getChannelConfig(cfg: unknown): CoredChannelConfig | undefined {\n const root = cfg as Record<string, unknown> | undefined;\n return root?.channels\n ? ((root.channels as Record<string, unknown>).cored as\n | CoredChannelConfig\n | undefined)\n : undefined;\n}\n\nfunction readEnvConfig(): Partial<CoredAccountConfig> {\n const env = process.env;\n const result: Partial<CoredAccountConfig> = {};\n\n if (env[`${ENV_PREFIX}APP_ID`]) result.appId = env[`${ENV_PREFIX}APP_ID`];\n if (env[`${ENV_PREFIX}APP_SECRET`])\n result.appSecret = env[`${ENV_PREFIX}APP_SECRET`];\n if (env[`${ENV_PREFIX}BACKEND_URL`])\n result.backendUrl = env[`${ENV_PREFIX}BACKEND_URL`];\n if (env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== undefined)\n result.enableEncryption =\n env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== \"false\";\n if (env[`${ENV_PREFIX}REQUEST_TIMEOUT`])\n result.requestTimeout = Number(env[`${ENV_PREFIX}REQUEST_TIMEOUT`]);\n\n return result;\n}\n\nexport function listAccountIds(cfg: unknown): string[] {\n const ch = getChannelConfig(cfg);\n if (!ch) {\n // Check env vars as fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n }\n if (ch.accounts) return Object.keys(ch.accounts);\n if (ch.appId) return [\"default\"];\n // env var fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n}\n\nexport function resolveAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const ch = getChannelConfig(cfg);\n const id = accountId ?? \"default\";\n const envConfig = readEnvConfig();\n\n const raw = ch?.accounts?.[id] ?? ch;\n\n return {\n accountId: id,\n appId: raw?.appId ?? envConfig.appId ?? \"\",\n appSecret: raw?.appSecret ?? envConfig.appSecret ?? \"\",\n backendUrl: raw?.backendUrl ?? envConfig.backendUrl ?? \"\",\n enabled: raw?.enabled ?? true,\n enableEncryption:\n raw?.enableEncryption ?? envConfig.enableEncryption ?? DEFAULTS.enableEncryption,\n requestTimeout:\n raw?.requestTimeout ?? envConfig.requestTimeout ?? DEFAULTS.requestTimeout,\n };\n}\n\nexport interface ConfigValidationError {\n field: string;\n message: string;\n}\n\nexport function validateAccountConfig(\n config: CoredAccountConfig,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (!config.appId) {\n errors.push({\n field: \"appId\",\n message: `Account \"${config.accountId}\": appId is required. Set it in channels.cored.appId or CORED_APP_ID env var.`,\n });\n }\n\n if (!config.appSecret) {\n errors.push({\n field: \"appSecret\",\n message: `Account \"${config.accountId}\": appSecret is required. Set it in channels.cored.appSecret or CORED_APP_SECRET env var.`,\n });\n }\n\n if (!config.backendUrl) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl is required. Set it in channels.cored.backendUrl or CORED_BACKEND_URL env var.`,\n });\n } else if (\n !config.backendUrl.startsWith(\"http://\") &&\n !config.backendUrl.startsWith(\"https://\")\n ) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl must start with http:// or https:// (got \"${config.backendUrl}\").`,\n });\n }\n\n if (\n typeof config.requestTimeout !== \"number\" ||\n !Number.isFinite(config.requestTimeout) ||\n config.requestTimeout <= 0\n ) {\n errors.push({\n field: \"requestTimeout\",\n message: `Account \"${config.accountId}\": requestTimeout must be a positive number in milliseconds (got ${config.requestTimeout}).`,\n });\n }\n\n return errors;\n}\n\nexport function resolveAndValidateAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const config = resolveAccountConfig(cfg, accountId);\n const errors = validateAccountConfig(config);\n\n if (errors.length > 0) {\n const messages = errors.map((e) => ` - ${e.message}`).join(\"\\n\");\n throw new Error(\n `[cored] Invalid config for account \"${config.accountId}\":\\n${messages}`,\n );\n }\n\n return config;\n}\n\nexport function listEnabledAccountConfigs(cfg: unknown): CoredAccountConfig[] {\n const ids = listAccountIds(cfg);\n return ids\n .map((id) => resolveAccountConfig(cfg, id))\n .filter((account) => account.enabled);\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cored client manager — wraps the @cored-im/sdk\n * and provides per-account lifecycle management.\n *\n * This module bridges the SDK's snake_case API with the plugin's camelCase\n * conventions and manages client instances by account ID.\n */\n\nimport { CoredClient, LoggerLevel, ApiError } from \"@cored-im/sdk\";\nimport type { Logger } from \"@cored-im/sdk\";\nimport type { CoredAccountConfig, ConnectionState, CoredMessageEvent } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Auth error detection\n// ---------------------------------------------------------------------------\n\n/** Cored auth failure error code (鉴权失败). */\nconst AUTH_ERROR_CODE = 40000006;\n\n/**\n * Check whether an error is a Cored auth/token failure.\n * Returns true for ApiError with code 40000006, which indicates\n * the token has expired or is otherwise invalid.\n */\nexport function isAuthError(err: unknown): boolean {\n return err instanceof ApiError && err.code === AUTH_ERROR_CODE;\n}\n\n// ---------------------------------------------------------------------------\n// Constants — the new SDK doesn't re-export message type enums from its\n// top-level barrel, so we define the constant locally. The value matches\n// the SDK's MessageType_TEXT = 'text' in message_enum.ts.\n// ---------------------------------------------------------------------------\n\nexport const MessageType_TEXT = \"text\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The SDK client instance returned by CoredClient.create().\n *\n * The new @cored-im/sdk uses:\n * - client.Im.v1.Message.sendMessage(req) — snake_case request fields\n * - client.Im.v1.Message.Event.onMessageReceive(handler) — sync, void return\n * - client.Im.v1.Chat.createTyping(req) — snake_case request fields\n * - client.preheat() / client.close() — camelCase lifecycle methods\n */\nexport type SdkClient = CoredClient;\n\n/**\n * Local send-message request shape matching the SDK's SendMessageReq.\n * Only text is used for now; add specific fields (image, card, etc.)\n * as outbound capabilities grow.\n */\nexport interface SendMessageReq {\n chat_id?: string;\n message_type?: string;\n message_content?: {\n text?: { content?: string };\n };\n reply_message_id?: string;\n}\n\n/**\n * Raw event shape from the SDK's onMessageReceive handler.\n *\n * The new SDK delivers typed events with shape:\n * { header: EventHeader, body: { message?: Message } }\n *\n * Message fields are snake_case. sender_id is a UserId object:\n * { user_id?, union_user_id?, open_user_id? }\n */\nexport interface SdkMessageEvent {\n header?: {\n event_id?: string;\n event_type?: string;\n event_created_at?: string;\n };\n body?: {\n message?: {\n message_id?: string;\n message_type?: string;\n message_status?: string;\n message_content?: unknown;\n message_created_at?: string | number;\n chat_id?: string;\n chat_seq_id?: string | number;\n sender_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n } | string;\n // These may appear in group chats\n chat_type?: string;\n mention_user_list?: Array<{\n user_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n };\n user_name?: string;\n }>;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Client state\n// ---------------------------------------------------------------------------\n\nexport interface ManagedClient {\n client: SdkClient;\n config: CoredAccountConfig;\n /** The raw handler reference, needed for offMessageReceive. */\n eventHandler?: (event: SdkMessageEvent) => void;\n /** Diagnostic connection state. Updated on create/destroy. */\n connectionState: ConnectionState;\n}\n\nconst clients = new Map<string, ManagedClient>();\n\n// ---------------------------------------------------------------------------\n// Logger adapter — bridge plugin's simple log callback to SDK's Logger interface\n// ---------------------------------------------------------------------------\n\nfunction makeLoggerAdapter(\n log?: (msg: string, ctx?: Record<string, unknown>) => void,\n): Logger {\n const emit = (level: string) => (msg: string, ...args: unknown[]) => {\n log?.(`[${level}] ${msg}${args.length ? \" \" + JSON.stringify(args) : \"\"}`);\n };\n return {\n debug: emit(\"debug\"),\n info: emit(\"info\"),\n warn: emit(\"warn\"),\n error: emit(\"error\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport interface CreateClientOptions {\n config: CoredAccountConfig;\n onMessage?: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => void;\n log?: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\n/**\n * Create and connect a Cored SDK client for the given account.\n * Stores it in the client map for later retrieval.\n */\nexport async function createClient(opts: CreateClientOptions): Promise<ManagedClient> {\n const { config, onMessage, log } = opts;\n const accountId = config.accountId;\n\n // Tear down existing client for this account if any\n if (clients.has(accountId)) {\n await destroyClient(accountId);\n }\n\n const sdkClient = await CoredClient.create(\n config.backendUrl,\n config.appId,\n config.appSecret,\n {\n enableEncryption: config.enableEncryption,\n requestTimeout: config.requestTimeout,\n logger: log ? makeLoggerAdapter(log) : undefined,\n logLevel: log ? LoggerLevel.Debug : LoggerLevel.Info,\n },\n );\n\n // Preheat warms the token and verifies connectivity\n await sdkClient.preheat();\n\n const managed: ManagedClient = { client: sdkClient, config, connectionState: \"connected\" };\n\n // Subscribe to incoming messages if handler provided\n if (onMessage) {\n const handler = (sdkEvent: SdkMessageEvent) => {\n const normalized = normalizeSdkEvent(sdkEvent);\n if (normalized) {\n onMessage(normalized, config);\n }\n };\n\n // New SDK: onMessageReceive is synchronous, returns void.\n // Store handler reference for offMessageReceive on teardown.\n sdkClient.Im.v1.Message.Event.onMessageReceive(\n handler as Parameters<typeof sdkClient.Im.v1.Message.Event.onMessageReceive>[0],\n );\n managed.eventHandler = handler;\n }\n\n clients.set(accountId, managed);\n return managed;\n}\n\n/**\n * Destroy and disconnect a client by account ID.\n */\nexport async function destroyClient(accountId: string): Promise<void> {\n const managed = clients.get(accountId);\n if (!managed) return;\n\n managed.connectionState = \"disconnecting\";\n\n // Unsubscribe from events using offMessageReceive\n if (managed.eventHandler) {\n managed.client.Im.v1.Message.Event.offMessageReceive(\n managed.eventHandler as Parameters<typeof managed.client.Im.v1.Message.Event.offMessageReceive>[0],\n );\n }\n try {\n await managed.client.close();\n } catch {\n // Best-effort close\n }\n clients.delete(accountId);\n}\n\n/**\n * Destroy all managed clients.\n */\nexport async function destroyAllClients(): Promise<void> {\n const ids = [...clients.keys()];\n await Promise.allSettled(ids.map((id) => destroyClient(id)));\n}\n\n/**\n * Get a managed client by account ID. Falls back to the first available client.\n */\nexport function getClient(accountId?: string): ManagedClient | undefined {\n if (accountId && clients.has(accountId)) return clients.get(accountId);\n if (clients.size > 0) return clients.values().next().value as ManagedClient;\n return undefined;\n}\n\n/**\n * Number of currently connected clients.\n */\nexport function clientCount(): number {\n return clients.size;\n}\n\n/**\n * Get diagnostic state for all managed clients.\n */\nexport function getClientStates(): Array<{ accountId: string; connectionState: ConnectionState }> {\n return [...clients.entries()].map(([id, m]) => ({\n accountId: id,\n connectionState: m.connectionState,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Event normalization — SDK snake_case event -> plugin camelCase\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a snake_case SDK event to the plugin's CoredMessageEvent format.\n * Returns null if the event is malformed.\n *\n * The new SDK delivers events with lowercase field names:\n * { header, body: { message: { message_id, sender_id: UserId, ... } } }\n *\n * sender_id is now a UserId object { user_id, union_user_id, open_user_id }\n * in the new SDK, but we keep backward compat with string for safety.\n */\nexport function normalizeSdkEvent(sdk: SdkMessageEvent): CoredMessageEvent | null {\n const msg = sdk.body?.message;\n if (!msg) return null;\n\n // Extract user ID from sender_id — may be UserId object or legacy string\n const senderId = msg.sender_id;\n let userId = \"\";\n if (typeof senderId === \"string\") {\n userId = senderId;\n } else if (senderId && typeof senderId === \"object\") {\n userId =\n senderId.user_id ??\n senderId.open_user_id ??\n senderId.union_user_id ??\n \"\";\n }\n\n // Extract mention user IDs from the new SDK's text mention format\n const mentionUsers = (msg.mention_user_list ?? []).map((u) => ({\n userId: u.user_id?.user_id ?? u.user_id?.open_user_id ?? u.user_id?.union_user_id ?? \"\",\n }));\n\n // message_created_at may be Int64 (string) in the new SDK\n const createdAt =\n typeof msg.message_created_at === \"string\"\n ? parseInt(msg.message_created_at, 10) || Date.now()\n : msg.message_created_at ?? Date.now();\n\n return {\n message: {\n messageId: msg.message_id ?? \"\",\n messageType: msg.message_type ?? \"\",\n messageContent: msg.message_content,\n chatId: msg.chat_id ?? \"\",\n chatType: msg.chat_type ?? \"direct\",\n sender: {\n userId,\n },\n createdAt,\n mentionUserList: mentionUsers,\n },\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Outbound message delivery — send text/typing/read to Cored chats.\n *\n * Pipeline: normalize target -> validate account/client -> send -> map errors\n *\n * Text-only for now; media/card/file delivery is follow-up (task 08).\n */\n\nimport {\n getClient,\n isAuthError,\n MessageType_TEXT,\n type ManagedClient,\n type SendMessageReq,\n} from \"../core/cored-client.js\";\n\n// ---------------------------------------------------------------------------\n// Send text\n// ---------------------------------------------------------------------------\n\nexport interface SendTextResult {\n ok: boolean;\n messageId?: string;\n error?: Error;\n provider?: string;\n}\n\n/**\n * Send a text message to a Cored chat.\n *\n * On auth error (code 40000006), forces a token refresh via preheat() and\n * retries once. This handles the SDK's token-refresh edge case without\n * requiring a gateway restart.\n */\nexport async function sendText(\n chatId: string,\n text: string,\n accountId?: string,\n replyMessageId?: string,\n logWarn?: (msg: string) => void,\n): Promise<SendTextResult> {\n const managed = getClient(accountId);\n if (!managed) {\n return {\n ok: false,\n error: new Error(\n `[cored] no connected client for account=${accountId ?? \"default\"}`,\n ),\n };\n }\n\n const req: SendMessageReq = {\n chat_id: chatId,\n message_type: MessageType_TEXT,\n message_content: {\n text: { content: text },\n },\n reply_message_id: replyMessageId,\n };\n\n try {\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (err) {\n if (!isAuthError(err)) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed for chat=${chatId}: ${err instanceof Error ? err.message : String(err)}`,\n ),\n };\n }\n\n // Auth token expired despite SDK auto-refresh — force refresh and retry once.\n // This avoids requiring a gateway restart when the SDK's background token\n // refresh hits an edge case (time sync drift, swallowed fetch failure, etc.).\n logWarn?.(\n `[cored] auth error on send (chat=${chatId}, account=${accountId ?? \"default\"}) — refreshing token and retrying`,\n );\n\n try {\n await managed.client.preheat();\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n logWarn?.(\n `[cored] auth retry succeeded (chat=${chatId}, account=${accountId ?? \"default\"})`,\n );\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (retryErr) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed after auth retry for chat=${chatId}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,\n ),\n };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Set typing indicator in a chat. Cored typing lasts ~5s.\n */\nexport async function setTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.createTyping({ chat_id: chatId });\n } catch {\n // Typing is best-effort — don't fail the message flow\n }\n}\n\n/**\n * Clear typing indicator.\n */\nexport async function clearTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.deleteTyping({ chat_id: chatId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Read receipt\n// ---------------------------------------------------------------------------\n\n/**\n * Mark a message as read.\n */\nexport async function readMessage(\n messageId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Message.readMessage({ message_id: messageId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery callback for inbound dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deliver function scoped to an account, suitable for passing\n * to processInboundMessage's InboundDispatchOptions.\n */\nexport function makeDeliver(\n accountId?: string,\n logWarn?: (msg: string) => void,\n): (chatId: string, text: string) => Promise<void> {\n return async (chatId: string, text: string) => {\n const result = await sendText(chatId, text, accountId, undefined, logWarn);\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Target parsing & validation for the --to argument.\n *\n * Accepted formats:\n * \"chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" }\n * \"user:83870344313569283\" -> { kind: \"user\", id: \"83870344313569283\" }\n * \"cored:chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (prefix stripped)\n * \"oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (bare ID defaults to chat)\n *\n * Returns null for empty, whitespace-only, or malformed targets (e.g. \"user:\" with no ID).\n */\n\nimport type { ParsedTarget } from \"./types.js\";\n\n/**\n * Parse a raw --to string into a structured target.\n * Returns null if the input is missing, empty, or has no usable ID.\n */\nexport function parseTarget(to?: string): ParsedTarget | null {\n const raw = String(to ?? \"\").trim();\n if (!raw) return null;\n\n // Strip optional \"cored:\" channel prefix (case-insensitive)\n const stripped = raw.replace(/^cored:/i, \"\");\n\n if (stripped.startsWith(\"user:\")) {\n const id = stripped.slice(\"user:\".length).trim();\n return id ? { kind: \"user\", id } : null;\n }\n\n if (stripped.startsWith(\"chat:\")) {\n const id = stripped.slice(\"chat:\".length).trim();\n return id ? { kind: \"chat\", id } : null;\n }\n\n // Bare ID — default to chat (Cored SDK uses ChatId for sending)\n return stripped ? { kind: \"chat\", id: stripped } : null;\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Inbound message processing pipeline for Cored IM.\n *\n * Pipeline: parse -> gate -> dedup -> build context -> dispatch\n *\n * Codes against the CoredClient interface contract (task 99) —\n * the client is not imported here; this module receives parsed events.\n */\n\nimport type {\n CoredAccountConfig,\n CoredMessage,\n CoredMessageEvent,\n} from \"../types.js\";\n\n// Plugin API surface used by this module.\n// At runtime the full OpenClawPluginApi is provided by the gateway;\n// we only declare the subset we access so the module stays decoupled\n// and testable without importing the full SDK.\nexport interface InboundPluginApi {\n config: Record<string, unknown>;\n runtime?: {\n channel?: {\n reply?: {\n dispatchReplyWithBufferedBlockDispatcher?: (...args: any[]) => any;\n };\n session?: {\n recordInboundSession?: (...args: any[]) => any;\n resolveStorePath?: (...args: any[]) => string;\n };\n routing?: {\n resolveAgentRoute?: (...args: any[]) => unknown;\n };\n };\n };\n logger?: {\n debug?: (msg: string) => void;\n info?: (msg: string) => void;\n warn?: (msg: string) => void;\n error?: (msg: string) => void;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Parse — extract usable text body from incoming message\n// ---------------------------------------------------------------------------\n\nexport interface ParsedInboundMessage {\n messageId: string;\n chatId: string;\n chatType: \"direct\" | \"group\";\n senderId: string;\n body: string;\n timestamp: number;\n mentionUserIds: string[];\n}\n\n/**\n * Normalize a raw Cored message event into a structured inbound message.\n * Returns `null` if the event cannot be parsed into a usable message.\n *\n * Only text messages are supported in this phase — media/card/file are\n * follow-up work (task 08).\n */\nexport function parseMessageEvent(\n event: CoredMessageEvent,\n): ParsedInboundMessage | null {\n const msg = event?.message;\n if (!msg || !msg.messageId || !msg.chatId) return null;\n\n const body = extractTextBody(msg);\n if (body === null) return null;\n\n const chatType = msg.chatType === \"group\" ? \"group\" : \"direct\";\n\n const senderId =\n msg.sender?.userId || msg.sender?.openUserId || msg.sender?.unionUserId;\n if (!senderId) return null;\n\n const mentionUserIds = (msg.mentionUserList ?? [])\n .map((u) => u.userId || u.openUserId || u.unionUserId || \"\")\n .filter(Boolean);\n\n return {\n messageId: msg.messageId,\n chatId: msg.chatId,\n chatType,\n senderId,\n body,\n timestamp: msg.createdAt ?? Date.now(),\n mentionUserIds,\n };\n}\n\n/**\n * Extract plain-text content from a Cored message.\n * Returns `null` for non-text or empty messages.\n */\nfunction extractTextBody(msg: CoredMessage): string | null {\n if (msg.messageType !== \"text\") return null;\n\n const content = msg.messageContent;\n if (typeof content === \"string\") {\n // Content may be JSON-encoded string (Cored convention)\n try {\n const parsed = JSON.parse(content);\n // Cored format: { text: { content: \"Hello\" } }\n if (parsed?.text && typeof parsed.text === \"object\" && typeof parsed.text.content === \"string\")\n return parsed.text.content.trim() || null;\n if (typeof parsed?.text === \"string\") return parsed.text.trim() || null;\n if (typeof parsed?.content === \"string\")\n return parsed.content.trim() || null;\n // Fall through to raw string\n } catch {\n // Not JSON — use raw string\n }\n return content.trim() || null;\n }\n\n if (content && typeof content === \"object\") {\n const obj = content as Record<string, unknown>;\n // Cored format: { text: { content: \"Hello\" } }\n if (obj.text && typeof obj.text === \"object\") {\n const textObj = obj.text as Record<string, unknown>;\n if (typeof textObj.content === \"string\") return textObj.content.trim() || null;\n }\n if (typeof obj.text === \"string\") return obj.text.trim() || null;\n if (typeof obj.content === \"string\") return obj.content.trim() || null;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Gate — filter out messages the plugin should not process\n// ---------------------------------------------------------------------------\n\nexport interface GateResult {\n pass: boolean;\n reason?: string;\n}\n\n/**\n * Determine whether a parsed inbound message should be processed.\n * Checks in order: self-message, whitelist, group mention requirement.\n */\nexport function checkMessageGate(\n msg: ParsedInboundMessage,\n account: CoredAccountConfig,\n): GateResult {\n return { pass: true };\n}\n\n// ---------------------------------------------------------------------------\n// Dedup — skip already-processed message IDs\n// ---------------------------------------------------------------------------\n\nconst DEDUP_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst DEDUP_CLEANUP_INTERVAL_MS = 60 * 1000; // clean every 1 minute\nconst DEDUP_MAX_SIZE = 10_000;\n\n/** In-memory set of recently processed message IDs with TTL. */\nconst processedMessages = new Map<string, number>();\nlet lastCleanup = Date.now();\n\n/**\n * Returns `true` if the message has already been processed (duplicate).\n * Records the message ID on first encounter.\n */\nexport function isDuplicate(messageId: string): boolean {\n cleanupIfNeeded();\n\n if (processedMessages.has(messageId)) return true;\n\n processedMessages.set(messageId, Date.now());\n return false;\n}\n\nfunction cleanupIfNeeded(): void {\n const now = Date.now();\n if (now - lastCleanup < DEDUP_CLEANUP_INTERVAL_MS) return;\n lastCleanup = now;\n\n const expiry = now - DEDUP_TTL_MS;\n for (const [id, ts] of processedMessages) {\n if (ts < expiry) processedMessages.delete(id);\n }\n\n // Hard cap to prevent unbounded growth\n if (processedMessages.size > DEDUP_MAX_SIZE) {\n const excess = processedMessages.size - DEDUP_MAX_SIZE;\n const iter = processedMessages.keys();\n for (let i = 0; i < excess; i++) {\n const key = iter.next().value;\n if (key !== undefined) processedMessages.delete(key);\n }\n }\n}\n\n/** Reset dedup state — exposed for testing only. */\nexport function _resetDedup(): void {\n processedMessages.clear();\n lastCleanup = Date.now();\n}\n\n// ---------------------------------------------------------------------------\n// Build context & dispatch\n// ---------------------------------------------------------------------------\n\nexport interface InboundContext {\n Body: string;\n From: string;\n To: string;\n SessionKey: string;\n AccountId: string;\n ChatType: \"direct\" | \"group\";\n Provider: \"cored\";\n Surface: \"cored\";\n MessageSid: string;\n Timestamp: number;\n CommandAuthorized: boolean;\n _cored: {\n accountId: string;\n isGroup: boolean;\n senderId: string;\n chatId: string;\n };\n}\n\n/**\n * Build the OpenClaw context payload from a parsed inbound message.\n */\nexport function buildContext(\n msg: ParsedInboundMessage,\n account: CoredAccountConfig,\n): InboundContext {\n const isGroup = msg.chatType === \"group\";\n\n // Session key: group chats key on chatId, DMs key on sender\n const sessionKey = isGroup\n ? `cored:chat:${msg.chatId}`\n : `cored:user:${msg.senderId}`;\n\n return {\n Body: msg.body,\n From: isGroup ? `cored:chat:${msg.chatId}` : `cored:user:${msg.senderId}`,\n To: `cored:bot:${account.appId}`,\n SessionKey: sessionKey,\n AccountId: account.accountId,\n ChatType: isGroup ? \"group\" : \"direct\",\n Provider: \"cored\",\n Surface: \"cored\",\n MessageSid: msg.messageId,\n Timestamp: msg.timestamp,\n CommandAuthorized: true,\n _cored: {\n accountId: account.accountId,\n isGroup,\n senderId: msg.senderId,\n chatId: msg.chatId,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Options for the deliver callback wired by the caller (e.g. the service\n * start handler in index.ts once task 06 connects the CoredClient).\n */\nexport interface InboundDispatchOptions {\n /** Send a reply back to the Cored chat. */\n deliver: (chatId: string, text: string) => Promise<void>;\n}\n\n/**\n * Process an inbound Cored message event end-to-end:\n * parse -> gate -> dedup -> build context -> dispatch.\n *\n * Returns `true` if the message was dispatched, `false` if filtered.\n */\nexport async function processInboundMessage(\n api: InboundPluginApi,\n account: CoredAccountConfig,\n event: CoredMessageEvent,\n opts: InboundDispatchOptions,\n): Promise<boolean> {\n const logger = api.logger;\n\n // Helper for safe logging\n const log = {\n debug: (msg: string) => { logger?.debug?.(msg); },\n info: (msg: string) => { logger?.info?.(msg); },\n warn: (msg: string) => { logger?.warn?.(msg); },\n error: (msg: string) => { logger?.error?.(msg); },\n };\n\n // 1. Parse\n const parsed = parseMessageEvent(event);\n if (!parsed) {\n log.debug(\n `[cored] ignoring unparseable event (messageId=${event?.message?.messageId ?? \"unknown\"} messageType=${event?.message?.messageType ?? \"undefined\"})`,\n );\n return false;\n }\n\n // 2. Gate\n const gate = checkMessageGate(parsed, account);\n if (!gate.pass) {\n log.debug(\n `[cored] gated message=${parsed.messageId} reason=${gate.reason} chat=${parsed.chatId}`,\n );\n return false;\n }\n\n // 3. Dedup\n if (isDuplicate(parsed.messageId)) {\n log.debug(\n `[cored] duplicate message=${parsed.messageId} chat=${parsed.chatId}`,\n );\n return false;\n }\n\n // 4. Build context\n const ctx = buildContext(parsed, account);\n\n log.info(\n `[cored] dispatching message=${parsed.messageId} chat=${parsed.chatId} sender=${parsed.senderId} type=${parsed.chatType}`,\n );\n\n // 5. Dispatch\n const runtime = api.runtime;\n if (!runtime?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) {\n log.warn(\"[cored] runtime.channel.reply not available — cannot dispatch\");\n return false;\n }\n\n // Record session for context continuity\n const cfgSession = (api.config as Record<string, unknown>)?.session as Record<string, unknown> | undefined;\n const storePath =\n runtime.channel.session?.resolveStorePath?.(\n cfgSession?.store,\n { agentId: \"main\" },\n ) ?? \"\";\n\n await runtime.channel.session?.recordInboundSession?.({\n storePath,\n sessionKey: ctx.SessionKey,\n ctx,\n updateLastRoute:\n ctx.ChatType === \"direct\"\n ? {\n sessionKey: ctx.SessionKey,\n channel: \"cored\",\n to: parsed.chatId,\n accountId: account.accountId,\n }\n : undefined,\n });\n\n // Dispatch reply with buffered block dispatcher\n log.debug(\n `[cored] dispatch starting for message=${parsed.messageId} session=${ctx.SessionKey}`,\n );\n\n await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx,\n cfg: api.config,\n dispatcherOptions: {\n deliver: async (payload: { text?: string }) => {\n log.info(\n `[cored] deliver callback called for message=${parsed.messageId} hasText=${!!payload.text} textLen=${payload.text?.length ?? 0}`,\n );\n if (payload.text) {\n await opts.deliver(parsed.chatId, payload.text);\n log.info(\n `[cored] deliver completed for message=${parsed.messageId} chat=${parsed.chatId}`,\n );\n }\n },\n onError: (err: unknown, info?: { kind?: string }) => {\n log.error(\n `[cored] ${info?.kind ?? \"reply\"} error for message=${parsed.messageId}: ${err}`,\n );\n },\n },\n });\n\n log.info(\n `[cored] dispatch finished for message=${parsed.messageId}`,\n );\n\n return true;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,IAAAA,eAAyC;;;ACAzC,kBAGO;;;ACDP,IAAM,WAAW;AAAA,EACf,kBAAkB;AAAA,EAClB,gBAAgB;AAClB;AAEA,IAAM,aAAa;AAEnB,SAAS,iBAAiB,KAA8C;AACtE,QAAM,OAAO;AACb,SAAO,MAAM,WACP,KAAK,SAAqC,QAG5C;AACN;AAEA,SAAS,gBAA6C;AACpD,QAAM,MAAM,QAAQ;AACpB,QAAM,SAAsC,CAAC;AAE7C,MAAI,IAAI,GAAG,UAAU,QAAQ,EAAG,QAAO,QAAQ,IAAI,GAAG,UAAU,QAAQ;AACxE,MAAI,IAAI,GAAG,UAAU,YAAY;AAC/B,WAAO,YAAY,IAAI,GAAG,UAAU,YAAY;AAClD,MAAI,IAAI,GAAG,UAAU,aAAa;AAChC,WAAO,aAAa,IAAI,GAAG,UAAU,aAAa;AACpD,MAAI,IAAI,GAAG,UAAU,mBAAmB,MAAM;AAC5C,WAAO,mBACL,IAAI,GAAG,UAAU,mBAAmB,MAAM;AAC9C,MAAI,IAAI,GAAG,UAAU,iBAAiB;AACpC,WAAO,iBAAiB,OAAO,IAAI,GAAG,UAAU,iBAAiB,CAAC;AAEpE,SAAO;AACT;AAEO,SAAS,eAAe,KAAwB;AACrD,QAAM,KAAK,iBAAiB,GAAG;AAC/B,MAAI,CAAC,IAAI;AAEP,QAAI,QAAQ,IAAI,GAAG,UAAU,QAAQ,EAAG,QAAO,CAAC,SAAS;AACzD,WAAO,CAAC;AAAA,EACV;AACA,MAAI,GAAG,SAAU,QAAO,OAAO,KAAK,GAAG,QAAQ;AAC/C,MAAI,GAAG,MAAO,QAAO,CAAC,SAAS;AAE/B,MAAI,QAAQ,IAAI,GAAG,UAAU,QAAQ,EAAG,QAAO,CAAC,SAAS;AACzD,SAAO,CAAC;AACV;AAEO,SAAS,qBACd,KACA,WACoB;AACpB,QAAM,KAAK,iBAAiB,GAAG;AAC/B,QAAM,KAAK,aAAa;AACxB,QAAM,YAAY,cAAc;AAEhC,QAAM,MAAM,IAAI,WAAW,EAAE,KAAK;AAElC,SAAO;AAAA,IACL,WAAW;AAAA,IACX,OAAO,KAAK,SAAS,UAAU,SAAS;AAAA,IACxC,WAAW,KAAK,aAAa,UAAU,aAAa;AAAA,IACpD,YAAY,KAAK,cAAc,UAAU,cAAc;AAAA,IACvD,SAAS,KAAK,WAAW;AAAA,IACzB,kBACE,KAAK,oBAAoB,UAAU,oBAAoB,SAAS;AAAA,IAClE,gBACE,KAAK,kBAAkB,UAAU,kBAAkB,SAAS;AAAA,EAChE;AACF;AAOO,SAAS,sBACd,QACyB;AACzB,QAAM,SAAkC,CAAC;AAEzC,MAAI,CAAC,OAAO,OAAO;AACjB,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,SAAS,YAAY,OAAO,SAAS;AAAA,IACvC,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,OAAO,WAAW;AACrB,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,SAAS,YAAY,OAAO,SAAS;AAAA,IACvC,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,OAAO,YAAY;AACtB,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,SAAS,YAAY,OAAO,SAAS;AAAA,IACvC,CAAC;AAAA,EACH,WACE,CAAC,OAAO,WAAW,WAAW,SAAS,KACvC,CAAC,OAAO,WAAW,WAAW,UAAU,GACxC;AACA,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,SAAS,YAAY,OAAO,SAAS,2DAA2D,OAAO,UAAU;AAAA,IACnH,CAAC;AAAA,EACH;AAEA,MACE,OAAO,OAAO,mBAAmB,YACjC,CAAC,OAAO,SAAS,OAAO,cAAc,KACtC,OAAO,kBAAkB,GACzB;AACA,WAAO,KAAK;AAAA,MACV,OAAO;AAAA,MACP,SAAS,YAAY,OAAO,SAAS,oEAAoE,OAAO,cAAc;AAAA,IAChI,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAmBO,SAAS,0BAA0B,KAAoC;AAC5E,QAAM,MAAM,eAAe,GAAG;AAC9B,SAAO,IACJ,IAAI,CAAC,OAAO,qBAAqB,KAAK,EAAE,CAAC,EACzC,OAAO,CAAC,YAAY,QAAQ,OAAO;AACxC;;;AC5IA,iBAAmD;AASnD,IAAM,kBAAkB;AAOjB,SAAS,YAAY,KAAuB;AACjD,SAAO,eAAe,uBAAY,IAAI,SAAS;AACjD;AAQO,IAAM,mBAAmB;AAuFhC,IAAM,UAAU,oBAAI,IAA2B;AAM/C,SAAS,kBACP,KACQ;AACR,QAAM,OAAO,CAAC,UAAkB,CAAC,QAAgB,SAAoB;AACnE,UAAM,IAAI,KAAK,KAAK,GAAG,GAAG,KAAK,SAAS,MAAM,KAAK,UAAU,IAAI,IAAI,EAAE,EAAE;AAAA,EAC3E;AACA,SAAO;AAAA,IACL,OAAO,KAAK,OAAO;AAAA,IACnB,MAAM,KAAK,MAAM;AAAA,IACjB,MAAM,KAAK,MAAM;AAAA,IACjB,OAAO,KAAK,OAAO;AAAA,EACrB;AACF;AAgBA,eAAsB,aAAa,MAAmD;AACpF,QAAM,EAAE,QAAQ,WAAW,IAAI,IAAI;AACnC,QAAM,YAAY,OAAO;AAGzB,MAAI,QAAQ,IAAI,SAAS,GAAG;AAC1B,UAAM,cAAc,SAAS;AAAA,EAC/B;AAEA,QAAM,YAAY,MAAM,uBAAY;AAAA,IAClC,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP;AAAA,MACE,kBAAkB,OAAO;AAAA,MACzB,gBAAgB,OAAO;AAAA,MACvB,QAAQ,MAAM,kBAAkB,GAAG,IAAI;AAAA,MACvC,UAAU,MAAM,uBAAY,QAAQ,uBAAY;AAAA,IAClD;AAAA,EACF;AAGA,QAAM,UAAU,QAAQ;AAExB,QAAM,UAAyB,EAAE,QAAQ,WAAW,QAAQ,iBAAiB,YAAY;AAGzF,MAAI,WAAW;AACb,UAAM,UAAU,CAAC,aAA8B;AAC7C,YAAM,aAAa,kBAAkB,QAAQ;AAC7C,UAAI,YAAY;AACd,kBAAU,YAAY,MAAM;AAAA,MAC9B;AAAA,IACF;AAIA,cAAU,GAAG,GAAG,QAAQ,MAAM;AAAA,MAC5B;AAAA,IACF;AACA,YAAQ,eAAe;AAAA,EACzB;AAEA,UAAQ,IAAI,WAAW,OAAO;AAC9B,SAAO;AACT;AAKA,eAAsB,cAAc,WAAkC;AACpE,QAAM,UAAU,QAAQ,IAAI,SAAS;AACrC,MAAI,CAAC,QAAS;AAEd,UAAQ,kBAAkB;AAG1B,MAAI,QAAQ,cAAc;AACxB,YAAQ,OAAO,GAAG,GAAG,QAAQ,MAAM;AAAA,MACjC,QAAQ;AAAA,IACV;AAAA,EACF;AACA,MAAI;AACF,UAAM,QAAQ,OAAO,MAAM;AAAA,EAC7B,QAAQ;AAAA,EAER;AACA,UAAQ,OAAO,SAAS;AAC1B;AAKA,eAAsB,oBAAmC;AACvD,QAAM,MAAM,CAAC,GAAG,QAAQ,KAAK,CAAC;AAC9B,QAAM,QAAQ,WAAW,IAAI,IAAI,CAAC,OAAO,cAAc,EAAE,CAAC,CAAC;AAC7D;AAKO,SAAS,UAAU,WAA+C;AACvE,MAAI,aAAa,QAAQ,IAAI,SAAS,EAAG,QAAO,QAAQ,IAAI,SAAS;AACrE,MAAI,QAAQ,OAAO,EAAG,QAAO,QAAQ,OAAO,EAAE,KAAK,EAAE;AACrD,SAAO;AACT;AAKO,SAAS,cAAsB;AACpC,SAAO,QAAQ;AACjB;AA0BO,SAAS,kBAAkB,KAAgD;AAChF,QAAM,MAAM,IAAI,MAAM;AACtB,MAAI,CAAC,IAAK,QAAO;AAGjB,QAAM,WAAW,IAAI;AACrB,MAAI,SAAS;AACb,MAAI,OAAO,aAAa,UAAU;AAChC,aAAS;AAAA,EACX,WAAW,YAAY,OAAO,aAAa,UAAU;AACnD,aACE,SAAS,WACT,SAAS,gBACT,SAAS,iBACT;AAAA,EACJ;AAGA,QAAM,gBAAgB,IAAI,qBAAqB,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,IAC7D,QAAQ,EAAE,SAAS,WAAW,EAAE,SAAS,gBAAgB,EAAE,SAAS,iBAAiB;AAAA,EACvF,EAAE;AAGF,QAAM,YACJ,OAAO,IAAI,uBAAuB,WAC9B,SAAS,IAAI,oBAAoB,EAAE,KAAK,KAAK,IAAI,IACjD,IAAI,sBAAsB,KAAK,IAAI;AAEzC,SAAO;AAAA,IACL,SAAS;AAAA,MACP,WAAW,IAAI,cAAc;AAAA,MAC7B,aAAa,IAAI,gBAAgB;AAAA,MACjC,gBAAgB,IAAI;AAAA,MACpB,QAAQ,IAAI,WAAW;AAAA,MACvB,UAAU,IAAI,aAAa;AAAA,MAC3B,QAAQ;AAAA,QACN;AAAA,MACF;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,IACnB;AAAA,EACF;AACF;;;ACzRA,eAAsB,SACpB,QACA,MACA,WACA,gBACA,SACyB;AACzB,QAAM,UAAU,UAAU,SAAS;AACnC,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,IAAI;AAAA,QACT,2CAA2C,aAAa,SAAS;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAsB;AAAA,IAC1B,SAAS;AAAA,IACT,cAAc;AAAA,IACd,iBAAiB;AAAA,MACf,MAAM,EAAE,SAAS,KAAK;AAAA,IACxB;AAAA,IACA,kBAAkB;AAAA,EACpB;AAEA,MAAI;AACF,UAAM,OAAO,MAAM,QAAQ,OAAO,GAAG,GAAG,QAAQ,YAAY,GAAG;AAC/D,WAAO,EAAE,IAAI,MAAM,WAAW,KAAK,YAAY,UAAU,QAAQ;AAAA,EACnE,SAAS,KAAK;AACZ,QAAI,CAAC,YAAY,GAAG,GAAG;AACrB,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,IAAI;AAAA,UACT,gCAAgC,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,QAC7F;AAAA,MACF;AAAA,IACF;AAKA;AAAA,MACE,oCAAoC,MAAM,aAAa,aAAa,SAAS;AAAA,IAC/E;AAEA,QAAI;AACF,YAAM,QAAQ,OAAO,QAAQ;AAC7B,YAAM,OAAO,MAAM,QAAQ,OAAO,GAAG,GAAG,QAAQ,YAAY,GAAG;AAC/D;AAAA,QACE,sCAAsC,MAAM,aAAa,aAAa,SAAS;AAAA,MACjF;AACA,aAAO,EAAE,IAAI,MAAM,WAAW,KAAK,YAAY,UAAU,QAAQ;AAAA,IACnE,SAAS,UAAU;AACjB,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,IAAI;AAAA,UACT,iDAAiD,MAAM,KAAK,oBAAoB,QAAQ,SAAS,UAAU,OAAO,QAAQ,CAAC;AAAA,QAC7H;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AASA,eAAsB,UACpB,QACA,WACe;AACf,QAAM,UAAU,UAAU,SAAS;AACnC,MAAI,CAAC,QAAS;AACd,MAAI;AACF,UAAM,QAAQ,OAAO,GAAG,GAAG,KAAK,aAAa,EAAE,SAAS,OAAO,CAAC;AAAA,EAClE,QAAQ;AAAA,EAER;AACF;AAKA,eAAsB,YACpB,QACA,WACe;AACf,QAAM,UAAU,UAAU,SAAS;AACnC,MAAI,CAAC,QAAS;AACd,MAAI;AACF,UAAM,QAAQ,OAAO,GAAG,GAAG,KAAK,aAAa,EAAE,SAAS,OAAO,CAAC;AAAA,EAClE,QAAQ;AAAA,EAER;AACF;AASA,eAAsB,YACpB,WACA,WACe;AACf,QAAM,UAAU,UAAU,SAAS;AACnC,MAAI,CAAC,QAAS;AACd,MAAI;AACF,UAAM,QAAQ,OAAO,GAAG,GAAG,QAAQ,YAAY,EAAE,YAAY,UAAU,CAAC;AAAA,EAC1E,QAAQ;AAAA,EAER;AACF;AAUO,SAAS,YACd,WACA,SACiD;AACjD,SAAO,OAAO,QAAgB,SAAiB;AAC7C,UAAM,SAAS,MAAM,SAAS,QAAQ,MAAM,WAAW,QAAW,OAAO;AACzE,QAAI,CAAC,OAAO,IAAI;AACd,YAAM,OAAO,SAAS,IAAI,MAAM,qBAAqB;AAAA,IACvD;AAAA,EACF;AACF;;;AC1JO,SAAS,YAAY,IAAkC;AAC5D,QAAM,MAAM,OAAO,MAAM,EAAE,EAAE,KAAK;AAClC,MAAI,CAAC,IAAK,QAAO;AAGjB,QAAM,WAAW,IAAI,QAAQ,YAAY,EAAE;AAE3C,MAAI,SAAS,WAAW,OAAO,GAAG;AAChC,UAAM,KAAK,SAAS,MAAM,QAAQ,MAAM,EAAE,KAAK;AAC/C,WAAO,KAAK,EAAE,MAAM,QAAQ,GAAG,IAAI;AAAA,EACrC;AAEA,MAAI,SAAS,WAAW,OAAO,GAAG;AAChC,UAAM,KAAK,SAAS,MAAM,QAAQ,MAAM,EAAE,KAAK;AAC/C,WAAO,KAAK,EAAE,MAAM,QAAQ,GAAG,IAAI;AAAA,EACrC;AAGA,SAAO,WAAW,EAAE,MAAM,QAAQ,IAAI,SAAS,IAAI;AACrD;;;AJ3BO,IAAM,WAAO,qCAA4C;AAAA,EAC9D,IAAI;AAAA,EAEJ,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,gBAAgB;AAAA,IAChB,UAAU;AAAA,IACV,OAAO;AAAA,IACP,SAAS,CAAC,IAAI;AAAA,EAChB;AAAA,EAEA,cAAc;AAAA,IACZ,WAAW,CAAC,UAAU,OAAO;AAAA,EAC/B;AAAA,EAEA,QAAQ;AAAA,IACN,gBAAgB,CAAC,QAAwB,eAAe,GAAG;AAAA,IAC3D,gBAAgB,CAAC,KAAqB,cACpC,qBAAqB,KAAK,aAAa,MAAS;AAAA,IAClD,eAAe,KAAqB,WAA2B;AAC7D,YAAM,WAAW,qBAAqB,KAAK,aAAa,MAAS;AACjE,YAAM,YAAY;AAAA,QAChB,SAAS,SAAS,SAAS,aAAa,SAAS;AAAA,MACnD;AACA,aAAO;AAAA,QACL,SAAS,SAAS;AAAA,QAClB,YAAY;AAAA,QACZ,aAAa,YAAY,cAAc;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,eAAe,CAAC,EAAE,MAAM,MAAM;AAC5B,YAAM,UAAoB,CAAC;AAC3B,UAAI,CAAC,MAAM,SAAU,SAAQ,KAAK,sBAAsB;AACxD,UAAI,CAAC,MAAM,MAAO,SAAQ,KAAK,sBAAsB;AACrD,UAAI,CAAC,MAAM,IAAK,SAAQ,KAAK,qBAAqB;AAClD,UAAI,QAAQ,SAAS,GAAG;AACtB,eAAO;AAAA,UACL,2BAA2B,QAAQ,KAAK,IAAI,CAAC;AAAA,UAC7C;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,EAAE,KAAK,IAAI;AAAA,MACb;AACA,aAAO;AAAA,IACT;AAAA,IACA,oBAAoB,CAAC,EAAE,KAAK,WAAW,MAAM,MAAM;AACjD,YAAM,UAAU,gBAAgB,GAAG;AACnC,UAAI,CAAC,QAAQ,SAAU,SAAQ,WAAW,CAAC;AAC3C,YAAM,KAAK,QAAQ;AACnB,UAAI,CAAC,GAAG,OAAO,EAAG,IAAG,OAAO,IAAI,CAAC;AACjC,YAAM,UAAU,GAAG,OAAO;AAI1B,YAAM,QAAQ,MAAM;AACpB,YAAM,YAAY,MAAM;AACxB,YAAM,aAAa,MAAM;AAEzB,UAAI,aAAa,cAAc,WAAW;AAExC,YAAI,CAAC,QAAQ,SAAU,SAAQ,WAAW,CAAC;AAC3C,cAAM,WAAW,QAAQ;AACzB,YAAI,CAAC,SAAS,SAAS,EAAG,UAAS,SAAS,IAAI,CAAC;AACjD,cAAM,UAAU,SAAS,SAAS;AAClC,YAAI,MAAO,SAAQ,QAAQ;AAC3B,YAAI,UAAW,SAAQ,YAAY;AACnC,YAAI,WAAY,SAAQ,aAAa;AAAA,MACvC,OAAO;AAEL,YAAI,MAAO,SAAQ,QAAQ;AAC3B,YAAI,UAAW,SAAQ,YAAY;AACnC,YAAI,WAAY,SAAQ,aAAa;AAAA,MACvC;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,aAAa;AAAA,IACX,SAAS;AAAA,IACT,QAAQ;AAAA,MACN,iBAAiB;AAAA,MACjB,mBAAmB;AAAA,MACnB,mBAAmB,CAAC,EAAE,IAAI,MAAM;AAC9B,cAAM,MAAM,eAAe,GAAG;AAC9B,eAAO,IAAI,KAAK,CAAC,OAAO;AACtB,gBAAM,WAAW,qBAAqB,KAAK,EAAE;AAC7C,iBAAO,QAAQ,SAAS,SAAS,SAAS,aAAa,SAAS,UAAU;AAAA,QAC5E,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,aAAa;AAAA,MACX;AAAA,QACE,UAAU;AAAA,QACV,cAAc;AAAA,QACd,iBAAiB;AAAA,QACjB,iBAAiB;AAAA,QACjB,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,SAAS,CAAC,EAAE,KAAK,UAAU,MAAM;AAC/B,gBAAM,WAAW,qBAAqB,KAAK,aAAa,MAAS;AACjE,iBAAO;AAAA,YACL,mBAAmB,QAAQ,SAAS,KAAK;AAAA,YACzC,oBAAoB,QAAQ,SAAS,KAAK;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,UAAU;AAAA,QACV,cAAc;AAAA,QACd,iBAAiB;AAAA,QACjB,iBAAiB;AAAA,QACjB,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,SAAS,CAAC,EAAE,KAAK,UAAU,MAAM;AAC/B,gBAAM,WAAW,qBAAqB,KAAK,aAAa,MAAS;AACjE,iBAAO;AAAA,YACL,mBAAmB,QAAQ,SAAS,SAAS;AAAA,YAC7C,oBAAoB,QAAQ,SAAS,SAAS;AAAA,UAChD;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,UAAU;AAAA,QACV,cAAc;AAAA,QACd,iBAAiB;AAAA,QACjB,iBAAiB;AAAA,QACjB,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,SAAS,CAAC,EAAE,KAAK,UAAU,MAAM;AAC/B,gBAAM,WAAW,qBAAqB,KAAK,aAAa,MAAS;AACjE,iBAAO;AAAA,YACL,mBAAmB,QAAQ,SAAS,UAAU;AAAA,YAC9C,oBAAoB,QAAQ,SAAS,UAAU;AAAA,UACjD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,CAAC;AAKM,IAAM,kBAAc,qCAA4C;AAAA,EACrE;AAAA;AAAA,EAGA,UAAU;AAAA,IACR,IAAI;AAAA,MACF,YAAY;AAAA,MACZ,eAAe,MAAM;AAAA,MACrB,kBAAkB,MAAM,CAAC;AAAA,MACzB,eAAe;AAAA,IACjB;AAAA,EACF;AAAA;AAAA,EAGA,WAAW,EAAE,qBAAqB,QAAQ;AAAA;AAAA,EAG1C,UAAU;AAAA,IACR,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,UAAU,OAAO,QAAQ;AACvB,cAAM,SAAS,YAAY,IAAI,EAAE;AACjC,YAAI,CAAC,QAAQ;AACX,gBAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE,EAAE;AAAA,QAC1D;AACA,cAAM,SAAS,MAAM;AAAA,UACnB,OAAO;AAAA,UACP,IAAI;AAAA,UACJ,IAAI,aAAa;AAAA,UACjB,IAAI,aAAa;AAAA,QACnB;AACA,YAAI,CAAC,OAAO,IAAI;AACd,gBAAM,OAAO,SAAS,IAAI,MAAM,qBAAqB;AAAA,QACvD;AACA,eAAO,EAAE,WAAW,OAAO,aAAa,GAAG;AAAA,MAC7C;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,cAAc;AAAA,MACd,eAAe,CAAC,EAAE,GAAG,MAAM;AACzB,YAAI,CAAC,GAAI,QAAO,EAAE,IAAI,OAAgB,OAAO,IAAI,MAAM,0BAA0B,EAAE;AACnF,cAAM,SAAS,YAAY,EAAE;AAC7B,YAAI,CAAC,QAAQ;AACX,iBAAO;AAAA,YACL,IAAI;AAAA,YACJ,OAAO,IAAI;AAAA,cACT,+CAA+C,KAAK,UAAU,EAAE,CAAC;AAAA,YACnE;AAAA,UACF;AAAA,QACF;AACA,eAAO,EAAE,IAAI,MAAe,IAAI,GAAG,OAAO,IAAI,IAAI,OAAO,EAAE,GAAG;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACF,CAAC;;;AK1JM,SAAS,kBACd,OAC6B;AAC7B,QAAM,MAAM,OAAO;AACnB,MAAI,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,IAAI,OAAQ,QAAO;AAElD,QAAM,OAAO,gBAAgB,GAAG;AAChC,MAAI,SAAS,KAAM,QAAO;AAE1B,QAAM,WAAW,IAAI,aAAa,UAAU,UAAU;AAEtD,QAAM,WACJ,IAAI,QAAQ,UAAU,IAAI,QAAQ,cAAc,IAAI,QAAQ;AAC9D,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,kBAAkB,IAAI,mBAAmB,CAAC,GAC7C,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,EAC1D,OAAO,OAAO;AAEjB,SAAO;AAAA,IACL,WAAW,IAAI;AAAA,IACf,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,IAAI,aAAa,KAAK,IAAI;AAAA,IACrC;AAAA,EACF;AACF;AAMA,SAAS,gBAAgB,KAAkC;AACzD,MAAI,IAAI,gBAAgB,OAAQ,QAAO;AAEvC,QAAM,UAAU,IAAI;AACpB,MAAI,OAAO,YAAY,UAAU;AAE/B,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,OAAO;AAEjC,UAAI,QAAQ,QAAQ,OAAO,OAAO,SAAS,YAAY,OAAO,OAAO,KAAK,YAAY;AACpF,eAAO,OAAO,KAAK,QAAQ,KAAK,KAAK;AACvC,UAAI,OAAO,QAAQ,SAAS,SAAU,QAAO,OAAO,KAAK,KAAK,KAAK;AACnE,UAAI,OAAO,QAAQ,YAAY;AAC7B,eAAO,OAAO,QAAQ,KAAK,KAAK;AAAA,IAEpC,QAAQ;AAAA,IAER;AACA,WAAO,QAAQ,KAAK,KAAK;AAAA,EAC3B;AAEA,MAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,UAAM,MAAM;AAEZ,QAAI,IAAI,QAAQ,OAAO,IAAI,SAAS,UAAU;AAC5C,YAAM,UAAU,IAAI;AACpB,UAAI,OAAO,QAAQ,YAAY,SAAU,QAAO,QAAQ,QAAQ,KAAK,KAAK;AAAA,IAC5E;AACA,QAAI,OAAO,IAAI,SAAS,SAAU,QAAO,IAAI,KAAK,KAAK,KAAK;AAC5D,QAAI,OAAO,IAAI,YAAY,SAAU,QAAO,IAAI,QAAQ,KAAK,KAAK;AAAA,EACpE;AAEA,SAAO;AACT;AAeO,SAAS,iBACd,KACA,SACY;AACZ,SAAO,EAAE,MAAM,KAAK;AACtB;AAMA,IAAM,eAAe,IAAI,KAAK;AAC9B,IAAM,4BAA4B,KAAK;AACvC,IAAM,iBAAiB;AAGvB,IAAM,oBAAoB,oBAAI,IAAoB;AAClD,IAAI,cAAc,KAAK,IAAI;AAMpB,SAAS,YAAY,WAA4B;AACtD,kBAAgB;AAEhB,MAAI,kBAAkB,IAAI,SAAS,EAAG,QAAO;AAE7C,oBAAkB,IAAI,WAAW,KAAK,IAAI,CAAC;AAC3C,SAAO;AACT;AAEA,SAAS,kBAAwB;AAC/B,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,MAAM,cAAc,0BAA2B;AACnD,gBAAc;AAEd,QAAM,SAAS,MAAM;AACrB,aAAW,CAAC,IAAI,EAAE,KAAK,mBAAmB;AACxC,QAAI,KAAK,OAAQ,mBAAkB,OAAO,EAAE;AAAA,EAC9C;AAGA,MAAI,kBAAkB,OAAO,gBAAgB;AAC3C,UAAM,SAAS,kBAAkB,OAAO;AACxC,UAAM,OAAO,kBAAkB,KAAK;AACpC,aAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,YAAM,MAAM,KAAK,KAAK,EAAE;AACxB,UAAI,QAAQ,OAAW,mBAAkB,OAAO,GAAG;AAAA,IACrD;AAAA,EACF;AACF;AAmCO,SAAS,aACd,KACA,SACgB;AAChB,QAAM,UAAU,IAAI,aAAa;AAGjC,QAAM,aAAa,UACf,cAAc,IAAI,MAAM,KACxB,cAAc,IAAI,QAAQ;AAE9B,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV,MAAM,UAAU,cAAc,IAAI,MAAM,KAAK,cAAc,IAAI,QAAQ;AAAA,IACvE,IAAI,aAAa,QAAQ,KAAK;AAAA,IAC9B,YAAY;AAAA,IACZ,WAAW,QAAQ;AAAA,IACnB,UAAU,UAAU,UAAU;AAAA,IAC9B,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY,IAAI;AAAA,IAChB,WAAW,IAAI;AAAA,IACf,mBAAmB;AAAA,IACnB,QAAQ;AAAA,MACN,WAAW,QAAQ;AAAA,MACnB;AAAA,MACA,UAAU,IAAI;AAAA,MACd,QAAQ,IAAI;AAAA,IACd;AAAA,EACF;AACF;AAqBA,eAAsB,sBACpB,KACA,SACA,OACA,MACkB;AAClB,QAAM,SAAS,IAAI;AAGnB,QAAM,MAAM;AAAA,IACV,OAAO,CAAC,QAAgB;AAAE,cAAQ,QAAQ,GAAG;AAAA,IAAG;AAAA,IAChD,MAAM,CAAC,QAAgB;AAAE,cAAQ,OAAO,GAAG;AAAA,IAAG;AAAA,IAC9C,MAAM,CAAC,QAAgB;AAAE,cAAQ,OAAO,GAAG;AAAA,IAAG;AAAA,IAC9C,OAAO,CAAC,QAAgB;AAAE,cAAQ,QAAQ,GAAG;AAAA,IAAG;AAAA,EAClD;AAGA,QAAM,SAAS,kBAAkB,KAAK;AACtC,MAAI,CAAC,QAAQ;AACX,QAAI;AAAA,MACF,iDAAiD,OAAO,SAAS,aAAa,SAAS,gBAAgB,OAAO,SAAS,eAAe,WAAW;AAAA,IACnJ;AACA,WAAO;AAAA,EACT;AAGA,QAAM,OAAO,iBAAiB,QAAQ,OAAO;AAC7C,MAAI,CAAC,KAAK,MAAM;AACd,QAAI;AAAA,MACF,yBAAyB,OAAO,SAAS,WAAW,KAAK,MAAM,SAAS,OAAO,MAAM;AAAA,IACvF;AACA,WAAO;AAAA,EACT;AAGA,MAAI,YAAY,OAAO,SAAS,GAAG;AACjC,QAAI;AAAA,MACF,6BAA6B,OAAO,SAAS,SAAS,OAAO,MAAM;AAAA,IACrE;AACA,WAAO;AAAA,EACT;AAGA,QAAM,MAAM,aAAa,QAAQ,OAAO;AAExC,MAAI;AAAA,IACF,+BAA+B,OAAO,SAAS,SAAS,OAAO,MAAM,WAAW,OAAO,QAAQ,SAAS,OAAO,QAAQ;AAAA,EACzH;AAGA,QAAM,UAAU,IAAI;AACpB,MAAI,CAAC,SAAS,SAAS,OAAO,0CAA0C;AACtE,QAAI,KAAK,oEAA+D;AACxE,WAAO;AAAA,EACT;AAGA,QAAM,aAAc,IAAI,QAAoC;AAC5D,QAAM,YACJ,QAAQ,QAAQ,SAAS;AAAA,IACvB,YAAY;AAAA,IACZ,EAAE,SAAS,OAAO;AAAA,EACpB,KAAK;AAEP,QAAM,QAAQ,QAAQ,SAAS,uBAAuB;AAAA,IACpD;AAAA,IACA,YAAY,IAAI;AAAA,IAChB;AAAA,IACA,iBACE,IAAI,aAAa,WACb;AAAA,MACE,YAAY,IAAI;AAAA,MAChB,SAAS;AAAA,MACT,IAAI,OAAO;AAAA,MACX,WAAW,QAAQ;AAAA,IACrB,IACA;AAAA,EACR,CAAC;AAGD,MAAI;AAAA,IACF,yCAAyC,OAAO,SAAS,YAAY,IAAI,UAAU;AAAA,EACrF;AAEA,QAAM,QAAQ,QAAQ,MAAM,yCAAyC;AAAA,IACnE;AAAA,IACA,KAAK,IAAI;AAAA,IACT,mBAAmB;AAAA,MACjB,SAAS,OAAO,YAA+B;AAC7C,YAAI;AAAA,UACF,+CAA+C,OAAO,SAAS,YAAY,CAAC,CAAC,QAAQ,IAAI,YAAY,QAAQ,MAAM,UAAU,CAAC;AAAA,QAChI;AACA,YAAI,QAAQ,MAAM;AAChB,gBAAM,KAAK,QAAQ,OAAO,QAAQ,QAAQ,IAAI;AAC9C,cAAI;AAAA,YACF,yCAAyC,OAAO,SAAS,SAAS,OAAO,MAAM;AAAA,UACjF;AAAA,QACF;AAAA,MACF;AAAA,MACA,SAAS,CAAC,KAAc,SAA6B;AACnD,YAAI;AAAA,UACF,WAAW,MAAM,QAAQ,OAAO,sBAAsB,OAAO,SAAS,KAAK,GAAG;AAAA,QAChF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAI;AAAA,IACF,yCAAyC,OAAO,SAAS;AAAA,EAC3D;AAEA,SAAO;AACT;;;AN9XA,IAAO,oBAAQ,uCAAyB;AAAA,EACtC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,aAAa,KAAK;AAChB,QAAI,gBAAgB;AAAA,MAClB,IAAI;AAAA,MACJ,OAAO,YAAY;AACjB,YAAI,YAAY,IAAI,EAAG;AAEvB,cAAM,WAAW,0BAA0B,IAAI,MAAM;AACrD,YAAI,SAAS,WAAW,GAAG;AACzB,cAAI,QAAQ,OAAO,6DAAwD;AAC3E;AAAA,QACF;AAEA,mBAAW,WAAW,UAAU;AAC9B,gBAAM,SAAS,sBAAsB,OAAO;AAC5C,cAAI,OAAO,SAAS,GAAG;AACrB,gBAAI,QAAQ;AAAA,cACV,4BAA4B,QAAQ,SAAS,KAAK,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,YAC3F;AACA;AAAA,UACF;AAEA,cAAI;AACF,kBAAM,aAAa,KAAK,OAAO;AAC/B,gBAAI,QAAQ;AAAA,cACV,mBAAmB,QAAQ,SAAS,qBAAqB,QAAQ,KAAK;AAAA,YACxE;AAAA,UACF,SAAS,KAAK;AACZ,gBAAI,QAAQ;AAAA,cACV,mBAAmB,QAAQ,SAAS,qBAAqB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,YAC3G;AAAA,UACF;AAAA,QACF;AAEA,YAAI,QAAQ,OAAO,gCAAgC,YAAY,CAAC,aAAa;AAAA,MAC/E;AAAA,MACA,MAAM,YAAY;AAChB,cAAM,kBAAkB;AACxB,YAAI,QAAQ,OAAO,yDAAoD;AAAA,MACzE;AAAA,IACF,CAAC;AAED,QAAI,QAAQ,OAAO,2BAA2B;AAAA,EAChD;AACF,CAAC;AAKD,eAAe,aACb,KACA,SACe;AACf,QAAM,UAAU,YAAY,QAAQ,WAAW,CAAC,QAAQ,IAAI,QAAQ,OAAO,GAAG,CAAC;AAE/E,QAAM,aAAa;AAAA,IACjB,QAAQ;AAAA,IACR,KAAK,CAAC,QAAgB,IAAI,QAAQ,QAAQ,GAAG;AAAA,IAC7C,WAAW,CAAC,OAA0B,kBAAsC;AAE1E,oBAAc,KAAK,eAAe,OAAO,OAAO,EAAE,MAAM,CAAC,QAAQ;AAC/D,YAAI,QAAQ;AAAA,UACV,+CAA+C,cAAc,SAAS,KAAK,GAAG;AAAA,QAChF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAKA,eAAe,cACb,KACA,SACA,OACA,SACe;AACf,QAAM,SAAS,MAAM,SAAS;AAC9B,QAAM,YAAY,MAAM,SAAS;AAGjC,MAAI,QAAQ;AACV,cAAU,QAAQ,QAAQ,SAAS,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACrD;AACA,MAAI,WAAW;AACb,gBAAY,WAAW,QAAQ,SAAS,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC1D;AAEA,MAAI;AACF,UAAM,sBAAsB,KAAK,SAAS,OAAO,EAAE,QAAQ,CAAC;AAAA,EAC9D,UAAE;AAEA,QAAI,QAAQ;AACV,kBAAY,QAAQ,QAAQ,SAAS,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACvD;AAAA,EACF;AACF;","names":["import_core"]}
package/dist/index.js CHANGED
@@ -337,7 +337,7 @@ var base = createChannelPluginBase({
337
337
  setup: {
338
338
  validateInput: ({ input }) => {
339
339
  const missing = [];
340
- if (!input.appToken) missing.push("--appToken (App ID)");
340
+ if (!input.appToken) missing.push("--app-token (App ID)");
341
341
  if (!input.token) missing.push("--token (App Secret)");
342
342
  if (!input.url) missing.push("--url (Backend URL)");
343
343
  if (missing.length > 0) {
@@ -345,7 +345,7 @@ var base = createChannelPluginBase({
345
345
  `Missing required flags: ${missing.join(", ")}`,
346
346
  "",
347
347
  "Either provide all flags:",
348
- ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
348
+ ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
349
349
  "",
350
350
  "Or use the interactive wizard:",
351
351
  " openclaw channels add"
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/channel.ts","../src/config.ts","../src/core/cored-client.ts","../src/messaging/outbound.ts","../src/targets.ts","../src/messaging/inbound.ts"],"sourcesContent":["// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport { defineChannelPluginEntry } from \"openclaw/plugin-sdk/core\";\nimport { coredPlugin } from \"./channel.js\";\nimport { listEnabledAccountConfigs, validateAccountConfig } from \"./config.js\";\nimport {\n createClient,\n destroyAllClients,\n clientCount,\n} from \"./core/cored-client.js\";\nimport type { OpenClawPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { processInboundMessage } from \"./messaging/inbound.js\";\nimport { makeDeliver, setTyping, clearTyping, readMessage } from \"./messaging/outbound.js\";\nimport type { CoredAccountConfig, CoredMessageEvent } from \"./types.js\";\n\nexport default defineChannelPluginEntry({\n id: \"cored\",\n name: \"Cored\",\n description: \"Connect OpenClaw with Cored\",\n plugin: coredPlugin,\n registerFull(api) {\n api.registerService({\n id: \"cored-sdk\",\n start: async () => {\n if (clientCount() > 0) return;\n\n const accounts = listEnabledAccountConfigs(api.config);\n if (accounts.length === 0) {\n api.logger?.warn?.(\"[cored] no enabled account config found — service idle\");\n return;\n }\n\n for (const account of accounts) {\n const errors = validateAccountConfig(account);\n if (errors.length > 0) {\n api.logger?.warn?.(\n `[cored] skipping account=${account.accountId}: ${errors.map((e) => e.message).join(\"; \")}`,\n );\n continue;\n }\n\n try {\n await startAccount(api, account);\n api.logger?.info?.(\n `[cored] account=${account.accountId} connected (appId=${account.appId})`,\n );\n } catch (err) {\n api.logger?.error?.(\n `[cored] account=${account.accountId} failed to start: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n api.logger?.info?.(`[cored] service started with ${clientCount()} account(s)`);\n },\n stop: async () => {\n await destroyAllClients();\n api.logger?.info?.(\"[cored] service stopped — all clients disconnected\");\n },\n });\n\n api.logger?.info?.(\"[cored] plugin registered\");\n },\n});\n\n/**\n * Start a single account — create client, subscribe to inbound events.\n */\nasync function startAccount(\n api: OpenClawPluginApi,\n account: CoredAccountConfig,\n): Promise<void> {\n const deliver = makeDeliver(account.accountId, (msg) => api.logger?.warn?.(msg));\n\n await createClient({\n config: account,\n log: (msg: string) => api.logger?.debug?.(msg),\n onMessage: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => {\n // Fire-and-forget: process inbound with typing indicator\n handleInbound(api, accountConfig, event, deliver).catch((err) => {\n api.logger?.error?.(\n `[cored] unhandled inbound error for account=${accountConfig.accountId}: ${err}`,\n );\n });\n },\n });\n}\n\n/**\n * Handle a single inbound message with typing indicator lifecycle.\n */\nasync function handleInbound(\n api: OpenClawPluginApi,\n account: CoredAccountConfig,\n event: CoredMessageEvent,\n deliver: (chatId: string, text: string) => Promise<void>,\n): Promise<void> {\n const chatId = event.message?.chatId;\n const messageId = event.message?.messageId;\n\n // Set typing indicator and mark message as read before processing\n if (chatId) {\n setTyping(chatId, account.accountId).catch(() => {});\n }\n if (messageId) {\n readMessage(messageId, account.accountId).catch(() => {});\n }\n\n try {\n await processInboundMessage(api, account, event, { deliver });\n } finally {\n // Clear typing after dispatch completes\n if (chatId) {\n clearTyping(chatId, account.accountId).catch(() => {});\n }\n }\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createChatChannelPlugin,\n createChannelPluginBase,\n} from \"openclaw/plugin-sdk/core\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { listAccountIds, resolveAccountConfig } from \"./config.js\";\nimport { sendText } from \"./messaging/outbound.js\";\nimport { parseTarget } from \"./targets.js\";\nimport type { CoredAccountConfig } from \"./types.js\";\n\nexport const base = createChannelPluginBase<CoredAccountConfig>({\n id: \"cored\",\n\n meta: {\n id: \"cored\",\n label: \"Cored\",\n selectionLabel: \"Cored\",\n docsPath: \"/channels/cored\",\n blurb: \"Connect OpenClaw to Cored\",\n aliases: [\"co\"],\n },\n\n capabilities: {\n chatTypes: [\"direct\", \"group\"],\n },\n\n config: {\n listAccountIds: (cfg: OpenClawConfig) => listAccountIds(cfg),\n resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>\n resolveAccountConfig(cfg, accountId ?? undefined),\n inspectAccount(cfg: OpenClawConfig, accountId?: string | null) {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n const hasConfig = Boolean(\n resolved.appId && resolved.appSecret && resolved.backendUrl,\n );\n return {\n enabled: resolved.enabled,\n configured: hasConfig,\n tokenStatus: hasConfig ? \"available\" : \"missing\",\n };\n },\n },\n\n setup: {\n validateInput: ({ input }) => {\n const missing: string[] = [];\n if (!input.appToken) missing.push(\"--appToken (App ID)\");\n if (!input.token) missing.push(\"--token (App Secret)\");\n if (!input.url) missing.push(\"--url (Backend URL)\");\n if (missing.length > 0) {\n return [\n `Missing required flags: ${missing.join(\", \")}`,\n \"\",\n \"Either provide all flags:\",\n ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,\n \"\",\n \"Or use the interactive wizard:\",\n \" openclaw channels add\",\n ].join(\"\\n\");\n }\n return null;\n },\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const updated = structuredClone(cfg) as Record<string, unknown>;\n if (!updated.channels) updated.channels = {};\n const ch = updated.channels as Record<string, Record<string, unknown>>;\n if (!ch[\"cored\"]) ch[\"cored\"] = {};\n const section = ch[\"cored\"];\n\n // Map ChannelSetupInput keys to our config shape:\n // appToken → appId, token → appSecret, url → backendUrl\n const appId = input.appToken;\n const appSecret = input.token;\n const backendUrl = input.url;\n\n if (accountId && accountId !== \"default\") {\n // Multi-account: write under accounts.<accountId>\n if (!section.accounts) section.accounts = {};\n const accounts = section.accounts as Record<string, Record<string, unknown>>;\n if (!accounts[accountId]) accounts[accountId] = {};\n const account = accounts[accountId];\n if (appId) account.appId = appId;\n if (appSecret) account.appSecret = appSecret;\n if (backendUrl) account.backendUrl = backendUrl;\n } else {\n // Single-account: write at top level\n if (appId) section.appId = appId;\n if (appSecret) section.appSecret = appSecret;\n if (backendUrl) section.backendUrl = backendUrl;\n }\n\n return updated as OpenClawConfig;\n },\n },\n\n setupWizard: {\n channel: \"cored\",\n status: {\n configuredLabel: \"Connected\",\n unconfiguredLabel: \"Not configured\",\n resolveConfigured: ({ cfg }) => {\n const ids = listAccountIds(cfg);\n return ids.some((id) => {\n const resolved = resolveAccountConfig(cfg, id);\n return Boolean(resolved.appId && resolved.appSecret && resolved.backendUrl);\n });\n },\n },\n credentials: [\n {\n inputKey: \"appToken\",\n providerHint: \"cored\",\n credentialLabel: \"App ID\",\n preferredEnvVar: \"CORED_APP_ID\",\n envPrompt: \"Use CORED_APP_ID from environment?\",\n keepPrompt: \"Keep current App ID?\",\n inputPrompt: \"Enter your Cored App ID:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appId),\n hasConfiguredValue: Boolean(resolved.appId),\n };\n },\n },\n {\n inputKey: \"token\",\n providerHint: \"cored\",\n credentialLabel: \"App Secret\",\n preferredEnvVar: \"CORED_APP_SECRET\",\n envPrompt: \"Use CORED_APP_SECRET from environment?\",\n keepPrompt: \"Keep current App Secret?\",\n inputPrompt: \"Enter your Cored App Secret:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appSecret),\n hasConfiguredValue: Boolean(resolved.appSecret),\n };\n },\n },\n {\n inputKey: \"url\",\n providerHint: \"cored\",\n credentialLabel: \"Backend URL\",\n preferredEnvVar: \"CORED_BACKEND_URL\",\n envPrompt: \"Use CORED_BACKEND_URL from environment?\",\n keepPrompt: \"Keep current Backend URL?\",\n inputPrompt: \"Enter your Cored backend server URL:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.backendUrl),\n hasConfiguredValue: Boolean(resolved.backendUrl),\n };\n },\n },\n ],\n },\n});\n\n// Cast needed: createChannelPluginBase returns Partial<config> but\n// createChatChannelPlugin requires config to be defined. We always\n// provide config above so the cast is safe.\nexport const coredPlugin = createChatChannelPlugin<CoredAccountConfig>({\n base: base as Parameters<typeof createChatChannelPlugin<CoredAccountConfig>>[0][\"base\"],\n\n // DM security: who can message the bot\n security: {\n dm: {\n channelKey: \"cored\",\n resolvePolicy: () => undefined,\n resolveAllowFrom: () => [],\n defaultPolicy: \"allowlist\",\n },\n },\n\n // Threading: how replies are delivered\n threading: { topLevelReplyToMode: \"reply\" },\n\n // Outbound: send messages to the platform\n outbound: {\n attachedResults: {\n channel: \"cored\",\n sendText: async (ctx) => {\n const target = parseTarget(ctx.to);\n if (!target) {\n throw new Error(`[cored] invalid send target: ${ctx.to}`);\n }\n const result = await sendText(\n target.id,\n ctx.text,\n ctx.accountId ?? undefined,\n ctx.replyToId ?? undefined,\n );\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n return { messageId: result.messageId ?? \"\" };\n },\n },\n base: {\n deliveryMode: \"direct\",\n resolveTarget: ({ to }) => {\n if (!to) return { ok: false as const, error: new Error(\"[cored] --to is required\") };\n const target = parseTarget(to);\n if (!target) {\n return {\n ok: false as const,\n error: new Error(\n `Cored requires --to <user:ID|chat:ID>, got: ${JSON.stringify(to)}`,\n ),\n };\n }\n return { ok: true as const, to: `${target.kind}:${target.id}` };\n },\n },\n },\n});\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { CoredChannelConfig, CoredAccountConfig } from \"./types.js\";\n\nconst DEFAULTS = {\n enableEncryption: true,\n requestTimeout: 30_000,\n} as const;\n\nconst ENV_PREFIX = \"CORED_\";\n\nfunction getChannelConfig(cfg: unknown): CoredChannelConfig | undefined {\n const root = cfg as Record<string, unknown> | undefined;\n return root?.channels\n ? ((root.channels as Record<string, unknown>).cored as\n | CoredChannelConfig\n | undefined)\n : undefined;\n}\n\nfunction readEnvConfig(): Partial<CoredAccountConfig> {\n const env = process.env;\n const result: Partial<CoredAccountConfig> = {};\n\n if (env[`${ENV_PREFIX}APP_ID`]) result.appId = env[`${ENV_PREFIX}APP_ID`];\n if (env[`${ENV_PREFIX}APP_SECRET`])\n result.appSecret = env[`${ENV_PREFIX}APP_SECRET`];\n if (env[`${ENV_PREFIX}BACKEND_URL`])\n result.backendUrl = env[`${ENV_PREFIX}BACKEND_URL`];\n if (env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== undefined)\n result.enableEncryption =\n env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== \"false\";\n if (env[`${ENV_PREFIX}REQUEST_TIMEOUT`])\n result.requestTimeout = Number(env[`${ENV_PREFIX}REQUEST_TIMEOUT`]);\n\n return result;\n}\n\nexport function listAccountIds(cfg: unknown): string[] {\n const ch = getChannelConfig(cfg);\n if (!ch) {\n // Check env vars as fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n }\n if (ch.accounts) return Object.keys(ch.accounts);\n if (ch.appId) return [\"default\"];\n // env var fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n}\n\nexport function resolveAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const ch = getChannelConfig(cfg);\n const id = accountId ?? \"default\";\n const envConfig = readEnvConfig();\n\n const raw = ch?.accounts?.[id] ?? ch;\n\n return {\n accountId: id,\n appId: raw?.appId ?? envConfig.appId ?? \"\",\n appSecret: raw?.appSecret ?? envConfig.appSecret ?? \"\",\n backendUrl: raw?.backendUrl ?? envConfig.backendUrl ?? \"\",\n enabled: raw?.enabled ?? true,\n enableEncryption:\n raw?.enableEncryption ?? envConfig.enableEncryption ?? DEFAULTS.enableEncryption,\n requestTimeout:\n raw?.requestTimeout ?? envConfig.requestTimeout ?? DEFAULTS.requestTimeout,\n };\n}\n\nexport interface ConfigValidationError {\n field: string;\n message: string;\n}\n\nexport function validateAccountConfig(\n config: CoredAccountConfig,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (!config.appId) {\n errors.push({\n field: \"appId\",\n message: `Account \"${config.accountId}\": appId is required. Set it in channels.cored.appId or CORED_APP_ID env var.`,\n });\n }\n\n if (!config.appSecret) {\n errors.push({\n field: \"appSecret\",\n message: `Account \"${config.accountId}\": appSecret is required. Set it in channels.cored.appSecret or CORED_APP_SECRET env var.`,\n });\n }\n\n if (!config.backendUrl) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl is required. Set it in channels.cored.backendUrl or CORED_BACKEND_URL env var.`,\n });\n } else if (\n !config.backendUrl.startsWith(\"http://\") &&\n !config.backendUrl.startsWith(\"https://\")\n ) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl must start with http:// or https:// (got \"${config.backendUrl}\").`,\n });\n }\n\n if (\n typeof config.requestTimeout !== \"number\" ||\n !Number.isFinite(config.requestTimeout) ||\n config.requestTimeout <= 0\n ) {\n errors.push({\n field: \"requestTimeout\",\n message: `Account \"${config.accountId}\": requestTimeout must be a positive number in milliseconds (got ${config.requestTimeout}).`,\n });\n }\n\n return errors;\n}\n\nexport function resolveAndValidateAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const config = resolveAccountConfig(cfg, accountId);\n const errors = validateAccountConfig(config);\n\n if (errors.length > 0) {\n const messages = errors.map((e) => ` - ${e.message}`).join(\"\\n\");\n throw new Error(\n `[cored] Invalid config for account \"${config.accountId}\":\\n${messages}`,\n );\n }\n\n return config;\n}\n\nexport function listEnabledAccountConfigs(cfg: unknown): CoredAccountConfig[] {\n const ids = listAccountIds(cfg);\n return ids\n .map((id) => resolveAccountConfig(cfg, id))\n .filter((account) => account.enabled);\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cored client manager — wraps the @cored-im/sdk\n * and provides per-account lifecycle management.\n *\n * This module bridges the SDK's snake_case API with the plugin's camelCase\n * conventions and manages client instances by account ID.\n */\n\nimport { CoredClient, LoggerLevel, ApiError } from \"@cored-im/sdk\";\nimport type { Logger } from \"@cored-im/sdk\";\nimport type { CoredAccountConfig, ConnectionState, CoredMessageEvent } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Auth error detection\n// ---------------------------------------------------------------------------\n\n/** Cored auth failure error code (鉴权失败). */\nconst AUTH_ERROR_CODE = 40000006;\n\n/**\n * Check whether an error is a Cored auth/token failure.\n * Returns true for ApiError with code 40000006, which indicates\n * the token has expired or is otherwise invalid.\n */\nexport function isAuthError(err: unknown): boolean {\n return err instanceof ApiError && err.code === AUTH_ERROR_CODE;\n}\n\n// ---------------------------------------------------------------------------\n// Constants — the new SDK doesn't re-export message type enums from its\n// top-level barrel, so we define the constant locally. The value matches\n// the SDK's MessageType_TEXT = 'text' in message_enum.ts.\n// ---------------------------------------------------------------------------\n\nexport const MessageType_TEXT = \"text\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The SDK client instance returned by CoredClient.create().\n *\n * The new @cored-im/sdk uses:\n * - client.Im.v1.Message.sendMessage(req) — snake_case request fields\n * - client.Im.v1.Message.Event.onMessageReceive(handler) — sync, void return\n * - client.Im.v1.Chat.createTyping(req) — snake_case request fields\n * - client.preheat() / client.close() — camelCase lifecycle methods\n */\nexport type SdkClient = CoredClient;\n\n/**\n * Local send-message request shape matching the SDK's SendMessageReq.\n * Only text is used for now; add specific fields (image, card, etc.)\n * as outbound capabilities grow.\n */\nexport interface SendMessageReq {\n chat_id?: string;\n message_type?: string;\n message_content?: {\n text?: { content?: string };\n };\n reply_message_id?: string;\n}\n\n/**\n * Raw event shape from the SDK's onMessageReceive handler.\n *\n * The new SDK delivers typed events with shape:\n * { header: EventHeader, body: { message?: Message } }\n *\n * Message fields are snake_case. sender_id is a UserId object:\n * { user_id?, union_user_id?, open_user_id? }\n */\nexport interface SdkMessageEvent {\n header?: {\n event_id?: string;\n event_type?: string;\n event_created_at?: string;\n };\n body?: {\n message?: {\n message_id?: string;\n message_type?: string;\n message_status?: string;\n message_content?: unknown;\n message_created_at?: string | number;\n chat_id?: string;\n chat_seq_id?: string | number;\n sender_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n } | string;\n // These may appear in group chats\n chat_type?: string;\n mention_user_list?: Array<{\n user_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n };\n user_name?: string;\n }>;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Client state\n// ---------------------------------------------------------------------------\n\nexport interface ManagedClient {\n client: SdkClient;\n config: CoredAccountConfig;\n /** The raw handler reference, needed for offMessageReceive. */\n eventHandler?: (event: SdkMessageEvent) => void;\n /** Diagnostic connection state. Updated on create/destroy. */\n connectionState: ConnectionState;\n}\n\nconst clients = new Map<string, ManagedClient>();\n\n// ---------------------------------------------------------------------------\n// Logger adapter — bridge plugin's simple log callback to SDK's Logger interface\n// ---------------------------------------------------------------------------\n\nfunction makeLoggerAdapter(\n log?: (msg: string, ctx?: Record<string, unknown>) => void,\n): Logger {\n const emit = (level: string) => (msg: string, ...args: unknown[]) => {\n log?.(`[${level}] ${msg}${args.length ? \" \" + JSON.stringify(args) : \"\"}`);\n };\n return {\n debug: emit(\"debug\"),\n info: emit(\"info\"),\n warn: emit(\"warn\"),\n error: emit(\"error\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport interface CreateClientOptions {\n config: CoredAccountConfig;\n onMessage?: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => void;\n log?: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\n/**\n * Create and connect a Cored SDK client for the given account.\n * Stores it in the client map for later retrieval.\n */\nexport async function createClient(opts: CreateClientOptions): Promise<ManagedClient> {\n const { config, onMessage, log } = opts;\n const accountId = config.accountId;\n\n // Tear down existing client for this account if any\n if (clients.has(accountId)) {\n await destroyClient(accountId);\n }\n\n const sdkClient = await CoredClient.create(\n config.backendUrl,\n config.appId,\n config.appSecret,\n {\n enableEncryption: config.enableEncryption,\n requestTimeout: config.requestTimeout,\n logger: log ? makeLoggerAdapter(log) : undefined,\n logLevel: log ? LoggerLevel.Debug : LoggerLevel.Info,\n },\n );\n\n // Preheat warms the token and verifies connectivity\n await sdkClient.preheat();\n\n const managed: ManagedClient = { client: sdkClient, config, connectionState: \"connected\" };\n\n // Subscribe to incoming messages if handler provided\n if (onMessage) {\n const handler = (sdkEvent: SdkMessageEvent) => {\n const normalized = normalizeSdkEvent(sdkEvent);\n if (normalized) {\n onMessage(normalized, config);\n }\n };\n\n // New SDK: onMessageReceive is synchronous, returns void.\n // Store handler reference for offMessageReceive on teardown.\n sdkClient.Im.v1.Message.Event.onMessageReceive(\n handler as Parameters<typeof sdkClient.Im.v1.Message.Event.onMessageReceive>[0],\n );\n managed.eventHandler = handler;\n }\n\n clients.set(accountId, managed);\n return managed;\n}\n\n/**\n * Destroy and disconnect a client by account ID.\n */\nexport async function destroyClient(accountId: string): Promise<void> {\n const managed = clients.get(accountId);\n if (!managed) return;\n\n managed.connectionState = \"disconnecting\";\n\n // Unsubscribe from events using offMessageReceive\n if (managed.eventHandler) {\n managed.client.Im.v1.Message.Event.offMessageReceive(\n managed.eventHandler as Parameters<typeof managed.client.Im.v1.Message.Event.offMessageReceive>[0],\n );\n }\n try {\n await managed.client.close();\n } catch {\n // Best-effort close\n }\n clients.delete(accountId);\n}\n\n/**\n * Destroy all managed clients.\n */\nexport async function destroyAllClients(): Promise<void> {\n const ids = [...clients.keys()];\n await Promise.allSettled(ids.map((id) => destroyClient(id)));\n}\n\n/**\n * Get a managed client by account ID. Falls back to the first available client.\n */\nexport function getClient(accountId?: string): ManagedClient | undefined {\n if (accountId && clients.has(accountId)) return clients.get(accountId);\n if (clients.size > 0) return clients.values().next().value as ManagedClient;\n return undefined;\n}\n\n/**\n * Number of currently connected clients.\n */\nexport function clientCount(): number {\n return clients.size;\n}\n\n/**\n * Get diagnostic state for all managed clients.\n */\nexport function getClientStates(): Array<{ accountId: string; connectionState: ConnectionState }> {\n return [...clients.entries()].map(([id, m]) => ({\n accountId: id,\n connectionState: m.connectionState,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Event normalization — SDK snake_case event -> plugin camelCase\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a snake_case SDK event to the plugin's CoredMessageEvent format.\n * Returns null if the event is malformed.\n *\n * The new SDK delivers events with lowercase field names:\n * { header, body: { message: { message_id, sender_id: UserId, ... } } }\n *\n * sender_id is now a UserId object { user_id, union_user_id, open_user_id }\n * in the new SDK, but we keep backward compat with string for safety.\n */\nexport function normalizeSdkEvent(sdk: SdkMessageEvent): CoredMessageEvent | null {\n const msg = sdk.body?.message;\n if (!msg) return null;\n\n // Extract user ID from sender_id — may be UserId object or legacy string\n const senderId = msg.sender_id;\n let userId = \"\";\n if (typeof senderId === \"string\") {\n userId = senderId;\n } else if (senderId && typeof senderId === \"object\") {\n userId =\n senderId.user_id ??\n senderId.open_user_id ??\n senderId.union_user_id ??\n \"\";\n }\n\n // Extract mention user IDs from the new SDK's text mention format\n const mentionUsers = (msg.mention_user_list ?? []).map((u) => ({\n userId: u.user_id?.user_id ?? u.user_id?.open_user_id ?? u.user_id?.union_user_id ?? \"\",\n }));\n\n // message_created_at may be Int64 (string) in the new SDK\n const createdAt =\n typeof msg.message_created_at === \"string\"\n ? parseInt(msg.message_created_at, 10) || Date.now()\n : msg.message_created_at ?? Date.now();\n\n return {\n message: {\n messageId: msg.message_id ?? \"\",\n messageType: msg.message_type ?? \"\",\n messageContent: msg.message_content,\n chatId: msg.chat_id ?? \"\",\n chatType: msg.chat_type ?? \"direct\",\n sender: {\n userId,\n },\n createdAt,\n mentionUserList: mentionUsers,\n },\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Outbound message delivery — send text/typing/read to Cored chats.\n *\n * Pipeline: normalize target -> validate account/client -> send -> map errors\n *\n * Text-only for now; media/card/file delivery is follow-up (task 08).\n */\n\nimport {\n getClient,\n isAuthError,\n MessageType_TEXT,\n type ManagedClient,\n type SendMessageReq,\n} from \"../core/cored-client.js\";\n\n// ---------------------------------------------------------------------------\n// Send text\n// ---------------------------------------------------------------------------\n\nexport interface SendTextResult {\n ok: boolean;\n messageId?: string;\n error?: Error;\n provider?: string;\n}\n\n/**\n * Send a text message to a Cored chat.\n *\n * On auth error (code 40000006), forces a token refresh via preheat() and\n * retries once. This handles the SDK's token-refresh edge case without\n * requiring a gateway restart.\n */\nexport async function sendText(\n chatId: string,\n text: string,\n accountId?: string,\n replyMessageId?: string,\n logWarn?: (msg: string) => void,\n): Promise<SendTextResult> {\n const managed = getClient(accountId);\n if (!managed) {\n return {\n ok: false,\n error: new Error(\n `[cored] no connected client for account=${accountId ?? \"default\"}`,\n ),\n };\n }\n\n const req: SendMessageReq = {\n chat_id: chatId,\n message_type: MessageType_TEXT,\n message_content: {\n text: { content: text },\n },\n reply_message_id: replyMessageId,\n };\n\n try {\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (err) {\n if (!isAuthError(err)) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed for chat=${chatId}: ${err instanceof Error ? err.message : String(err)}`,\n ),\n };\n }\n\n // Auth token expired despite SDK auto-refresh — force refresh and retry once.\n // This avoids requiring a gateway restart when the SDK's background token\n // refresh hits an edge case (time sync drift, swallowed fetch failure, etc.).\n logWarn?.(\n `[cored] auth error on send (chat=${chatId}, account=${accountId ?? \"default\"}) — refreshing token and retrying`,\n );\n\n try {\n await managed.client.preheat();\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n logWarn?.(\n `[cored] auth retry succeeded (chat=${chatId}, account=${accountId ?? \"default\"})`,\n );\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (retryErr) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed after auth retry for chat=${chatId}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,\n ),\n };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Set typing indicator in a chat. Cored typing lasts ~5s.\n */\nexport async function setTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.createTyping({ chat_id: chatId });\n } catch {\n // Typing is best-effort — don't fail the message flow\n }\n}\n\n/**\n * Clear typing indicator.\n */\nexport async function clearTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.deleteTyping({ chat_id: chatId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Read receipt\n// ---------------------------------------------------------------------------\n\n/**\n * Mark a message as read.\n */\nexport async function readMessage(\n messageId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Message.readMessage({ message_id: messageId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery callback for inbound dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deliver function scoped to an account, suitable for passing\n * to processInboundMessage's InboundDispatchOptions.\n */\nexport function makeDeliver(\n accountId?: string,\n logWarn?: (msg: string) => void,\n): (chatId: string, text: string) => Promise<void> {\n return async (chatId: string, text: string) => {\n const result = await sendText(chatId, text, accountId, undefined, logWarn);\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Target parsing & validation for the --to argument.\n *\n * Accepted formats:\n * \"chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" }\n * \"user:83870344313569283\" -> { kind: \"user\", id: \"83870344313569283\" }\n * \"cored:chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (prefix stripped)\n * \"oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (bare ID defaults to chat)\n *\n * Returns null for empty, whitespace-only, or malformed targets (e.g. \"user:\" with no ID).\n */\n\nimport type { ParsedTarget } from \"./types.js\";\n\n/**\n * Parse a raw --to string into a structured target.\n * Returns null if the input is missing, empty, or has no usable ID.\n */\nexport function parseTarget(to?: string): ParsedTarget | null {\n const raw = String(to ?? \"\").trim();\n if (!raw) return null;\n\n // Strip optional \"cored:\" channel prefix (case-insensitive)\n const stripped = raw.replace(/^cored:/i, \"\");\n\n if (stripped.startsWith(\"user:\")) {\n const id = stripped.slice(\"user:\".length).trim();\n return id ? { kind: \"user\", id } : null;\n }\n\n if (stripped.startsWith(\"chat:\")) {\n const id = stripped.slice(\"chat:\".length).trim();\n return id ? { kind: \"chat\", id } : null;\n }\n\n // Bare ID — default to chat (Cored SDK uses ChatId for sending)\n return stripped ? { kind: \"chat\", id: stripped } : null;\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Inbound message processing pipeline for Cored IM.\n *\n * Pipeline: parse -> gate -> dedup -> build context -> dispatch\n *\n * Codes against the CoredClient interface contract (task 99) —\n * the client is not imported here; this module receives parsed events.\n */\n\nimport type {\n CoredAccountConfig,\n CoredMessage,\n CoredMessageEvent,\n} from \"../types.js\";\n\n// Plugin API surface used by this module.\n// At runtime the full OpenClawPluginApi is provided by the gateway;\n// we only declare the subset we access so the module stays decoupled\n// and testable without importing the full SDK.\nexport interface InboundPluginApi {\n config: Record<string, unknown>;\n runtime?: {\n channel?: {\n reply?: {\n dispatchReplyWithBufferedBlockDispatcher?: (...args: any[]) => any;\n };\n session?: {\n recordInboundSession?: (...args: any[]) => any;\n resolveStorePath?: (...args: any[]) => string;\n };\n routing?: {\n resolveAgentRoute?: (...args: any[]) => unknown;\n };\n };\n };\n logger?: {\n debug?: (msg: string) => void;\n info?: (msg: string) => void;\n warn?: (msg: string) => void;\n error?: (msg: string) => void;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Parse — extract usable text body from incoming message\n// ---------------------------------------------------------------------------\n\nexport interface ParsedInboundMessage {\n messageId: string;\n chatId: string;\n chatType: \"direct\" | \"group\";\n senderId: string;\n body: string;\n timestamp: number;\n mentionUserIds: string[];\n}\n\n/**\n * Normalize a raw Cored message event into a structured inbound message.\n * Returns `null` if the event cannot be parsed into a usable message.\n *\n * Only text messages are supported in this phase — media/card/file are\n * follow-up work (task 08).\n */\nexport function parseMessageEvent(\n event: CoredMessageEvent,\n): ParsedInboundMessage | null {\n const msg = event?.message;\n if (!msg || !msg.messageId || !msg.chatId) return null;\n\n const body = extractTextBody(msg);\n if (body === null) return null;\n\n const chatType = msg.chatType === \"group\" ? \"group\" : \"direct\";\n\n const senderId =\n msg.sender?.userId || msg.sender?.openUserId || msg.sender?.unionUserId;\n if (!senderId) return null;\n\n const mentionUserIds = (msg.mentionUserList ?? [])\n .map((u) => u.userId || u.openUserId || u.unionUserId || \"\")\n .filter(Boolean);\n\n return {\n messageId: msg.messageId,\n chatId: msg.chatId,\n chatType,\n senderId,\n body,\n timestamp: msg.createdAt ?? Date.now(),\n mentionUserIds,\n };\n}\n\n/**\n * Extract plain-text content from a Cored message.\n * Returns `null` for non-text or empty messages.\n */\nfunction extractTextBody(msg: CoredMessage): string | null {\n if (msg.messageType !== \"text\") return null;\n\n const content = msg.messageContent;\n if (typeof content === \"string\") {\n // Content may be JSON-encoded string (Cored convention)\n try {\n const parsed = JSON.parse(content);\n // Cored format: { text: { content: \"Hello\" } }\n if (parsed?.text && typeof parsed.text === \"object\" && typeof parsed.text.content === \"string\")\n return parsed.text.content.trim() || null;\n if (typeof parsed?.text === \"string\") return parsed.text.trim() || null;\n if (typeof parsed?.content === \"string\")\n return parsed.content.trim() || null;\n // Fall through to raw string\n } catch {\n // Not JSON — use raw string\n }\n return content.trim() || null;\n }\n\n if (content && typeof content === \"object\") {\n const obj = content as Record<string, unknown>;\n // Cored format: { text: { content: \"Hello\" } }\n if (obj.text && typeof obj.text === \"object\") {\n const textObj = obj.text as Record<string, unknown>;\n if (typeof textObj.content === \"string\") return textObj.content.trim() || null;\n }\n if (typeof obj.text === \"string\") return obj.text.trim() || null;\n if (typeof obj.content === \"string\") return obj.content.trim() || null;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Gate — filter out messages the plugin should not process\n// ---------------------------------------------------------------------------\n\nexport interface GateResult {\n pass: boolean;\n reason?: string;\n}\n\n/**\n * Determine whether a parsed inbound message should be processed.\n * Checks in order: self-message, whitelist, group mention requirement.\n */\nexport function checkMessageGate(\n msg: ParsedInboundMessage,\n account: CoredAccountConfig,\n): GateResult {\n return { pass: true };\n}\n\n// ---------------------------------------------------------------------------\n// Dedup — skip already-processed message IDs\n// ---------------------------------------------------------------------------\n\nconst DEDUP_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst DEDUP_CLEANUP_INTERVAL_MS = 60 * 1000; // clean every 1 minute\nconst DEDUP_MAX_SIZE = 10_000;\n\n/** In-memory set of recently processed message IDs with TTL. */\nconst processedMessages = new Map<string, number>();\nlet lastCleanup = Date.now();\n\n/**\n * Returns `true` if the message has already been processed (duplicate).\n * Records the message ID on first encounter.\n */\nexport function isDuplicate(messageId: string): boolean {\n cleanupIfNeeded();\n\n if (processedMessages.has(messageId)) return true;\n\n processedMessages.set(messageId, Date.now());\n return false;\n}\n\nfunction cleanupIfNeeded(): void {\n const now = Date.now();\n if (now - lastCleanup < DEDUP_CLEANUP_INTERVAL_MS) return;\n lastCleanup = now;\n\n const expiry = now - DEDUP_TTL_MS;\n for (const [id, ts] of processedMessages) {\n if (ts < expiry) processedMessages.delete(id);\n }\n\n // Hard cap to prevent unbounded growth\n if (processedMessages.size > DEDUP_MAX_SIZE) {\n const excess = processedMessages.size - DEDUP_MAX_SIZE;\n const iter = processedMessages.keys();\n for (let i = 0; i < excess; i++) {\n const key = iter.next().value;\n if (key !== undefined) processedMessages.delete(key);\n }\n }\n}\n\n/** Reset dedup state — exposed for testing only. */\nexport function _resetDedup(): void {\n processedMessages.clear();\n lastCleanup = Date.now();\n}\n\n// ---------------------------------------------------------------------------\n// Build context & dispatch\n// ---------------------------------------------------------------------------\n\nexport interface InboundContext {\n Body: string;\n From: string;\n To: string;\n SessionKey: string;\n AccountId: string;\n ChatType: \"direct\" | \"group\";\n Provider: \"cored\";\n Surface: \"cored\";\n MessageSid: string;\n Timestamp: number;\n CommandAuthorized: boolean;\n _cored: {\n accountId: string;\n isGroup: boolean;\n senderId: string;\n chatId: string;\n };\n}\n\n/**\n * Build the OpenClaw context payload from a parsed inbound message.\n */\nexport function buildContext(\n msg: ParsedInboundMessage,\n account: CoredAccountConfig,\n): InboundContext {\n const isGroup = msg.chatType === \"group\";\n\n // Session key: group chats key on chatId, DMs key on sender\n const sessionKey = isGroup\n ? `cored:chat:${msg.chatId}`\n : `cored:user:${msg.senderId}`;\n\n return {\n Body: msg.body,\n From: isGroup ? `cored:chat:${msg.chatId}` : `cored:user:${msg.senderId}`,\n To: `cored:bot:${account.appId}`,\n SessionKey: sessionKey,\n AccountId: account.accountId,\n ChatType: isGroup ? \"group\" : \"direct\",\n Provider: \"cored\",\n Surface: \"cored\",\n MessageSid: msg.messageId,\n Timestamp: msg.timestamp,\n CommandAuthorized: true,\n _cored: {\n accountId: account.accountId,\n isGroup,\n senderId: msg.senderId,\n chatId: msg.chatId,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Options for the deliver callback wired by the caller (e.g. the service\n * start handler in index.ts once task 06 connects the CoredClient).\n */\nexport interface InboundDispatchOptions {\n /** Send a reply back to the Cored chat. */\n deliver: (chatId: string, text: string) => Promise<void>;\n}\n\n/**\n * Process an inbound Cored message event end-to-end:\n * parse -> gate -> dedup -> build context -> dispatch.\n *\n * Returns `true` if the message was dispatched, `false` if filtered.\n */\nexport async function processInboundMessage(\n api: InboundPluginApi,\n account: CoredAccountConfig,\n event: CoredMessageEvent,\n opts: InboundDispatchOptions,\n): Promise<boolean> {\n const logger = api.logger;\n\n // Helper for safe logging\n const log = {\n debug: (msg: string) => { logger?.debug?.(msg); },\n info: (msg: string) => { logger?.info?.(msg); },\n warn: (msg: string) => { logger?.warn?.(msg); },\n error: (msg: string) => { logger?.error?.(msg); },\n };\n\n // 1. Parse\n const parsed = parseMessageEvent(event);\n if (!parsed) {\n log.debug(\n `[cored] ignoring unparseable event (messageId=${event?.message?.messageId ?? \"unknown\"} messageType=${event?.message?.messageType ?? \"undefined\"})`,\n );\n return false;\n }\n\n // 2. Gate\n const gate = checkMessageGate(parsed, account);\n if (!gate.pass) {\n log.debug(\n `[cored] gated message=${parsed.messageId} reason=${gate.reason} chat=${parsed.chatId}`,\n );\n return false;\n }\n\n // 3. Dedup\n if (isDuplicate(parsed.messageId)) {\n log.debug(\n `[cored] duplicate message=${parsed.messageId} chat=${parsed.chatId}`,\n );\n return false;\n }\n\n // 4. Build context\n const ctx = buildContext(parsed, account);\n\n log.info(\n `[cored] dispatching message=${parsed.messageId} chat=${parsed.chatId} sender=${parsed.senderId} type=${parsed.chatType}`,\n );\n\n // 5. Dispatch\n const runtime = api.runtime;\n if (!runtime?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) {\n log.warn(\"[cored] runtime.channel.reply not available — cannot dispatch\");\n return false;\n }\n\n // Record session for context continuity\n const cfgSession = (api.config as Record<string, unknown>)?.session as Record<string, unknown> | undefined;\n const storePath =\n runtime.channel.session?.resolveStorePath?.(\n cfgSession?.store,\n { agentId: \"main\" },\n ) ?? \"\";\n\n await runtime.channel.session?.recordInboundSession?.({\n storePath,\n sessionKey: ctx.SessionKey,\n ctx,\n updateLastRoute:\n ctx.ChatType === \"direct\"\n ? {\n sessionKey: ctx.SessionKey,\n channel: \"cored\",\n to: parsed.chatId,\n accountId: account.accountId,\n }\n : undefined,\n });\n\n // Dispatch reply with buffered block dispatcher\n log.debug(\n `[cored] dispatch starting for message=${parsed.messageId} session=${ctx.SessionKey}`,\n );\n\n await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx,\n cfg: api.config,\n dispatcherOptions: {\n deliver: async (payload: { text?: string }) => {\n log.info(\n `[cored] deliver callback called for message=${parsed.messageId} hasText=${!!payload.text} textLen=${payload.text?.length ?? 0}`,\n );\n if (payload.text) {\n await opts.deliver(parsed.chatId, payload.text);\n log.info(\n `[cored] deliver completed for message=${parsed.messageId} chat=${parsed.chatId}`,\n );\n }\n },\n onError: (err: unknown, info?: { kind?: string }) => {\n log.error(\n `[cored] ${info?.kind ?? \"reply\"} error for message=${parsed.messageId}: ${err}`,\n );\n },\n },\n });\n\n log.info(\n `[cored] dispatch finished for message=${parsed.messageId}`,\n );\n\n return true;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/channel.ts","../src/config.ts","../src/core/cored-client.ts","../src/messaging/outbound.ts","../src/targets.ts","../src/messaging/inbound.ts"],"sourcesContent":["// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport { defineChannelPluginEntry } from \"openclaw/plugin-sdk/core\";\nimport { coredPlugin } from \"./channel.js\";\nimport { listEnabledAccountConfigs, validateAccountConfig } from \"./config.js\";\nimport {\n createClient,\n destroyAllClients,\n clientCount,\n} from \"./core/cored-client.js\";\nimport type { OpenClawPluginApi } from \"openclaw/plugin-sdk/core\";\nimport { processInboundMessage } from \"./messaging/inbound.js\";\nimport { makeDeliver, setTyping, clearTyping, readMessage } from \"./messaging/outbound.js\";\nimport type { CoredAccountConfig, CoredMessageEvent } from \"./types.js\";\n\nexport default defineChannelPluginEntry({\n id: \"cored\",\n name: \"Cored\",\n description: \"Connect OpenClaw with Cored\",\n plugin: coredPlugin,\n registerFull(api) {\n api.registerService({\n id: \"cored-sdk\",\n start: async () => {\n if (clientCount() > 0) return;\n\n const accounts = listEnabledAccountConfigs(api.config);\n if (accounts.length === 0) {\n api.logger?.warn?.(\"[cored] no enabled account config found — service idle\");\n return;\n }\n\n for (const account of accounts) {\n const errors = validateAccountConfig(account);\n if (errors.length > 0) {\n api.logger?.warn?.(\n `[cored] skipping account=${account.accountId}: ${errors.map((e) => e.message).join(\"; \")}`,\n );\n continue;\n }\n\n try {\n await startAccount(api, account);\n api.logger?.info?.(\n `[cored] account=${account.accountId} connected (appId=${account.appId})`,\n );\n } catch (err) {\n api.logger?.error?.(\n `[cored] account=${account.accountId} failed to start: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n api.logger?.info?.(`[cored] service started with ${clientCount()} account(s)`);\n },\n stop: async () => {\n await destroyAllClients();\n api.logger?.info?.(\"[cored] service stopped — all clients disconnected\");\n },\n });\n\n api.logger?.info?.(\"[cored] plugin registered\");\n },\n});\n\n/**\n * Start a single account — create client, subscribe to inbound events.\n */\nasync function startAccount(\n api: OpenClawPluginApi,\n account: CoredAccountConfig,\n): Promise<void> {\n const deliver = makeDeliver(account.accountId, (msg) => api.logger?.warn?.(msg));\n\n await createClient({\n config: account,\n log: (msg: string) => api.logger?.debug?.(msg),\n onMessage: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => {\n // Fire-and-forget: process inbound with typing indicator\n handleInbound(api, accountConfig, event, deliver).catch((err) => {\n api.logger?.error?.(\n `[cored] unhandled inbound error for account=${accountConfig.accountId}: ${err}`,\n );\n });\n },\n });\n}\n\n/**\n * Handle a single inbound message with typing indicator lifecycle.\n */\nasync function handleInbound(\n api: OpenClawPluginApi,\n account: CoredAccountConfig,\n event: CoredMessageEvent,\n deliver: (chatId: string, text: string) => Promise<void>,\n): Promise<void> {\n const chatId = event.message?.chatId;\n const messageId = event.message?.messageId;\n\n // Set typing indicator and mark message as read before processing\n if (chatId) {\n setTyping(chatId, account.accountId).catch(() => {});\n }\n if (messageId) {\n readMessage(messageId, account.accountId).catch(() => {});\n }\n\n try {\n await processInboundMessage(api, account, event, { deliver });\n } finally {\n // Clear typing after dispatch completes\n if (chatId) {\n clearTyping(chatId, account.accountId).catch(() => {});\n }\n }\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createChatChannelPlugin,\n createChannelPluginBase,\n} from \"openclaw/plugin-sdk/core\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { listAccountIds, resolveAccountConfig } from \"./config.js\";\nimport { sendText } from \"./messaging/outbound.js\";\nimport { parseTarget } from \"./targets.js\";\nimport type { CoredAccountConfig } from \"./types.js\";\n\nexport const base = createChannelPluginBase<CoredAccountConfig>({\n id: \"cored\",\n\n meta: {\n id: \"cored\",\n label: \"Cored\",\n selectionLabel: \"Cored\",\n docsPath: \"/channels/cored\",\n blurb: \"Connect OpenClaw to Cored\",\n aliases: [\"co\"],\n },\n\n capabilities: {\n chatTypes: [\"direct\", \"group\"],\n },\n\n config: {\n listAccountIds: (cfg: OpenClawConfig) => listAccountIds(cfg),\n resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>\n resolveAccountConfig(cfg, accountId ?? undefined),\n inspectAccount(cfg: OpenClawConfig, accountId?: string | null) {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n const hasConfig = Boolean(\n resolved.appId && resolved.appSecret && resolved.backendUrl,\n );\n return {\n enabled: resolved.enabled,\n configured: hasConfig,\n tokenStatus: hasConfig ? \"available\" : \"missing\",\n };\n },\n },\n\n setup: {\n validateInput: ({ input }) => {\n const missing: string[] = [];\n if (!input.appToken) missing.push(\"--app-token (App ID)\");\n if (!input.token) missing.push(\"--token (App Secret)\");\n if (!input.url) missing.push(\"--url (Backend URL)\");\n if (missing.length > 0) {\n return [\n `Missing required flags: ${missing.join(\", \")}`,\n \"\",\n \"Either provide all flags:\",\n ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,\n \"\",\n \"Or use the interactive wizard:\",\n \" openclaw channels add\",\n ].join(\"\\n\");\n }\n return null;\n },\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const updated = structuredClone(cfg) as Record<string, unknown>;\n if (!updated.channels) updated.channels = {};\n const ch = updated.channels as Record<string, Record<string, unknown>>;\n if (!ch[\"cored\"]) ch[\"cored\"] = {};\n const section = ch[\"cored\"];\n\n // Map ChannelSetupInput keys to our config shape:\n // appToken → appId, token → appSecret, url → backendUrl\n const appId = input.appToken;\n const appSecret = input.token;\n const backendUrl = input.url;\n\n if (accountId && accountId !== \"default\") {\n // Multi-account: write under accounts.<accountId>\n if (!section.accounts) section.accounts = {};\n const accounts = section.accounts as Record<string, Record<string, unknown>>;\n if (!accounts[accountId]) accounts[accountId] = {};\n const account = accounts[accountId];\n if (appId) account.appId = appId;\n if (appSecret) account.appSecret = appSecret;\n if (backendUrl) account.backendUrl = backendUrl;\n } else {\n // Single-account: write at top level\n if (appId) section.appId = appId;\n if (appSecret) section.appSecret = appSecret;\n if (backendUrl) section.backendUrl = backendUrl;\n }\n\n return updated as OpenClawConfig;\n },\n },\n\n setupWizard: {\n channel: \"cored\",\n status: {\n configuredLabel: \"Connected\",\n unconfiguredLabel: \"Not configured\",\n resolveConfigured: ({ cfg }) => {\n const ids = listAccountIds(cfg);\n return ids.some((id) => {\n const resolved = resolveAccountConfig(cfg, id);\n return Boolean(resolved.appId && resolved.appSecret && resolved.backendUrl);\n });\n },\n },\n credentials: [\n {\n inputKey: \"appToken\",\n providerHint: \"cored\",\n credentialLabel: \"App ID\",\n preferredEnvVar: \"CORED_APP_ID\",\n envPrompt: \"Use CORED_APP_ID from environment?\",\n keepPrompt: \"Keep current App ID?\",\n inputPrompt: \"Enter your Cored App ID:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appId),\n hasConfiguredValue: Boolean(resolved.appId),\n };\n },\n },\n {\n inputKey: \"token\",\n providerHint: \"cored\",\n credentialLabel: \"App Secret\",\n preferredEnvVar: \"CORED_APP_SECRET\",\n envPrompt: \"Use CORED_APP_SECRET from environment?\",\n keepPrompt: \"Keep current App Secret?\",\n inputPrompt: \"Enter your Cored App Secret:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appSecret),\n hasConfiguredValue: Boolean(resolved.appSecret),\n };\n },\n },\n {\n inputKey: \"url\",\n providerHint: \"cored\",\n credentialLabel: \"Backend URL\",\n preferredEnvVar: \"CORED_BACKEND_URL\",\n envPrompt: \"Use CORED_BACKEND_URL from environment?\",\n keepPrompt: \"Keep current Backend URL?\",\n inputPrompt: \"Enter your Cored backend server URL:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.backendUrl),\n hasConfiguredValue: Boolean(resolved.backendUrl),\n };\n },\n },\n ],\n },\n});\n\n// Cast needed: createChannelPluginBase returns Partial<config> but\n// createChatChannelPlugin requires config to be defined. We always\n// provide config above so the cast is safe.\nexport const coredPlugin = createChatChannelPlugin<CoredAccountConfig>({\n base: base as Parameters<typeof createChatChannelPlugin<CoredAccountConfig>>[0][\"base\"],\n\n // DM security: who can message the bot\n security: {\n dm: {\n channelKey: \"cored\",\n resolvePolicy: () => undefined,\n resolveAllowFrom: () => [],\n defaultPolicy: \"allowlist\",\n },\n },\n\n // Threading: how replies are delivered\n threading: { topLevelReplyToMode: \"reply\" },\n\n // Outbound: send messages to the platform\n outbound: {\n attachedResults: {\n channel: \"cored\",\n sendText: async (ctx) => {\n const target = parseTarget(ctx.to);\n if (!target) {\n throw new Error(`[cored] invalid send target: ${ctx.to}`);\n }\n const result = await sendText(\n target.id,\n ctx.text,\n ctx.accountId ?? undefined,\n ctx.replyToId ?? undefined,\n );\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n return { messageId: result.messageId ?? \"\" };\n },\n },\n base: {\n deliveryMode: \"direct\",\n resolveTarget: ({ to }) => {\n if (!to) return { ok: false as const, error: new Error(\"[cored] --to is required\") };\n const target = parseTarget(to);\n if (!target) {\n return {\n ok: false as const,\n error: new Error(\n `Cored requires --to <user:ID|chat:ID>, got: ${JSON.stringify(to)}`,\n ),\n };\n }\n return { ok: true as const, to: `${target.kind}:${target.id}` };\n },\n },\n },\n});\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { CoredChannelConfig, CoredAccountConfig } from \"./types.js\";\n\nconst DEFAULTS = {\n enableEncryption: true,\n requestTimeout: 30_000,\n} as const;\n\nconst ENV_PREFIX = \"CORED_\";\n\nfunction getChannelConfig(cfg: unknown): CoredChannelConfig | undefined {\n const root = cfg as Record<string, unknown> | undefined;\n return root?.channels\n ? ((root.channels as Record<string, unknown>).cored as\n | CoredChannelConfig\n | undefined)\n : undefined;\n}\n\nfunction readEnvConfig(): Partial<CoredAccountConfig> {\n const env = process.env;\n const result: Partial<CoredAccountConfig> = {};\n\n if (env[`${ENV_PREFIX}APP_ID`]) result.appId = env[`${ENV_PREFIX}APP_ID`];\n if (env[`${ENV_PREFIX}APP_SECRET`])\n result.appSecret = env[`${ENV_PREFIX}APP_SECRET`];\n if (env[`${ENV_PREFIX}BACKEND_URL`])\n result.backendUrl = env[`${ENV_PREFIX}BACKEND_URL`];\n if (env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== undefined)\n result.enableEncryption =\n env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== \"false\";\n if (env[`${ENV_PREFIX}REQUEST_TIMEOUT`])\n result.requestTimeout = Number(env[`${ENV_PREFIX}REQUEST_TIMEOUT`]);\n\n return result;\n}\n\nexport function listAccountIds(cfg: unknown): string[] {\n const ch = getChannelConfig(cfg);\n if (!ch) {\n // Check env vars as fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n }\n if (ch.accounts) return Object.keys(ch.accounts);\n if (ch.appId) return [\"default\"];\n // env var fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n}\n\nexport function resolveAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const ch = getChannelConfig(cfg);\n const id = accountId ?? \"default\";\n const envConfig = readEnvConfig();\n\n const raw = ch?.accounts?.[id] ?? ch;\n\n return {\n accountId: id,\n appId: raw?.appId ?? envConfig.appId ?? \"\",\n appSecret: raw?.appSecret ?? envConfig.appSecret ?? \"\",\n backendUrl: raw?.backendUrl ?? envConfig.backendUrl ?? \"\",\n enabled: raw?.enabled ?? true,\n enableEncryption:\n raw?.enableEncryption ?? envConfig.enableEncryption ?? DEFAULTS.enableEncryption,\n requestTimeout:\n raw?.requestTimeout ?? envConfig.requestTimeout ?? DEFAULTS.requestTimeout,\n };\n}\n\nexport interface ConfigValidationError {\n field: string;\n message: string;\n}\n\nexport function validateAccountConfig(\n config: CoredAccountConfig,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (!config.appId) {\n errors.push({\n field: \"appId\",\n message: `Account \"${config.accountId}\": appId is required. Set it in channels.cored.appId or CORED_APP_ID env var.`,\n });\n }\n\n if (!config.appSecret) {\n errors.push({\n field: \"appSecret\",\n message: `Account \"${config.accountId}\": appSecret is required. Set it in channels.cored.appSecret or CORED_APP_SECRET env var.`,\n });\n }\n\n if (!config.backendUrl) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl is required. Set it in channels.cored.backendUrl or CORED_BACKEND_URL env var.`,\n });\n } else if (\n !config.backendUrl.startsWith(\"http://\") &&\n !config.backendUrl.startsWith(\"https://\")\n ) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl must start with http:// or https:// (got \"${config.backendUrl}\").`,\n });\n }\n\n if (\n typeof config.requestTimeout !== \"number\" ||\n !Number.isFinite(config.requestTimeout) ||\n config.requestTimeout <= 0\n ) {\n errors.push({\n field: \"requestTimeout\",\n message: `Account \"${config.accountId}\": requestTimeout must be a positive number in milliseconds (got ${config.requestTimeout}).`,\n });\n }\n\n return errors;\n}\n\nexport function resolveAndValidateAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const config = resolveAccountConfig(cfg, accountId);\n const errors = validateAccountConfig(config);\n\n if (errors.length > 0) {\n const messages = errors.map((e) => ` - ${e.message}`).join(\"\\n\");\n throw new Error(\n `[cored] Invalid config for account \"${config.accountId}\":\\n${messages}`,\n );\n }\n\n return config;\n}\n\nexport function listEnabledAccountConfigs(cfg: unknown): CoredAccountConfig[] {\n const ids = listAccountIds(cfg);\n return ids\n .map((id) => resolveAccountConfig(cfg, id))\n .filter((account) => account.enabled);\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cored client manager — wraps the @cored-im/sdk\n * and provides per-account lifecycle management.\n *\n * This module bridges the SDK's snake_case API with the plugin's camelCase\n * conventions and manages client instances by account ID.\n */\n\nimport { CoredClient, LoggerLevel, ApiError } from \"@cored-im/sdk\";\nimport type { Logger } from \"@cored-im/sdk\";\nimport type { CoredAccountConfig, ConnectionState, CoredMessageEvent } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Auth error detection\n// ---------------------------------------------------------------------------\n\n/** Cored auth failure error code (鉴权失败). */\nconst AUTH_ERROR_CODE = 40000006;\n\n/**\n * Check whether an error is a Cored auth/token failure.\n * Returns true for ApiError with code 40000006, which indicates\n * the token has expired or is otherwise invalid.\n */\nexport function isAuthError(err: unknown): boolean {\n return err instanceof ApiError && err.code === AUTH_ERROR_CODE;\n}\n\n// ---------------------------------------------------------------------------\n// Constants — the new SDK doesn't re-export message type enums from its\n// top-level barrel, so we define the constant locally. The value matches\n// the SDK's MessageType_TEXT = 'text' in message_enum.ts.\n// ---------------------------------------------------------------------------\n\nexport const MessageType_TEXT = \"text\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The SDK client instance returned by CoredClient.create().\n *\n * The new @cored-im/sdk uses:\n * - client.Im.v1.Message.sendMessage(req) — snake_case request fields\n * - client.Im.v1.Message.Event.onMessageReceive(handler) — sync, void return\n * - client.Im.v1.Chat.createTyping(req) — snake_case request fields\n * - client.preheat() / client.close() — camelCase lifecycle methods\n */\nexport type SdkClient = CoredClient;\n\n/**\n * Local send-message request shape matching the SDK's SendMessageReq.\n * Only text is used for now; add specific fields (image, card, etc.)\n * as outbound capabilities grow.\n */\nexport interface SendMessageReq {\n chat_id?: string;\n message_type?: string;\n message_content?: {\n text?: { content?: string };\n };\n reply_message_id?: string;\n}\n\n/**\n * Raw event shape from the SDK's onMessageReceive handler.\n *\n * The new SDK delivers typed events with shape:\n * { header: EventHeader, body: { message?: Message } }\n *\n * Message fields are snake_case. sender_id is a UserId object:\n * { user_id?, union_user_id?, open_user_id? }\n */\nexport interface SdkMessageEvent {\n header?: {\n event_id?: string;\n event_type?: string;\n event_created_at?: string;\n };\n body?: {\n message?: {\n message_id?: string;\n message_type?: string;\n message_status?: string;\n message_content?: unknown;\n message_created_at?: string | number;\n chat_id?: string;\n chat_seq_id?: string | number;\n sender_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n } | string;\n // These may appear in group chats\n chat_type?: string;\n mention_user_list?: Array<{\n user_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n };\n user_name?: string;\n }>;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Client state\n// ---------------------------------------------------------------------------\n\nexport interface ManagedClient {\n client: SdkClient;\n config: CoredAccountConfig;\n /** The raw handler reference, needed for offMessageReceive. */\n eventHandler?: (event: SdkMessageEvent) => void;\n /** Diagnostic connection state. Updated on create/destroy. */\n connectionState: ConnectionState;\n}\n\nconst clients = new Map<string, ManagedClient>();\n\n// ---------------------------------------------------------------------------\n// Logger adapter — bridge plugin's simple log callback to SDK's Logger interface\n// ---------------------------------------------------------------------------\n\nfunction makeLoggerAdapter(\n log?: (msg: string, ctx?: Record<string, unknown>) => void,\n): Logger {\n const emit = (level: string) => (msg: string, ...args: unknown[]) => {\n log?.(`[${level}] ${msg}${args.length ? \" \" + JSON.stringify(args) : \"\"}`);\n };\n return {\n debug: emit(\"debug\"),\n info: emit(\"info\"),\n warn: emit(\"warn\"),\n error: emit(\"error\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport interface CreateClientOptions {\n config: CoredAccountConfig;\n onMessage?: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => void;\n log?: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\n/**\n * Create and connect a Cored SDK client for the given account.\n * Stores it in the client map for later retrieval.\n */\nexport async function createClient(opts: CreateClientOptions): Promise<ManagedClient> {\n const { config, onMessage, log } = opts;\n const accountId = config.accountId;\n\n // Tear down existing client for this account if any\n if (clients.has(accountId)) {\n await destroyClient(accountId);\n }\n\n const sdkClient = await CoredClient.create(\n config.backendUrl,\n config.appId,\n config.appSecret,\n {\n enableEncryption: config.enableEncryption,\n requestTimeout: config.requestTimeout,\n logger: log ? makeLoggerAdapter(log) : undefined,\n logLevel: log ? LoggerLevel.Debug : LoggerLevel.Info,\n },\n );\n\n // Preheat warms the token and verifies connectivity\n await sdkClient.preheat();\n\n const managed: ManagedClient = { client: sdkClient, config, connectionState: \"connected\" };\n\n // Subscribe to incoming messages if handler provided\n if (onMessage) {\n const handler = (sdkEvent: SdkMessageEvent) => {\n const normalized = normalizeSdkEvent(sdkEvent);\n if (normalized) {\n onMessage(normalized, config);\n }\n };\n\n // New SDK: onMessageReceive is synchronous, returns void.\n // Store handler reference for offMessageReceive on teardown.\n sdkClient.Im.v1.Message.Event.onMessageReceive(\n handler as Parameters<typeof sdkClient.Im.v1.Message.Event.onMessageReceive>[0],\n );\n managed.eventHandler = handler;\n }\n\n clients.set(accountId, managed);\n return managed;\n}\n\n/**\n * Destroy and disconnect a client by account ID.\n */\nexport async function destroyClient(accountId: string): Promise<void> {\n const managed = clients.get(accountId);\n if (!managed) return;\n\n managed.connectionState = \"disconnecting\";\n\n // Unsubscribe from events using offMessageReceive\n if (managed.eventHandler) {\n managed.client.Im.v1.Message.Event.offMessageReceive(\n managed.eventHandler as Parameters<typeof managed.client.Im.v1.Message.Event.offMessageReceive>[0],\n );\n }\n try {\n await managed.client.close();\n } catch {\n // Best-effort close\n }\n clients.delete(accountId);\n}\n\n/**\n * Destroy all managed clients.\n */\nexport async function destroyAllClients(): Promise<void> {\n const ids = [...clients.keys()];\n await Promise.allSettled(ids.map((id) => destroyClient(id)));\n}\n\n/**\n * Get a managed client by account ID. Falls back to the first available client.\n */\nexport function getClient(accountId?: string): ManagedClient | undefined {\n if (accountId && clients.has(accountId)) return clients.get(accountId);\n if (clients.size > 0) return clients.values().next().value as ManagedClient;\n return undefined;\n}\n\n/**\n * Number of currently connected clients.\n */\nexport function clientCount(): number {\n return clients.size;\n}\n\n/**\n * Get diagnostic state for all managed clients.\n */\nexport function getClientStates(): Array<{ accountId: string; connectionState: ConnectionState }> {\n return [...clients.entries()].map(([id, m]) => ({\n accountId: id,\n connectionState: m.connectionState,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Event normalization — SDK snake_case event -> plugin camelCase\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a snake_case SDK event to the plugin's CoredMessageEvent format.\n * Returns null if the event is malformed.\n *\n * The new SDK delivers events with lowercase field names:\n * { header, body: { message: { message_id, sender_id: UserId, ... } } }\n *\n * sender_id is now a UserId object { user_id, union_user_id, open_user_id }\n * in the new SDK, but we keep backward compat with string for safety.\n */\nexport function normalizeSdkEvent(sdk: SdkMessageEvent): CoredMessageEvent | null {\n const msg = sdk.body?.message;\n if (!msg) return null;\n\n // Extract user ID from sender_id — may be UserId object or legacy string\n const senderId = msg.sender_id;\n let userId = \"\";\n if (typeof senderId === \"string\") {\n userId = senderId;\n } else if (senderId && typeof senderId === \"object\") {\n userId =\n senderId.user_id ??\n senderId.open_user_id ??\n senderId.union_user_id ??\n \"\";\n }\n\n // Extract mention user IDs from the new SDK's text mention format\n const mentionUsers = (msg.mention_user_list ?? []).map((u) => ({\n userId: u.user_id?.user_id ?? u.user_id?.open_user_id ?? u.user_id?.union_user_id ?? \"\",\n }));\n\n // message_created_at may be Int64 (string) in the new SDK\n const createdAt =\n typeof msg.message_created_at === \"string\"\n ? parseInt(msg.message_created_at, 10) || Date.now()\n : msg.message_created_at ?? Date.now();\n\n return {\n message: {\n messageId: msg.message_id ?? \"\",\n messageType: msg.message_type ?? \"\",\n messageContent: msg.message_content,\n chatId: msg.chat_id ?? \"\",\n chatType: msg.chat_type ?? \"direct\",\n sender: {\n userId,\n },\n createdAt,\n mentionUserList: mentionUsers,\n },\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Outbound message delivery — send text/typing/read to Cored chats.\n *\n * Pipeline: normalize target -> validate account/client -> send -> map errors\n *\n * Text-only for now; media/card/file delivery is follow-up (task 08).\n */\n\nimport {\n getClient,\n isAuthError,\n MessageType_TEXT,\n type ManagedClient,\n type SendMessageReq,\n} from \"../core/cored-client.js\";\n\n// ---------------------------------------------------------------------------\n// Send text\n// ---------------------------------------------------------------------------\n\nexport interface SendTextResult {\n ok: boolean;\n messageId?: string;\n error?: Error;\n provider?: string;\n}\n\n/**\n * Send a text message to a Cored chat.\n *\n * On auth error (code 40000006), forces a token refresh via preheat() and\n * retries once. This handles the SDK's token-refresh edge case without\n * requiring a gateway restart.\n */\nexport async function sendText(\n chatId: string,\n text: string,\n accountId?: string,\n replyMessageId?: string,\n logWarn?: (msg: string) => void,\n): Promise<SendTextResult> {\n const managed = getClient(accountId);\n if (!managed) {\n return {\n ok: false,\n error: new Error(\n `[cored] no connected client for account=${accountId ?? \"default\"}`,\n ),\n };\n }\n\n const req: SendMessageReq = {\n chat_id: chatId,\n message_type: MessageType_TEXT,\n message_content: {\n text: { content: text },\n },\n reply_message_id: replyMessageId,\n };\n\n try {\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (err) {\n if (!isAuthError(err)) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed for chat=${chatId}: ${err instanceof Error ? err.message : String(err)}`,\n ),\n };\n }\n\n // Auth token expired despite SDK auto-refresh — force refresh and retry once.\n // This avoids requiring a gateway restart when the SDK's background token\n // refresh hits an edge case (time sync drift, swallowed fetch failure, etc.).\n logWarn?.(\n `[cored] auth error on send (chat=${chatId}, account=${accountId ?? \"default\"}) — refreshing token and retrying`,\n );\n\n try {\n await managed.client.preheat();\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n logWarn?.(\n `[cored] auth retry succeeded (chat=${chatId}, account=${accountId ?? \"default\"})`,\n );\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (retryErr) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed after auth retry for chat=${chatId}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,\n ),\n };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Set typing indicator in a chat. Cored typing lasts ~5s.\n */\nexport async function setTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.createTyping({ chat_id: chatId });\n } catch {\n // Typing is best-effort — don't fail the message flow\n }\n}\n\n/**\n * Clear typing indicator.\n */\nexport async function clearTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.deleteTyping({ chat_id: chatId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Read receipt\n// ---------------------------------------------------------------------------\n\n/**\n * Mark a message as read.\n */\nexport async function readMessage(\n messageId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Message.readMessage({ message_id: messageId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery callback for inbound dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deliver function scoped to an account, suitable for passing\n * to processInboundMessage's InboundDispatchOptions.\n */\nexport function makeDeliver(\n accountId?: string,\n logWarn?: (msg: string) => void,\n): (chatId: string, text: string) => Promise<void> {\n return async (chatId: string, text: string) => {\n const result = await sendText(chatId, text, accountId, undefined, logWarn);\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Target parsing & validation for the --to argument.\n *\n * Accepted formats:\n * \"chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" }\n * \"user:83870344313569283\" -> { kind: \"user\", id: \"83870344313569283\" }\n * \"cored:chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (prefix stripped)\n * \"oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (bare ID defaults to chat)\n *\n * Returns null for empty, whitespace-only, or malformed targets (e.g. \"user:\" with no ID).\n */\n\nimport type { ParsedTarget } from \"./types.js\";\n\n/**\n * Parse a raw --to string into a structured target.\n * Returns null if the input is missing, empty, or has no usable ID.\n */\nexport function parseTarget(to?: string): ParsedTarget | null {\n const raw = String(to ?? \"\").trim();\n if (!raw) return null;\n\n // Strip optional \"cored:\" channel prefix (case-insensitive)\n const stripped = raw.replace(/^cored:/i, \"\");\n\n if (stripped.startsWith(\"user:\")) {\n const id = stripped.slice(\"user:\".length).trim();\n return id ? { kind: \"user\", id } : null;\n }\n\n if (stripped.startsWith(\"chat:\")) {\n const id = stripped.slice(\"chat:\".length).trim();\n return id ? { kind: \"chat\", id } : null;\n }\n\n // Bare ID — default to chat (Cored SDK uses ChatId for sending)\n return stripped ? { kind: \"chat\", id: stripped } : null;\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Inbound message processing pipeline for Cored IM.\n *\n * Pipeline: parse -> gate -> dedup -> build context -> dispatch\n *\n * Codes against the CoredClient interface contract (task 99) —\n * the client is not imported here; this module receives parsed events.\n */\n\nimport type {\n CoredAccountConfig,\n CoredMessage,\n CoredMessageEvent,\n} from \"../types.js\";\n\n// Plugin API surface used by this module.\n// At runtime the full OpenClawPluginApi is provided by the gateway;\n// we only declare the subset we access so the module stays decoupled\n// and testable without importing the full SDK.\nexport interface InboundPluginApi {\n config: Record<string, unknown>;\n runtime?: {\n channel?: {\n reply?: {\n dispatchReplyWithBufferedBlockDispatcher?: (...args: any[]) => any;\n };\n session?: {\n recordInboundSession?: (...args: any[]) => any;\n resolveStorePath?: (...args: any[]) => string;\n };\n routing?: {\n resolveAgentRoute?: (...args: any[]) => unknown;\n };\n };\n };\n logger?: {\n debug?: (msg: string) => void;\n info?: (msg: string) => void;\n warn?: (msg: string) => void;\n error?: (msg: string) => void;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Parse — extract usable text body from incoming message\n// ---------------------------------------------------------------------------\n\nexport interface ParsedInboundMessage {\n messageId: string;\n chatId: string;\n chatType: \"direct\" | \"group\";\n senderId: string;\n body: string;\n timestamp: number;\n mentionUserIds: string[];\n}\n\n/**\n * Normalize a raw Cored message event into a structured inbound message.\n * Returns `null` if the event cannot be parsed into a usable message.\n *\n * Only text messages are supported in this phase — media/card/file are\n * follow-up work (task 08).\n */\nexport function parseMessageEvent(\n event: CoredMessageEvent,\n): ParsedInboundMessage | null {\n const msg = event?.message;\n if (!msg || !msg.messageId || !msg.chatId) return null;\n\n const body = extractTextBody(msg);\n if (body === null) return null;\n\n const chatType = msg.chatType === \"group\" ? \"group\" : \"direct\";\n\n const senderId =\n msg.sender?.userId || msg.sender?.openUserId || msg.sender?.unionUserId;\n if (!senderId) return null;\n\n const mentionUserIds = (msg.mentionUserList ?? [])\n .map((u) => u.userId || u.openUserId || u.unionUserId || \"\")\n .filter(Boolean);\n\n return {\n messageId: msg.messageId,\n chatId: msg.chatId,\n chatType,\n senderId,\n body,\n timestamp: msg.createdAt ?? Date.now(),\n mentionUserIds,\n };\n}\n\n/**\n * Extract plain-text content from a Cored message.\n * Returns `null` for non-text or empty messages.\n */\nfunction extractTextBody(msg: CoredMessage): string | null {\n if (msg.messageType !== \"text\") return null;\n\n const content = msg.messageContent;\n if (typeof content === \"string\") {\n // Content may be JSON-encoded string (Cored convention)\n try {\n const parsed = JSON.parse(content);\n // Cored format: { text: { content: \"Hello\" } }\n if (parsed?.text && typeof parsed.text === \"object\" && typeof parsed.text.content === \"string\")\n return parsed.text.content.trim() || null;\n if (typeof parsed?.text === \"string\") return parsed.text.trim() || null;\n if (typeof parsed?.content === \"string\")\n return parsed.content.trim() || null;\n // Fall through to raw string\n } catch {\n // Not JSON — use raw string\n }\n return content.trim() || null;\n }\n\n if (content && typeof content === \"object\") {\n const obj = content as Record<string, unknown>;\n // Cored format: { text: { content: \"Hello\" } }\n if (obj.text && typeof obj.text === \"object\") {\n const textObj = obj.text as Record<string, unknown>;\n if (typeof textObj.content === \"string\") return textObj.content.trim() || null;\n }\n if (typeof obj.text === \"string\") return obj.text.trim() || null;\n if (typeof obj.content === \"string\") return obj.content.trim() || null;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Gate — filter out messages the plugin should not process\n// ---------------------------------------------------------------------------\n\nexport interface GateResult {\n pass: boolean;\n reason?: string;\n}\n\n/**\n * Determine whether a parsed inbound message should be processed.\n * Checks in order: self-message, whitelist, group mention requirement.\n */\nexport function checkMessageGate(\n msg: ParsedInboundMessage,\n account: CoredAccountConfig,\n): GateResult {\n return { pass: true };\n}\n\n// ---------------------------------------------------------------------------\n// Dedup — skip already-processed message IDs\n// ---------------------------------------------------------------------------\n\nconst DEDUP_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst DEDUP_CLEANUP_INTERVAL_MS = 60 * 1000; // clean every 1 minute\nconst DEDUP_MAX_SIZE = 10_000;\n\n/** In-memory set of recently processed message IDs with TTL. */\nconst processedMessages = new Map<string, number>();\nlet lastCleanup = Date.now();\n\n/**\n * Returns `true` if the message has already been processed (duplicate).\n * Records the message ID on first encounter.\n */\nexport function isDuplicate(messageId: string): boolean {\n cleanupIfNeeded();\n\n if (processedMessages.has(messageId)) return true;\n\n processedMessages.set(messageId, Date.now());\n return false;\n}\n\nfunction cleanupIfNeeded(): void {\n const now = Date.now();\n if (now - lastCleanup < DEDUP_CLEANUP_INTERVAL_MS) return;\n lastCleanup = now;\n\n const expiry = now - DEDUP_TTL_MS;\n for (const [id, ts] of processedMessages) {\n if (ts < expiry) processedMessages.delete(id);\n }\n\n // Hard cap to prevent unbounded growth\n if (processedMessages.size > DEDUP_MAX_SIZE) {\n const excess = processedMessages.size - DEDUP_MAX_SIZE;\n const iter = processedMessages.keys();\n for (let i = 0; i < excess; i++) {\n const key = iter.next().value;\n if (key !== undefined) processedMessages.delete(key);\n }\n }\n}\n\n/** Reset dedup state — exposed for testing only. */\nexport function _resetDedup(): void {\n processedMessages.clear();\n lastCleanup = Date.now();\n}\n\n// ---------------------------------------------------------------------------\n// Build context & dispatch\n// ---------------------------------------------------------------------------\n\nexport interface InboundContext {\n Body: string;\n From: string;\n To: string;\n SessionKey: string;\n AccountId: string;\n ChatType: \"direct\" | \"group\";\n Provider: \"cored\";\n Surface: \"cored\";\n MessageSid: string;\n Timestamp: number;\n CommandAuthorized: boolean;\n _cored: {\n accountId: string;\n isGroup: boolean;\n senderId: string;\n chatId: string;\n };\n}\n\n/**\n * Build the OpenClaw context payload from a parsed inbound message.\n */\nexport function buildContext(\n msg: ParsedInboundMessage,\n account: CoredAccountConfig,\n): InboundContext {\n const isGroup = msg.chatType === \"group\";\n\n // Session key: group chats key on chatId, DMs key on sender\n const sessionKey = isGroup\n ? `cored:chat:${msg.chatId}`\n : `cored:user:${msg.senderId}`;\n\n return {\n Body: msg.body,\n From: isGroup ? `cored:chat:${msg.chatId}` : `cored:user:${msg.senderId}`,\n To: `cored:bot:${account.appId}`,\n SessionKey: sessionKey,\n AccountId: account.accountId,\n ChatType: isGroup ? \"group\" : \"direct\",\n Provider: \"cored\",\n Surface: \"cored\",\n MessageSid: msg.messageId,\n Timestamp: msg.timestamp,\n CommandAuthorized: true,\n _cored: {\n accountId: account.accountId,\n isGroup,\n senderId: msg.senderId,\n chatId: msg.chatId,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Top-level dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Options for the deliver callback wired by the caller (e.g. the service\n * start handler in index.ts once task 06 connects the CoredClient).\n */\nexport interface InboundDispatchOptions {\n /** Send a reply back to the Cored chat. */\n deliver: (chatId: string, text: string) => Promise<void>;\n}\n\n/**\n * Process an inbound Cored message event end-to-end:\n * parse -> gate -> dedup -> build context -> dispatch.\n *\n * Returns `true` if the message was dispatched, `false` if filtered.\n */\nexport async function processInboundMessage(\n api: InboundPluginApi,\n account: CoredAccountConfig,\n event: CoredMessageEvent,\n opts: InboundDispatchOptions,\n): Promise<boolean> {\n const logger = api.logger;\n\n // Helper for safe logging\n const log = {\n debug: (msg: string) => { logger?.debug?.(msg); },\n info: (msg: string) => { logger?.info?.(msg); },\n warn: (msg: string) => { logger?.warn?.(msg); },\n error: (msg: string) => { logger?.error?.(msg); },\n };\n\n // 1. Parse\n const parsed = parseMessageEvent(event);\n if (!parsed) {\n log.debug(\n `[cored] ignoring unparseable event (messageId=${event?.message?.messageId ?? \"unknown\"} messageType=${event?.message?.messageType ?? \"undefined\"})`,\n );\n return false;\n }\n\n // 2. Gate\n const gate = checkMessageGate(parsed, account);\n if (!gate.pass) {\n log.debug(\n `[cored] gated message=${parsed.messageId} reason=${gate.reason} chat=${parsed.chatId}`,\n );\n return false;\n }\n\n // 3. Dedup\n if (isDuplicate(parsed.messageId)) {\n log.debug(\n `[cored] duplicate message=${parsed.messageId} chat=${parsed.chatId}`,\n );\n return false;\n }\n\n // 4. Build context\n const ctx = buildContext(parsed, account);\n\n log.info(\n `[cored] dispatching message=${parsed.messageId} chat=${parsed.chatId} sender=${parsed.senderId} type=${parsed.chatType}`,\n );\n\n // 5. Dispatch\n const runtime = api.runtime;\n if (!runtime?.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) {\n log.warn(\"[cored] runtime.channel.reply not available — cannot dispatch\");\n return false;\n }\n\n // Record session for context continuity\n const cfgSession = (api.config as Record<string, unknown>)?.session as Record<string, unknown> | undefined;\n const storePath =\n runtime.channel.session?.resolveStorePath?.(\n cfgSession?.store,\n { agentId: \"main\" },\n ) ?? \"\";\n\n await runtime.channel.session?.recordInboundSession?.({\n storePath,\n sessionKey: ctx.SessionKey,\n ctx,\n updateLastRoute:\n ctx.ChatType === \"direct\"\n ? {\n sessionKey: ctx.SessionKey,\n channel: \"cored\",\n to: parsed.chatId,\n accountId: account.accountId,\n }\n : undefined,\n });\n\n // Dispatch reply with buffered block dispatcher\n log.debug(\n `[cored] dispatch starting for message=${parsed.messageId} session=${ctx.SessionKey}`,\n );\n\n await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({\n ctx,\n cfg: api.config,\n dispatcherOptions: {\n deliver: async (payload: { text?: string }) => {\n log.info(\n `[cored] deliver callback called for message=${parsed.messageId} hasText=${!!payload.text} textLen=${payload.text?.length ?? 0}`,\n );\n if (payload.text) {\n await opts.deliver(parsed.chatId, payload.text);\n log.info(\n `[cored] deliver completed for message=${parsed.messageId} chat=${parsed.chatId}`,\n );\n }\n },\n onError: (err: unknown, info?: { kind?: string }) => {\n log.error(\n `[cored] ${info?.kind ?? \"reply\"} error for message=${parsed.messageId}: ${err}`,\n );\n },\n },\n });\n\n log.info(\n `[cored] dispatch finished for message=${parsed.messageId}`,\n );\n\n return true;\n}\n"],"mappings":"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","names":[]}
@@ -193,7 +193,7 @@ var base = (0, import_core.createChannelPluginBase)({
193
193
  setup: {
194
194
  validateInput: ({ input }) => {
195
195
  const missing = [];
196
- if (!input.appToken) missing.push("--appToken (App ID)");
196
+ if (!input.appToken) missing.push("--app-token (App ID)");
197
197
  if (!input.token) missing.push("--token (App Secret)");
198
198
  if (!input.url) missing.push("--url (Backend URL)");
199
199
  if (missing.length > 0) {
@@ -201,7 +201,7 @@ var base = (0, import_core.createChannelPluginBase)({
201
201
  `Missing required flags: ${missing.join(", ")}`,
202
202
  "",
203
203
  "Either provide all flags:",
204
- ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
204
+ ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
205
205
  "",
206
206
  "Or use the interactive wizard:",
207
207
  " openclaw channels add"
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/setup-entry.ts","../src/channel.ts","../src/config.ts","../src/core/cored-client.ts","../src/messaging/outbound.ts","../src/targets.ts"],"sourcesContent":["// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport { defineSetupPluginEntry } from \"openclaw/plugin-sdk/core\";\nimport { base } from \"./channel.js\";\n\nexport default defineSetupPluginEntry(base);\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createChatChannelPlugin,\n createChannelPluginBase,\n} from \"openclaw/plugin-sdk/core\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { listAccountIds, resolveAccountConfig } from \"./config.js\";\nimport { sendText } from \"./messaging/outbound.js\";\nimport { parseTarget } from \"./targets.js\";\nimport type { CoredAccountConfig } from \"./types.js\";\n\nexport const base = createChannelPluginBase<CoredAccountConfig>({\n id: \"cored\",\n\n meta: {\n id: \"cored\",\n label: \"Cored\",\n selectionLabel: \"Cored\",\n docsPath: \"/channels/cored\",\n blurb: \"Connect OpenClaw to Cored\",\n aliases: [\"co\"],\n },\n\n capabilities: {\n chatTypes: [\"direct\", \"group\"],\n },\n\n config: {\n listAccountIds: (cfg: OpenClawConfig) => listAccountIds(cfg),\n resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>\n resolveAccountConfig(cfg, accountId ?? undefined),\n inspectAccount(cfg: OpenClawConfig, accountId?: string | null) {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n const hasConfig = Boolean(\n resolved.appId && resolved.appSecret && resolved.backendUrl,\n );\n return {\n enabled: resolved.enabled,\n configured: hasConfig,\n tokenStatus: hasConfig ? \"available\" : \"missing\",\n };\n },\n },\n\n setup: {\n validateInput: ({ input }) => {\n const missing: string[] = [];\n if (!input.appToken) missing.push(\"--appToken (App ID)\");\n if (!input.token) missing.push(\"--token (App Secret)\");\n if (!input.url) missing.push(\"--url (Backend URL)\");\n if (missing.length > 0) {\n return [\n `Missing required flags: ${missing.join(\", \")}`,\n \"\",\n \"Either provide all flags:\",\n ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,\n \"\",\n \"Or use the interactive wizard:\",\n \" openclaw channels add\",\n ].join(\"\\n\");\n }\n return null;\n },\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const updated = structuredClone(cfg) as Record<string, unknown>;\n if (!updated.channels) updated.channels = {};\n const ch = updated.channels as Record<string, Record<string, unknown>>;\n if (!ch[\"cored\"]) ch[\"cored\"] = {};\n const section = ch[\"cored\"];\n\n // Map ChannelSetupInput keys to our config shape:\n // appToken → appId, token → appSecret, url → backendUrl\n const appId = input.appToken;\n const appSecret = input.token;\n const backendUrl = input.url;\n\n if (accountId && accountId !== \"default\") {\n // Multi-account: write under accounts.<accountId>\n if (!section.accounts) section.accounts = {};\n const accounts = section.accounts as Record<string, Record<string, unknown>>;\n if (!accounts[accountId]) accounts[accountId] = {};\n const account = accounts[accountId];\n if (appId) account.appId = appId;\n if (appSecret) account.appSecret = appSecret;\n if (backendUrl) account.backendUrl = backendUrl;\n } else {\n // Single-account: write at top level\n if (appId) section.appId = appId;\n if (appSecret) section.appSecret = appSecret;\n if (backendUrl) section.backendUrl = backendUrl;\n }\n\n return updated as OpenClawConfig;\n },\n },\n\n setupWizard: {\n channel: \"cored\",\n status: {\n configuredLabel: \"Connected\",\n unconfiguredLabel: \"Not configured\",\n resolveConfigured: ({ cfg }) => {\n const ids = listAccountIds(cfg);\n return ids.some((id) => {\n const resolved = resolveAccountConfig(cfg, id);\n return Boolean(resolved.appId && resolved.appSecret && resolved.backendUrl);\n });\n },\n },\n credentials: [\n {\n inputKey: \"appToken\",\n providerHint: \"cored\",\n credentialLabel: \"App ID\",\n preferredEnvVar: \"CORED_APP_ID\",\n envPrompt: \"Use CORED_APP_ID from environment?\",\n keepPrompt: \"Keep current App ID?\",\n inputPrompt: \"Enter your Cored App ID:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appId),\n hasConfiguredValue: Boolean(resolved.appId),\n };\n },\n },\n {\n inputKey: \"token\",\n providerHint: \"cored\",\n credentialLabel: \"App Secret\",\n preferredEnvVar: \"CORED_APP_SECRET\",\n envPrompt: \"Use CORED_APP_SECRET from environment?\",\n keepPrompt: \"Keep current App Secret?\",\n inputPrompt: \"Enter your Cored App Secret:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appSecret),\n hasConfiguredValue: Boolean(resolved.appSecret),\n };\n },\n },\n {\n inputKey: \"url\",\n providerHint: \"cored\",\n credentialLabel: \"Backend URL\",\n preferredEnvVar: \"CORED_BACKEND_URL\",\n envPrompt: \"Use CORED_BACKEND_URL from environment?\",\n keepPrompt: \"Keep current Backend URL?\",\n inputPrompt: \"Enter your Cored backend server URL:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.backendUrl),\n hasConfiguredValue: Boolean(resolved.backendUrl),\n };\n },\n },\n ],\n },\n});\n\n// Cast needed: createChannelPluginBase returns Partial<config> but\n// createChatChannelPlugin requires config to be defined. We always\n// provide config above so the cast is safe.\nexport const coredPlugin = createChatChannelPlugin<CoredAccountConfig>({\n base: base as Parameters<typeof createChatChannelPlugin<CoredAccountConfig>>[0][\"base\"],\n\n // DM security: who can message the bot\n security: {\n dm: {\n channelKey: \"cored\",\n resolvePolicy: () => undefined,\n resolveAllowFrom: () => [],\n defaultPolicy: \"allowlist\",\n },\n },\n\n // Threading: how replies are delivered\n threading: { topLevelReplyToMode: \"reply\" },\n\n // Outbound: send messages to the platform\n outbound: {\n attachedResults: {\n channel: \"cored\",\n sendText: async (ctx) => {\n const target = parseTarget(ctx.to);\n if (!target) {\n throw new Error(`[cored] invalid send target: ${ctx.to}`);\n }\n const result = await sendText(\n target.id,\n ctx.text,\n ctx.accountId ?? undefined,\n ctx.replyToId ?? undefined,\n );\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n return { messageId: result.messageId ?? \"\" };\n },\n },\n base: {\n deliveryMode: \"direct\",\n resolveTarget: ({ to }) => {\n if (!to) return { ok: false as const, error: new Error(\"[cored] --to is required\") };\n const target = parseTarget(to);\n if (!target) {\n return {\n ok: false as const,\n error: new Error(\n `Cored requires --to <user:ID|chat:ID>, got: ${JSON.stringify(to)}`,\n ),\n };\n }\n return { ok: true as const, to: `${target.kind}:${target.id}` };\n },\n },\n },\n});\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { CoredChannelConfig, CoredAccountConfig } from \"./types.js\";\n\nconst DEFAULTS = {\n enableEncryption: true,\n requestTimeout: 30_000,\n} as const;\n\nconst ENV_PREFIX = \"CORED_\";\n\nfunction getChannelConfig(cfg: unknown): CoredChannelConfig | undefined {\n const root = cfg as Record<string, unknown> | undefined;\n return root?.channels\n ? ((root.channels as Record<string, unknown>).cored as\n | CoredChannelConfig\n | undefined)\n : undefined;\n}\n\nfunction readEnvConfig(): Partial<CoredAccountConfig> {\n const env = process.env;\n const result: Partial<CoredAccountConfig> = {};\n\n if (env[`${ENV_PREFIX}APP_ID`]) result.appId = env[`${ENV_PREFIX}APP_ID`];\n if (env[`${ENV_PREFIX}APP_SECRET`])\n result.appSecret = env[`${ENV_PREFIX}APP_SECRET`];\n if (env[`${ENV_PREFIX}BACKEND_URL`])\n result.backendUrl = env[`${ENV_PREFIX}BACKEND_URL`];\n if (env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== undefined)\n result.enableEncryption =\n env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== \"false\";\n if (env[`${ENV_PREFIX}REQUEST_TIMEOUT`])\n result.requestTimeout = Number(env[`${ENV_PREFIX}REQUEST_TIMEOUT`]);\n\n return result;\n}\n\nexport function listAccountIds(cfg: unknown): string[] {\n const ch = getChannelConfig(cfg);\n if (!ch) {\n // Check env vars as fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n }\n if (ch.accounts) return Object.keys(ch.accounts);\n if (ch.appId) return [\"default\"];\n // env var fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n}\n\nexport function resolveAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const ch = getChannelConfig(cfg);\n const id = accountId ?? \"default\";\n const envConfig = readEnvConfig();\n\n const raw = ch?.accounts?.[id] ?? ch;\n\n return {\n accountId: id,\n appId: raw?.appId ?? envConfig.appId ?? \"\",\n appSecret: raw?.appSecret ?? envConfig.appSecret ?? \"\",\n backendUrl: raw?.backendUrl ?? envConfig.backendUrl ?? \"\",\n enabled: raw?.enabled ?? true,\n enableEncryption:\n raw?.enableEncryption ?? envConfig.enableEncryption ?? DEFAULTS.enableEncryption,\n requestTimeout:\n raw?.requestTimeout ?? envConfig.requestTimeout ?? DEFAULTS.requestTimeout,\n };\n}\n\nexport interface ConfigValidationError {\n field: string;\n message: string;\n}\n\nexport function validateAccountConfig(\n config: CoredAccountConfig,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (!config.appId) {\n errors.push({\n field: \"appId\",\n message: `Account \"${config.accountId}\": appId is required. Set it in channels.cored.appId or CORED_APP_ID env var.`,\n });\n }\n\n if (!config.appSecret) {\n errors.push({\n field: \"appSecret\",\n message: `Account \"${config.accountId}\": appSecret is required. Set it in channels.cored.appSecret or CORED_APP_SECRET env var.`,\n });\n }\n\n if (!config.backendUrl) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl is required. Set it in channels.cored.backendUrl or CORED_BACKEND_URL env var.`,\n });\n } else if (\n !config.backendUrl.startsWith(\"http://\") &&\n !config.backendUrl.startsWith(\"https://\")\n ) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl must start with http:// or https:// (got \"${config.backendUrl}\").`,\n });\n }\n\n if (\n typeof config.requestTimeout !== \"number\" ||\n !Number.isFinite(config.requestTimeout) ||\n config.requestTimeout <= 0\n ) {\n errors.push({\n field: \"requestTimeout\",\n message: `Account \"${config.accountId}\": requestTimeout must be a positive number in milliseconds (got ${config.requestTimeout}).`,\n });\n }\n\n return errors;\n}\n\nexport function resolveAndValidateAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const config = resolveAccountConfig(cfg, accountId);\n const errors = validateAccountConfig(config);\n\n if (errors.length > 0) {\n const messages = errors.map((e) => ` - ${e.message}`).join(\"\\n\");\n throw new Error(\n `[cored] Invalid config for account \"${config.accountId}\":\\n${messages}`,\n );\n }\n\n return config;\n}\n\nexport function listEnabledAccountConfigs(cfg: unknown): CoredAccountConfig[] {\n const ids = listAccountIds(cfg);\n return ids\n .map((id) => resolveAccountConfig(cfg, id))\n .filter((account) => account.enabled);\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cored client manager — wraps the @cored-im/sdk\n * and provides per-account lifecycle management.\n *\n * This module bridges the SDK's snake_case API with the plugin's camelCase\n * conventions and manages client instances by account ID.\n */\n\nimport { CoredClient, LoggerLevel, ApiError } from \"@cored-im/sdk\";\nimport type { Logger } from \"@cored-im/sdk\";\nimport type { CoredAccountConfig, ConnectionState, CoredMessageEvent } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Auth error detection\n// ---------------------------------------------------------------------------\n\n/** Cored auth failure error code (鉴权失败). */\nconst AUTH_ERROR_CODE = 40000006;\n\n/**\n * Check whether an error is a Cored auth/token failure.\n * Returns true for ApiError with code 40000006, which indicates\n * the token has expired or is otherwise invalid.\n */\nexport function isAuthError(err: unknown): boolean {\n return err instanceof ApiError && err.code === AUTH_ERROR_CODE;\n}\n\n// ---------------------------------------------------------------------------\n// Constants — the new SDK doesn't re-export message type enums from its\n// top-level barrel, so we define the constant locally. The value matches\n// the SDK's MessageType_TEXT = 'text' in message_enum.ts.\n// ---------------------------------------------------------------------------\n\nexport const MessageType_TEXT = \"text\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The SDK client instance returned by CoredClient.create().\n *\n * The new @cored-im/sdk uses:\n * - client.Im.v1.Message.sendMessage(req) — snake_case request fields\n * - client.Im.v1.Message.Event.onMessageReceive(handler) — sync, void return\n * - client.Im.v1.Chat.createTyping(req) — snake_case request fields\n * - client.preheat() / client.close() — camelCase lifecycle methods\n */\nexport type SdkClient = CoredClient;\n\n/**\n * Local send-message request shape matching the SDK's SendMessageReq.\n * Only text is used for now; add specific fields (image, card, etc.)\n * as outbound capabilities grow.\n */\nexport interface SendMessageReq {\n chat_id?: string;\n message_type?: string;\n message_content?: {\n text?: { content?: string };\n };\n reply_message_id?: string;\n}\n\n/**\n * Raw event shape from the SDK's onMessageReceive handler.\n *\n * The new SDK delivers typed events with shape:\n * { header: EventHeader, body: { message?: Message } }\n *\n * Message fields are snake_case. sender_id is a UserId object:\n * { user_id?, union_user_id?, open_user_id? }\n */\nexport interface SdkMessageEvent {\n header?: {\n event_id?: string;\n event_type?: string;\n event_created_at?: string;\n };\n body?: {\n message?: {\n message_id?: string;\n message_type?: string;\n message_status?: string;\n message_content?: unknown;\n message_created_at?: string | number;\n chat_id?: string;\n chat_seq_id?: string | number;\n sender_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n } | string;\n // These may appear in group chats\n chat_type?: string;\n mention_user_list?: Array<{\n user_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n };\n user_name?: string;\n }>;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Client state\n// ---------------------------------------------------------------------------\n\nexport interface ManagedClient {\n client: SdkClient;\n config: CoredAccountConfig;\n /** The raw handler reference, needed for offMessageReceive. */\n eventHandler?: (event: SdkMessageEvent) => void;\n /** Diagnostic connection state. Updated on create/destroy. */\n connectionState: ConnectionState;\n}\n\nconst clients = new Map<string, ManagedClient>();\n\n// ---------------------------------------------------------------------------\n// Logger adapter — bridge plugin's simple log callback to SDK's Logger interface\n// ---------------------------------------------------------------------------\n\nfunction makeLoggerAdapter(\n log?: (msg: string, ctx?: Record<string, unknown>) => void,\n): Logger {\n const emit = (level: string) => (msg: string, ...args: unknown[]) => {\n log?.(`[${level}] ${msg}${args.length ? \" \" + JSON.stringify(args) : \"\"}`);\n };\n return {\n debug: emit(\"debug\"),\n info: emit(\"info\"),\n warn: emit(\"warn\"),\n error: emit(\"error\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport interface CreateClientOptions {\n config: CoredAccountConfig;\n onMessage?: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => void;\n log?: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\n/**\n * Create and connect a Cored SDK client for the given account.\n * Stores it in the client map for later retrieval.\n */\nexport async function createClient(opts: CreateClientOptions): Promise<ManagedClient> {\n const { config, onMessage, log } = opts;\n const accountId = config.accountId;\n\n // Tear down existing client for this account if any\n if (clients.has(accountId)) {\n await destroyClient(accountId);\n }\n\n const sdkClient = await CoredClient.create(\n config.backendUrl,\n config.appId,\n config.appSecret,\n {\n enableEncryption: config.enableEncryption,\n requestTimeout: config.requestTimeout,\n logger: log ? makeLoggerAdapter(log) : undefined,\n logLevel: log ? LoggerLevel.Debug : LoggerLevel.Info,\n },\n );\n\n // Preheat warms the token and verifies connectivity\n await sdkClient.preheat();\n\n const managed: ManagedClient = { client: sdkClient, config, connectionState: \"connected\" };\n\n // Subscribe to incoming messages if handler provided\n if (onMessage) {\n const handler = (sdkEvent: SdkMessageEvent) => {\n const normalized = normalizeSdkEvent(sdkEvent);\n if (normalized) {\n onMessage(normalized, config);\n }\n };\n\n // New SDK: onMessageReceive is synchronous, returns void.\n // Store handler reference for offMessageReceive on teardown.\n sdkClient.Im.v1.Message.Event.onMessageReceive(\n handler as Parameters<typeof sdkClient.Im.v1.Message.Event.onMessageReceive>[0],\n );\n managed.eventHandler = handler;\n }\n\n clients.set(accountId, managed);\n return managed;\n}\n\n/**\n * Destroy and disconnect a client by account ID.\n */\nexport async function destroyClient(accountId: string): Promise<void> {\n const managed = clients.get(accountId);\n if (!managed) return;\n\n managed.connectionState = \"disconnecting\";\n\n // Unsubscribe from events using offMessageReceive\n if (managed.eventHandler) {\n managed.client.Im.v1.Message.Event.offMessageReceive(\n managed.eventHandler as Parameters<typeof managed.client.Im.v1.Message.Event.offMessageReceive>[0],\n );\n }\n try {\n await managed.client.close();\n } catch {\n // Best-effort close\n }\n clients.delete(accountId);\n}\n\n/**\n * Destroy all managed clients.\n */\nexport async function destroyAllClients(): Promise<void> {\n const ids = [...clients.keys()];\n await Promise.allSettled(ids.map((id) => destroyClient(id)));\n}\n\n/**\n * Get a managed client by account ID. Falls back to the first available client.\n */\nexport function getClient(accountId?: string): ManagedClient | undefined {\n if (accountId && clients.has(accountId)) return clients.get(accountId);\n if (clients.size > 0) return clients.values().next().value as ManagedClient;\n return undefined;\n}\n\n/**\n * Number of currently connected clients.\n */\nexport function clientCount(): number {\n return clients.size;\n}\n\n/**\n * Get diagnostic state for all managed clients.\n */\nexport function getClientStates(): Array<{ accountId: string; connectionState: ConnectionState }> {\n return [...clients.entries()].map(([id, m]) => ({\n accountId: id,\n connectionState: m.connectionState,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Event normalization — SDK snake_case event -> plugin camelCase\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a snake_case SDK event to the plugin's CoredMessageEvent format.\n * Returns null if the event is malformed.\n *\n * The new SDK delivers events with lowercase field names:\n * { header, body: { message: { message_id, sender_id: UserId, ... } } }\n *\n * sender_id is now a UserId object { user_id, union_user_id, open_user_id }\n * in the new SDK, but we keep backward compat with string for safety.\n */\nexport function normalizeSdkEvent(sdk: SdkMessageEvent): CoredMessageEvent | null {\n const msg = sdk.body?.message;\n if (!msg) return null;\n\n // Extract user ID from sender_id — may be UserId object or legacy string\n const senderId = msg.sender_id;\n let userId = \"\";\n if (typeof senderId === \"string\") {\n userId = senderId;\n } else if (senderId && typeof senderId === \"object\") {\n userId =\n senderId.user_id ??\n senderId.open_user_id ??\n senderId.union_user_id ??\n \"\";\n }\n\n // Extract mention user IDs from the new SDK's text mention format\n const mentionUsers = (msg.mention_user_list ?? []).map((u) => ({\n userId: u.user_id?.user_id ?? u.user_id?.open_user_id ?? u.user_id?.union_user_id ?? \"\",\n }));\n\n // message_created_at may be Int64 (string) in the new SDK\n const createdAt =\n typeof msg.message_created_at === \"string\"\n ? parseInt(msg.message_created_at, 10) || Date.now()\n : msg.message_created_at ?? Date.now();\n\n return {\n message: {\n messageId: msg.message_id ?? \"\",\n messageType: msg.message_type ?? \"\",\n messageContent: msg.message_content,\n chatId: msg.chat_id ?? \"\",\n chatType: msg.chat_type ?? \"direct\",\n sender: {\n userId,\n },\n createdAt,\n mentionUserList: mentionUsers,\n },\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Outbound message delivery — send text/typing/read to Cored chats.\n *\n * Pipeline: normalize target -> validate account/client -> send -> map errors\n *\n * Text-only for now; media/card/file delivery is follow-up (task 08).\n */\n\nimport {\n getClient,\n isAuthError,\n MessageType_TEXT,\n type ManagedClient,\n type SendMessageReq,\n} from \"../core/cored-client.js\";\n\n// ---------------------------------------------------------------------------\n// Send text\n// ---------------------------------------------------------------------------\n\nexport interface SendTextResult {\n ok: boolean;\n messageId?: string;\n error?: Error;\n provider?: string;\n}\n\n/**\n * Send a text message to a Cored chat.\n *\n * On auth error (code 40000006), forces a token refresh via preheat() and\n * retries once. This handles the SDK's token-refresh edge case without\n * requiring a gateway restart.\n */\nexport async function sendText(\n chatId: string,\n text: string,\n accountId?: string,\n replyMessageId?: string,\n logWarn?: (msg: string) => void,\n): Promise<SendTextResult> {\n const managed = getClient(accountId);\n if (!managed) {\n return {\n ok: false,\n error: new Error(\n `[cored] no connected client for account=${accountId ?? \"default\"}`,\n ),\n };\n }\n\n const req: SendMessageReq = {\n chat_id: chatId,\n message_type: MessageType_TEXT,\n message_content: {\n text: { content: text },\n },\n reply_message_id: replyMessageId,\n };\n\n try {\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (err) {\n if (!isAuthError(err)) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed for chat=${chatId}: ${err instanceof Error ? err.message : String(err)}`,\n ),\n };\n }\n\n // Auth token expired despite SDK auto-refresh — force refresh and retry once.\n // This avoids requiring a gateway restart when the SDK's background token\n // refresh hits an edge case (time sync drift, swallowed fetch failure, etc.).\n logWarn?.(\n `[cored] auth error on send (chat=${chatId}, account=${accountId ?? \"default\"}) — refreshing token and retrying`,\n );\n\n try {\n await managed.client.preheat();\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n logWarn?.(\n `[cored] auth retry succeeded (chat=${chatId}, account=${accountId ?? \"default\"})`,\n );\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (retryErr) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed after auth retry for chat=${chatId}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,\n ),\n };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Set typing indicator in a chat. Cored typing lasts ~5s.\n */\nexport async function setTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.createTyping({ chat_id: chatId });\n } catch {\n // Typing is best-effort — don't fail the message flow\n }\n}\n\n/**\n * Clear typing indicator.\n */\nexport async function clearTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.deleteTyping({ chat_id: chatId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Read receipt\n// ---------------------------------------------------------------------------\n\n/**\n * Mark a message as read.\n */\nexport async function readMessage(\n messageId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Message.readMessage({ message_id: messageId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery callback for inbound dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deliver function scoped to an account, suitable for passing\n * to processInboundMessage's InboundDispatchOptions.\n */\nexport function makeDeliver(\n accountId?: string,\n logWarn?: (msg: string) => void,\n): (chatId: string, text: string) => Promise<void> {\n return async (chatId: string, text: string) => {\n const result = await sendText(chatId, text, accountId, undefined, logWarn);\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Target parsing & validation for the --to argument.\n *\n * Accepted formats:\n * \"chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" }\n * \"user:83870344313569283\" -> { kind: \"user\", id: \"83870344313569283\" }\n * \"cored:chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (prefix stripped)\n * \"oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (bare ID defaults to chat)\n *\n * Returns null for empty, whitespace-only, or malformed targets (e.g. \"user:\" with no ID).\n */\n\nimport type { ParsedTarget } from \"./types.js\";\n\n/**\n * Parse a raw --to string into a structured target.\n * Returns null if the input is missing, empty, or has no usable ID.\n */\nexport function parseTarget(to?: string): ParsedTarget | null {\n const raw = String(to ?? \"\").trim();\n if (!raw) return null;\n\n // Strip optional \"cored:\" channel prefix (case-insensitive)\n const stripped = raw.replace(/^cored:/i, \"\");\n\n if (stripped.startsWith(\"user:\")) {\n const id = stripped.slice(\"user:\".length).trim();\n return id ? { kind: \"user\", id } : null;\n }\n\n if (stripped.startsWith(\"chat:\")) {\n const id = stripped.slice(\"chat:\".length).trim();\n return id ? { kind: \"chat\", id } : null;\n }\n\n // Bare ID — default to chat (Cored SDK uses ChatId for sending)\n return stripped ? { kind: \"chat\", id: stripped } : null;\n}\n"],"mappings":"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","names":["import_core"]}
1
+ {"version":3,"sources":["../src/setup-entry.ts","../src/channel.ts","../src/config.ts","../src/core/cored-client.ts","../src/messaging/outbound.ts","../src/targets.ts"],"sourcesContent":["// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport { defineSetupPluginEntry } from \"openclaw/plugin-sdk/core\";\nimport { base } from \"./channel.js\";\n\nexport default defineSetupPluginEntry(base);\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createChatChannelPlugin,\n createChannelPluginBase,\n} from \"openclaw/plugin-sdk/core\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { listAccountIds, resolveAccountConfig } from \"./config.js\";\nimport { sendText } from \"./messaging/outbound.js\";\nimport { parseTarget } from \"./targets.js\";\nimport type { CoredAccountConfig } from \"./types.js\";\n\nexport const base = createChannelPluginBase<CoredAccountConfig>({\n id: \"cored\",\n\n meta: {\n id: \"cored\",\n label: \"Cored\",\n selectionLabel: \"Cored\",\n docsPath: \"/channels/cored\",\n blurb: \"Connect OpenClaw to Cored\",\n aliases: [\"co\"],\n },\n\n capabilities: {\n chatTypes: [\"direct\", \"group\"],\n },\n\n config: {\n listAccountIds: (cfg: OpenClawConfig) => listAccountIds(cfg),\n resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>\n resolveAccountConfig(cfg, accountId ?? undefined),\n inspectAccount(cfg: OpenClawConfig, accountId?: string | null) {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n const hasConfig = Boolean(\n resolved.appId && resolved.appSecret && resolved.backendUrl,\n );\n return {\n enabled: resolved.enabled,\n configured: hasConfig,\n tokenStatus: hasConfig ? \"available\" : \"missing\",\n };\n },\n },\n\n setup: {\n validateInput: ({ input }) => {\n const missing: string[] = [];\n if (!input.appToken) missing.push(\"--app-token (App ID)\");\n if (!input.token) missing.push(\"--token (App Secret)\");\n if (!input.url) missing.push(\"--url (Backend URL)\");\n if (missing.length > 0) {\n return [\n `Missing required flags: ${missing.join(\", \")}`,\n \"\",\n \"Either provide all flags:\",\n ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,\n \"\",\n \"Or use the interactive wizard:\",\n \" openclaw channels add\",\n ].join(\"\\n\");\n }\n return null;\n },\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const updated = structuredClone(cfg) as Record<string, unknown>;\n if (!updated.channels) updated.channels = {};\n const ch = updated.channels as Record<string, Record<string, unknown>>;\n if (!ch[\"cored\"]) ch[\"cored\"] = {};\n const section = ch[\"cored\"];\n\n // Map ChannelSetupInput keys to our config shape:\n // appToken → appId, token → appSecret, url → backendUrl\n const appId = input.appToken;\n const appSecret = input.token;\n const backendUrl = input.url;\n\n if (accountId && accountId !== \"default\") {\n // Multi-account: write under accounts.<accountId>\n if (!section.accounts) section.accounts = {};\n const accounts = section.accounts as Record<string, Record<string, unknown>>;\n if (!accounts[accountId]) accounts[accountId] = {};\n const account = accounts[accountId];\n if (appId) account.appId = appId;\n if (appSecret) account.appSecret = appSecret;\n if (backendUrl) account.backendUrl = backendUrl;\n } else {\n // Single-account: write at top level\n if (appId) section.appId = appId;\n if (appSecret) section.appSecret = appSecret;\n if (backendUrl) section.backendUrl = backendUrl;\n }\n\n return updated as OpenClawConfig;\n },\n },\n\n setupWizard: {\n channel: \"cored\",\n status: {\n configuredLabel: \"Connected\",\n unconfiguredLabel: \"Not configured\",\n resolveConfigured: ({ cfg }) => {\n const ids = listAccountIds(cfg);\n return ids.some((id) => {\n const resolved = resolveAccountConfig(cfg, id);\n return Boolean(resolved.appId && resolved.appSecret && resolved.backendUrl);\n });\n },\n },\n credentials: [\n {\n inputKey: \"appToken\",\n providerHint: \"cored\",\n credentialLabel: \"App ID\",\n preferredEnvVar: \"CORED_APP_ID\",\n envPrompt: \"Use CORED_APP_ID from environment?\",\n keepPrompt: \"Keep current App ID?\",\n inputPrompt: \"Enter your Cored App ID:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appId),\n hasConfiguredValue: Boolean(resolved.appId),\n };\n },\n },\n {\n inputKey: \"token\",\n providerHint: \"cored\",\n credentialLabel: \"App Secret\",\n preferredEnvVar: \"CORED_APP_SECRET\",\n envPrompt: \"Use CORED_APP_SECRET from environment?\",\n keepPrompt: \"Keep current App Secret?\",\n inputPrompt: \"Enter your Cored App Secret:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appSecret),\n hasConfiguredValue: Boolean(resolved.appSecret),\n };\n },\n },\n {\n inputKey: \"url\",\n providerHint: \"cored\",\n credentialLabel: \"Backend URL\",\n preferredEnvVar: \"CORED_BACKEND_URL\",\n envPrompt: \"Use CORED_BACKEND_URL from environment?\",\n keepPrompt: \"Keep current Backend URL?\",\n inputPrompt: \"Enter your Cored backend server URL:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.backendUrl),\n hasConfiguredValue: Boolean(resolved.backendUrl),\n };\n },\n },\n ],\n },\n});\n\n// Cast needed: createChannelPluginBase returns Partial<config> but\n// createChatChannelPlugin requires config to be defined. We always\n// provide config above so the cast is safe.\nexport const coredPlugin = createChatChannelPlugin<CoredAccountConfig>({\n base: base as Parameters<typeof createChatChannelPlugin<CoredAccountConfig>>[0][\"base\"],\n\n // DM security: who can message the bot\n security: {\n dm: {\n channelKey: \"cored\",\n resolvePolicy: () => undefined,\n resolveAllowFrom: () => [],\n defaultPolicy: \"allowlist\",\n },\n },\n\n // Threading: how replies are delivered\n threading: { topLevelReplyToMode: \"reply\" },\n\n // Outbound: send messages to the platform\n outbound: {\n attachedResults: {\n channel: \"cored\",\n sendText: async (ctx) => {\n const target = parseTarget(ctx.to);\n if (!target) {\n throw new Error(`[cored] invalid send target: ${ctx.to}`);\n }\n const result = await sendText(\n target.id,\n ctx.text,\n ctx.accountId ?? undefined,\n ctx.replyToId ?? undefined,\n );\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n return { messageId: result.messageId ?? \"\" };\n },\n },\n base: {\n deliveryMode: \"direct\",\n resolveTarget: ({ to }) => {\n if (!to) return { ok: false as const, error: new Error(\"[cored] --to is required\") };\n const target = parseTarget(to);\n if (!target) {\n return {\n ok: false as const,\n error: new Error(\n `Cored requires --to <user:ID|chat:ID>, got: ${JSON.stringify(to)}`,\n ),\n };\n }\n return { ok: true as const, to: `${target.kind}:${target.id}` };\n },\n },\n },\n});\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { CoredChannelConfig, CoredAccountConfig } from \"./types.js\";\n\nconst DEFAULTS = {\n enableEncryption: true,\n requestTimeout: 30_000,\n} as const;\n\nconst ENV_PREFIX = \"CORED_\";\n\nfunction getChannelConfig(cfg: unknown): CoredChannelConfig | undefined {\n const root = cfg as Record<string, unknown> | undefined;\n return root?.channels\n ? ((root.channels as Record<string, unknown>).cored as\n | CoredChannelConfig\n | undefined)\n : undefined;\n}\n\nfunction readEnvConfig(): Partial<CoredAccountConfig> {\n const env = process.env;\n const result: Partial<CoredAccountConfig> = {};\n\n if (env[`${ENV_PREFIX}APP_ID`]) result.appId = env[`${ENV_PREFIX}APP_ID`];\n if (env[`${ENV_PREFIX}APP_SECRET`])\n result.appSecret = env[`${ENV_PREFIX}APP_SECRET`];\n if (env[`${ENV_PREFIX}BACKEND_URL`])\n result.backendUrl = env[`${ENV_PREFIX}BACKEND_URL`];\n if (env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== undefined)\n result.enableEncryption =\n env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== \"false\";\n if (env[`${ENV_PREFIX}REQUEST_TIMEOUT`])\n result.requestTimeout = Number(env[`${ENV_PREFIX}REQUEST_TIMEOUT`]);\n\n return result;\n}\n\nexport function listAccountIds(cfg: unknown): string[] {\n const ch = getChannelConfig(cfg);\n if (!ch) {\n // Check env vars as fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n }\n if (ch.accounts) return Object.keys(ch.accounts);\n if (ch.appId) return [\"default\"];\n // env var fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n}\n\nexport function resolveAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const ch = getChannelConfig(cfg);\n const id = accountId ?? \"default\";\n const envConfig = readEnvConfig();\n\n const raw = ch?.accounts?.[id] ?? ch;\n\n return {\n accountId: id,\n appId: raw?.appId ?? envConfig.appId ?? \"\",\n appSecret: raw?.appSecret ?? envConfig.appSecret ?? \"\",\n backendUrl: raw?.backendUrl ?? envConfig.backendUrl ?? \"\",\n enabled: raw?.enabled ?? true,\n enableEncryption:\n raw?.enableEncryption ?? envConfig.enableEncryption ?? DEFAULTS.enableEncryption,\n requestTimeout:\n raw?.requestTimeout ?? envConfig.requestTimeout ?? DEFAULTS.requestTimeout,\n };\n}\n\nexport interface ConfigValidationError {\n field: string;\n message: string;\n}\n\nexport function validateAccountConfig(\n config: CoredAccountConfig,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (!config.appId) {\n errors.push({\n field: \"appId\",\n message: `Account \"${config.accountId}\": appId is required. Set it in channels.cored.appId or CORED_APP_ID env var.`,\n });\n }\n\n if (!config.appSecret) {\n errors.push({\n field: \"appSecret\",\n message: `Account \"${config.accountId}\": appSecret is required. Set it in channels.cored.appSecret or CORED_APP_SECRET env var.`,\n });\n }\n\n if (!config.backendUrl) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl is required. Set it in channels.cored.backendUrl or CORED_BACKEND_URL env var.`,\n });\n } else if (\n !config.backendUrl.startsWith(\"http://\") &&\n !config.backendUrl.startsWith(\"https://\")\n ) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl must start with http:// or https:// (got \"${config.backendUrl}\").`,\n });\n }\n\n if (\n typeof config.requestTimeout !== \"number\" ||\n !Number.isFinite(config.requestTimeout) ||\n config.requestTimeout <= 0\n ) {\n errors.push({\n field: \"requestTimeout\",\n message: `Account \"${config.accountId}\": requestTimeout must be a positive number in milliseconds (got ${config.requestTimeout}).`,\n });\n }\n\n return errors;\n}\n\nexport function resolveAndValidateAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const config = resolveAccountConfig(cfg, accountId);\n const errors = validateAccountConfig(config);\n\n if (errors.length > 0) {\n const messages = errors.map((e) => ` - ${e.message}`).join(\"\\n\");\n throw new Error(\n `[cored] Invalid config for account \"${config.accountId}\":\\n${messages}`,\n );\n }\n\n return config;\n}\n\nexport function listEnabledAccountConfigs(cfg: unknown): CoredAccountConfig[] {\n const ids = listAccountIds(cfg);\n return ids\n .map((id) => resolveAccountConfig(cfg, id))\n .filter((account) => account.enabled);\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cored client manager — wraps the @cored-im/sdk\n * and provides per-account lifecycle management.\n *\n * This module bridges the SDK's snake_case API with the plugin's camelCase\n * conventions and manages client instances by account ID.\n */\n\nimport { CoredClient, LoggerLevel, ApiError } from \"@cored-im/sdk\";\nimport type { Logger } from \"@cored-im/sdk\";\nimport type { CoredAccountConfig, ConnectionState, CoredMessageEvent } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Auth error detection\n// ---------------------------------------------------------------------------\n\n/** Cored auth failure error code (鉴权失败). */\nconst AUTH_ERROR_CODE = 40000006;\n\n/**\n * Check whether an error is a Cored auth/token failure.\n * Returns true for ApiError with code 40000006, which indicates\n * the token has expired or is otherwise invalid.\n */\nexport function isAuthError(err: unknown): boolean {\n return err instanceof ApiError && err.code === AUTH_ERROR_CODE;\n}\n\n// ---------------------------------------------------------------------------\n// Constants — the new SDK doesn't re-export message type enums from its\n// top-level barrel, so we define the constant locally. The value matches\n// the SDK's MessageType_TEXT = 'text' in message_enum.ts.\n// ---------------------------------------------------------------------------\n\nexport const MessageType_TEXT = \"text\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The SDK client instance returned by CoredClient.create().\n *\n * The new @cored-im/sdk uses:\n * - client.Im.v1.Message.sendMessage(req) — snake_case request fields\n * - client.Im.v1.Message.Event.onMessageReceive(handler) — sync, void return\n * - client.Im.v1.Chat.createTyping(req) — snake_case request fields\n * - client.preheat() / client.close() — camelCase lifecycle methods\n */\nexport type SdkClient = CoredClient;\n\n/**\n * Local send-message request shape matching the SDK's SendMessageReq.\n * Only text is used for now; add specific fields (image, card, etc.)\n * as outbound capabilities grow.\n */\nexport interface SendMessageReq {\n chat_id?: string;\n message_type?: string;\n message_content?: {\n text?: { content?: string };\n };\n reply_message_id?: string;\n}\n\n/**\n * Raw event shape from the SDK's onMessageReceive handler.\n *\n * The new SDK delivers typed events with shape:\n * { header: EventHeader, body: { message?: Message } }\n *\n * Message fields are snake_case. sender_id is a UserId object:\n * { user_id?, union_user_id?, open_user_id? }\n */\nexport interface SdkMessageEvent {\n header?: {\n event_id?: string;\n event_type?: string;\n event_created_at?: string;\n };\n body?: {\n message?: {\n message_id?: string;\n message_type?: string;\n message_status?: string;\n message_content?: unknown;\n message_created_at?: string | number;\n chat_id?: string;\n chat_seq_id?: string | number;\n sender_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n } | string;\n // These may appear in group chats\n chat_type?: string;\n mention_user_list?: Array<{\n user_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n };\n user_name?: string;\n }>;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Client state\n// ---------------------------------------------------------------------------\n\nexport interface ManagedClient {\n client: SdkClient;\n config: CoredAccountConfig;\n /** The raw handler reference, needed for offMessageReceive. */\n eventHandler?: (event: SdkMessageEvent) => void;\n /** Diagnostic connection state. Updated on create/destroy. */\n connectionState: ConnectionState;\n}\n\nconst clients = new Map<string, ManagedClient>();\n\n// ---------------------------------------------------------------------------\n// Logger adapter — bridge plugin's simple log callback to SDK's Logger interface\n// ---------------------------------------------------------------------------\n\nfunction makeLoggerAdapter(\n log?: (msg: string, ctx?: Record<string, unknown>) => void,\n): Logger {\n const emit = (level: string) => (msg: string, ...args: unknown[]) => {\n log?.(`[${level}] ${msg}${args.length ? \" \" + JSON.stringify(args) : \"\"}`);\n };\n return {\n debug: emit(\"debug\"),\n info: emit(\"info\"),\n warn: emit(\"warn\"),\n error: emit(\"error\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport interface CreateClientOptions {\n config: CoredAccountConfig;\n onMessage?: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => void;\n log?: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\n/**\n * Create and connect a Cored SDK client for the given account.\n * Stores it in the client map for later retrieval.\n */\nexport async function createClient(opts: CreateClientOptions): Promise<ManagedClient> {\n const { config, onMessage, log } = opts;\n const accountId = config.accountId;\n\n // Tear down existing client for this account if any\n if (clients.has(accountId)) {\n await destroyClient(accountId);\n }\n\n const sdkClient = await CoredClient.create(\n config.backendUrl,\n config.appId,\n config.appSecret,\n {\n enableEncryption: config.enableEncryption,\n requestTimeout: config.requestTimeout,\n logger: log ? makeLoggerAdapter(log) : undefined,\n logLevel: log ? LoggerLevel.Debug : LoggerLevel.Info,\n },\n );\n\n // Preheat warms the token and verifies connectivity\n await sdkClient.preheat();\n\n const managed: ManagedClient = { client: sdkClient, config, connectionState: \"connected\" };\n\n // Subscribe to incoming messages if handler provided\n if (onMessage) {\n const handler = (sdkEvent: SdkMessageEvent) => {\n const normalized = normalizeSdkEvent(sdkEvent);\n if (normalized) {\n onMessage(normalized, config);\n }\n };\n\n // New SDK: onMessageReceive is synchronous, returns void.\n // Store handler reference for offMessageReceive on teardown.\n sdkClient.Im.v1.Message.Event.onMessageReceive(\n handler as Parameters<typeof sdkClient.Im.v1.Message.Event.onMessageReceive>[0],\n );\n managed.eventHandler = handler;\n }\n\n clients.set(accountId, managed);\n return managed;\n}\n\n/**\n * Destroy and disconnect a client by account ID.\n */\nexport async function destroyClient(accountId: string): Promise<void> {\n const managed = clients.get(accountId);\n if (!managed) return;\n\n managed.connectionState = \"disconnecting\";\n\n // Unsubscribe from events using offMessageReceive\n if (managed.eventHandler) {\n managed.client.Im.v1.Message.Event.offMessageReceive(\n managed.eventHandler as Parameters<typeof managed.client.Im.v1.Message.Event.offMessageReceive>[0],\n );\n }\n try {\n await managed.client.close();\n } catch {\n // Best-effort close\n }\n clients.delete(accountId);\n}\n\n/**\n * Destroy all managed clients.\n */\nexport async function destroyAllClients(): Promise<void> {\n const ids = [...clients.keys()];\n await Promise.allSettled(ids.map((id) => destroyClient(id)));\n}\n\n/**\n * Get a managed client by account ID. Falls back to the first available client.\n */\nexport function getClient(accountId?: string): ManagedClient | undefined {\n if (accountId && clients.has(accountId)) return clients.get(accountId);\n if (clients.size > 0) return clients.values().next().value as ManagedClient;\n return undefined;\n}\n\n/**\n * Number of currently connected clients.\n */\nexport function clientCount(): number {\n return clients.size;\n}\n\n/**\n * Get diagnostic state for all managed clients.\n */\nexport function getClientStates(): Array<{ accountId: string; connectionState: ConnectionState }> {\n return [...clients.entries()].map(([id, m]) => ({\n accountId: id,\n connectionState: m.connectionState,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Event normalization — SDK snake_case event -> plugin camelCase\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a snake_case SDK event to the plugin's CoredMessageEvent format.\n * Returns null if the event is malformed.\n *\n * The new SDK delivers events with lowercase field names:\n * { header, body: { message: { message_id, sender_id: UserId, ... } } }\n *\n * sender_id is now a UserId object { user_id, union_user_id, open_user_id }\n * in the new SDK, but we keep backward compat with string for safety.\n */\nexport function normalizeSdkEvent(sdk: SdkMessageEvent): CoredMessageEvent | null {\n const msg = sdk.body?.message;\n if (!msg) return null;\n\n // Extract user ID from sender_id — may be UserId object or legacy string\n const senderId = msg.sender_id;\n let userId = \"\";\n if (typeof senderId === \"string\") {\n userId = senderId;\n } else if (senderId && typeof senderId === \"object\") {\n userId =\n senderId.user_id ??\n senderId.open_user_id ??\n senderId.union_user_id ??\n \"\";\n }\n\n // Extract mention user IDs from the new SDK's text mention format\n const mentionUsers = (msg.mention_user_list ?? []).map((u) => ({\n userId: u.user_id?.user_id ?? u.user_id?.open_user_id ?? u.user_id?.union_user_id ?? \"\",\n }));\n\n // message_created_at may be Int64 (string) in the new SDK\n const createdAt =\n typeof msg.message_created_at === \"string\"\n ? parseInt(msg.message_created_at, 10) || Date.now()\n : msg.message_created_at ?? Date.now();\n\n return {\n message: {\n messageId: msg.message_id ?? \"\",\n messageType: msg.message_type ?? \"\",\n messageContent: msg.message_content,\n chatId: msg.chat_id ?? \"\",\n chatType: msg.chat_type ?? \"direct\",\n sender: {\n userId,\n },\n createdAt,\n mentionUserList: mentionUsers,\n },\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Outbound message delivery — send text/typing/read to Cored chats.\n *\n * Pipeline: normalize target -> validate account/client -> send -> map errors\n *\n * Text-only for now; media/card/file delivery is follow-up (task 08).\n */\n\nimport {\n getClient,\n isAuthError,\n MessageType_TEXT,\n type ManagedClient,\n type SendMessageReq,\n} from \"../core/cored-client.js\";\n\n// ---------------------------------------------------------------------------\n// Send text\n// ---------------------------------------------------------------------------\n\nexport interface SendTextResult {\n ok: boolean;\n messageId?: string;\n error?: Error;\n provider?: string;\n}\n\n/**\n * Send a text message to a Cored chat.\n *\n * On auth error (code 40000006), forces a token refresh via preheat() and\n * retries once. This handles the SDK's token-refresh edge case without\n * requiring a gateway restart.\n */\nexport async function sendText(\n chatId: string,\n text: string,\n accountId?: string,\n replyMessageId?: string,\n logWarn?: (msg: string) => void,\n): Promise<SendTextResult> {\n const managed = getClient(accountId);\n if (!managed) {\n return {\n ok: false,\n error: new Error(\n `[cored] no connected client for account=${accountId ?? \"default\"}`,\n ),\n };\n }\n\n const req: SendMessageReq = {\n chat_id: chatId,\n message_type: MessageType_TEXT,\n message_content: {\n text: { content: text },\n },\n reply_message_id: replyMessageId,\n };\n\n try {\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (err) {\n if (!isAuthError(err)) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed for chat=${chatId}: ${err instanceof Error ? err.message : String(err)}`,\n ),\n };\n }\n\n // Auth token expired despite SDK auto-refresh — force refresh and retry once.\n // This avoids requiring a gateway restart when the SDK's background token\n // refresh hits an edge case (time sync drift, swallowed fetch failure, etc.).\n logWarn?.(\n `[cored] auth error on send (chat=${chatId}, account=${accountId ?? \"default\"}) — refreshing token and retrying`,\n );\n\n try {\n await managed.client.preheat();\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n logWarn?.(\n `[cored] auth retry succeeded (chat=${chatId}, account=${accountId ?? \"default\"})`,\n );\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (retryErr) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed after auth retry for chat=${chatId}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,\n ),\n };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Set typing indicator in a chat. Cored typing lasts ~5s.\n */\nexport async function setTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.createTyping({ chat_id: chatId });\n } catch {\n // Typing is best-effort — don't fail the message flow\n }\n}\n\n/**\n * Clear typing indicator.\n */\nexport async function clearTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.deleteTyping({ chat_id: chatId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Read receipt\n// ---------------------------------------------------------------------------\n\n/**\n * Mark a message as read.\n */\nexport async function readMessage(\n messageId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Message.readMessage({ message_id: messageId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery callback for inbound dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deliver function scoped to an account, suitable for passing\n * to processInboundMessage's InboundDispatchOptions.\n */\nexport function makeDeliver(\n accountId?: string,\n logWarn?: (msg: string) => void,\n): (chatId: string, text: string) => Promise<void> {\n return async (chatId: string, text: string) => {\n const result = await sendText(chatId, text, accountId, undefined, logWarn);\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Target parsing & validation for the --to argument.\n *\n * Accepted formats:\n * \"chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" }\n * \"user:83870344313569283\" -> { kind: \"user\", id: \"83870344313569283\" }\n * \"cored:chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (prefix stripped)\n * \"oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (bare ID defaults to chat)\n *\n * Returns null for empty, whitespace-only, or malformed targets (e.g. \"user:\" with no ID).\n */\n\nimport type { ParsedTarget } from \"./types.js\";\n\n/**\n * Parse a raw --to string into a structured target.\n * Returns null if the input is missing, empty, or has no usable ID.\n */\nexport function parseTarget(to?: string): ParsedTarget | null {\n const raw = String(to ?? \"\").trim();\n if (!raw) return null;\n\n // Strip optional \"cored:\" channel prefix (case-insensitive)\n const stripped = raw.replace(/^cored:/i, \"\");\n\n if (stripped.startsWith(\"user:\")) {\n const id = stripped.slice(\"user:\".length).trim();\n return id ? { kind: \"user\", id } : null;\n }\n\n if (stripped.startsWith(\"chat:\")) {\n const id = stripped.slice(\"chat:\".length).trim();\n return id ? { kind: \"chat\", id } : null;\n }\n\n // Bare ID — default to chat (Cored SDK uses ChatId for sending)\n return stripped ? { kind: \"chat\", id: stripped } : null;\n}\n"],"mappings":"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","names":["import_core"]}
@@ -172,7 +172,7 @@ var base = createChannelPluginBase({
172
172
  setup: {
173
173
  validateInput: ({ input }) => {
174
174
  const missing = [];
175
- if (!input.appToken) missing.push("--appToken (App ID)");
175
+ if (!input.appToken) missing.push("--app-token (App ID)");
176
176
  if (!input.token) missing.push("--token (App Secret)");
177
177
  if (!input.url) missing.push("--url (Backend URL)");
178
178
  if (missing.length > 0) {
@@ -180,7 +180,7 @@ var base = createChannelPluginBase({
180
180
  `Missing required flags: ${missing.join(", ")}`,
181
181
  "",
182
182
  "Either provide all flags:",
183
- ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
183
+ ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
184
184
  "",
185
185
  "Or use the interactive wizard:",
186
186
  " openclaw channels add"
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/setup-entry.ts","../src/channel.ts","../src/config.ts","../src/core/cored-client.ts","../src/messaging/outbound.ts","../src/targets.ts"],"sourcesContent":["// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport { defineSetupPluginEntry } from \"openclaw/plugin-sdk/core\";\nimport { base } from \"./channel.js\";\n\nexport default defineSetupPluginEntry(base);\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createChatChannelPlugin,\n createChannelPluginBase,\n} from \"openclaw/plugin-sdk/core\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { listAccountIds, resolveAccountConfig } from \"./config.js\";\nimport { sendText } from \"./messaging/outbound.js\";\nimport { parseTarget } from \"./targets.js\";\nimport type { CoredAccountConfig } from \"./types.js\";\n\nexport const base = createChannelPluginBase<CoredAccountConfig>({\n id: \"cored\",\n\n meta: {\n id: \"cored\",\n label: \"Cored\",\n selectionLabel: \"Cored\",\n docsPath: \"/channels/cored\",\n blurb: \"Connect OpenClaw to Cored\",\n aliases: [\"co\"],\n },\n\n capabilities: {\n chatTypes: [\"direct\", \"group\"],\n },\n\n config: {\n listAccountIds: (cfg: OpenClawConfig) => listAccountIds(cfg),\n resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>\n resolveAccountConfig(cfg, accountId ?? undefined),\n inspectAccount(cfg: OpenClawConfig, accountId?: string | null) {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n const hasConfig = Boolean(\n resolved.appId && resolved.appSecret && resolved.backendUrl,\n );\n return {\n enabled: resolved.enabled,\n configured: hasConfig,\n tokenStatus: hasConfig ? \"available\" : \"missing\",\n };\n },\n },\n\n setup: {\n validateInput: ({ input }) => {\n const missing: string[] = [];\n if (!input.appToken) missing.push(\"--appToken (App ID)\");\n if (!input.token) missing.push(\"--token (App Secret)\");\n if (!input.url) missing.push(\"--url (Backend URL)\");\n if (missing.length > 0) {\n return [\n `Missing required flags: ${missing.join(\", \")}`,\n \"\",\n \"Either provide all flags:\",\n ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,\n \"\",\n \"Or use the interactive wizard:\",\n \" openclaw channels add\",\n ].join(\"\\n\");\n }\n return null;\n },\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const updated = structuredClone(cfg) as Record<string, unknown>;\n if (!updated.channels) updated.channels = {};\n const ch = updated.channels as Record<string, Record<string, unknown>>;\n if (!ch[\"cored\"]) ch[\"cored\"] = {};\n const section = ch[\"cored\"];\n\n // Map ChannelSetupInput keys to our config shape:\n // appToken → appId, token → appSecret, url → backendUrl\n const appId = input.appToken;\n const appSecret = input.token;\n const backendUrl = input.url;\n\n if (accountId && accountId !== \"default\") {\n // Multi-account: write under accounts.<accountId>\n if (!section.accounts) section.accounts = {};\n const accounts = section.accounts as Record<string, Record<string, unknown>>;\n if (!accounts[accountId]) accounts[accountId] = {};\n const account = accounts[accountId];\n if (appId) account.appId = appId;\n if (appSecret) account.appSecret = appSecret;\n if (backendUrl) account.backendUrl = backendUrl;\n } else {\n // Single-account: write at top level\n if (appId) section.appId = appId;\n if (appSecret) section.appSecret = appSecret;\n if (backendUrl) section.backendUrl = backendUrl;\n }\n\n return updated as OpenClawConfig;\n },\n },\n\n setupWizard: {\n channel: \"cored\",\n status: {\n configuredLabel: \"Connected\",\n unconfiguredLabel: \"Not configured\",\n resolveConfigured: ({ cfg }) => {\n const ids = listAccountIds(cfg);\n return ids.some((id) => {\n const resolved = resolveAccountConfig(cfg, id);\n return Boolean(resolved.appId && resolved.appSecret && resolved.backendUrl);\n });\n },\n },\n credentials: [\n {\n inputKey: \"appToken\",\n providerHint: \"cored\",\n credentialLabel: \"App ID\",\n preferredEnvVar: \"CORED_APP_ID\",\n envPrompt: \"Use CORED_APP_ID from environment?\",\n keepPrompt: \"Keep current App ID?\",\n inputPrompt: \"Enter your Cored App ID:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appId),\n hasConfiguredValue: Boolean(resolved.appId),\n };\n },\n },\n {\n inputKey: \"token\",\n providerHint: \"cored\",\n credentialLabel: \"App Secret\",\n preferredEnvVar: \"CORED_APP_SECRET\",\n envPrompt: \"Use CORED_APP_SECRET from environment?\",\n keepPrompt: \"Keep current App Secret?\",\n inputPrompt: \"Enter your Cored App Secret:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appSecret),\n hasConfiguredValue: Boolean(resolved.appSecret),\n };\n },\n },\n {\n inputKey: \"url\",\n providerHint: \"cored\",\n credentialLabel: \"Backend URL\",\n preferredEnvVar: \"CORED_BACKEND_URL\",\n envPrompt: \"Use CORED_BACKEND_URL from environment?\",\n keepPrompt: \"Keep current Backend URL?\",\n inputPrompt: \"Enter your Cored backend server URL:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.backendUrl),\n hasConfiguredValue: Boolean(resolved.backendUrl),\n };\n },\n },\n ],\n },\n});\n\n// Cast needed: createChannelPluginBase returns Partial<config> but\n// createChatChannelPlugin requires config to be defined. We always\n// provide config above so the cast is safe.\nexport const coredPlugin = createChatChannelPlugin<CoredAccountConfig>({\n base: base as Parameters<typeof createChatChannelPlugin<CoredAccountConfig>>[0][\"base\"],\n\n // DM security: who can message the bot\n security: {\n dm: {\n channelKey: \"cored\",\n resolvePolicy: () => undefined,\n resolveAllowFrom: () => [],\n defaultPolicy: \"allowlist\",\n },\n },\n\n // Threading: how replies are delivered\n threading: { topLevelReplyToMode: \"reply\" },\n\n // Outbound: send messages to the platform\n outbound: {\n attachedResults: {\n channel: \"cored\",\n sendText: async (ctx) => {\n const target = parseTarget(ctx.to);\n if (!target) {\n throw new Error(`[cored] invalid send target: ${ctx.to}`);\n }\n const result = await sendText(\n target.id,\n ctx.text,\n ctx.accountId ?? undefined,\n ctx.replyToId ?? undefined,\n );\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n return { messageId: result.messageId ?? \"\" };\n },\n },\n base: {\n deliveryMode: \"direct\",\n resolveTarget: ({ to }) => {\n if (!to) return { ok: false as const, error: new Error(\"[cored] --to is required\") };\n const target = parseTarget(to);\n if (!target) {\n return {\n ok: false as const,\n error: new Error(\n `Cored requires --to <user:ID|chat:ID>, got: ${JSON.stringify(to)}`,\n ),\n };\n }\n return { ok: true as const, to: `${target.kind}:${target.id}` };\n },\n },\n },\n});\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { CoredChannelConfig, CoredAccountConfig } from \"./types.js\";\n\nconst DEFAULTS = {\n enableEncryption: true,\n requestTimeout: 30_000,\n} as const;\n\nconst ENV_PREFIX = \"CORED_\";\n\nfunction getChannelConfig(cfg: unknown): CoredChannelConfig | undefined {\n const root = cfg as Record<string, unknown> | undefined;\n return root?.channels\n ? ((root.channels as Record<string, unknown>).cored as\n | CoredChannelConfig\n | undefined)\n : undefined;\n}\n\nfunction readEnvConfig(): Partial<CoredAccountConfig> {\n const env = process.env;\n const result: Partial<CoredAccountConfig> = {};\n\n if (env[`${ENV_PREFIX}APP_ID`]) result.appId = env[`${ENV_PREFIX}APP_ID`];\n if (env[`${ENV_PREFIX}APP_SECRET`])\n result.appSecret = env[`${ENV_PREFIX}APP_SECRET`];\n if (env[`${ENV_PREFIX}BACKEND_URL`])\n result.backendUrl = env[`${ENV_PREFIX}BACKEND_URL`];\n if (env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== undefined)\n result.enableEncryption =\n env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== \"false\";\n if (env[`${ENV_PREFIX}REQUEST_TIMEOUT`])\n result.requestTimeout = Number(env[`${ENV_PREFIX}REQUEST_TIMEOUT`]);\n\n return result;\n}\n\nexport function listAccountIds(cfg: unknown): string[] {\n const ch = getChannelConfig(cfg);\n if (!ch) {\n // Check env vars as fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n }\n if (ch.accounts) return Object.keys(ch.accounts);\n if (ch.appId) return [\"default\"];\n // env var fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n}\n\nexport function resolveAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const ch = getChannelConfig(cfg);\n const id = accountId ?? \"default\";\n const envConfig = readEnvConfig();\n\n const raw = ch?.accounts?.[id] ?? ch;\n\n return {\n accountId: id,\n appId: raw?.appId ?? envConfig.appId ?? \"\",\n appSecret: raw?.appSecret ?? envConfig.appSecret ?? \"\",\n backendUrl: raw?.backendUrl ?? envConfig.backendUrl ?? \"\",\n enabled: raw?.enabled ?? true,\n enableEncryption:\n raw?.enableEncryption ?? envConfig.enableEncryption ?? DEFAULTS.enableEncryption,\n requestTimeout:\n raw?.requestTimeout ?? envConfig.requestTimeout ?? DEFAULTS.requestTimeout,\n };\n}\n\nexport interface ConfigValidationError {\n field: string;\n message: string;\n}\n\nexport function validateAccountConfig(\n config: CoredAccountConfig,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (!config.appId) {\n errors.push({\n field: \"appId\",\n message: `Account \"${config.accountId}\": appId is required. Set it in channels.cored.appId or CORED_APP_ID env var.`,\n });\n }\n\n if (!config.appSecret) {\n errors.push({\n field: \"appSecret\",\n message: `Account \"${config.accountId}\": appSecret is required. Set it in channels.cored.appSecret or CORED_APP_SECRET env var.`,\n });\n }\n\n if (!config.backendUrl) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl is required. Set it in channels.cored.backendUrl or CORED_BACKEND_URL env var.`,\n });\n } else if (\n !config.backendUrl.startsWith(\"http://\") &&\n !config.backendUrl.startsWith(\"https://\")\n ) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl must start with http:// or https:// (got \"${config.backendUrl}\").`,\n });\n }\n\n if (\n typeof config.requestTimeout !== \"number\" ||\n !Number.isFinite(config.requestTimeout) ||\n config.requestTimeout <= 0\n ) {\n errors.push({\n field: \"requestTimeout\",\n message: `Account \"${config.accountId}\": requestTimeout must be a positive number in milliseconds (got ${config.requestTimeout}).`,\n });\n }\n\n return errors;\n}\n\nexport function resolveAndValidateAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const config = resolveAccountConfig(cfg, accountId);\n const errors = validateAccountConfig(config);\n\n if (errors.length > 0) {\n const messages = errors.map((e) => ` - ${e.message}`).join(\"\\n\");\n throw new Error(\n `[cored] Invalid config for account \"${config.accountId}\":\\n${messages}`,\n );\n }\n\n return config;\n}\n\nexport function listEnabledAccountConfigs(cfg: unknown): CoredAccountConfig[] {\n const ids = listAccountIds(cfg);\n return ids\n .map((id) => resolveAccountConfig(cfg, id))\n .filter((account) => account.enabled);\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cored client manager — wraps the @cored-im/sdk\n * and provides per-account lifecycle management.\n *\n * This module bridges the SDK's snake_case API with the plugin's camelCase\n * conventions and manages client instances by account ID.\n */\n\nimport { CoredClient, LoggerLevel, ApiError } from \"@cored-im/sdk\";\nimport type { Logger } from \"@cored-im/sdk\";\nimport type { CoredAccountConfig, ConnectionState, CoredMessageEvent } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Auth error detection\n// ---------------------------------------------------------------------------\n\n/** Cored auth failure error code (鉴权失败). */\nconst AUTH_ERROR_CODE = 40000006;\n\n/**\n * Check whether an error is a Cored auth/token failure.\n * Returns true for ApiError with code 40000006, which indicates\n * the token has expired or is otherwise invalid.\n */\nexport function isAuthError(err: unknown): boolean {\n return err instanceof ApiError && err.code === AUTH_ERROR_CODE;\n}\n\n// ---------------------------------------------------------------------------\n// Constants — the new SDK doesn't re-export message type enums from its\n// top-level barrel, so we define the constant locally. The value matches\n// the SDK's MessageType_TEXT = 'text' in message_enum.ts.\n// ---------------------------------------------------------------------------\n\nexport const MessageType_TEXT = \"text\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The SDK client instance returned by CoredClient.create().\n *\n * The new @cored-im/sdk uses:\n * - client.Im.v1.Message.sendMessage(req) — snake_case request fields\n * - client.Im.v1.Message.Event.onMessageReceive(handler) — sync, void return\n * - client.Im.v1.Chat.createTyping(req) — snake_case request fields\n * - client.preheat() / client.close() — camelCase lifecycle methods\n */\nexport type SdkClient = CoredClient;\n\n/**\n * Local send-message request shape matching the SDK's SendMessageReq.\n * Only text is used for now; add specific fields (image, card, etc.)\n * as outbound capabilities grow.\n */\nexport interface SendMessageReq {\n chat_id?: string;\n message_type?: string;\n message_content?: {\n text?: { content?: string };\n };\n reply_message_id?: string;\n}\n\n/**\n * Raw event shape from the SDK's onMessageReceive handler.\n *\n * The new SDK delivers typed events with shape:\n * { header: EventHeader, body: { message?: Message } }\n *\n * Message fields are snake_case. sender_id is a UserId object:\n * { user_id?, union_user_id?, open_user_id? }\n */\nexport interface SdkMessageEvent {\n header?: {\n event_id?: string;\n event_type?: string;\n event_created_at?: string;\n };\n body?: {\n message?: {\n message_id?: string;\n message_type?: string;\n message_status?: string;\n message_content?: unknown;\n message_created_at?: string | number;\n chat_id?: string;\n chat_seq_id?: string | number;\n sender_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n } | string;\n // These may appear in group chats\n chat_type?: string;\n mention_user_list?: Array<{\n user_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n };\n user_name?: string;\n }>;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Client state\n// ---------------------------------------------------------------------------\n\nexport interface ManagedClient {\n client: SdkClient;\n config: CoredAccountConfig;\n /** The raw handler reference, needed for offMessageReceive. */\n eventHandler?: (event: SdkMessageEvent) => void;\n /** Diagnostic connection state. Updated on create/destroy. */\n connectionState: ConnectionState;\n}\n\nconst clients = new Map<string, ManagedClient>();\n\n// ---------------------------------------------------------------------------\n// Logger adapter — bridge plugin's simple log callback to SDK's Logger interface\n// ---------------------------------------------------------------------------\n\nfunction makeLoggerAdapter(\n log?: (msg: string, ctx?: Record<string, unknown>) => void,\n): Logger {\n const emit = (level: string) => (msg: string, ...args: unknown[]) => {\n log?.(`[${level}] ${msg}${args.length ? \" \" + JSON.stringify(args) : \"\"}`);\n };\n return {\n debug: emit(\"debug\"),\n info: emit(\"info\"),\n warn: emit(\"warn\"),\n error: emit(\"error\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport interface CreateClientOptions {\n config: CoredAccountConfig;\n onMessage?: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => void;\n log?: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\n/**\n * Create and connect a Cored SDK client for the given account.\n * Stores it in the client map for later retrieval.\n */\nexport async function createClient(opts: CreateClientOptions): Promise<ManagedClient> {\n const { config, onMessage, log } = opts;\n const accountId = config.accountId;\n\n // Tear down existing client for this account if any\n if (clients.has(accountId)) {\n await destroyClient(accountId);\n }\n\n const sdkClient = await CoredClient.create(\n config.backendUrl,\n config.appId,\n config.appSecret,\n {\n enableEncryption: config.enableEncryption,\n requestTimeout: config.requestTimeout,\n logger: log ? makeLoggerAdapter(log) : undefined,\n logLevel: log ? LoggerLevel.Debug : LoggerLevel.Info,\n },\n );\n\n // Preheat warms the token and verifies connectivity\n await sdkClient.preheat();\n\n const managed: ManagedClient = { client: sdkClient, config, connectionState: \"connected\" };\n\n // Subscribe to incoming messages if handler provided\n if (onMessage) {\n const handler = (sdkEvent: SdkMessageEvent) => {\n const normalized = normalizeSdkEvent(sdkEvent);\n if (normalized) {\n onMessage(normalized, config);\n }\n };\n\n // New SDK: onMessageReceive is synchronous, returns void.\n // Store handler reference for offMessageReceive on teardown.\n sdkClient.Im.v1.Message.Event.onMessageReceive(\n handler as Parameters<typeof sdkClient.Im.v1.Message.Event.onMessageReceive>[0],\n );\n managed.eventHandler = handler;\n }\n\n clients.set(accountId, managed);\n return managed;\n}\n\n/**\n * Destroy and disconnect a client by account ID.\n */\nexport async function destroyClient(accountId: string): Promise<void> {\n const managed = clients.get(accountId);\n if (!managed) return;\n\n managed.connectionState = \"disconnecting\";\n\n // Unsubscribe from events using offMessageReceive\n if (managed.eventHandler) {\n managed.client.Im.v1.Message.Event.offMessageReceive(\n managed.eventHandler as Parameters<typeof managed.client.Im.v1.Message.Event.offMessageReceive>[0],\n );\n }\n try {\n await managed.client.close();\n } catch {\n // Best-effort close\n }\n clients.delete(accountId);\n}\n\n/**\n * Destroy all managed clients.\n */\nexport async function destroyAllClients(): Promise<void> {\n const ids = [...clients.keys()];\n await Promise.allSettled(ids.map((id) => destroyClient(id)));\n}\n\n/**\n * Get a managed client by account ID. Falls back to the first available client.\n */\nexport function getClient(accountId?: string): ManagedClient | undefined {\n if (accountId && clients.has(accountId)) return clients.get(accountId);\n if (clients.size > 0) return clients.values().next().value as ManagedClient;\n return undefined;\n}\n\n/**\n * Number of currently connected clients.\n */\nexport function clientCount(): number {\n return clients.size;\n}\n\n/**\n * Get diagnostic state for all managed clients.\n */\nexport function getClientStates(): Array<{ accountId: string; connectionState: ConnectionState }> {\n return [...clients.entries()].map(([id, m]) => ({\n accountId: id,\n connectionState: m.connectionState,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Event normalization — SDK snake_case event -> plugin camelCase\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a snake_case SDK event to the plugin's CoredMessageEvent format.\n * Returns null if the event is malformed.\n *\n * The new SDK delivers events with lowercase field names:\n * { header, body: { message: { message_id, sender_id: UserId, ... } } }\n *\n * sender_id is now a UserId object { user_id, union_user_id, open_user_id }\n * in the new SDK, but we keep backward compat with string for safety.\n */\nexport function normalizeSdkEvent(sdk: SdkMessageEvent): CoredMessageEvent | null {\n const msg = sdk.body?.message;\n if (!msg) return null;\n\n // Extract user ID from sender_id — may be UserId object or legacy string\n const senderId = msg.sender_id;\n let userId = \"\";\n if (typeof senderId === \"string\") {\n userId = senderId;\n } else if (senderId && typeof senderId === \"object\") {\n userId =\n senderId.user_id ??\n senderId.open_user_id ??\n senderId.union_user_id ??\n \"\";\n }\n\n // Extract mention user IDs from the new SDK's text mention format\n const mentionUsers = (msg.mention_user_list ?? []).map((u) => ({\n userId: u.user_id?.user_id ?? u.user_id?.open_user_id ?? u.user_id?.union_user_id ?? \"\",\n }));\n\n // message_created_at may be Int64 (string) in the new SDK\n const createdAt =\n typeof msg.message_created_at === \"string\"\n ? parseInt(msg.message_created_at, 10) || Date.now()\n : msg.message_created_at ?? Date.now();\n\n return {\n message: {\n messageId: msg.message_id ?? \"\",\n messageType: msg.message_type ?? \"\",\n messageContent: msg.message_content,\n chatId: msg.chat_id ?? \"\",\n chatType: msg.chat_type ?? \"direct\",\n sender: {\n userId,\n },\n createdAt,\n mentionUserList: mentionUsers,\n },\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Outbound message delivery — send text/typing/read to Cored chats.\n *\n * Pipeline: normalize target -> validate account/client -> send -> map errors\n *\n * Text-only for now; media/card/file delivery is follow-up (task 08).\n */\n\nimport {\n getClient,\n isAuthError,\n MessageType_TEXT,\n type ManagedClient,\n type SendMessageReq,\n} from \"../core/cored-client.js\";\n\n// ---------------------------------------------------------------------------\n// Send text\n// ---------------------------------------------------------------------------\n\nexport interface SendTextResult {\n ok: boolean;\n messageId?: string;\n error?: Error;\n provider?: string;\n}\n\n/**\n * Send a text message to a Cored chat.\n *\n * On auth error (code 40000006), forces a token refresh via preheat() and\n * retries once. This handles the SDK's token-refresh edge case without\n * requiring a gateway restart.\n */\nexport async function sendText(\n chatId: string,\n text: string,\n accountId?: string,\n replyMessageId?: string,\n logWarn?: (msg: string) => void,\n): Promise<SendTextResult> {\n const managed = getClient(accountId);\n if (!managed) {\n return {\n ok: false,\n error: new Error(\n `[cored] no connected client for account=${accountId ?? \"default\"}`,\n ),\n };\n }\n\n const req: SendMessageReq = {\n chat_id: chatId,\n message_type: MessageType_TEXT,\n message_content: {\n text: { content: text },\n },\n reply_message_id: replyMessageId,\n };\n\n try {\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (err) {\n if (!isAuthError(err)) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed for chat=${chatId}: ${err instanceof Error ? err.message : String(err)}`,\n ),\n };\n }\n\n // Auth token expired despite SDK auto-refresh — force refresh and retry once.\n // This avoids requiring a gateway restart when the SDK's background token\n // refresh hits an edge case (time sync drift, swallowed fetch failure, etc.).\n logWarn?.(\n `[cored] auth error on send (chat=${chatId}, account=${accountId ?? \"default\"}) — refreshing token and retrying`,\n );\n\n try {\n await managed.client.preheat();\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n logWarn?.(\n `[cored] auth retry succeeded (chat=${chatId}, account=${accountId ?? \"default\"})`,\n );\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (retryErr) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed after auth retry for chat=${chatId}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,\n ),\n };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Set typing indicator in a chat. Cored typing lasts ~5s.\n */\nexport async function setTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.createTyping({ chat_id: chatId });\n } catch {\n // Typing is best-effort — don't fail the message flow\n }\n}\n\n/**\n * Clear typing indicator.\n */\nexport async function clearTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.deleteTyping({ chat_id: chatId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Read receipt\n// ---------------------------------------------------------------------------\n\n/**\n * Mark a message as read.\n */\nexport async function readMessage(\n messageId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Message.readMessage({ message_id: messageId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery callback for inbound dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deliver function scoped to an account, suitable for passing\n * to processInboundMessage's InboundDispatchOptions.\n */\nexport function makeDeliver(\n accountId?: string,\n logWarn?: (msg: string) => void,\n): (chatId: string, text: string) => Promise<void> {\n return async (chatId: string, text: string) => {\n const result = await sendText(chatId, text, accountId, undefined, logWarn);\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Target parsing & validation for the --to argument.\n *\n * Accepted formats:\n * \"chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" }\n * \"user:83870344313569283\" -> { kind: \"user\", id: \"83870344313569283\" }\n * \"cored:chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (prefix stripped)\n * \"oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (bare ID defaults to chat)\n *\n * Returns null for empty, whitespace-only, or malformed targets (e.g. \"user:\" with no ID).\n */\n\nimport type { ParsedTarget } from \"./types.js\";\n\n/**\n * Parse a raw --to string into a structured target.\n * Returns null if the input is missing, empty, or has no usable ID.\n */\nexport function parseTarget(to?: string): ParsedTarget | null {\n const raw = String(to ?? \"\").trim();\n if (!raw) return null;\n\n // Strip optional \"cored:\" channel prefix (case-insensitive)\n const stripped = raw.replace(/^cored:/i, \"\");\n\n if (stripped.startsWith(\"user:\")) {\n const id = stripped.slice(\"user:\".length).trim();\n return id ? { kind: \"user\", id } : null;\n }\n\n if (stripped.startsWith(\"chat:\")) {\n const id = stripped.slice(\"chat:\".length).trim();\n return id ? { kind: \"chat\", id } : null;\n }\n\n // Bare ID — default to chat (Cored SDK uses ChatId for sending)\n return stripped ? { kind: \"chat\", id: stripped } : null;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/setup-entry.ts","../src/channel.ts","../src/config.ts","../src/core/cored-client.ts","../src/messaging/outbound.ts","../src/targets.ts"],"sourcesContent":["// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport { defineSetupPluginEntry } from \"openclaw/plugin-sdk/core\";\nimport { base } from \"./channel.js\";\n\nexport default defineSetupPluginEntry(base);\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n createChatChannelPlugin,\n createChannelPluginBase,\n} from \"openclaw/plugin-sdk/core\";\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/core\";\nimport { listAccountIds, resolveAccountConfig } from \"./config.js\";\nimport { sendText } from \"./messaging/outbound.js\";\nimport { parseTarget } from \"./targets.js\";\nimport type { CoredAccountConfig } from \"./types.js\";\n\nexport const base = createChannelPluginBase<CoredAccountConfig>({\n id: \"cored\",\n\n meta: {\n id: \"cored\",\n label: \"Cored\",\n selectionLabel: \"Cored\",\n docsPath: \"/channels/cored\",\n blurb: \"Connect OpenClaw to Cored\",\n aliases: [\"co\"],\n },\n\n capabilities: {\n chatTypes: [\"direct\", \"group\"],\n },\n\n config: {\n listAccountIds: (cfg: OpenClawConfig) => listAccountIds(cfg),\n resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) =>\n resolveAccountConfig(cfg, accountId ?? undefined),\n inspectAccount(cfg: OpenClawConfig, accountId?: string | null) {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n const hasConfig = Boolean(\n resolved.appId && resolved.appSecret && resolved.backendUrl,\n );\n return {\n enabled: resolved.enabled,\n configured: hasConfig,\n tokenStatus: hasConfig ? \"available\" : \"missing\",\n };\n },\n },\n\n setup: {\n validateInput: ({ input }) => {\n const missing: string[] = [];\n if (!input.appToken) missing.push(\"--app-token (App ID)\");\n if (!input.token) missing.push(\"--token (App Secret)\");\n if (!input.url) missing.push(\"--url (Backend URL)\");\n if (missing.length > 0) {\n return [\n `Missing required flags: ${missing.join(\", \")}`,\n \"\",\n \"Either provide all flags:\",\n ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,\n \"\",\n \"Or use the interactive wizard:\",\n \" openclaw channels add\",\n ].join(\"\\n\");\n }\n return null;\n },\n applyAccountConfig: ({ cfg, accountId, input }) => {\n const updated = structuredClone(cfg) as Record<string, unknown>;\n if (!updated.channels) updated.channels = {};\n const ch = updated.channels as Record<string, Record<string, unknown>>;\n if (!ch[\"cored\"]) ch[\"cored\"] = {};\n const section = ch[\"cored\"];\n\n // Map ChannelSetupInput keys to our config shape:\n // appToken → appId, token → appSecret, url → backendUrl\n const appId = input.appToken;\n const appSecret = input.token;\n const backendUrl = input.url;\n\n if (accountId && accountId !== \"default\") {\n // Multi-account: write under accounts.<accountId>\n if (!section.accounts) section.accounts = {};\n const accounts = section.accounts as Record<string, Record<string, unknown>>;\n if (!accounts[accountId]) accounts[accountId] = {};\n const account = accounts[accountId];\n if (appId) account.appId = appId;\n if (appSecret) account.appSecret = appSecret;\n if (backendUrl) account.backendUrl = backendUrl;\n } else {\n // Single-account: write at top level\n if (appId) section.appId = appId;\n if (appSecret) section.appSecret = appSecret;\n if (backendUrl) section.backendUrl = backendUrl;\n }\n\n return updated as OpenClawConfig;\n },\n },\n\n setupWizard: {\n channel: \"cored\",\n status: {\n configuredLabel: \"Connected\",\n unconfiguredLabel: \"Not configured\",\n resolveConfigured: ({ cfg }) => {\n const ids = listAccountIds(cfg);\n return ids.some((id) => {\n const resolved = resolveAccountConfig(cfg, id);\n return Boolean(resolved.appId && resolved.appSecret && resolved.backendUrl);\n });\n },\n },\n credentials: [\n {\n inputKey: \"appToken\",\n providerHint: \"cored\",\n credentialLabel: \"App ID\",\n preferredEnvVar: \"CORED_APP_ID\",\n envPrompt: \"Use CORED_APP_ID from environment?\",\n keepPrompt: \"Keep current App ID?\",\n inputPrompt: \"Enter your Cored App ID:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appId),\n hasConfiguredValue: Boolean(resolved.appId),\n };\n },\n },\n {\n inputKey: \"token\",\n providerHint: \"cored\",\n credentialLabel: \"App Secret\",\n preferredEnvVar: \"CORED_APP_SECRET\",\n envPrompt: \"Use CORED_APP_SECRET from environment?\",\n keepPrompt: \"Keep current App Secret?\",\n inputPrompt: \"Enter your Cored App Secret:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.appSecret),\n hasConfiguredValue: Boolean(resolved.appSecret),\n };\n },\n },\n {\n inputKey: \"url\",\n providerHint: \"cored\",\n credentialLabel: \"Backend URL\",\n preferredEnvVar: \"CORED_BACKEND_URL\",\n envPrompt: \"Use CORED_BACKEND_URL from environment?\",\n keepPrompt: \"Keep current Backend URL?\",\n inputPrompt: \"Enter your Cored backend server URL:\",\n inspect: ({ cfg, accountId }) => {\n const resolved = resolveAccountConfig(cfg, accountId ?? undefined);\n return {\n accountConfigured: Boolean(resolved.backendUrl),\n hasConfiguredValue: Boolean(resolved.backendUrl),\n };\n },\n },\n ],\n },\n});\n\n// Cast needed: createChannelPluginBase returns Partial<config> but\n// createChatChannelPlugin requires config to be defined. We always\n// provide config above so the cast is safe.\nexport const coredPlugin = createChatChannelPlugin<CoredAccountConfig>({\n base: base as Parameters<typeof createChatChannelPlugin<CoredAccountConfig>>[0][\"base\"],\n\n // DM security: who can message the bot\n security: {\n dm: {\n channelKey: \"cored\",\n resolvePolicy: () => undefined,\n resolveAllowFrom: () => [],\n defaultPolicy: \"allowlist\",\n },\n },\n\n // Threading: how replies are delivered\n threading: { topLevelReplyToMode: \"reply\" },\n\n // Outbound: send messages to the platform\n outbound: {\n attachedResults: {\n channel: \"cored\",\n sendText: async (ctx) => {\n const target = parseTarget(ctx.to);\n if (!target) {\n throw new Error(`[cored] invalid send target: ${ctx.to}`);\n }\n const result = await sendText(\n target.id,\n ctx.text,\n ctx.accountId ?? undefined,\n ctx.replyToId ?? undefined,\n );\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n return { messageId: result.messageId ?? \"\" };\n },\n },\n base: {\n deliveryMode: \"direct\",\n resolveTarget: ({ to }) => {\n if (!to) return { ok: false as const, error: new Error(\"[cored] --to is required\") };\n const target = parseTarget(to);\n if (!target) {\n return {\n ok: false as const,\n error: new Error(\n `Cored requires --to <user:ID|chat:ID>, got: ${JSON.stringify(to)}`,\n ),\n };\n }\n return { ok: true as const, to: `${target.kind}:${target.id}` };\n },\n },\n },\n});\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { CoredChannelConfig, CoredAccountConfig } from \"./types.js\";\n\nconst DEFAULTS = {\n enableEncryption: true,\n requestTimeout: 30_000,\n} as const;\n\nconst ENV_PREFIX = \"CORED_\";\n\nfunction getChannelConfig(cfg: unknown): CoredChannelConfig | undefined {\n const root = cfg as Record<string, unknown> | undefined;\n return root?.channels\n ? ((root.channels as Record<string, unknown>).cored as\n | CoredChannelConfig\n | undefined)\n : undefined;\n}\n\nfunction readEnvConfig(): Partial<CoredAccountConfig> {\n const env = process.env;\n const result: Partial<CoredAccountConfig> = {};\n\n if (env[`${ENV_PREFIX}APP_ID`]) result.appId = env[`${ENV_PREFIX}APP_ID`];\n if (env[`${ENV_PREFIX}APP_SECRET`])\n result.appSecret = env[`${ENV_PREFIX}APP_SECRET`];\n if (env[`${ENV_PREFIX}BACKEND_URL`])\n result.backendUrl = env[`${ENV_PREFIX}BACKEND_URL`];\n if (env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== undefined)\n result.enableEncryption =\n env[`${ENV_PREFIX}ENABLE_ENCRYPTION`] !== \"false\";\n if (env[`${ENV_PREFIX}REQUEST_TIMEOUT`])\n result.requestTimeout = Number(env[`${ENV_PREFIX}REQUEST_TIMEOUT`]);\n\n return result;\n}\n\nexport function listAccountIds(cfg: unknown): string[] {\n const ch = getChannelConfig(cfg);\n if (!ch) {\n // Check env vars as fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n }\n if (ch.accounts) return Object.keys(ch.accounts);\n if (ch.appId) return [\"default\"];\n // env var fallback\n if (process.env[`${ENV_PREFIX}APP_ID`]) return [\"default\"];\n return [];\n}\n\nexport function resolveAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const ch = getChannelConfig(cfg);\n const id = accountId ?? \"default\";\n const envConfig = readEnvConfig();\n\n const raw = ch?.accounts?.[id] ?? ch;\n\n return {\n accountId: id,\n appId: raw?.appId ?? envConfig.appId ?? \"\",\n appSecret: raw?.appSecret ?? envConfig.appSecret ?? \"\",\n backendUrl: raw?.backendUrl ?? envConfig.backendUrl ?? \"\",\n enabled: raw?.enabled ?? true,\n enableEncryption:\n raw?.enableEncryption ?? envConfig.enableEncryption ?? DEFAULTS.enableEncryption,\n requestTimeout:\n raw?.requestTimeout ?? envConfig.requestTimeout ?? DEFAULTS.requestTimeout,\n };\n}\n\nexport interface ConfigValidationError {\n field: string;\n message: string;\n}\n\nexport function validateAccountConfig(\n config: CoredAccountConfig,\n): ConfigValidationError[] {\n const errors: ConfigValidationError[] = [];\n\n if (!config.appId) {\n errors.push({\n field: \"appId\",\n message: `Account \"${config.accountId}\": appId is required. Set it in channels.cored.appId or CORED_APP_ID env var.`,\n });\n }\n\n if (!config.appSecret) {\n errors.push({\n field: \"appSecret\",\n message: `Account \"${config.accountId}\": appSecret is required. Set it in channels.cored.appSecret or CORED_APP_SECRET env var.`,\n });\n }\n\n if (!config.backendUrl) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl is required. Set it in channels.cored.backendUrl or CORED_BACKEND_URL env var.`,\n });\n } else if (\n !config.backendUrl.startsWith(\"http://\") &&\n !config.backendUrl.startsWith(\"https://\")\n ) {\n errors.push({\n field: \"backendUrl\",\n message: `Account \"${config.accountId}\": backendUrl must start with http:// or https:// (got \"${config.backendUrl}\").`,\n });\n }\n\n if (\n typeof config.requestTimeout !== \"number\" ||\n !Number.isFinite(config.requestTimeout) ||\n config.requestTimeout <= 0\n ) {\n errors.push({\n field: \"requestTimeout\",\n message: `Account \"${config.accountId}\": requestTimeout must be a positive number in milliseconds (got ${config.requestTimeout}).`,\n });\n }\n\n return errors;\n}\n\nexport function resolveAndValidateAccountConfig(\n cfg: unknown,\n accountId?: string,\n): CoredAccountConfig {\n const config = resolveAccountConfig(cfg, accountId);\n const errors = validateAccountConfig(config);\n\n if (errors.length > 0) {\n const messages = errors.map((e) => ` - ${e.message}`).join(\"\\n\");\n throw new Error(\n `[cored] Invalid config for account \"${config.accountId}\":\\n${messages}`,\n );\n }\n\n return config;\n}\n\nexport function listEnabledAccountConfigs(cfg: unknown): CoredAccountConfig[] {\n const ids = listAccountIds(cfg);\n return ids\n .map((id) => resolveAccountConfig(cfg, id))\n .filter((account) => account.enabled);\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cored client manager — wraps the @cored-im/sdk\n * and provides per-account lifecycle management.\n *\n * This module bridges the SDK's snake_case API with the plugin's camelCase\n * conventions and manages client instances by account ID.\n */\n\nimport { CoredClient, LoggerLevel, ApiError } from \"@cored-im/sdk\";\nimport type { Logger } from \"@cored-im/sdk\";\nimport type { CoredAccountConfig, ConnectionState, CoredMessageEvent } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Auth error detection\n// ---------------------------------------------------------------------------\n\n/** Cored auth failure error code (鉴权失败). */\nconst AUTH_ERROR_CODE = 40000006;\n\n/**\n * Check whether an error is a Cored auth/token failure.\n * Returns true for ApiError with code 40000006, which indicates\n * the token has expired or is otherwise invalid.\n */\nexport function isAuthError(err: unknown): boolean {\n return err instanceof ApiError && err.code === AUTH_ERROR_CODE;\n}\n\n// ---------------------------------------------------------------------------\n// Constants — the new SDK doesn't re-export message type enums from its\n// top-level barrel, so we define the constant locally. The value matches\n// the SDK's MessageType_TEXT = 'text' in message_enum.ts.\n// ---------------------------------------------------------------------------\n\nexport const MessageType_TEXT = \"text\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The SDK client instance returned by CoredClient.create().\n *\n * The new @cored-im/sdk uses:\n * - client.Im.v1.Message.sendMessage(req) — snake_case request fields\n * - client.Im.v1.Message.Event.onMessageReceive(handler) — sync, void return\n * - client.Im.v1.Chat.createTyping(req) — snake_case request fields\n * - client.preheat() / client.close() — camelCase lifecycle methods\n */\nexport type SdkClient = CoredClient;\n\n/**\n * Local send-message request shape matching the SDK's SendMessageReq.\n * Only text is used for now; add specific fields (image, card, etc.)\n * as outbound capabilities grow.\n */\nexport interface SendMessageReq {\n chat_id?: string;\n message_type?: string;\n message_content?: {\n text?: { content?: string };\n };\n reply_message_id?: string;\n}\n\n/**\n * Raw event shape from the SDK's onMessageReceive handler.\n *\n * The new SDK delivers typed events with shape:\n * { header: EventHeader, body: { message?: Message } }\n *\n * Message fields are snake_case. sender_id is a UserId object:\n * { user_id?, union_user_id?, open_user_id? }\n */\nexport interface SdkMessageEvent {\n header?: {\n event_id?: string;\n event_type?: string;\n event_created_at?: string;\n };\n body?: {\n message?: {\n message_id?: string;\n message_type?: string;\n message_status?: string;\n message_content?: unknown;\n message_created_at?: string | number;\n chat_id?: string;\n chat_seq_id?: string | number;\n sender_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n } | string;\n // These may appear in group chats\n chat_type?: string;\n mention_user_list?: Array<{\n user_id?: {\n user_id?: string;\n union_user_id?: string;\n open_user_id?: string;\n };\n user_name?: string;\n }>;\n };\n };\n}\n\n// ---------------------------------------------------------------------------\n// Client state\n// ---------------------------------------------------------------------------\n\nexport interface ManagedClient {\n client: SdkClient;\n config: CoredAccountConfig;\n /** The raw handler reference, needed for offMessageReceive. */\n eventHandler?: (event: SdkMessageEvent) => void;\n /** Diagnostic connection state. Updated on create/destroy. */\n connectionState: ConnectionState;\n}\n\nconst clients = new Map<string, ManagedClient>();\n\n// ---------------------------------------------------------------------------\n// Logger adapter — bridge plugin's simple log callback to SDK's Logger interface\n// ---------------------------------------------------------------------------\n\nfunction makeLoggerAdapter(\n log?: (msg: string, ctx?: Record<string, unknown>) => void,\n): Logger {\n const emit = (level: string) => (msg: string, ...args: unknown[]) => {\n log?.(`[${level}] ${msg}${args.length ? \" \" + JSON.stringify(args) : \"\"}`);\n };\n return {\n debug: emit(\"debug\"),\n info: emit(\"info\"),\n warn: emit(\"warn\"),\n error: emit(\"error\"),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Lifecycle\n// ---------------------------------------------------------------------------\n\nexport interface CreateClientOptions {\n config: CoredAccountConfig;\n onMessage?: (event: CoredMessageEvent, accountConfig: CoredAccountConfig) => void;\n log?: (msg: string, ctx?: Record<string, unknown>) => void;\n}\n\n/**\n * Create and connect a Cored SDK client for the given account.\n * Stores it in the client map for later retrieval.\n */\nexport async function createClient(opts: CreateClientOptions): Promise<ManagedClient> {\n const { config, onMessage, log } = opts;\n const accountId = config.accountId;\n\n // Tear down existing client for this account if any\n if (clients.has(accountId)) {\n await destroyClient(accountId);\n }\n\n const sdkClient = await CoredClient.create(\n config.backendUrl,\n config.appId,\n config.appSecret,\n {\n enableEncryption: config.enableEncryption,\n requestTimeout: config.requestTimeout,\n logger: log ? makeLoggerAdapter(log) : undefined,\n logLevel: log ? LoggerLevel.Debug : LoggerLevel.Info,\n },\n );\n\n // Preheat warms the token and verifies connectivity\n await sdkClient.preheat();\n\n const managed: ManagedClient = { client: sdkClient, config, connectionState: \"connected\" };\n\n // Subscribe to incoming messages if handler provided\n if (onMessage) {\n const handler = (sdkEvent: SdkMessageEvent) => {\n const normalized = normalizeSdkEvent(sdkEvent);\n if (normalized) {\n onMessage(normalized, config);\n }\n };\n\n // New SDK: onMessageReceive is synchronous, returns void.\n // Store handler reference for offMessageReceive on teardown.\n sdkClient.Im.v1.Message.Event.onMessageReceive(\n handler as Parameters<typeof sdkClient.Im.v1.Message.Event.onMessageReceive>[0],\n );\n managed.eventHandler = handler;\n }\n\n clients.set(accountId, managed);\n return managed;\n}\n\n/**\n * Destroy and disconnect a client by account ID.\n */\nexport async function destroyClient(accountId: string): Promise<void> {\n const managed = clients.get(accountId);\n if (!managed) return;\n\n managed.connectionState = \"disconnecting\";\n\n // Unsubscribe from events using offMessageReceive\n if (managed.eventHandler) {\n managed.client.Im.v1.Message.Event.offMessageReceive(\n managed.eventHandler as Parameters<typeof managed.client.Im.v1.Message.Event.offMessageReceive>[0],\n );\n }\n try {\n await managed.client.close();\n } catch {\n // Best-effort close\n }\n clients.delete(accountId);\n}\n\n/**\n * Destroy all managed clients.\n */\nexport async function destroyAllClients(): Promise<void> {\n const ids = [...clients.keys()];\n await Promise.allSettled(ids.map((id) => destroyClient(id)));\n}\n\n/**\n * Get a managed client by account ID. Falls back to the first available client.\n */\nexport function getClient(accountId?: string): ManagedClient | undefined {\n if (accountId && clients.has(accountId)) return clients.get(accountId);\n if (clients.size > 0) return clients.values().next().value as ManagedClient;\n return undefined;\n}\n\n/**\n * Number of currently connected clients.\n */\nexport function clientCount(): number {\n return clients.size;\n}\n\n/**\n * Get diagnostic state for all managed clients.\n */\nexport function getClientStates(): Array<{ accountId: string; connectionState: ConnectionState }> {\n return [...clients.entries()].map(([id, m]) => ({\n accountId: id,\n connectionState: m.connectionState,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Event normalization — SDK snake_case event -> plugin camelCase\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a snake_case SDK event to the plugin's CoredMessageEvent format.\n * Returns null if the event is malformed.\n *\n * The new SDK delivers events with lowercase field names:\n * { header, body: { message: { message_id, sender_id: UserId, ... } } }\n *\n * sender_id is now a UserId object { user_id, union_user_id, open_user_id }\n * in the new SDK, but we keep backward compat with string for safety.\n */\nexport function normalizeSdkEvent(sdk: SdkMessageEvent): CoredMessageEvent | null {\n const msg = sdk.body?.message;\n if (!msg) return null;\n\n // Extract user ID from sender_id — may be UserId object or legacy string\n const senderId = msg.sender_id;\n let userId = \"\";\n if (typeof senderId === \"string\") {\n userId = senderId;\n } else if (senderId && typeof senderId === \"object\") {\n userId =\n senderId.user_id ??\n senderId.open_user_id ??\n senderId.union_user_id ??\n \"\";\n }\n\n // Extract mention user IDs from the new SDK's text mention format\n const mentionUsers = (msg.mention_user_list ?? []).map((u) => ({\n userId: u.user_id?.user_id ?? u.user_id?.open_user_id ?? u.user_id?.union_user_id ?? \"\",\n }));\n\n // message_created_at may be Int64 (string) in the new SDK\n const createdAt =\n typeof msg.message_created_at === \"string\"\n ? parseInt(msg.message_created_at, 10) || Date.now()\n : msg.message_created_at ?? Date.now();\n\n return {\n message: {\n messageId: msg.message_id ?? \"\",\n messageType: msg.message_type ?? \"\",\n messageContent: msg.message_content,\n chatId: msg.chat_id ?? \"\",\n chatType: msg.chat_type ?? \"direct\",\n sender: {\n userId,\n },\n createdAt,\n mentionUserList: mentionUsers,\n },\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Outbound message delivery — send text/typing/read to Cored chats.\n *\n * Pipeline: normalize target -> validate account/client -> send -> map errors\n *\n * Text-only for now; media/card/file delivery is follow-up (task 08).\n */\n\nimport {\n getClient,\n isAuthError,\n MessageType_TEXT,\n type ManagedClient,\n type SendMessageReq,\n} from \"../core/cored-client.js\";\n\n// ---------------------------------------------------------------------------\n// Send text\n// ---------------------------------------------------------------------------\n\nexport interface SendTextResult {\n ok: boolean;\n messageId?: string;\n error?: Error;\n provider?: string;\n}\n\n/**\n * Send a text message to a Cored chat.\n *\n * On auth error (code 40000006), forces a token refresh via preheat() and\n * retries once. This handles the SDK's token-refresh edge case without\n * requiring a gateway restart.\n */\nexport async function sendText(\n chatId: string,\n text: string,\n accountId?: string,\n replyMessageId?: string,\n logWarn?: (msg: string) => void,\n): Promise<SendTextResult> {\n const managed = getClient(accountId);\n if (!managed) {\n return {\n ok: false,\n error: new Error(\n `[cored] no connected client for account=${accountId ?? \"default\"}`,\n ),\n };\n }\n\n const req: SendMessageReq = {\n chat_id: chatId,\n message_type: MessageType_TEXT,\n message_content: {\n text: { content: text },\n },\n reply_message_id: replyMessageId,\n };\n\n try {\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (err) {\n if (!isAuthError(err)) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed for chat=${chatId}: ${err instanceof Error ? err.message : String(err)}`,\n ),\n };\n }\n\n // Auth token expired despite SDK auto-refresh — force refresh and retry once.\n // This avoids requiring a gateway restart when the SDK's background token\n // refresh hits an edge case (time sync drift, swallowed fetch failure, etc.).\n logWarn?.(\n `[cored] auth error on send (chat=${chatId}, account=${accountId ?? \"default\"}) — refreshing token and retrying`,\n );\n\n try {\n await managed.client.preheat();\n const resp = await managed.client.Im.v1.Message.sendMessage(req);\n logWarn?.(\n `[cored] auth retry succeeded (chat=${chatId}, account=${accountId ?? \"default\"})`,\n );\n return { ok: true, messageId: resp.message_id, provider: \"cored\" };\n } catch (retryErr) {\n return {\n ok: false,\n error: new Error(\n `[cored] send failed after auth retry for chat=${chatId}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,\n ),\n };\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Typing indicator\n// ---------------------------------------------------------------------------\n\n/**\n * Set typing indicator in a chat. Cored typing lasts ~5s.\n */\nexport async function setTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.createTyping({ chat_id: chatId });\n } catch {\n // Typing is best-effort — don't fail the message flow\n }\n}\n\n/**\n * Clear typing indicator.\n */\nexport async function clearTyping(\n chatId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Chat.deleteTyping({ chat_id: chatId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Read receipt\n// ---------------------------------------------------------------------------\n\n/**\n * Mark a message as read.\n */\nexport async function readMessage(\n messageId: string,\n accountId?: string,\n): Promise<void> {\n const managed = getClient(accountId);\n if (!managed) return;\n try {\n await managed.client.Im.v1.Message.readMessage({ message_id: messageId });\n } catch {\n // Best-effort\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery callback for inbound dispatch\n// ---------------------------------------------------------------------------\n\n/**\n * Create a deliver function scoped to an account, suitable for passing\n * to processInboundMessage's InboundDispatchOptions.\n */\nexport function makeDeliver(\n accountId?: string,\n logWarn?: (msg: string) => void,\n): (chatId: string, text: string) => Promise<void> {\n return async (chatId: string, text: string) => {\n const result = await sendText(chatId, text, accountId, undefined, logWarn);\n if (!result.ok) {\n throw result.error ?? new Error(\"[cored] send failed\");\n }\n };\n}\n","// Copyright (c) 2026 Cored Limited\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Target parsing & validation for the --to argument.\n *\n * Accepted formats:\n * \"chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" }\n * \"user:83870344313569283\" -> { kind: \"user\", id: \"83870344313569283\" }\n * \"cored:chat:oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (prefix stripped)\n * \"oc_abc123\" -> { kind: \"chat\", id: \"oc_abc123\" } (bare ID defaults to chat)\n *\n * Returns null for empty, whitespace-only, or malformed targets (e.g. \"user:\" with no ID).\n */\n\nimport type { ParsedTarget } from \"./types.js\";\n\n/**\n * Parse a raw --to string into a structured target.\n * Returns null if the input is missing, empty, or has no usable ID.\n */\nexport function parseTarget(to?: string): ParsedTarget | null {\n const raw = String(to ?? \"\").trim();\n if (!raw) return null;\n\n // Strip optional \"cored:\" channel prefix (case-insensitive)\n const stripped = raw.replace(/^cored:/i, \"\");\n\n if (stripped.startsWith(\"user:\")) {\n const id = stripped.slice(\"user:\".length).trim();\n return id ? { kind: \"user\", id } : null;\n }\n\n if (stripped.startsWith(\"chat:\")) {\n const id = stripped.slice(\"chat:\".length).trim();\n return id ? { kind: \"chat\", id } : null;\n }\n\n // Bare ID — default to chat (Cored SDK uses ChatId for sending)\n return stripped ? { kind: \"chat\", id: stripped } : null;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cored-im/openclaw-plugin",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Cored IM channel plugin for OpenClaw",
5
5
  "author": "Cored Limited",
6
6
  "type": "module",
package/src/channel.ts CHANGED
@@ -47,7 +47,7 @@ export const base = createChannelPluginBase<CoredAccountConfig>({
47
47
  setup: {
48
48
  validateInput: ({ input }) => {
49
49
  const missing: string[] = [];
50
- if (!input.appToken) missing.push("--appToken (App ID)");
50
+ if (!input.appToken) missing.push("--app-token (App ID)");
51
51
  if (!input.token) missing.push("--token (App Secret)");
52
52
  if (!input.url) missing.push("--url (Backend URL)");
53
53
  if (missing.length > 0) {
@@ -55,7 +55,7 @@ export const base = createChannelPluginBase<CoredAccountConfig>({
55
55
  `Missing required flags: ${missing.join(", ")}`,
56
56
  "",
57
57
  "Either provide all flags:",
58
- ` openclaw channels add --channel cored --appToken <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
58
+ ` openclaw channels add --channel cored --app-token <APP_ID> --token <APP_SECRET> --url <BACKEND_URL>`,
59
59
  "",
60
60
  "Or use the interactive wizard:",
61
61
  " openclaw channels add",