Merge remote-tracking branch 'origin/main' into feat/add_qwen_official_api
# Conflicts: # .agents/skills/parallels-discord-roundtrip/SKILL.md # src/commands/auth-choice.apply.api-key-providers.ts
This commit is contained in:
commit
b532ae75f9
134
CHANGELOG.md
134
CHANGELOG.md
@ -6,26 +6,27 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Android/mobile: add a system-aware dark theme across onboarding and post-onboarding screens so the app follows the device theme through setup, chat, and voice flows. (#46249) Thanks @sibbl.
|
||||
- Commands/btw: add `/btw` side questions for quick tool-less answers about the current session without changing future session context, with dismissible in-session TUI answers and explicit BTW replies on external channels. (#45444) Thanks @ngutman.
|
||||
- Gateway/health monitor: add configurable stale-event thresholds and restart limits, plus per-channel and per-account `healthMonitor.enabled` overrides, while keeping the existing global disable path on `gateway.channelHealthCheckMinutes=0`. (#42107) Thanks @rstar327.
|
||||
- Feishu/cards: add identity-aware structured card headers and note footers for Feishu replies and direct sends, while keeping that presentation wired through the shared outbound identity path. (#29938) Thanks @nszhsl.
|
||||
- Feishu/streaming: add `onReasoningStream` and `onReasoningEnd` support to streaming cards, so `/reasoning stream` renders thinking tokens as markdown blockquotes in the same card — matching the Telegram channel's reasoning lane behavior. (#46029)
|
||||
- Web tools/Firecrawl: add Firecrawl as an `onboard`/configure search provider via a bundled plugin, expose explicit `firecrawl_search` and `firecrawl_scrape` tools, and align core `web_fetch` fallback behavior with Firecrawl base-URL/env fallback plus guarded endpoint fetches.
|
||||
- Refactor/channels: remove the legacy channel shim directories and point channel-specific imports directly at the extension-owned implementations. (#45967) thanks @scoootscooob.
|
||||
- Android/nodes: add `callLog.search` plus shared Call Log permission wiring so Android nodes can search recent call history through the gateway. (#44073) Thanks @lxk7280.
|
||||
- Docs/Zalo: clarify the Marketplace-bot support matrix and config guidance so the Zalo channel docs match current Bot Creator behavior more closely. (#47552) Thanks @No898.
|
||||
- Install/update: allow package-manager installs from GitHub `main` via `openclaw update --tag main`, installer `--version main`, or direct npm/pnpm git specs.
|
||||
- Plugins/providers: move OpenRouter, GitHub Copilot, and OpenAI Codex provider/runtime logic into bundled plugins, including dynamic model fallback, runtime auth exchange, stream wrappers, capability hints, and cache-TTL policy.
|
||||
- Plugins/MiniMax: merge the bundled MiniMax API and MiniMax OAuth plugin surfaces into a single default-on `minimax` plugin, while keeping legacy `minimax-portal-auth` config ids aliased for compatibility.
|
||||
- Plugins/bundles: add compatible Codex, Claude, and Cursor bundle discovery/install support, map bundle skills into OpenClaw skills, and apply Claude bundle `settings.json` defaults to embedded Pi with shell overrides sanitized.
|
||||
- Plugins/agent integrations: broaden the plugin surface for app-server integrations with channel-aware commands, interactive callbacks, inbound claims, and Discord/Telegram conversation binding support. (#45318) Thanks @huntharo and @vincentkoc.
|
||||
- Telegram/actions: add `topic-edit` for forum-topic renames and icon updates while sharing the same Telegram topic-edit transport used by the plugin runtime. (#47798) Thanks @obviyus.
|
||||
- secrets: harden read-only SecretRef command paths and diagnostics. (#47794) Thanks @joshavant.
|
||||
- Sandbox/runtime: add pluggable sandbox backends, ship an OpenShell backend with `mirror` and `remote` workspace modes, and make sandbox list/recreate/prune backend-aware instead of Docker-only.
|
||||
- Sandbox/SSH: add a core SSH sandbox backend with secret-backed key, certificate, and known_hosts inputs, move shared remote exec/filesystem tooling into core, and keep OpenShell focused on sandbox lifecycle plus optional `mirror` mode.
|
||||
- Feishu/cards: add structured interactive approval and quick-action launcher cards, preserve callback user and conversation context through routing, and keep legacy card-action fallback behavior so common actions can run without typing raw commands. (#47873)
|
||||
- Web tools/Firecrawl: add Firecrawl as an `onboard`/configure search provider via a bundled plugin, expose explicit `firecrawl_search` and `firecrawl_scrape` tools, and align core `web_fetch` fallback behavior with Firecrawl base-URL/env fallback plus guarded endpoint fetches.
|
||||
- Plugins/bundles: add compatible Codex, Claude, and Cursor bundle discovery/install support, map bundle skills into OpenClaw skills, and apply Claude bundle `settings.json` defaults to embedded Pi with shell overrides sanitized.
|
||||
- Plugins/providers: move OpenRouter, GitHub Copilot, and OpenAI Codex provider/runtime logic into bundled plugins, including dynamic model fallback, runtime auth exchange, stream wrappers, capability hints, and cache-TTL policy.
|
||||
- Plugins/agent integrations: broaden the plugin surface for app-server integrations with channel-aware commands, interactive callbacks, inbound claims, and Discord/Telegram conversation binding support. (#45318) Thanks @huntharo and @vincentkoc.
|
||||
- Install/update: allow package-manager installs from GitHub `main` via `openclaw update --tag main`, installer `--version main`, or direct npm/pnpm git specs.
|
||||
- Gateway/health monitor: add configurable stale-event thresholds and restart limits, plus per-channel and per-account `healthMonitor.enabled` overrides, while keeping the existing global disable path on `gateway.channelHealthCheckMinutes=0`. (#42107) Thanks @rstar327.
|
||||
- Android/mobile: add a system-aware dark theme across onboarding and post-onboarding screens so the app follows the device theme through setup, chat, and voice flows. (#46249) Thanks @sibbl.
|
||||
- Feishu/ACP: add current-conversation ACP and subagent session binding for supported DMs and topic conversations, including completion delivery back to the originating Feishu conversation. (#46819)
|
||||
- Plugins/marketplaces: add Claude marketplace registry resolution, `plugin@marketplace` installs, marketplace listing, and update support, plus Docker E2E coverage for local and official marketplace flows. Thanks @vincentkoc.
|
||||
- Feishu/cards: add structured interactive approval and quick-action launcher cards, preserve callback user and conversation context through routing, and keep legacy card-action fallback behavior so common actions can run without typing raw commands. (#47873)
|
||||
- Feishu/streaming: add `onReasoningStream` and `onReasoningEnd` support to streaming cards, so `/reasoning stream` renders thinking tokens as markdown blockquotes in the same card — matching the Telegram channel's reasoning lane behavior. (#46029)
|
||||
- Feishu/cards: add identity-aware structured card headers and note footers for Feishu replies and direct sends, while keeping that presentation wired through the shared outbound identity path. (#29938) Thanks @nszhsl.
|
||||
- Android/nodes: add `callLog.search` plus shared Call Log permission wiring so Android nodes can search recent call history through the gateway. (#44073) Thanks @lxk7280.
|
||||
- Plugins/MiniMax: merge the bundled MiniMax API and MiniMax OAuth plugin surfaces into a single default-on `minimax` plugin, while keeping legacy `minimax-portal-auth` config ids aliased for compatibility.
|
||||
- Telegram/actions: add `topic-edit` for forum-topic renames and icon updates while sharing the same Telegram topic-edit transport used by the plugin runtime. (#47798) Thanks @obviyus.
|
||||
- Refactor/channels: remove the legacy channel shim directories and point channel-specific imports directly at the extension-owned implementations. (#45967) thanks @scoootscooob.
|
||||
- Docs/Zalo: clarify the Marketplace-bot support matrix and config guidance so the Zalo channel docs match current Bot Creator behavior more closely. (#47552) Thanks @No898.
|
||||
- secrets: harden read-only SecretRef command paths and diagnostics. (#47794) Thanks @joshavant.
|
||||
|
||||
### Breaking
|
||||
|
||||
@ -33,65 +34,64 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Group mention gating: reject invalid and unsafe nested-repetition `mentionPatterns`, reuse the shared safe config-regex compiler across mention stripping and detection, and cache strip-time regex compilation so noisy groups avoid repeated recompiles.
|
||||
- Control UI/chat sessions: show human-readable labels in the grouped session dropdown again, keep unique scoped fallbacks when metadata is missing, and disambiguate duplicate labels only when needed. (#45130) thanks @luzhidong.
|
||||
- Control UI: scope persisted session selection per gateway, prevent stale session bleed across tokenized gateway opens, and cap stored gateway session history. (#47453) Thanks @sallyom.
|
||||
- Slack/interactive replies: preserve `channelData.slack.blocks` through live DM delivery and preview-finalized edits so Block Kit button and select directives render instead of falling back to raw text. (#45890) Thanks @vincentkoc.
|
||||
- Feishu/topic threads: fetch full thread context, including prior bot replies, when starting a topic-thread session so follow-up turns in Feishu topics keep the right conversation state. (#45254) Thanks @Coobiw.
|
||||
- Feishu/media: keep native image, file, audio, and video/media handling aligned across outbound sends, inbound downloads, thread replies, directory/action aliases, and capability docs so unsupported areas are explicit instead of implied.
|
||||
- Feishu/actions: expand the runtime action surface with message read/edit, explicit thread replies, pinning, and operator-facing chat/member inspection so Feishu can operate more of the workspace directly.
|
||||
- Configure/startup: move outbound send-deps resolution into a lightweight helper so `openclaw configure` no longer stalls after the banner while eagerly loading channel plugins. (#46301) thanks @scoootscooob.
|
||||
- Control UI/dashboard: preserve structured gateway shutdown reasons across restart disconnects so config-triggered restarts no longer fall back to `disconnected (1006): no reason`. (#46532) Thanks @vincentkoc.
|
||||
- Android/chat: theme the thinking dropdown and TLS trust dialogs explicitly so popup surfaces match the active app theme instead of falling back to mismatched Material defaults.
|
||||
- Z.AI/onboarding: detect a working default model even for explicit `zai-coding-*` endpoint choices, so Coding Plan setup can keep the selected endpoint while defaulting to `glm-5` when available or `glm-4.7` as fallback. (#45969)
|
||||
- Models/OpenRouter runtime capabilities: fetch uncatalogued OpenRouter model metadata on first use so newly added vision models keep image input instead of silently degrading to text-only, with top-level capability field fallbacks for `/api/v1/models`. (#45824) Thanks @DJjjjhao.
|
||||
- Z.AI/onboarding: add `glm-5-turbo` to the default Z.AI provider catalog so onboarding-generated configs expose the new model alongside the existing GLM defaults. (#46670) Thanks @tomsun28.
|
||||
- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#40146)
|
||||
- Channels/plugins: keep shared interactive payloads merge-ready by fixing Slack custom callback routing and repeat-click dedupe, allowing interactive-only sends, and preserving ordered Discord shared text blocks. (#47715) Thanks @vincentkoc.
|
||||
- Google auth/Node 25: patch `gaxios` to use native fetch without injecting `globalThis.window`, while translating proxy and mTLS transport settings so Google Vertex and Google Chat auth keep working on Node 25. (#47914) Thanks @pdd-cli.
|
||||
- Gateway/startup: load bundled channel plugins from compiled `dist/extensions` entries in built installs, so gateway boot no longer recompiles bundled extension TypeScript on every startup and WhatsApp-class cold starts drop back to seconds instead of tens of seconds or worse.
|
||||
- Plugins/context engines: enforce owner-aware context-engine registration on both loader and public SDK paths so plugins cannot spoof privileged ownership, claim the core `legacy` engine id, or overwrite an existing engine id through direct SDK imports. (#47595) Thanks @vincentkoc.
|
||||
- Browser/remote CDP: honor strict browser SSRF policy during remote CDP reachability and `/json/version` discovery checks, redact sensitive `cdpUrl` tokens from status output, and warn when remote CDP targets private/internal hosts.
|
||||
- Plugins/install precedence: keep bundled plugins ahead of auto-discovered globals by default, but let an explicitly installed plugin record win its own duplicate-id tie so installed channel plugins load from `~/.openclaw/extensions` after `openclaw plugins install`.
|
||||
- ACP/acpx: resolve the bundled plugin root from the actual plugin directory so plugin-local installs stay under `dist/extensions/acpx` instead of escaping to `dist/extensions` and failing runtime setup.
|
||||
- Gateway/auth: ignore spoofed loopback hops in trusted forwarding chains and block device approvals that request scopes above the caller session. Thanks @vincentkoc.
|
||||
- Gateway/config views: strip embedded credentials from URL-based endpoint fields before returning read-only account and config snapshots. Thanks @vincentkoc.
|
||||
- Tools/apply-patch: revalidate workspace-only delete and directory targets immediately before mutating host paths. Thanks @vincentkoc.
|
||||
- Webhooks/runtime: move auth earlier and tighten pre-auth body limits and timeouts across bundled webhook handlers, including slow-body handling for Mattermost slash commands. Thanks @vincentkoc.
|
||||
- Gateway/plugins: pin runtime webhook routes to the gateway startup registry so channel webhooks keep working across plugin-registry churn, and make plugin auth + dispatch resolve routes from the same live HTTP-route registry. Fixes #46924 and #47041.
|
||||
- Subagents/follow-ups: require the same controller ownership checks for `/subagents send` as other control actions, so leaf sessions cannot message nested child runs they do not control. Thanks @vincentkoc.
|
||||
- Inbound policy hardening: tighten callback and webhook sender checks across Mattermost and Google Chat, match Nextcloud Talk rooms by stable room token, and treat explicit empty Twitch allowlists as deny-all. (#46787) Thanks @zpbrent, @ijxpwastaken and @vincentkoc.
|
||||
- macOS/canvas actions: keep unattended local agent actions on trusted in-app canvas surfaces only, and stop exposing the deep-link fallback key to arbitrary page scripts. (#46790) Thanks @vincentkoc.
|
||||
- Agents/compaction: extend the enclosing run deadline once while compaction is actively in flight, and abort the underlying SDK compaction on timeout/cancel so large-session compactions stop freezing mid-run. (#46889) Thanks @asyncjason.
|
||||
- Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition `strict` fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava.
|
||||
- WhatsApp/reconnect: restore the append recency filter in the extension inbox monitor and handle protobuf `Long` timestamps correctly, so fresh post-reconnect append messages are processed while stale history sync stays suppressed. (#42588) thanks @MonkeyLeeT.
|
||||
- WhatsApp/login: wait for pending creds writes before reopening after Baileys `515` pairing restarts in both QR login and `channels login` flows, and keep the restart coverage pinned to the real wrapped error shape plus per-account creds queues. (#27910) Thanks @asyncjason.
|
||||
- Agents/openai-compatible tool calls: deduplicate repeated tool call ids across live assistant messages and replayed history so OpenAI-compatible backends no longer reject duplicate `tool_call_id` values with HTTP 400. (#40996) Thanks @xaeon2026.
|
||||
- Security/device pairing: harden `device.token.rotate` deny handling by keeping public failures generic while logging internal deny reasons and preserving approved-baseline enforcement. (`GHSA-7jrw-x62h-64p8`)
|
||||
- Slack/interactive replies: preserve `channelData.slack.blocks` through live DM delivery and preview-finalized edits so Block Kit button and select directives render instead of falling back to raw text. (#45890) Thanks @vincentkoc.
|
||||
- Gateway/auth: ignore spoofed loopback hops in trusted forwarding chains and block device approvals that request scopes above the caller session. Thanks @vincentkoc.
|
||||
- Gateway/restart: defer externally signaled unmanaged restarts through the in-process idle drain, and preserve the restored subagent run as remap fallback during orphan recovery so resumed sessions do not duplicate work. (#47719) Thanks @joeykrug.
|
||||
- Zalo/plugin runtime: export `resolveClientIp` from `openclaw/plugin-sdk/zalo` so installed builds no longer crash on startup when the webhook monitor loads from the packaged extension instead of the monorepo source tree. (#46549) Thanks @No898.
|
||||
- CI/channel test routing: move the built-in channel suites into `test:channels` and keep them out of `test:extensions`, so extension CI no longer fails after the channel migration while targeted test routing still sends Slack, Signal, and iMessage suites to the right lane. (#46066) Thanks @scoootscooob.
|
||||
- Browser/profiles: drop the auto-created `chrome-relay` browser profile; users who need the Chrome extension relay must now create their own profile via `openclaw browser create-profile`. (#45777) Thanks @odysseus0.
|
||||
- Docs/Mintlify: fix MDX marker syntax on Perplexity, Model Providers, Moonshot, and exec approvals pages so local docs preview no longer breaks rendering or leaves stale pages unpublished. (#46695) Thanks @velvet-shark.
|
||||
- Email/webhook wrapping: sanitize sender and subject metadata before external-content wrapping so metadata fields cannot break the wrapper structure. Thanks @vincentkoc.
|
||||
- Node/startup: remove leftover debug `console.log("node host PATH: ...")` that printed the resolved PATH on every `openclaw node run` invocation. (#46411)
|
||||
- Nodes/pending actions: re-check queued foreground actions against the current node command policy before returning them to the node. (#46815) Thanks @zpbrent and @vincentkoc.
|
||||
- ACP/approvals: use canonical tool identity for prompting decisions and fail closed when conflicting tool identity hints are present. (#46817) Thanks @zpbrent and @vincentkoc.
|
||||
- Telegram/message send: forward `--force-document` through the `sendPayload` path as well as `sendMedia`, so Telegram payload sends with `channelData` keep uploading images as documents instead of silently falling back to compressed photo sends. (#47119) Thanks @thepagent.
|
||||
- Telegram/message chunking: preserve spaces, paragraph separators, and word boundaries when HTML overflow rechunking splits formatted replies. (#47274)
|
||||
- Plugins/scoped ids: preserve scoped plugin ids during install and config keying, and keep bundled plugins ahead of discovered duplicate ids by default so `@scope/name` plugins no longer collide with unscoped installs. Thanks @vincentkoc.
|
||||
- CLI: avoid loading provider discovery during startup model normalization. (#46522) Thanks @ItsAditya-xyz and @vincentkoc.
|
||||
- Tlon: honor explicit empty allowlists and defer cite expansion. (#46788) Thanks @zpbrent and @vincentkoc.
|
||||
- ACP: require admin scope for mutating internal actions. (#46789) Thanks @tdjackey and @vincentkoc.
|
||||
- Gateway/config validation: stop treating the implicit default memory slot as a required explicit plugin config, so startup no longer fails with `plugins.slots.memory: plugin not found: memory-core` when `memory-core` was only inferred. (#47494) Thanks @ngutman.
|
||||
- Control UI/session routing: preserve established external delivery routes when webchat views or sends in externally originated sessions, so subagent completions still return to the original channel instead of the dashboard. (#47797) Thanks @brokemac79.
|
||||
- Configure/startup: move outbound send-deps resolution into a lightweight helper so `openclaw configure` no longer stalls after the banner while eagerly loading channel plugins. (#46301) thanks @scoootscooob.
|
||||
- CLI/startup: lazy-load channel add and root help startup paths to trim avoidable RSS and help latency on constrained hosts. (#46784) Thanks @vincentkoc.
|
||||
- CLI/onboarding: import static provider definitions directly for onboarding model/config helpers so those paths no longer pull provider discovery just for built-in defaults. (#47467) Thanks @vincentkoc.
|
||||
- CLI/auth choice: lazy-load plugin/provider fallback resolution so mapped auth choices stay on the static path and only unknown choices pay the heavy provider load. (#47495) Thanks @vincentkoc.
|
||||
- CLI/completion: reduce recursive completion-script string churn and fix nested PowerShell command-path matching so generated nested completions resolve on PowerShell too. (#45537) Thanks @yiShanXin and @vincentkoc.
|
||||
- Gateway/startup: load bundled channel plugins from compiled `dist/extensions` entries in built installs, so gateway boot no longer recompiles bundled extension TypeScript on every startup and WhatsApp-class cold starts drop back to seconds instead of tens of seconds or worse.
|
||||
- CLI: avoid loading provider discovery during startup model normalization. (#46522) Thanks @ItsAditya-xyz and @vincentkoc.
|
||||
- Security/device pairing: harden `device.token.rotate` deny handling by keeping public failures generic while logging internal deny reasons and preserving approved-baseline enforcement. (`GHSA-7jrw-x62h-64p8`)
|
||||
- Inbound policy hardening: tighten callback and webhook sender checks across Mattermost and Google Chat, match Nextcloud Talk rooms by stable room token, and treat explicit empty Twitch allowlists as deny-all. (#46787) Thanks @zpbrent, @ijxpwastaken and @vincentkoc.
|
||||
- Webhooks/runtime: move auth earlier and tighten pre-auth body limits and timeouts across bundled webhook handlers, including slow-body handling for Mattermost slash commands. Thanks @vincentkoc.
|
||||
- Email/webhook wrapping: sanitize sender and subject metadata before external-content wrapping so metadata fields cannot break the wrapper structure. Thanks @vincentkoc.
|
||||
- Tools/apply-patch: revalidate workspace-only delete and directory targets immediately before mutating host paths. Thanks @vincentkoc.
|
||||
- Gateway/config views: strip embedded credentials from URL-based endpoint fields before returning read-only account and config snapshots. Thanks @vincentkoc.
|
||||
- ACP/approvals: use canonical tool identity for prompting decisions and fail closed when conflicting tool identity hints are present. (#46817) Thanks @zpbrent and @vincentkoc.
|
||||
- ACP: require admin scope for mutating internal actions. (#46789) Thanks @tdjackey and @vincentkoc.
|
||||
- Subagents/follow-ups: require the same controller ownership checks for `/subagents send` as other control actions, so leaf sessions cannot message nested child runs they do not control. Thanks @vincentkoc.
|
||||
- macOS/canvas actions: keep unattended local agent actions on trusted in-app canvas surfaces only, and stop exposing the deep-link fallback key to arbitrary page scripts. (#46790) Thanks @vincentkoc.
|
||||
- Agents/compaction: extend the enclosing run deadline once while compaction is actively in flight, and abort the underlying SDK compaction on timeout/cancel so large-session compactions stop freezing mid-run. (#46889) Thanks @asyncjason.
|
||||
- Agents/openai-compatible tool calls: deduplicate repeated tool call ids across live assistant messages and replayed history so OpenAI-compatible backends no longer reject duplicate `tool_call_id` values with HTTP 400. (#40996) Thanks @xaeon2026.
|
||||
- Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition `strict` fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava.
|
||||
- Models/OpenRouter runtime capabilities: fetch uncatalogued OpenRouter model metadata on first use so newly added vision models keep image input instead of silently degrading to text-only, with top-level capability field fallbacks for `/api/v1/models`. (#45824) Thanks @DJjjjhao.
|
||||
- Channels/plugins: keep shared interactive payloads merge-ready by fixing Slack custom callback routing and repeat-click dedupe, allowing interactive-only sends, and preserving ordered Discord shared text blocks. (#47715) Thanks @vincentkoc.
|
||||
- Slack/interactive replies: preserve `channelData.slack.blocks` through live DM delivery and preview-finalized edits so Block Kit button and select directives render instead of falling back to raw text. (#45890) Thanks @vincentkoc.
|
||||
- Slack/interactive replies: preserve `channelData.slack.blocks` through live DM delivery and preview-finalized edits so Block Kit button and select directives render instead of falling back to raw text. (#45890) Thanks @vincentkoc.
|
||||
- Feishu/actions: expand the runtime action surface with message read/edit, explicit thread replies, pinning, and operator-facing chat/member inspection so Feishu can operate more of the workspace directly.
|
||||
- Feishu/topic threads: fetch full thread context, including prior bot replies, when starting a topic-thread session so follow-up turns in Feishu topics keep the right conversation state. (#45254) Thanks @Coobiw.
|
||||
- Feishu/media: keep native image, file, audio, and video/media handling aligned across outbound sends, inbound downloads, thread replies, directory/action aliases, and capability docs so unsupported areas are explicit instead of implied.
|
||||
- WhatsApp/reconnect: restore the append recency filter in the extension inbox monitor and handle protobuf `Long` timestamps correctly, so fresh post-reconnect append messages are processed while stale history sync stays suppressed. (#42588) thanks @MonkeyLeeT.
|
||||
- WhatsApp/login: wait for pending creds writes before reopening after Baileys `515` pairing restarts in both QR login and `channels login` flows, and keep the restart coverage pinned to the real wrapped error shape plus per-account creds queues. (#27910) Thanks @asyncjason.
|
||||
- Telegram/message send: forward `--force-document` through the `sendPayload` path as well as `sendMedia`, so Telegram payload sends with `channelData` keep uploading images as documents instead of silently falling back to compressed photo sends. (#47119) Thanks @thepagent.
|
||||
- Telegram/message chunking: preserve spaces, paragraph separators, and word boundaries when HTML overflow rechunking splits formatted replies. (#47274)
|
||||
- Z.AI/onboarding: detect a working default model even for explicit `zai-coding-*` endpoint choices, so Coding Plan setup can keep the selected endpoint while defaulting to `glm-5` when available or `glm-4.7` as fallback. (#45969)
|
||||
- Z.AI/onboarding: add `glm-5-turbo` to the default Z.AI provider catalog so onboarding-generated configs expose the new model alongside the existing GLM defaults. (#46670) Thanks @tomsun28.
|
||||
- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#40146)
|
||||
- Zalo/plugin runtime: export `resolveClientIp` from `openclaw/plugin-sdk/zalo` so installed builds no longer crash on startup when the webhook monitor loads from the packaged extension instead of the monorepo source tree. (#46549) Thanks @No898.
|
||||
- Plugins/install precedence: keep bundled plugins ahead of auto-discovered globals by default, but let an explicitly installed plugin record win its own duplicate-id tie so installed channel plugins load from `~/.openclaw/extensions` after `openclaw plugins install`.
|
||||
- Plugins/scoped ids: preserve scoped plugin ids during install and config keying, and keep bundled plugins ahead of discovered duplicate ids by default so `@scope/name` plugins no longer collide with unscoped installs. Thanks @vincentkoc.
|
||||
- Gateway/watch mode: restart on bundled-plugin package and manifest metadata changes, rebuild `dist` for extension source and `tsdown.config.ts` changes, and still ignore extension docs. (#47571) thanks @gumadeiras.
|
||||
- Gateway/watch mode: recreate bundled plugin runtime metadata after clean or stale `dist` states, so `pnpm gateway:watch` no longer fails on missing `dist/extensions/*/openclaw.plugin.json` manifests after a rebuild. Thanks @gumadeiras.
|
||||
- Plugins/context engines: enforce owner-aware context-engine registration on both loader and public SDK paths so plugins cannot spoof privileged ownership, claim the core `legacy` engine id, or overwrite an existing engine id through direct SDK imports. (#47595) Thanks @vincentkoc.
|
||||
- Windows/gateway status: accept `schtasks` `Last Result` output as an alias for `Last Run Result`, so running scheduled-task installs no longer show `Runtime: unknown`. (#47844) Thanks @MoerAI.
|
||||
- Control UI/session routing: preserve established external delivery routes when webchat views or sends in externally originated sessions, so subagent completions still return to the original channel instead of the dashboard. (#47797) Thanks @brokemac79.
|
||||
- Control UI/chat sessions: show human-readable labels in the grouped session dropdown again, keep unique scoped fallbacks when metadata is missing, and disambiguate duplicate labels only when needed. (#45130) thanks @luzhidong.
|
||||
- Control UI: scope persisted session selection per gateway, prevent stale session bleed across tokenized gateway opens, and cap stored gateway session history. (#47453) Thanks @sallyom.
|
||||
- Control UI/dashboard: preserve structured gateway shutdown reasons across restart disconnects so config-triggered restarts no longer fall back to `disconnected (1006): no reason`. (#46532) Thanks @vincentkoc.
|
||||
- Android/chat: theme the thinking dropdown and TLS trust dialogs explicitly so popup surfaces match the active app theme instead of falling back to mismatched Material defaults.
|
||||
- Group mention gating: reject invalid and unsafe nested-repetition `mentionPatterns`, reuse the shared safe config-regex compiler across mention stripping and detection, and cache strip-time regex compilation so noisy groups avoid repeated recompiles.
|
||||
- Browser/profiles: drop the auto-created `chrome-relay` browser profile; users who need the Chrome extension relay must now create their own profile via `openclaw browser create-profile`. (#45777) Thanks @odysseus0.
|
||||
- CI/channel test routing: move the built-in channel suites into `test:channels` and keep them out of `test:extensions`, so extension CI no longer fails after the channel migration while targeted test routing still sends Slack, Signal, and iMessage suites to the right lane. (#46066) Thanks @scoootscooob.
|
||||
- Docs/Mintlify: fix MDX marker syntax on Perplexity, Model Providers, Moonshot, and exec approvals pages so local docs preview no longer breaks rendering or leaves stale pages unpublished. (#46695) Thanks @velvet-shark.
|
||||
- Gateway/config validation: stop treating the implicit default memory slot as a required explicit plugin config, so startup no longer fails with `plugins.slots.memory: plugin not found: memory-core` when `memory-core` was only inferred. (#47494) Thanks @ngutman.
|
||||
- Tlon: honor explicit empty allowlists and defer cite expansion. (#46788) Thanks @zpbrent and @vincentkoc.
|
||||
- Nodes/pending actions: re-check queued foreground actions against the current node command policy before returning them to the node. (#46815) Thanks @zpbrent and @vincentkoc.
|
||||
- Node/startup: remove leftover debug `console.log("node host PATH: ...")` that printed the resolved PATH on every `openclaw node run` invocation. (#46411)
|
||||
- CLI/completion: reduce recursive completion-script string churn and fix nested PowerShell command-path matching so generated nested completions resolve on PowerShell too. (#45537) Thanks @yiShanXin and @vincentkoc.
|
||||
|
||||
## 2026.3.13
|
||||
|
||||
|
||||
@ -8,6 +8,24 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
|
||||
static let messageName = "openclawCanvasA2UIAction"
|
||||
static let allMessageNames = [messageName]
|
||||
|
||||
// Compatibility helper for debug/test shims. Runtime dispatch remains
|
||||
// limited to in-app canvas schemes in `didReceive`.
|
||||
static func isLocalNetworkCanvasURL(_ url: URL) -> Bool {
|
||||
guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" else {
|
||||
return false
|
||||
}
|
||||
guard let host = url.host?.lowercased(), !host.isEmpty else {
|
||||
return false
|
||||
}
|
||||
if host == "localhost" {
|
||||
return true
|
||||
}
|
||||
guard let ip = Self.parseIPv4(host) else {
|
||||
return false
|
||||
}
|
||||
return Self.isLocalNetworkIPv4(ip)
|
||||
}
|
||||
|
||||
private let sessionKey: String
|
||||
|
||||
init(sessionKey: String) {
|
||||
@ -104,5 +122,24 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseIPv4(_ host: String) -> (UInt8, UInt8, UInt8, UInt8)? {
|
||||
let parts = host.split(separator: ".", omittingEmptySubsequences: false)
|
||||
guard parts.count == 4 else { return nil }
|
||||
let bytes = parts.compactMap { UInt8($0) }
|
||||
guard bytes.count == 4 else { return nil }
|
||||
return (bytes[0], bytes[1], bytes[2], bytes[3])
|
||||
}
|
||||
|
||||
private static func isLocalNetworkIPv4(_ ip: (UInt8, UInt8, UInt8, UInt8)) -> Bool {
|
||||
let (a, b, _, _) = ip
|
||||
if a == 10 { return true }
|
||||
if a == 172, (16...31).contains(Int(b)) { return true }
|
||||
if a == 192, b == 168 { return true }
|
||||
if a == 127 { return true }
|
||||
if a == 169, b == 254 { return true }
|
||||
if a == 100, (64...127).contains(Int(b)) { return true }
|
||||
return false
|
||||
}
|
||||
// Formatting helpers live in OpenClawKit (`OpenClawCanvasA2UIAction`).
|
||||
}
|
||||
|
||||
@ -254,6 +254,71 @@ struct CronJob: Identifiable, Codable, Equatable {
|
||||
case state
|
||||
}
|
||||
|
||||
init(
|
||||
id: String,
|
||||
agentId: String?,
|
||||
name: String,
|
||||
description: String?,
|
||||
enabled: Bool,
|
||||
deleteAfterRun: Bool?,
|
||||
createdAtMs: Int,
|
||||
updatedAtMs: Int,
|
||||
schedule: CronSchedule,
|
||||
sessionTarget: CronSessionTarget,
|
||||
wakeMode: CronWakeMode,
|
||||
payload: CronPayload,
|
||||
delivery: CronDelivery?,
|
||||
state: CronJobState)
|
||||
{
|
||||
self.init(
|
||||
id: id,
|
||||
agentId: agentId,
|
||||
name: name,
|
||||
description: description,
|
||||
enabled: enabled,
|
||||
deleteAfterRun: deleteAfterRun,
|
||||
createdAtMs: createdAtMs,
|
||||
updatedAtMs: updatedAtMs,
|
||||
schedule: schedule,
|
||||
sessionTarget: .predefined(sessionTarget),
|
||||
wakeMode: wakeMode,
|
||||
payload: payload,
|
||||
delivery: delivery,
|
||||
state: state)
|
||||
}
|
||||
|
||||
init(
|
||||
id: String,
|
||||
agentId: String?,
|
||||
name: String,
|
||||
description: String?,
|
||||
enabled: Bool,
|
||||
deleteAfterRun: Bool?,
|
||||
createdAtMs: Int,
|
||||
updatedAtMs: Int,
|
||||
schedule: CronSchedule,
|
||||
sessionTarget: CronCustomSessionTarget,
|
||||
wakeMode: CronWakeMode,
|
||||
payload: CronPayload,
|
||||
delivery: CronDelivery?,
|
||||
state: CronJobState)
|
||||
{
|
||||
self.id = id
|
||||
self.agentId = agentId
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.enabled = enabled
|
||||
self.deleteAfterRun = deleteAfterRun
|
||||
self.createdAtMs = createdAtMs
|
||||
self.updatedAtMs = updatedAtMs
|
||||
self.schedule = schedule
|
||||
self.sessionTargetRaw = sessionTarget.rawValue
|
||||
self.wakeMode = wakeMode
|
||||
self.payload = payload
|
||||
self.delivery = delivery
|
||||
self.state = state
|
||||
}
|
||||
|
||||
/// Parsed session target (predefined or custom session ID)
|
||||
var parsedSessionTarget: CronCustomSessionTarget {
|
||||
CronCustomSessionTarget.from(self.sessionTargetRaw)
|
||||
|
||||
@ -44995,6 +44995,75 @@
|
||||
"help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.amazon-bedrock",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "@openclaw/amazon-bedrock-provider",
|
||||
"help": "OpenClaw Amazon Bedrock provider plugin (plugin: amazon-bedrock)",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.amazon-bedrock.config",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "@openclaw/amazon-bedrock-provider Config",
|
||||
"help": "Plugin-defined config payload for amazon-bedrock.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.amazon-bedrock.enabled",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Enable @openclaw/amazon-bedrock-provider",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.amazon-bedrock.hooks",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Hook Policy",
|
||||
"help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.amazon-bedrock.hooks.allowPromptInjection",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"access"
|
||||
],
|
||||
"label": "Allow Prompt Injection Hooks",
|
||||
"help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.anthropic",
|
||||
"kind": "plugin",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5094}
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5098}
|
||||
{"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true}
|
||||
{"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true}
|
||||
{"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@ -3986,6 +3986,11 @@
|
||||
{"recordType":"path","path":"plugins.entries.acpx.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable ACPX Runtime","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.acpx.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.acpx.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.amazon-bedrock","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/amazon-bedrock-provider","help":"OpenClaw Amazon Bedrock provider plugin (plugin: amazon-bedrock)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.amazon-bedrock.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/amazon-bedrock-provider Config","help":"Plugin-defined config payload for amazon-bedrock.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.amazon-bedrock.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/amazon-bedrock-provider","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.amazon-bedrock.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.amazon-bedrock.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-provider","help":"OpenClaw Anthropic provider plugin (plugin: anthropic)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-provider Config","help":"Plugin-defined config payload for anthropic.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/anthropic-provider","hasChildren":false}
|
||||
|
||||
@ -573,12 +573,12 @@ authoring plugins:
|
||||
|
||||
- `openclaw/plugin-sdk/core` for generic plugin APIs, provider auth types, and shared helpers such as routing/session utilities and logger-backed runtimes.
|
||||
- `openclaw/plugin-sdk/compat` for bundled/internal plugin code that needs broader shared runtime helpers than `core`.
|
||||
- `openclaw/plugin-sdk/telegram` for Telegram channel plugins.
|
||||
- `openclaw/plugin-sdk/discord` for Discord channel plugins.
|
||||
- `openclaw/plugin-sdk/slack` for Slack channel plugins.
|
||||
- `openclaw/plugin-sdk/signal` for Signal channel plugins.
|
||||
- `openclaw/plugin-sdk/imessage` for iMessage channel plugins.
|
||||
- `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugins.
|
||||
- `openclaw/plugin-sdk/telegram` for Telegram channel plugin types and shared channel-facing helpers. Built-in Telegram implementation internals stay private to the bundled extension.
|
||||
- `openclaw/plugin-sdk/discord` for Discord channel plugin types and shared channel-facing helpers. Built-in Discord implementation internals stay private to the bundled extension.
|
||||
- `openclaw/plugin-sdk/slack` for Slack channel plugin types and shared channel-facing helpers. Built-in Slack implementation internals stay private to the bundled extension.
|
||||
- `openclaw/plugin-sdk/signal` for Signal channel plugin types and shared channel-facing helpers. Built-in Signal implementation internals stay private to the bundled extension.
|
||||
- `openclaw/plugin-sdk/imessage` for iMessage channel plugin types and shared channel-facing helpers. Built-in iMessage implementation internals stay private to the bundled extension.
|
||||
- `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugin types and shared channel-facing helpers. Built-in WhatsApp implementation internals stay private to the bundled extension.
|
||||
- `openclaw/plugin-sdk/line` for LINE channel plugins.
|
||||
- `openclaw/plugin-sdk/msteams` for the bundled Microsoft Teams plugin surface.
|
||||
- Bundled extension-specific subpaths are also available:
|
||||
|
||||
22
extensions/amazon-bedrock/index.test.ts
Normal file
22
extensions/amazon-bedrock/index.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { registerSingleProviderPlugin } from "../../src/test-utils/plugin-registration.js";
|
||||
import amazonBedrockPlugin from "./index.js";
|
||||
|
||||
describe("amazon-bedrock provider plugin", () => {
|
||||
it("marks Claude 4.6 Bedrock models as adaptive by default", () => {
|
||||
const provider = registerSingleProviderPlugin(amazonBedrockPlugin);
|
||||
|
||||
expect(
|
||||
provider.resolveDefaultThinkingLevel?.({
|
||||
provider: "amazon-bedrock",
|
||||
modelId: "us.anthropic.claude-opus-4-6-v1",
|
||||
} as never),
|
||||
).toBe("adaptive");
|
||||
expect(
|
||||
provider.resolveDefaultThinkingLevel?.({
|
||||
provider: "amazon-bedrock",
|
||||
modelId: "amazon.nova-micro-v1:0",
|
||||
} as never),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
23
extensions/amazon-bedrock/index.ts
Normal file
23
extensions/amazon-bedrock/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { emptyPluginConfigSchema, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
||||
|
||||
const PROVIDER_ID = "amazon-bedrock";
|
||||
const CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
|
||||
|
||||
const amazonBedrockPlugin = {
|
||||
id: PROVIDER_ID,
|
||||
name: "Amazon Bedrock Provider",
|
||||
description: "Bundled Amazon Bedrock provider policy plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Amazon Bedrock",
|
||||
docsPath: "/providers/models",
|
||||
auth: [],
|
||||
resolveDefaultThinkingLevel: ({ modelId }) =>
|
||||
CLAUDE_46_MODEL_RE.test(modelId.trim()) ? "adaptive" : undefined,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default amazonBedrockPlugin;
|
||||
9
extensions/amazon-bedrock/openclaw.plugin.json
Normal file
9
extensions/amazon-bedrock/openclaw.plugin.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "amazon-bedrock",
|
||||
"providers": ["amazon-bedrock"],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
12
extensions/amazon-bedrock/package.json
Normal file
12
extensions/amazon-bedrock/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/amazon-bedrock-provider",
|
||||
"version": "2026.3.14",
|
||||
"private": true,
|
||||
"description": "OpenClaw Amazon Bedrock provider plugin",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -143,7 +143,10 @@ const cloudflareAiGatewayPlugin = {
|
||||
await ensureApiKeyFromOptionEnvOrPrompt({
|
||||
token: normalizeOptionalSecretInput(ctx.opts?.cloudflareAiGatewayApiKey),
|
||||
tokenProvider: "cloudflare-ai-gateway",
|
||||
secretInputMode: ctx.secretInputMode,
|
||||
secretInputMode:
|
||||
ctx.allowSecretRefPrompt === false
|
||||
? (ctx.secretInputMode ?? "plaintext")
|
||||
: ctx.secretInputMode,
|
||||
config: ctx.config,
|
||||
expectedProviders: [PROVIDER_ID],
|
||||
provider: PROVIDER_ID,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/discord";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { discordPlugin } from "./src/channel.js";
|
||||
import { setDiscordRuntime } from "./src/runtime.js";
|
||||
import { registerDiscordSubagentHooks } from "./src/subagent-hooks.js";
|
||||
|
||||
@ -7,13 +7,15 @@ import {
|
||||
buildChannelConfigSchema,
|
||||
DiscordConfigSchema,
|
||||
getChatChannelMeta,
|
||||
inspectDiscordAccount,
|
||||
type ChannelPlugin,
|
||||
} from "openclaw/plugin-sdk/discord";
|
||||
import { inspectDiscordAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listDiscordAccountIds,
|
||||
resolveDefaultDiscordAccountId,
|
||||
resolveDiscordAccount,
|
||||
type ChannelPlugin,
|
||||
type ResolvedDiscordAccount,
|
||||
} from "openclaw/plugin-sdk/discord";
|
||||
} from "./accounts.js";
|
||||
import { createDiscordSetupWizardProxy, discordSetupAdapter } from "./setup-core.js";
|
||||
|
||||
async function loadDiscordChannelRuntime() {
|
||||
|
||||
@ -8,45 +8,54 @@ import {
|
||||
createScopedAccountConfigAccessors,
|
||||
formatAllowFromLowercase,
|
||||
} from "openclaw/plugin-sdk/compat";
|
||||
import {
|
||||
buildAgentSessionKey,
|
||||
resolveThreadSessionKeys,
|
||||
type RoutePeer,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
buildComputedAccountStatusSnapshot,
|
||||
buildChannelConfigSchema,
|
||||
buildTokenChannelStatusSummary,
|
||||
collectDiscordAuditChannelIds,
|
||||
collectDiscordStatusIssues,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
DiscordConfigSchema,
|
||||
getChatChannelMeta,
|
||||
inspectDiscordAccount,
|
||||
listDiscordAccountIds,
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
looksLikeDiscordTargetId,
|
||||
normalizeDiscordMessagingTarget,
|
||||
normalizeDiscordOutboundTarget,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
resolveDiscordAccount,
|
||||
resolveDefaultDiscordAccountId,
|
||||
resolveDiscordGroupRequireMention,
|
||||
resolveDiscordGroupToolPolicy,
|
||||
type ChannelMessageActionAdapter,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
type ResolvedDiscordAccount,
|
||||
} from "openclaw/plugin-sdk/discord";
|
||||
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
|
||||
import { normalizeMessageChannel } from "../../../src/utils/message-channel.js";
|
||||
import { inspectDiscordAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listDiscordAccountIds,
|
||||
resolveDiscordAccount,
|
||||
resolveDefaultDiscordAccountId,
|
||||
type ResolvedDiscordAccount,
|
||||
} from "./accounts.js";
|
||||
import { collectDiscordAuditChannelIds } from "./audit.js";
|
||||
import {
|
||||
isDiscordExecApprovalClientEnabled,
|
||||
shouldSuppressLocalDiscordExecApprovalPrompt,
|
||||
} from "./exec-approvals.js";
|
||||
import {
|
||||
looksLikeDiscordTargetId,
|
||||
normalizeDiscordMessagingTarget,
|
||||
normalizeDiscordOutboundTarget,
|
||||
} from "./normalize.js";
|
||||
import type { DiscordProbe } from "./probe.js";
|
||||
import { resolveDiscordUserAllowlist } from "./resolve-users.js";
|
||||
import { getDiscordRuntime } from "./runtime.js";
|
||||
import { fetchChannelPermissionsDiscord } from "./send.js";
|
||||
import { createDiscordSetupWizardProxy, discordSetupAdapter } from "./setup-core.js";
|
||||
import { collectDiscordStatusIssues } from "./status-issues.js";
|
||||
import { parseDiscordTarget } from "./targets.js";
|
||||
import { DiscordUiContainer } from "./ui.js";
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/discord";
|
||||
|
||||
const { setRuntime: setDiscordRuntime, getRuntime: getDiscordRuntime } =
|
||||
createPluginRuntimeStore<PluginRuntime>("Discord runtime not initialized");
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { resolveDiscordAccount } from "./accounts.js";
|
||||
import {
|
||||
autoBindSpawnedDiscordSubagent,
|
||||
listThreadBindingsBySessionKey,
|
||||
resolveDiscordAccount,
|
||||
unbindThreadBindingsBySessionKey,
|
||||
} from "openclaw/plugin-sdk/discord";
|
||||
} from "./monitor/thread-bindings.js";
|
||||
|
||||
function summarizeError(err: unknown): string {
|
||||
if (err instanceof Error) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/imessage";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/imessage";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { imessagePlugin } from "./src/channel.js";
|
||||
import { setIMessageRuntime } from "./src/runtime.js";
|
||||
|
||||
|
||||
@ -9,15 +9,17 @@ import {
|
||||
formatTrimmedAllowFromEntries,
|
||||
getChatChannelMeta,
|
||||
IMessageConfigSchema,
|
||||
listIMessageAccountIds,
|
||||
resolveDefaultIMessageAccountId,
|
||||
resolveIMessageAccount,
|
||||
resolveIMessageConfigAllowFrom,
|
||||
resolveIMessageConfigDefaultTo,
|
||||
setAccountEnabledInConfigSection,
|
||||
type ChannelPlugin,
|
||||
type ResolvedIMessageAccount,
|
||||
} from "openclaw/plugin-sdk/imessage";
|
||||
import {
|
||||
listIMessageAccountIds,
|
||||
resolveDefaultIMessageAccountId,
|
||||
resolveIMessageAccount,
|
||||
type ResolvedIMessageAccount,
|
||||
} from "./accounts.js";
|
||||
import { createIMessageSetupWizardProxy, imessageSetupAdapter } from "./setup-core.js";
|
||||
|
||||
async function loadIMessageChannelRuntime() {
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
buildAccountScopedDmSecurityPolicy,
|
||||
collectAllowlistProviderRestrictSendersWarnings,
|
||||
} from "openclaw/plugin-sdk/compat";
|
||||
import { buildAgentSessionKey, type RoutePeer } from "openclaw/plugin-sdk/core";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
collectStatusIssuesFromLastError,
|
||||
@ -11,23 +12,25 @@ import {
|
||||
formatTrimmedAllowFromEntries,
|
||||
getChatChannelMeta,
|
||||
IMessageConfigSchema,
|
||||
listIMessageAccountIds,
|
||||
looksLikeIMessageTargetId,
|
||||
normalizeIMessageMessagingTarget,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveChannelMediaMaxBytes,
|
||||
resolveDefaultIMessageAccountId,
|
||||
resolveIMessageAccount,
|
||||
resolveIMessageConfigAllowFrom,
|
||||
resolveIMessageConfigDefaultTo,
|
||||
resolveIMessageGroupRequireMention,
|
||||
resolveIMessageGroupToolPolicy,
|
||||
setAccountEnabledInConfigSection,
|
||||
type ChannelPlugin,
|
||||
type ResolvedIMessageAccount,
|
||||
} from "openclaw/plugin-sdk/imessage";
|
||||
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
|
||||
import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js";
|
||||
import {
|
||||
listIMessageAccountIds,
|
||||
resolveDefaultIMessageAccountId,
|
||||
resolveIMessageAccount,
|
||||
type ResolvedIMessageAccount,
|
||||
} from "./accounts.js";
|
||||
import { getIMessageRuntime } from "./runtime.js";
|
||||
import { createIMessageSetupWizardProxy, imessageSetupAdapter } from "./setup-core.js";
|
||||
import { normalizeIMessageHandle, parseIMessageTarget } from "./targets.js";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/imessage";
|
||||
|
||||
const { setRuntime: setIMessageRuntime, getRuntime: getIMessageRuntime } =
|
||||
createPluginRuntimeStore<PluginRuntime>("iMessage runtime not initialized");
|
||||
|
||||
@ -31,7 +31,11 @@ function getDefaultBaseUrl(region: MiniMaxRegion): string {
|
||||
return region === "cn" ? DEFAULT_BASE_URL_CN : DEFAULT_BASE_URL_GLOBAL;
|
||||
}
|
||||
|
||||
function modelRef(modelId: string): string {
|
||||
function apiModelRef(modelId: string): string {
|
||||
return `${API_PROVIDER_ID}/${modelId}`;
|
||||
}
|
||||
|
||||
function portalModelRef(modelId: string): string {
|
||||
return `${PORTAL_PROVIDER_ID}/${modelId}`;
|
||||
}
|
||||
|
||||
@ -109,7 +113,7 @@ function createOAuthHandler(region: MiniMaxRegion) {
|
||||
|
||||
return buildOauthProviderAuthResult({
|
||||
providerId: PORTAL_PROVIDER_ID,
|
||||
defaultModel: modelRef(DEFAULT_MODEL),
|
||||
defaultModel: portalModelRef(DEFAULT_MODEL),
|
||||
access: result.access,
|
||||
refresh: result.refresh,
|
||||
expires: result.expires,
|
||||
@ -125,11 +129,11 @@ function createOAuthHandler(region: MiniMaxRegion) {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
[modelRef("MiniMax-M2.5")]: { alias: "minimax-m2.5" },
|
||||
[modelRef("MiniMax-M2.5-highspeed")]: {
|
||||
[portalModelRef("MiniMax-M2.5")]: { alias: "minimax-m2.5" },
|
||||
[portalModelRef("MiniMax-M2.5-highspeed")]: {
|
||||
alias: "minimax-m2.5-highspeed",
|
||||
},
|
||||
[modelRef("MiniMax-M2.5-Lightning")]: {
|
||||
[portalModelRef("MiniMax-M2.5-Lightning")]: {
|
||||
alias: "minimax-m2.5-lightning",
|
||||
},
|
||||
},
|
||||
@ -177,7 +181,8 @@ const minimaxPlugin = {
|
||||
promptMessage:
|
||||
"Enter MiniMax API key (sk-api- or sk-cp-)\nhttps://platform.minimax.io/user-center/basic-information/interface-key",
|
||||
profileId: "minimax:global",
|
||||
defaultModel: modelRef(DEFAULT_MODEL),
|
||||
allowProfile: false,
|
||||
defaultModel: apiModelRef(DEFAULT_MODEL),
|
||||
expectedProviders: ["minimax"],
|
||||
applyConfig: (cfg) => applyMinimaxApiConfig(cfg),
|
||||
wizard: {
|
||||
@ -200,7 +205,8 @@ const minimaxPlugin = {
|
||||
promptMessage:
|
||||
"Enter MiniMax CN API key (sk-api- or sk-cp-)\nhttps://platform.minimaxi.com/user-center/basic-information/interface-key",
|
||||
profileId: "minimax:cn",
|
||||
defaultModel: modelRef(DEFAULT_MODEL),
|
||||
allowProfile: false,
|
||||
defaultModel: apiModelRef(DEFAULT_MODEL),
|
||||
expectedProviders: ["minimax", "minimax-cn"],
|
||||
applyConfig: (cfg) => applyMinimaxApiConfigCn(cfg),
|
||||
wizard: {
|
||||
|
||||
@ -26,6 +26,7 @@ const opencodeGoPlugin = {
|
||||
flagName: "--opencode-go-api-key",
|
||||
envVar: "OPENCODE_API_KEY",
|
||||
promptMessage: "Enter OpenCode API key",
|
||||
profileIds: ["opencode:default", "opencode-go:default"],
|
||||
defaultModel: OPENCODE_GO_DEFAULT_MODEL_REF,
|
||||
expectedProviders: ["opencode", "opencode-go"],
|
||||
applyConfig: (cfg) => applyOpencodeGoConfig(cfg),
|
||||
|
||||
@ -35,6 +35,7 @@ const opencodePlugin = {
|
||||
flagName: "--opencode-zen-api-key",
|
||||
envVar: "OPENCODE_API_KEY",
|
||||
promptMessage: "Enter OpenCode API key",
|
||||
profileIds: ["opencode:default", "opencode-go:default"],
|
||||
defaultModel: OPENCODE_ZEN_DEFAULT_MODEL,
|
||||
expectedProviders: ["opencode", "opencode-go"],
|
||||
applyConfig: (cfg) => applyOpencodeZenConfig(cfg),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/signal";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/signal";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { signalPlugin } from "./src/channel.js";
|
||||
import { setSignalRuntime } from "./src/runtime.js";
|
||||
|
||||
|
||||
@ -8,15 +8,17 @@ import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
deleteAccountFromConfigSection,
|
||||
getChatChannelMeta,
|
||||
listSignalAccountIds,
|
||||
normalizeE164,
|
||||
resolveDefaultSignalAccountId,
|
||||
resolveSignalAccount,
|
||||
setAccountEnabledInConfigSection,
|
||||
SignalConfigSchema,
|
||||
type ChannelPlugin,
|
||||
type ResolvedSignalAccount,
|
||||
} from "openclaw/plugin-sdk/signal";
|
||||
import {
|
||||
listSignalAccountIds,
|
||||
resolveDefaultSignalAccountId,
|
||||
resolveSignalAccount,
|
||||
type ResolvedSignalAccount,
|
||||
} from "./accounts.js";
|
||||
import { createSignalSetupWizardProxy, signalSetupAdapter } from "./setup-core.js";
|
||||
|
||||
async function loadSignalChannelRuntime() {
|
||||
|
||||
@ -14,23 +14,25 @@ import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
deleteAccountFromConfigSection,
|
||||
getChatChannelMeta,
|
||||
listSignalAccountIds,
|
||||
looksLikeSignalTargetId,
|
||||
normalizeE164,
|
||||
normalizeSignalMessagingTarget,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveChannelMediaMaxBytes,
|
||||
resolveDefaultSignalAccountId,
|
||||
resolveSignalAccount,
|
||||
setAccountEnabledInConfigSection,
|
||||
SignalConfigSchema,
|
||||
type ChannelMessageActionAdapter,
|
||||
type ChannelPlugin,
|
||||
type ResolvedSignalAccount,
|
||||
} from "openclaw/plugin-sdk/signal";
|
||||
import { resolveTextChunkLimit } from "../../../src/auto-reply/chunk.js";
|
||||
import { resolveMarkdownTableMode } from "../../../src/config/markdown-tables.js";
|
||||
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
|
||||
import {
|
||||
listSignalAccountIds,
|
||||
resolveDefaultSignalAccountId,
|
||||
resolveSignalAccount,
|
||||
type ResolvedSignalAccount,
|
||||
} from "./accounts.js";
|
||||
import { markdownToSignalTextChunks } from "./format.js";
|
||||
import {
|
||||
looksLikeUuid,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/signal";
|
||||
|
||||
const { setRuntime: setSignalRuntime, getRuntime: getSignalRuntime } =
|
||||
createPluginRuntimeStore<PluginRuntime>("Signal runtime not initialized");
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/slack";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/slack";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { slackPlugin } from "./src/channel.js";
|
||||
import { setSlackRuntime } from "./src/runtime.js";
|
||||
|
||||
|
||||
@ -6,15 +6,17 @@ import {
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
getChatChannelMeta,
|
||||
inspectSlackAccount,
|
||||
isSlackInteractiveRepliesEnabled,
|
||||
SlackConfigSchema,
|
||||
type ChannelPlugin,
|
||||
} from "openclaw/plugin-sdk/slack";
|
||||
import { inspectSlackAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listSlackAccountIds,
|
||||
resolveDefaultSlackAccountId,
|
||||
resolveSlackAccount,
|
||||
SlackConfigSchema,
|
||||
type ChannelPlugin,
|
||||
type ResolvedSlackAccount,
|
||||
} from "openclaw/plugin-sdk/slack";
|
||||
} from "./accounts.js";
|
||||
import { isSlackInteractiveRepliesEnabled } from "./interactive-replies.js";
|
||||
import { createSlackSetupWizardProxy, slackSetupAdapter } from "./setup-core.js";
|
||||
|
||||
async function loadSlackChannelRuntime() {
|
||||
|
||||
@ -16,12 +16,7 @@ import {
|
||||
buildComputedAccountStatusSnapshot,
|
||||
buildChannelConfigSchema,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
extractSlackToolSend,
|
||||
getChatChannelMeta,
|
||||
handleSlackMessageAction,
|
||||
inspectSlackAccount,
|
||||
listSlackMessageActions,
|
||||
listSlackAccountIds,
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
looksLikeSlackTargetId,
|
||||
@ -29,22 +24,28 @@ import {
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromRequiredCredentialStatuses,
|
||||
resolveDefaultSlackAccountId,
|
||||
resolveSlackAccount,
|
||||
resolveSlackReplyToMode,
|
||||
isSlackInteractiveRepliesEnabled,
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
buildSlackThreadingToolContext,
|
||||
SlackConfigSchema,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
type ResolvedSlackAccount,
|
||||
} from "openclaw/plugin-sdk/slack";
|
||||
import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js";
|
||||
import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js";
|
||||
import { inspectSlackAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listEnabledSlackAccounts,
|
||||
listSlackAccountIds,
|
||||
resolveDefaultSlackAccountId,
|
||||
resolveSlackAccount,
|
||||
resolveSlackReplyToMode,
|
||||
type ResolvedSlackAccount,
|
||||
} from "./accounts.js";
|
||||
import { parseSlackBlocksInput } from "./blocks-input.js";
|
||||
import { createSlackWebClient } from "./client.js";
|
||||
import { isSlackInteractiveRepliesEnabled } from "./interactive-replies.js";
|
||||
import { handleSlackMessageAction } from "./message-action-dispatch.js";
|
||||
import { extractSlackToolSend, listSlackMessageActions } from "./message-actions.js";
|
||||
import { normalizeAllowListLower } from "./monitor/allow-list.js";
|
||||
import type { SlackProbe } from "./probe.js";
|
||||
import { resolveSlackUserAllowlist } from "./resolve-users.js";
|
||||
@ -52,6 +53,7 @@ import { getSlackRuntime } from "./runtime.js";
|
||||
import { fetchSlackScopes } from "./scopes.js";
|
||||
import { createSlackSetupWizardProxy, slackSetupAdapter } from "./setup-core.js";
|
||||
import { parseSlackTarget } from "./targets.js";
|
||||
import { buildSlackThreadingToolContext } from "./threading-tool-context.js";
|
||||
|
||||
const meta = getChatChannelMeta("slack");
|
||||
const SLACK_CHANNEL_TYPE_CACHE = new Map<string, "channel" | "group" | "dm" | "unknown">();
|
||||
|
||||
334
extensions/slack/src/message-action-dispatch.ts
Normal file
334
extensions/slack/src/message-action-dispatch.ts
Normal file
@ -0,0 +1,334 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import type { ChannelMessageActionContext } from "openclaw/plugin-sdk";
|
||||
import { parseSlackBlocksInput } from "./blocks-input.js";
|
||||
import { buildSlackInteractiveBlocks } from "./blocks-render.js";
|
||||
|
||||
type SlackActionInvoke = (
|
||||
action: Record<string, unknown>,
|
||||
cfg: ChannelMessageActionContext["cfg"],
|
||||
toolContext?: ChannelMessageActionContext["toolContext"],
|
||||
) => Promise<AgentToolResult<unknown>>;
|
||||
|
||||
type InteractiveButtonStyle = "primary" | "secondary" | "success" | "danger";
|
||||
|
||||
type InteractiveReplyButton = {
|
||||
label: string;
|
||||
value: string;
|
||||
style?: InteractiveButtonStyle;
|
||||
};
|
||||
|
||||
type InteractiveReplyOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type InteractiveReplyBlock =
|
||||
| { type: "text"; text: string }
|
||||
| { type: "buttons"; buttons: InteractiveReplyButton[] }
|
||||
| { type: "select"; placeholder?: string; options: InteractiveReplyOption[] };
|
||||
|
||||
type InteractiveReply = {
|
||||
blocks: InteractiveReplyBlock[];
|
||||
};
|
||||
|
||||
function readTrimmedString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
function normalizeButtonStyle(value: unknown): InteractiveButtonStyle | undefined {
|
||||
const style = readTrimmedString(value)?.toLowerCase();
|
||||
return style === "primary" || style === "secondary" || style === "success" || style === "danger"
|
||||
? style
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function normalizeInteractiveButton(raw: unknown): InteractiveReplyButton | undefined {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
||||
return undefined;
|
||||
}
|
||||
const record = raw as Record<string, unknown>;
|
||||
const label = readTrimmedString(record.label) ?? readTrimmedString(record.text);
|
||||
const value =
|
||||
readTrimmedString(record.value) ??
|
||||
readTrimmedString(record.callbackData) ??
|
||||
readTrimmedString(record.callback_data);
|
||||
if (!label || !value) {
|
||||
return undefined;
|
||||
}
|
||||
return { label, value, style: normalizeButtonStyle(record.style) };
|
||||
}
|
||||
|
||||
function normalizeInteractiveOption(raw: unknown): InteractiveReplyOption | undefined {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
||||
return undefined;
|
||||
}
|
||||
const record = raw as Record<string, unknown>;
|
||||
const label = readTrimmedString(record.label) ?? readTrimmedString(record.text);
|
||||
const value = readTrimmedString(record.value);
|
||||
return label && value ? { label, value } : undefined;
|
||||
}
|
||||
|
||||
function normalizeInteractiveReply(raw: unknown): InteractiveReply | undefined {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
||||
return undefined;
|
||||
}
|
||||
const record = raw as Record<string, unknown>;
|
||||
const blocks = Array.isArray(record.blocks)
|
||||
? record.blocks
|
||||
.map((entry) => {
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
return undefined;
|
||||
}
|
||||
const block = entry as Record<string, unknown>;
|
||||
const type = readTrimmedString(block.type)?.toLowerCase();
|
||||
if (type === "text") {
|
||||
const text = readTrimmedString(block.text);
|
||||
return text ? ({ type: "text", text } as const) : undefined;
|
||||
}
|
||||
if (type === "buttons") {
|
||||
const buttons = Array.isArray(block.buttons)
|
||||
? block.buttons
|
||||
.map((button) => normalizeInteractiveButton(button))
|
||||
.filter((button): button is InteractiveReplyButton => Boolean(button))
|
||||
: [];
|
||||
return buttons.length > 0 ? ({ type: "buttons", buttons } as const) : undefined;
|
||||
}
|
||||
if (type === "select") {
|
||||
const options = Array.isArray(block.options)
|
||||
? block.options
|
||||
.map((option) => normalizeInteractiveOption(option))
|
||||
.filter((option): option is InteractiveReplyOption => Boolean(option))
|
||||
: [];
|
||||
return options.length > 0
|
||||
? ({
|
||||
type: "select",
|
||||
placeholder: readTrimmedString(block.placeholder),
|
||||
options,
|
||||
} as const)
|
||||
: undefined;
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter((entry): entry is InteractiveReplyBlock => Boolean(entry))
|
||||
: [];
|
||||
return blocks.length > 0 ? { blocks } : undefined;
|
||||
}
|
||||
|
||||
function readStringParam(
|
||||
params: Record<string, unknown>,
|
||||
key: string,
|
||||
options: { required?: boolean; trim?: boolean; label?: string; allowEmpty?: boolean } = {},
|
||||
): string | undefined {
|
||||
const { required = false, trim = true, label = key, allowEmpty = false } = options;
|
||||
const raw = params[key];
|
||||
if (typeof raw !== "string") {
|
||||
if (required) {
|
||||
throw new Error(`${label} required`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
const value = trim ? raw.trim() : raw;
|
||||
if (!value && !allowEmpty) {
|
||||
if (required) {
|
||||
throw new Error(`${label} required`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function readNumberParam(
|
||||
params: Record<string, unknown>,
|
||||
key: string,
|
||||
options: { required?: boolean; label?: string; integer?: boolean; strict?: boolean } = {},
|
||||
): number | undefined {
|
||||
const { required = false, label = key, integer = false, strict = false } = options;
|
||||
const raw = params[key];
|
||||
let value: number | undefined;
|
||||
if (typeof raw === "number" && Number.isFinite(raw)) {
|
||||
value = raw;
|
||||
} else if (typeof raw === "string") {
|
||||
const trimmed = raw.trim();
|
||||
if (trimmed) {
|
||||
const parsed = strict ? Number(trimmed) : Number.parseFloat(trimmed);
|
||||
if (Number.isFinite(parsed)) {
|
||||
value = parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value === undefined) {
|
||||
if (required) {
|
||||
throw new Error(`${label} required`);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return integer ? Math.trunc(value) : value;
|
||||
}
|
||||
|
||||
function readSlackBlocksParam(actionParams: Record<string, unknown>) {
|
||||
return parseSlackBlocksInput(actionParams.blocks) as Record<string, unknown>[] | undefined;
|
||||
}
|
||||
|
||||
export async function handleSlackMessageAction(params: {
|
||||
providerId: string;
|
||||
ctx: ChannelMessageActionContext;
|
||||
invoke: SlackActionInvoke;
|
||||
normalizeChannelId?: (channelId: string) => string;
|
||||
includeReadThreadId?: boolean;
|
||||
}): Promise<AgentToolResult<unknown>> {
|
||||
const { providerId, ctx, invoke, normalizeChannelId, includeReadThreadId = false } = params;
|
||||
const { action, cfg, params: actionParams } = ctx;
|
||||
const accountId = ctx.accountId ?? undefined;
|
||||
const resolveChannelId = () => {
|
||||
const channelId =
|
||||
readStringParam(actionParams, "channelId") ??
|
||||
readStringParam(actionParams, "to", { required: true });
|
||||
if (!channelId) {
|
||||
throw new Error("channelId required");
|
||||
}
|
||||
return normalizeChannelId ? normalizeChannelId(channelId) : channelId;
|
||||
};
|
||||
|
||||
if (action === "send") {
|
||||
const to = readStringParam(actionParams, "to", { required: true });
|
||||
const content = readStringParam(actionParams, "message", { allowEmpty: true });
|
||||
const mediaUrl = readStringParam(actionParams, "media", { trim: false });
|
||||
const interactive = normalizeInteractiveReply(actionParams.interactive);
|
||||
const interactiveBlocks = interactive ? buildSlackInteractiveBlocks(interactive) : undefined;
|
||||
const blocks = readSlackBlocksParam(actionParams) ?? interactiveBlocks;
|
||||
if (!content && !mediaUrl && !blocks) {
|
||||
throw new Error("Slack send requires message, blocks, or media.");
|
||||
}
|
||||
if (mediaUrl && blocks) {
|
||||
throw new Error("Slack send does not support blocks with media.");
|
||||
}
|
||||
const threadId = readStringParam(actionParams, "threadId");
|
||||
const replyTo = readStringParam(actionParams, "replyTo");
|
||||
return await invoke(
|
||||
{
|
||||
action: "sendMessage",
|
||||
to,
|
||||
content: content ?? "",
|
||||
mediaUrl: mediaUrl ?? undefined,
|
||||
accountId,
|
||||
threadTs: threadId ?? replyTo ?? undefined,
|
||||
...(blocks ? { blocks } : {}),
|
||||
},
|
||||
cfg,
|
||||
ctx.toolContext,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "react") {
|
||||
const messageId = readStringParam(actionParams, "messageId", { required: true });
|
||||
const emoji = readStringParam(actionParams, "emoji", { allowEmpty: true });
|
||||
const remove = typeof actionParams.remove === "boolean" ? actionParams.remove : undefined;
|
||||
return await invoke(
|
||||
{ action: "react", channelId: resolveChannelId(), messageId, emoji, remove, accountId },
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "reactions") {
|
||||
const messageId = readStringParam(actionParams, "messageId", { required: true });
|
||||
const limit = readNumberParam(actionParams, "limit", { integer: true });
|
||||
return await invoke(
|
||||
{ action: "reactions", channelId: resolveChannelId(), messageId, limit, accountId },
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "read") {
|
||||
const limit = readNumberParam(actionParams, "limit", { integer: true });
|
||||
const readAction: Record<string, unknown> = {
|
||||
action: "readMessages",
|
||||
channelId: resolveChannelId(),
|
||||
limit,
|
||||
before: readStringParam(actionParams, "before"),
|
||||
after: readStringParam(actionParams, "after"),
|
||||
accountId,
|
||||
};
|
||||
if (includeReadThreadId) {
|
||||
readAction.threadId = readStringParam(actionParams, "threadId");
|
||||
}
|
||||
return await invoke(readAction, cfg);
|
||||
}
|
||||
|
||||
if (action === "edit") {
|
||||
const messageId = readStringParam(actionParams, "messageId", { required: true });
|
||||
const content = readStringParam(actionParams, "message", { allowEmpty: true });
|
||||
const blocks = readSlackBlocksParam(actionParams);
|
||||
if (!content && !blocks) {
|
||||
throw new Error("Slack edit requires message or blocks.");
|
||||
}
|
||||
return await invoke(
|
||||
{
|
||||
action: "editMessage",
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
content: content ?? "",
|
||||
blocks,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "delete") {
|
||||
const messageId = readStringParam(actionParams, "messageId", { required: true });
|
||||
return await invoke(
|
||||
{ action: "deleteMessage", channelId: resolveChannelId(), messageId, accountId },
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "pin" || action === "unpin" || action === "list-pins") {
|
||||
const messageId =
|
||||
action === "list-pins"
|
||||
? undefined
|
||||
: readStringParam(actionParams, "messageId", { required: true });
|
||||
return await invoke(
|
||||
{
|
||||
action: action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins",
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "member-info") {
|
||||
const userId = readStringParam(actionParams, "userId", { required: true });
|
||||
return await invoke({ action: "memberInfo", userId, accountId }, cfg);
|
||||
}
|
||||
|
||||
if (action === "emoji-list") {
|
||||
const limit = readNumberParam(actionParams, "limit", { integer: true });
|
||||
return await invoke({ action: "emojiList", limit, accountId }, cfg);
|
||||
}
|
||||
|
||||
if (action === "download-file") {
|
||||
const fileId = readStringParam(actionParams, "fileId", { required: true });
|
||||
const channelId =
|
||||
readStringParam(actionParams, "channelId") ?? readStringParam(actionParams, "to");
|
||||
const threadId =
|
||||
readStringParam(actionParams, "threadId") ?? readStringParam(actionParams, "replyTo");
|
||||
return await invoke(
|
||||
{
|
||||
action: "downloadFile",
|
||||
fileId,
|
||||
channelId: channelId ?? undefined,
|
||||
threadId: threadId ?? undefined,
|
||||
accountId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/slack";
|
||||
|
||||
const { setRuntime: setSlackRuntime, getRuntime: getSlackRuntime } =
|
||||
createPluginRuntimeStore<PluginRuntime>("Slack runtime not initialized");
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk/telegram";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/telegram";
|
||||
import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { telegramPlugin } from "./src/channel.js";
|
||||
import { setTelegramRuntime } from "./src/runtime.js";
|
||||
|
||||
|
||||
@ -6,17 +6,19 @@ import {
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
getChatChannelMeta,
|
||||
inspectTelegramAccount,
|
||||
listTelegramAccountIds,
|
||||
normalizeAccountId,
|
||||
resolveDefaultTelegramAccountId,
|
||||
resolveTelegramAccount,
|
||||
TelegramConfigSchema,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
type ResolvedTelegramAccount,
|
||||
type TelegramProbe,
|
||||
} from "openclaw/plugin-sdk/telegram";
|
||||
import { inspectTelegramAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
resolveDefaultTelegramAccountId,
|
||||
resolveTelegramAccount,
|
||||
type ResolvedTelegramAccount,
|
||||
} from "./accounts.js";
|
||||
import type { TelegramProbe } from "./probe.js";
|
||||
import { telegramSetupAdapter } from "./setup-core.js";
|
||||
import { telegramSetupWizard } from "./setup-surface.js";
|
||||
|
||||
|
||||
@ -3,10 +3,10 @@ import type {
|
||||
ChannelGatewayContext,
|
||||
OpenClawConfig,
|
||||
PluginRuntime,
|
||||
ResolvedTelegramAccount,
|
||||
} from "openclaw/plugin-sdk/telegram";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../test-utils/runtime-env.js";
|
||||
import type { ResolvedTelegramAccount } from "./accounts.js";
|
||||
import { telegramPlugin } from "./channel.js";
|
||||
import { setTelegramRuntime } from "./runtime.js";
|
||||
|
||||
|
||||
@ -16,32 +16,20 @@ import {
|
||||
buildChannelConfigSchema,
|
||||
buildTokenChannelStatusSummary,
|
||||
clearAccountEntryFields,
|
||||
collectTelegramStatusIssues,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
getChatChannelMeta,
|
||||
inspectTelegramAccount,
|
||||
listTelegramAccountIds,
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
looksLikeTelegramTargetId,
|
||||
normalizeAccountId,
|
||||
normalizeTelegramMessagingTarget,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
parseTelegramReplyToMessageId,
|
||||
parseTelegramThreadId,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
resolveDefaultTelegramAccountId,
|
||||
resolveTelegramAccount,
|
||||
resolveTelegramGroupRequireMention,
|
||||
resolveTelegramGroupToolPolicy,
|
||||
sendTelegramPayloadMessages,
|
||||
TelegramConfigSchema,
|
||||
type ChannelMessageActionAdapter,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
type ResolvedTelegramAccount,
|
||||
type TelegramProbe,
|
||||
} from "openclaw/plugin-sdk/telegram";
|
||||
import { parseTelegramTopicConversation } from "../../../src/acp/conversation-id.js";
|
||||
import { resolveExecApprovalCommandDisplay } from "../../../src/infra/exec-approval-command-display.js";
|
||||
@ -51,16 +39,28 @@ import {
|
||||
resolveOutboundSendDep,
|
||||
} from "../../../src/infra/outbound/send-deps.js";
|
||||
import { normalizeMessageChannel } from "../../../src/utils/message-channel.js";
|
||||
import { inspectTelegramAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
resolveDefaultTelegramAccountId,
|
||||
resolveTelegramAccount,
|
||||
type ResolvedTelegramAccount,
|
||||
} from "./accounts.js";
|
||||
import { buildTelegramExecApprovalButtons } from "./approval-buttons.js";
|
||||
import { buildTelegramGroupPeerId } from "./bot/helpers.js";
|
||||
import {
|
||||
isTelegramExecApprovalClientEnabled,
|
||||
resolveTelegramExecApprovalTarget,
|
||||
} from "./exec-approvals.js";
|
||||
import { looksLikeTelegramTargetId, normalizeTelegramMessagingTarget } from "./normalize.js";
|
||||
import { sendTelegramPayloadMessages } from "./outbound-adapter.js";
|
||||
import { parseTelegramReplyToMessageId, parseTelegramThreadId } from "./outbound-params.js";
|
||||
import type { TelegramProbe } from "./probe.js";
|
||||
import { getTelegramRuntime } from "./runtime.js";
|
||||
import { sendTypingTelegram } from "./send.js";
|
||||
import { telegramSetupAdapter } from "./setup-core.js";
|
||||
import { telegramSetupWizard } from "./setup-surface.js";
|
||||
import { collectTelegramStatusIssues } from "./status-issues.js";
|
||||
import { parseTelegramTarget } from "./targets.js";
|
||||
|
||||
type TelegramSendFn = ReturnType<
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/telegram";
|
||||
|
||||
const { setRuntime: setTelegramRuntime, getRuntime: getTelegramRuntime } =
|
||||
createPluginRuntimeStore<PluginRuntime>("Telegram runtime not initialized");
|
||||
|
||||
@ -88,7 +88,7 @@ export async function promptTelegramAllowFromForAccount(params: {
|
||||
);
|
||||
}
|
||||
const { promptResolvedAllowFrom } =
|
||||
await import("../../../src/channels/plugins/setup-wizard-helpers.js");
|
||||
await import("../../../src/channels/plugins/setup-wizard-helpers.runtime.js");
|
||||
const unique = await promptResolvedAllowFrom({
|
||||
prompter: params.prompter,
|
||||
existing: resolved.config.allowFrom ?? [],
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/whatsapp";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/whatsapp";
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { whatsappPlugin } from "./src/channel.js";
|
||||
import { setWhatsAppRuntime } from "./src/runtime.js";
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createPluginRuntimeStore, type PluginRuntime } from "openclaw/plugin-sdk/whatsapp";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
|
||||
|
||||
const { setRuntime: setWhatsAppRuntime, getRuntime: getWhatsAppRuntime } =
|
||||
createPluginRuntimeStore<PluginRuntime>("WhatsApp runtime not initialized");
|
||||
|
||||
21
extensions/zai/detect.ts
Normal file
21
extensions/zai/detect.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {
|
||||
detectZaiEndpoint as detectZaiEndpointCore,
|
||||
type ZaiDetectedEndpoint,
|
||||
type ZaiEndpointId,
|
||||
} from "../../src/commands/zai-endpoint-detect.js";
|
||||
|
||||
type DetectZaiEndpointFn = typeof detectZaiEndpointCore;
|
||||
|
||||
let detectZaiEndpointImpl: DetectZaiEndpointFn = detectZaiEndpointCore;
|
||||
|
||||
export function setDetectZaiEndpointForTesting(fn?: DetectZaiEndpointFn): void {
|
||||
detectZaiEndpointImpl = fn ?? detectZaiEndpointCore;
|
||||
}
|
||||
|
||||
export async function detectZaiEndpoint(
|
||||
...args: Parameters<DetectZaiEndpointFn>
|
||||
): ReturnType<DetectZaiEndpointFn> {
|
||||
return await detectZaiEndpointImpl(...args);
|
||||
}
|
||||
|
||||
export type { ZaiDetectedEndpoint, ZaiEndpointId };
|
||||
@ -26,11 +26,11 @@ import {
|
||||
applyZaiProviderConfig,
|
||||
ZAI_DEFAULT_MODEL_REF,
|
||||
} from "../../src/commands/onboard-auth.js";
|
||||
import { detectZaiEndpoint, type ZaiEndpointId } from "../../src/commands/zai-endpoint-detect.js";
|
||||
import type { SecretInput } from "../../src/config/types.secrets.js";
|
||||
import { resolveRequiredHomeDir } from "../../src/infra/home-dir.js";
|
||||
import { fetchZaiUsage } from "../../src/infra/provider-usage.fetch.js";
|
||||
import { normalizeOptionalSecretInput } from "../../src/utils/normalize-secret-input.js";
|
||||
import { detectZaiEndpoint, type ZaiEndpointId } from "./detect.js";
|
||||
|
||||
const PROVIDER_ID = "zai";
|
||||
const GLM5_MODEL_ID = "glm-5";
|
||||
@ -97,6 +97,27 @@ function resolveZaiDefaultModel(modelIdOverride?: string): string {
|
||||
return modelIdOverride ? `zai/${modelIdOverride}` : ZAI_DEFAULT_MODEL_REF;
|
||||
}
|
||||
|
||||
async function promptForZaiEndpoint(ctx: ProviderAuthContext): Promise<ZaiEndpointId> {
|
||||
return await ctx.prompter.select<ZaiEndpointId>({
|
||||
message: "Select Z.AI endpoint",
|
||||
initialValue: "global",
|
||||
options: [
|
||||
{ value: "global", label: "Global", hint: "Z.AI Global (api.z.ai)" },
|
||||
{ value: "cn", label: "CN", hint: "Z.AI CN (open.bigmodel.cn)" },
|
||||
{
|
||||
value: "coding-global",
|
||||
label: "Coding-Plan-Global",
|
||||
hint: "GLM Coding Plan Global (api.z.ai)",
|
||||
},
|
||||
{
|
||||
value: "coding-cn",
|
||||
label: "Coding-Plan-CN",
|
||||
hint: "GLM Coding Plan CN (open.bigmodel.cn)",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async function runZaiApiKeyAuth(
|
||||
ctx: ProviderAuthContext,
|
||||
endpoint?: ZaiEndpointId,
|
||||
@ -116,7 +137,10 @@ async function runZaiApiKeyAuth(
|
||||
tokenProvider: normalizeOptionalSecretInput(ctx.opts?.zaiApiKey)
|
||||
? PROVIDER_ID
|
||||
: normalizeOptionalSecretInput(ctx.opts?.tokenProvider),
|
||||
secretInputMode: ctx.secretInputMode,
|
||||
secretInputMode:
|
||||
ctx.allowSecretRefPrompt === false
|
||||
? (ctx.secretInputMode ?? "plaintext")
|
||||
: ctx.secretInputMode,
|
||||
config: ctx.config,
|
||||
expectedProviders: [PROVIDER_ID, "z-ai"],
|
||||
provider: PROVIDER_ID,
|
||||
@ -138,7 +162,7 @@ async function runZaiApiKeyAuth(
|
||||
|
||||
const detected = await detectZaiEndpoint({ apiKey, ...(endpoint ? { endpoint } : {}) });
|
||||
const modelIdOverride = detected?.modelId;
|
||||
const nextEndpoint = detected?.endpoint ?? endpoint;
|
||||
const nextEndpoint = detected?.endpoint ?? endpoint ?? (await promptForZaiEndpoint(ctx));
|
||||
return {
|
||||
profiles: [
|
||||
{
|
||||
|
||||
@ -382,6 +382,7 @@
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^5.2.1",
|
||||
"file-type": "^21.3.2",
|
||||
"gaxios": "^7.1.3",
|
||||
"grammy": "^1.41.1",
|
||||
"hono": "4.12.7",
|
||||
"https-proxy-agent": "^8.0.0",
|
||||
|
||||
5
pnpm-lock.yaml
generated
5
pnpm-lock.yaml
generated
@ -125,6 +125,9 @@ importers:
|
||||
file-type:
|
||||
specifier: 21.3.2
|
||||
version: 21.3.2
|
||||
gaxios:
|
||||
specifier: ^7.1.3
|
||||
version: 7.1.3
|
||||
grammy:
|
||||
specifier: ^1.41.1
|
||||
version: 1.41.1
|
||||
@ -271,6 +274,8 @@ importers:
|
||||
specifier: 0.3.0
|
||||
version: 0.3.0(zod@4.3.6)
|
||||
|
||||
extensions/amazon-bedrock: {}
|
||||
|
||||
extensions/anthropic: {}
|
||||
|
||||
extensions/bluebubbles:
|
||||
|
||||
@ -8,6 +8,8 @@ import {
|
||||
} from "./runtime-postbuild-shared.mjs";
|
||||
|
||||
const GENERATED_BUNDLED_SKILLS_DIR = "bundled-skills";
|
||||
const TRANSIENT_COPY_ERROR_CODES = new Set(["EEXIST", "ENOENT", "ENOTEMPTY", "EBUSY"]);
|
||||
const COPY_RETRY_DELAYS_MS = [10, 25, 50];
|
||||
|
||||
export function rewritePackageExtensions(entries) {
|
||||
if (!Array.isArray(entries)) {
|
||||
@ -82,6 +84,39 @@ function resolveBundledSkillTarget(rawPath) {
|
||||
};
|
||||
}
|
||||
|
||||
function isTransientCopyError(error) {
|
||||
return (
|
||||
!!error &&
|
||||
typeof error === "object" &&
|
||||
typeof error.code === "string" &&
|
||||
TRANSIENT_COPY_ERROR_CODES.has(error.code)
|
||||
);
|
||||
}
|
||||
|
||||
function sleepSync(ms) {
|
||||
if (!Number.isFinite(ms) || ms <= 0) {
|
||||
return;
|
||||
}
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
||||
}
|
||||
|
||||
function copySkillPathWithRetry(params) {
|
||||
const maxAttempts = COPY_RETRY_DELAYS_MS.length + 1;
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
||||
try {
|
||||
removePathIfExists(params.targetPath);
|
||||
fs.mkdirSync(path.dirname(params.targetPath), { recursive: true });
|
||||
fs.cpSync(params.sourcePath, params.targetPath, params.copyOptions);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (!isTransientCopyError(error) || attempt === maxAttempts - 1) {
|
||||
throw error;
|
||||
}
|
||||
sleepSync(COPY_RETRY_DELAYS_MS[attempt] ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyDeclaredPluginSkillPaths(params) {
|
||||
const skills = Array.isArray(params.manifest.skills) ? params.manifest.skills : [];
|
||||
const copiedSkills = [];
|
||||
@ -104,21 +139,23 @@ function copyDeclaredPluginSkillPaths(params) {
|
||||
continue;
|
||||
}
|
||||
const targetPath = ensurePathInsideRoot(params.distPluginDir, target.outputPath);
|
||||
removePathIfExists(targetPath);
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
const shouldExcludeNestedNodeModules = /^node_modules(?:\/|$)/u.test(
|
||||
normalizeManifestRelativePath(raw),
|
||||
);
|
||||
fs.cpSync(sourcePath, targetPath, {
|
||||
dereference: true,
|
||||
force: true,
|
||||
recursive: true,
|
||||
filter: (candidatePath) => {
|
||||
if (!shouldExcludeNestedNodeModules || candidatePath === sourcePath) {
|
||||
return true;
|
||||
}
|
||||
const relativeCandidate = path.relative(sourcePath, candidatePath).replaceAll("\\", "/");
|
||||
return !relativeCandidate.split("/").includes("node_modules");
|
||||
copySkillPathWithRetry({
|
||||
sourcePath,
|
||||
targetPath,
|
||||
copyOptions: {
|
||||
dereference: true,
|
||||
force: true,
|
||||
recursive: true,
|
||||
filter: (candidatePath) => {
|
||||
if (!shouldExcludeNestedNodeModules || candidatePath === sourcePath) {
|
||||
return true;
|
||||
}
|
||||
const relativeCandidate = path.relative(sourcePath, candidatePath).replaceAll("\\", "/");
|
||||
return !relativeCandidate.split("/").includes("node_modules");
|
||||
},
|
||||
},
|
||||
});
|
||||
copiedSkills.push(target.manifestPath);
|
||||
|
||||
@ -1,38 +1,44 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:24-bookworm@sha256:9f3b13503acdf9bc1e0213ccb25ebe86ac881cad17636733a1da1be1d44509df
|
||||
FROM node:24-bookworm@sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
RUN useradd --create-home --shell /bin/bash appuser \
|
||||
&& mkdir -p /app \
|
||||
&& chown appuser:appuser /app
|
||||
|
||||
ENV HOME="/home/appuser"
|
||||
ENV NODE_OPTIONS="--disable-warning=ExperimentalWarning"
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY extensions/memory-core/package.json ./extensions/memory-core/package.json
|
||||
COPY patches ./patches
|
||||
USER appuser
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
COPY --chown=appuser:appuser package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY --chown=appuser:appuser ui/package.json ./ui/package.json
|
||||
COPY --chown=appuser:appuser extensions/memory-core/package.json ./extensions/memory-core/package.json
|
||||
COPY --chown=appuser:appuser patches ./patches
|
||||
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/home/appuser/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
COPY tsconfig.json tsconfig.plugin-sdk.dts.json tsdown.config.ts vitest.config.ts vitest.e2e.config.ts openclaw.mjs ./
|
||||
COPY src ./src
|
||||
COPY test ./test
|
||||
COPY scripts ./scripts
|
||||
COPY docs ./docs
|
||||
COPY skills ./skills
|
||||
COPY ui ./ui
|
||||
COPY extensions/memory-core ./extensions/memory-core
|
||||
COPY vendor/a2ui/renderers/lit ./vendor/a2ui/renderers/lit
|
||||
COPY apps/shared/OpenClawKit/Sources/OpenClawKit/Resources ./apps/shared/OpenClawKit/Sources/OpenClawKit/Resources
|
||||
COPY apps/shared/OpenClawKit/Tools/CanvasA2UI ./apps/shared/OpenClawKit/Tools/CanvasA2UI
|
||||
COPY --chown=appuser:appuser tsconfig.json tsconfig.plugin-sdk.dts.json tsdown.config.ts vitest.config.ts vitest.e2e.config.ts openclaw.mjs ./
|
||||
COPY --chown=appuser:appuser src ./src
|
||||
COPY --chown=appuser:appuser test ./test
|
||||
COPY --chown=appuser:appuser scripts ./scripts
|
||||
COPY --chown=appuser:appuser docs ./docs
|
||||
COPY --chown=appuser:appuser skills ./skills
|
||||
COPY --chown=appuser:appuser ui ./ui
|
||||
COPY --chown=appuser:appuser extensions ./extensions
|
||||
COPY --chown=appuser:appuser vendor/a2ui/renderers/lit ./vendor/a2ui/renderers/lit
|
||||
COPY --chown=appuser:appuser apps/shared/OpenClawKit/Sources/OpenClawKit/Resources ./apps/shared/OpenClawKit/Sources/OpenClawKit/Resources
|
||||
COPY --chown=appuser:appuser apps/shared/OpenClawKit/Tools/CanvasA2UI ./apps/shared/OpenClawKit/Tools/CanvasA2UI
|
||||
|
||||
RUN pnpm build
|
||||
RUN pnpm ui:build
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash appuser \
|
||||
&& chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
CMD ["bash"]
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:24-bookworm@sha256:9f3b13503acdf9bc1e0213ccb25ebe86ac881cad17636733a1da1be1d44509df
|
||||
FROM node:24-bookworm@sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash appuser \
|
||||
&& mkdir -p /app \
|
||||
&& chown appuser:appuser /app
|
||||
|
||||
ENV HOME="/home/appuser"
|
||||
|
||||
USER appuser
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY patches ./patches
|
||||
COPY --chown=appuser:appuser package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY --chown=appuser:appuser ui/package.json ./ui/package.json
|
||||
COPY --chown=appuser:appuser patches ./patches
|
||||
|
||||
# This image only exercises the root qrcode-terminal dependency path.
|
||||
# Keep the pre-install copy set limited to the manifests needed for root
|
||||
# workspace resolution so unrelated extension edits do not bust the layer.
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/home/appuser/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash appuser \
|
||||
&& chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
COPY --chown=appuser:appuser . .
|
||||
|
||||
@ -8,24 +8,69 @@ echo "Building Docker image..."
|
||||
docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR"
|
||||
|
||||
echo "Running plugins Docker E2E..."
|
||||
docker run --rm -t "$IMAGE_NAME" bash -lc '
|
||||
set -euo pipefail
|
||||
if [ -f dist/index.mjs ]; then
|
||||
OPENCLAW_ENTRY="dist/index.mjs"
|
||||
elif [ -f dist/index.js ]; then
|
||||
OPENCLAW_ENTRY="dist/index.js"
|
||||
else
|
||||
echo "Missing dist/index.(m)js (build output):"
|
||||
ls -la dist || true
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_ENTRY
|
||||
docker run --rm -i "$IMAGE_NAME" bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
home_dir=$(mktemp -d "/tmp/openclaw-plugins-e2e.XXXXXX")
|
||||
export HOME="$home_dir"
|
||||
mkdir -p "$HOME/.openclaw/extensions/demo-plugin"
|
||||
if [ -f dist/index.mjs ]; then
|
||||
OPENCLAW_ENTRY="dist/index.mjs"
|
||||
elif [ -f dist/index.js ]; then
|
||||
OPENCLAW_ENTRY="dist/index.js"
|
||||
else
|
||||
echo "Missing dist/index.(m)js (build output):"
|
||||
ls -la dist || true
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_ENTRY
|
||||
|
||||
cat > "$HOME/.openclaw/extensions/demo-plugin/index.js" <<'"'"'JS'"'"'
|
||||
home_dir=$(mktemp -d "/tmp/openclaw-plugins-e2e.XXXXXX")
|
||||
export HOME="$home_dir"
|
||||
|
||||
write_fixture_plugin() {
|
||||
local dir="$1"
|
||||
local id="$2"
|
||||
local version="$3"
|
||||
local method="$4"
|
||||
local name="$5"
|
||||
|
||||
mkdir -p "$dir"
|
||||
cat > "$dir/package.json" <<JSON
|
||||
{
|
||||
"name": "@openclaw/$id",
|
||||
"version": "$version",
|
||||
"openclaw": { "extensions": ["./index.js"] }
|
||||
}
|
||||
JSON
|
||||
cat > "$dir/index.js" <<JS
|
||||
module.exports = {
|
||||
id: "$id",
|
||||
name: "$name",
|
||||
register(api) {
|
||||
api.registerGatewayMethod("$method", async () => ({ ok: true }));
|
||||
},
|
||||
};
|
||||
JS
|
||||
cat > "$dir/openclaw.plugin.json" <<'JSON'
|
||||
{
|
||||
"id": "placeholder",
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
node - <<'NODE' "$dir/openclaw.plugin.json" "$id"
|
||||
const fs = require("node:fs");
|
||||
const file = process.argv[2];
|
||||
const id = process.argv[3];
|
||||
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
||||
parsed.id = id;
|
||||
fs.writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`);
|
||||
NODE
|
||||
}
|
||||
|
||||
mkdir -p "$HOME/.openclaw/extensions/demo-plugin"
|
||||
|
||||
cat > "$HOME/.openclaw/extensions/demo-plugin/index.js" <<'JS'
|
||||
module.exports = {
|
||||
id: "demo-plugin",
|
||||
name: "Demo Plugin",
|
||||
@ -38,7 +83,7 @@ module.exports = {
|
||||
},
|
||||
};
|
||||
JS
|
||||
cat > "$HOME/.openclaw/extensions/demo-plugin/openclaw.plugin.json" <<'"'"'JSON'"'"'
|
||||
cat > "$HOME/.openclaw/extensions/demo-plugin/openclaw.plugin.json" <<'JSON'
|
||||
{
|
||||
"id": "demo-plugin",
|
||||
"configSchema": {
|
||||
@ -48,9 +93,9 @@ JS
|
||||
}
|
||||
JSON
|
||||
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins.json
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins.json
|
||||
|
||||
node - <<'"'"'NODE'"'"'
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
|
||||
const data = JSON.parse(fs.readFileSync("/tmp/plugins.json", "utf8"));
|
||||
@ -79,17 +124,17 @@ if (diagErrors.length > 0) {
|
||||
console.log("ok");
|
||||
NODE
|
||||
|
||||
echo "Testing tgz install flow..."
|
||||
pack_dir="$(mktemp -d "/tmp/openclaw-plugin-pack.XXXXXX")"
|
||||
mkdir -p "$pack_dir/package"
|
||||
cat > "$pack_dir/package/package.json" <<'"'"'JSON'"'"'
|
||||
echo "Testing tgz install flow..."
|
||||
pack_dir="$(mktemp -d "/tmp/openclaw-plugin-pack.XXXXXX")"
|
||||
mkdir -p "$pack_dir/package"
|
||||
cat > "$pack_dir/package/package.json" <<'JSON'
|
||||
{
|
||||
"name": "@openclaw/demo-plugin-tgz",
|
||||
"version": "0.0.1",
|
||||
"openclaw": { "extensions": ["./index.js"] }
|
||||
}
|
||||
JSON
|
||||
cat > "$pack_dir/package/index.js" <<'"'"'JS'"'"'
|
||||
cat > "$pack_dir/package/index.js" <<'JS'
|
||||
module.exports = {
|
||||
id: "demo-plugin-tgz",
|
||||
name: "Demo Plugin TGZ",
|
||||
@ -98,7 +143,7 @@ module.exports = {
|
||||
},
|
||||
};
|
||||
JS
|
||||
cat > "$pack_dir/package/openclaw.plugin.json" <<'"'"'JSON'"'"'
|
||||
cat > "$pack_dir/package/openclaw.plugin.json" <<'JSON'
|
||||
{
|
||||
"id": "demo-plugin-tgz",
|
||||
"configSchema": {
|
||||
@ -107,12 +152,12 @@ JS
|
||||
}
|
||||
}
|
||||
JSON
|
||||
tar -czf /tmp/demo-plugin-tgz.tgz -C "$pack_dir" package
|
||||
tar -czf /tmp/demo-plugin-tgz.tgz -C "$pack_dir" package
|
||||
|
||||
node "$OPENCLAW_ENTRY" plugins install /tmp/demo-plugin-tgz.tgz
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins2.json
|
||||
node "$OPENCLAW_ENTRY" plugins install /tmp/demo-plugin-tgz.tgz
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins2.json
|
||||
|
||||
node - <<'"'"'NODE'"'"'
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
|
||||
const data = JSON.parse(fs.readFileSync("/tmp/plugins2.json", "utf8"));
|
||||
@ -127,16 +172,16 @@ if (!Array.isArray(plugin.gatewayMethods) || !plugin.gatewayMethods.includes("de
|
||||
console.log("ok");
|
||||
NODE
|
||||
|
||||
echo "Testing install from local folder (plugins.load.paths)..."
|
||||
dir_plugin="$(mktemp -d "/tmp/openclaw-plugin-dir.XXXXXX")"
|
||||
cat > "$dir_plugin/package.json" <<'"'"'JSON'"'"'
|
||||
echo "Testing install from local folder (plugins.load.paths)..."
|
||||
dir_plugin="$(mktemp -d "/tmp/openclaw-plugin-dir.XXXXXX")"
|
||||
cat > "$dir_plugin/package.json" <<'JSON'
|
||||
{
|
||||
"name": "@openclaw/demo-plugin-dir",
|
||||
"version": "0.0.1",
|
||||
"openclaw": { "extensions": ["./index.js"] }
|
||||
}
|
||||
JSON
|
||||
cat > "$dir_plugin/index.js" <<'"'"'JS'"'"'
|
||||
cat > "$dir_plugin/index.js" <<'JS'
|
||||
module.exports = {
|
||||
id: "demo-plugin-dir",
|
||||
name: "Demo Plugin DIR",
|
||||
@ -145,7 +190,7 @@ module.exports = {
|
||||
},
|
||||
};
|
||||
JS
|
||||
cat > "$dir_plugin/openclaw.plugin.json" <<'"'"'JSON'"'"'
|
||||
cat > "$dir_plugin/openclaw.plugin.json" <<'JSON'
|
||||
{
|
||||
"id": "demo-plugin-dir",
|
||||
"configSchema": {
|
||||
@ -155,10 +200,10 @@ JS
|
||||
}
|
||||
JSON
|
||||
|
||||
node "$OPENCLAW_ENTRY" plugins install "$dir_plugin"
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins3.json
|
||||
node "$OPENCLAW_ENTRY" plugins install "$dir_plugin"
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins3.json
|
||||
|
||||
node - <<'"'"'NODE'"'"'
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
|
||||
const data = JSON.parse(fs.readFileSync("/tmp/plugins3.json", "utf8"));
|
||||
@ -173,17 +218,17 @@ if (!Array.isArray(plugin.gatewayMethods) || !plugin.gatewayMethods.includes("de
|
||||
console.log("ok");
|
||||
NODE
|
||||
|
||||
echo "Testing install from npm spec (file:)..."
|
||||
file_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-filepack.XXXXXX")"
|
||||
mkdir -p "$file_pack_dir/package"
|
||||
cat > "$file_pack_dir/package/package.json" <<'"'"'JSON'"'"'
|
||||
echo "Testing install from npm spec (file:)..."
|
||||
file_pack_dir="$(mktemp -d "/tmp/openclaw-plugin-filepack.XXXXXX")"
|
||||
mkdir -p "$file_pack_dir/package"
|
||||
cat > "$file_pack_dir/package/package.json" <<'JSON'
|
||||
{
|
||||
"name": "@openclaw/demo-plugin-file",
|
||||
"version": "0.0.1",
|
||||
"openclaw": { "extensions": ["./index.js"] }
|
||||
}
|
||||
JSON
|
||||
cat > "$file_pack_dir/package/index.js" <<'"'"'JS'"'"'
|
||||
cat > "$file_pack_dir/package/index.js" <<'JS'
|
||||
module.exports = {
|
||||
id: "demo-plugin-file",
|
||||
name: "Demo Plugin FILE",
|
||||
@ -192,7 +237,7 @@ module.exports = {
|
||||
},
|
||||
};
|
||||
JS
|
||||
cat > "$file_pack_dir/package/openclaw.plugin.json" <<'"'"'JSON'"'"'
|
||||
cat > "$file_pack_dir/package/openclaw.plugin.json" <<'JSON'
|
||||
{
|
||||
"id": "demo-plugin-file",
|
||||
"configSchema": {
|
||||
@ -202,10 +247,10 @@ JS
|
||||
}
|
||||
JSON
|
||||
|
||||
node "$OPENCLAW_ENTRY" plugins install "file:$file_pack_dir/package"
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins4.json
|
||||
node "$OPENCLAW_ENTRY" plugins install "file:$file_pack_dir/package"
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins4.json
|
||||
|
||||
node - <<'"'"'NODE'"'"'
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
|
||||
const data = JSON.parse(fs.readFileSync("/tmp/plugins4.json", "utf8"));
|
||||
@ -220,8 +265,155 @@ if (!Array.isArray(plugin.gatewayMethods) || !plugin.gatewayMethods.includes("de
|
||||
console.log("ok");
|
||||
NODE
|
||||
|
||||
echo "Running bundle MCP CLI-agent e2e..."
|
||||
pnpm exec vitest run --config vitest.e2e.config.ts src/agents/cli-runner.bundle-mcp.e2e.test.ts
|
||||
'
|
||||
echo "Testing marketplace install and update flows..."
|
||||
marketplace_root="$HOME/.claude/plugins/marketplaces/fixture-marketplace"
|
||||
mkdir -p "$HOME/.claude/plugins" "$marketplace_root/.claude-plugin"
|
||||
write_fixture_plugin \
|
||||
"$marketplace_root/plugins/marketplace-shortcut" \
|
||||
"marketplace-shortcut" \
|
||||
"0.0.1" \
|
||||
"demo.marketplace.shortcut.v1" \
|
||||
"Marketplace Shortcut"
|
||||
write_fixture_plugin \
|
||||
"$marketplace_root/plugins/marketplace-direct" \
|
||||
"marketplace-direct" \
|
||||
"0.0.1" \
|
||||
"demo.marketplace.direct.v1" \
|
||||
"Marketplace Direct"
|
||||
cat > "$marketplace_root/.claude-plugin/marketplace.json" <<'JSON'
|
||||
{
|
||||
"name": "Fixture Marketplace",
|
||||
"version": "1.0.0",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "marketplace-shortcut",
|
||||
"version": "0.0.1",
|
||||
"description": "Shortcut install fixture",
|
||||
"source": "./plugins/marketplace-shortcut"
|
||||
},
|
||||
{
|
||||
"name": "marketplace-direct",
|
||||
"version": "0.0.1",
|
||||
"description": "Explicit marketplace fixture",
|
||||
"source": {
|
||||
"type": "path",
|
||||
"path": "./plugins/marketplace-direct"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
cat > "$HOME/.claude/plugins/known_marketplaces.json" <<JSON
|
||||
{
|
||||
"claude-fixtures": {
|
||||
"installLocation": "$marketplace_root",
|
||||
"source": {
|
||||
"type": "github",
|
||||
"repo": "openclaw/fixture-marketplace"
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
node "$OPENCLAW_ENTRY" plugins marketplace list claude-fixtures --json > /tmp/marketplace-list.json
|
||||
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
|
||||
const data = JSON.parse(fs.readFileSync("/tmp/marketplace-list.json", "utf8"));
|
||||
const names = (data.plugins || []).map((entry) => entry.name).sort();
|
||||
if (data.name !== "Fixture Marketplace") {
|
||||
throw new Error(`unexpected marketplace name: ${data.name}`);
|
||||
}
|
||||
if (!names.includes("marketplace-shortcut") || !names.includes("marketplace-direct")) {
|
||||
throw new Error(`unexpected marketplace plugins: ${names.join(", ")}`);
|
||||
}
|
||||
console.log("ok");
|
||||
NODE
|
||||
|
||||
node "$OPENCLAW_ENTRY" plugins install marketplace-shortcut@claude-fixtures
|
||||
node "$OPENCLAW_ENTRY" plugins install marketplace-direct --marketplace claude-fixtures
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins-marketplace.json
|
||||
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
|
||||
const data = JSON.parse(fs.readFileSync("/tmp/plugins-marketplace.json", "utf8"));
|
||||
const getPlugin = (id) => {
|
||||
const plugin = (data.plugins || []).find((entry) => entry.id === id);
|
||||
if (!plugin) throw new Error(`plugin not found: ${id}`);
|
||||
if (plugin.status !== "loaded") {
|
||||
throw new Error(`unexpected status for ${id}: ${plugin.status}`);
|
||||
}
|
||||
return plugin;
|
||||
};
|
||||
|
||||
const shortcut = getPlugin("marketplace-shortcut");
|
||||
const direct = getPlugin("marketplace-direct");
|
||||
if (shortcut.version !== "0.0.1") {
|
||||
throw new Error(`unexpected shortcut version: ${shortcut.version}`);
|
||||
}
|
||||
if (direct.version !== "0.0.1") {
|
||||
throw new Error(`unexpected direct version: ${direct.version}`);
|
||||
}
|
||||
if (!shortcut.gatewayMethods.includes("demo.marketplace.shortcut.v1")) {
|
||||
throw new Error("expected marketplace shortcut gateway method");
|
||||
}
|
||||
if (!direct.gatewayMethods.includes("demo.marketplace.direct.v1")) {
|
||||
throw new Error("expected marketplace direct gateway method");
|
||||
}
|
||||
console.log("ok");
|
||||
NODE
|
||||
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json");
|
||||
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
for (const id of ["marketplace-shortcut", "marketplace-direct"]) {
|
||||
const record = config.plugins?.installs?.[id];
|
||||
if (!record) throw new Error(`missing install record for ${id}`);
|
||||
if (record.source !== "marketplace") {
|
||||
throw new Error(`unexpected source for ${id}: ${record.source}`);
|
||||
}
|
||||
if (record.marketplaceSource !== "claude-fixtures") {
|
||||
throw new Error(`unexpected marketplace source for ${id}: ${record.marketplaceSource}`);
|
||||
}
|
||||
if (record.marketplacePlugin !== id) {
|
||||
throw new Error(`unexpected marketplace plugin for ${id}: ${record.marketplacePlugin}`);
|
||||
}
|
||||
}
|
||||
console.log("ok");
|
||||
NODE
|
||||
|
||||
write_fixture_plugin \
|
||||
"$marketplace_root/plugins/marketplace-shortcut" \
|
||||
"marketplace-shortcut" \
|
||||
"0.0.2" \
|
||||
"demo.marketplace.shortcut.v2" \
|
||||
"Marketplace Shortcut"
|
||||
node "$OPENCLAW_ENTRY" plugins update marketplace-shortcut --dry-run
|
||||
node "$OPENCLAW_ENTRY" plugins update marketplace-shortcut
|
||||
node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins-marketplace-updated.json
|
||||
|
||||
node - <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
|
||||
const data = JSON.parse(fs.readFileSync("/tmp/plugins-marketplace-updated.json", "utf8"));
|
||||
const plugin = (data.plugins || []).find((entry) => entry.id === "marketplace-shortcut");
|
||||
if (!plugin) throw new Error("updated marketplace plugin not found");
|
||||
if (plugin.version !== "0.0.2") {
|
||||
throw new Error(`unexpected updated version: ${plugin.version}`);
|
||||
}
|
||||
if (!plugin.gatewayMethods.includes("demo.marketplace.shortcut.v2")) {
|
||||
throw new Error(`expected updated gateway method, got ${plugin.gatewayMethods.join(", ")}`);
|
||||
}
|
||||
console.log("ok");
|
||||
NODE
|
||||
|
||||
echo "Running bundle MCP CLI-agent e2e..."
|
||||
pnpm exec vitest run --config vitest.e2e.config.ts src/agents/cli-runner.bundle-mcp.e2e.test.ts
|
||||
EOF
|
||||
|
||||
echo "OK"
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { normalizeProviderId } from "../model-selection.js";
|
||||
import { listProfilesForProvider } from "./profiles.js";
|
||||
import { suggestOAuthProfileIdForLegacyDefault } from "./repair.js";
|
||||
import type { AuthProfileStore } from "./types.js";
|
||||
|
||||
let providerRuntimePromise:
|
||||
@ -34,38 +31,5 @@ export async function formatAuthDoctorHint(params: {
|
||||
if (typeof pluginHint === "string" && pluginHint.trim()) {
|
||||
return pluginHint;
|
||||
}
|
||||
|
||||
const providerKey = normalizeProviderId(params.provider);
|
||||
if (providerKey !== "anthropic") {
|
||||
return "";
|
||||
}
|
||||
|
||||
const legacyProfileId = params.profileId ?? "anthropic:default";
|
||||
const suggested = suggestOAuthProfileIdForLegacyDefault({
|
||||
cfg: params.cfg,
|
||||
store: params.store,
|
||||
provider: providerKey,
|
||||
legacyProfileId,
|
||||
});
|
||||
if (!suggested || suggested === legacyProfileId) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const storeOauthProfiles = listProfilesForProvider(params.store, providerKey)
|
||||
.filter((id) => params.store.profiles[id]?.type === "oauth")
|
||||
.join(", ");
|
||||
|
||||
const cfgMode = params.cfg?.auth?.profiles?.[legacyProfileId]?.mode;
|
||||
const cfgProvider = params.cfg?.auth?.profiles?.[legacyProfileId]?.provider;
|
||||
|
||||
return [
|
||||
"Doctor hint (for GitHub issue):",
|
||||
`- provider: ${providerKey}`,
|
||||
`- config: ${legacyProfileId}${
|
||||
cfgProvider || cfgMode ? ` (provider=${cfgProvider ?? "?"}, mode=${cfgMode ?? "?"})` : ""
|
||||
}`,
|
||||
`- auth store oauth profiles: ${storeOauthProfiles || "(none)"}`,
|
||||
`- suggested profile: ${suggested}`,
|
||||
`Fix: run "${formatCliCommand("openclaw doctor --yes")}"`,
|
||||
].join("\n");
|
||||
return "";
|
||||
}
|
||||
|
||||
@ -3,12 +3,20 @@ import { describe, expect, it, vi } from "vitest";
|
||||
vi.mock("../../plugins/provider-runtime.js", () => ({
|
||||
resolveProviderCacheTtlEligibility: (params: {
|
||||
context: { provider: string; modelId: string };
|
||||
}) =>
|
||||
params.context.provider === "openrouter"
|
||||
? ["anthropic/", "moonshot/", "moonshotai/", "zai/"].some((prefix) =>
|
||||
params.context.modelId.startsWith(prefix),
|
||||
)
|
||||
: undefined,
|
||||
}) => {
|
||||
if (params.context.provider === "anthropic") {
|
||||
return true;
|
||||
}
|
||||
if (params.context.provider === "moonshot" || params.context.provider === "zai") {
|
||||
return true;
|
||||
}
|
||||
if (params.context.provider === "openrouter") {
|
||||
return ["anthropic/", "moonshot/", "moonshotai/", "zai/"].some((prefix) =>
|
||||
params.context.modelId.startsWith(prefix),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
}));
|
||||
|
||||
import { isCacheTtlEligibleProvider } from "./cache-ttl.js";
|
||||
|
||||
@ -10,8 +10,6 @@ export type CacheTtlEntryData = {
|
||||
modelId?: string;
|
||||
};
|
||||
|
||||
const CACHE_TTL_NATIVE_PROVIDERS = new Set(["moonshot", "zai"]);
|
||||
|
||||
export function isCacheTtlEligibleProvider(provider: string, modelId: string): boolean {
|
||||
const normalizedProvider = provider.toLowerCase();
|
||||
const normalizedModelId = modelId.toLowerCase();
|
||||
@ -25,17 +23,6 @@ export function isCacheTtlEligibleProvider(provider: string, modelId: string): b
|
||||
if (pluginEligibility !== undefined) {
|
||||
return pluginEligibility;
|
||||
}
|
||||
if (normalizedProvider === "kilocode" && normalizedModelId.startsWith("anthropic/")) {
|
||||
return true;
|
||||
}
|
||||
// Legacy fallback for tests / plugin-disabled contexts. The Anthropic plugin
|
||||
// owns this policy in normal runtime.
|
||||
if (normalizedProvider === "anthropic") {
|
||||
return true;
|
||||
}
|
||||
if (CACHE_TTL_NATIVE_PROVIDERS.has(normalizedProvider)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -7,9 +7,6 @@ import {
|
||||
estimateTokens,
|
||||
SessionManager,
|
||||
} from "@mariozechner/pi-coding-agent";
|
||||
import { resolveSignalReactionLevel } from "../../../extensions/signal/src/reaction-level.js";
|
||||
import { resolveTelegramInlineButtonsScope } from "../../../extensions/telegram/src/inline-buttons.js";
|
||||
import { resolveTelegramReactionLevel } from "../../../extensions/telegram/src/reaction-level.js";
|
||||
import { resolveHeartbeatPrompt } from "../../auto-reply/heartbeat.js";
|
||||
import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import { resolveChannelCapabilities } from "../../config/channel-capabilities.js";
|
||||
@ -22,6 +19,11 @@ import { createInternalHookEvent, triggerInternalHook } from "../../hooks/intern
|
||||
import { getMachineDisplayName } from "../../infra/machine-name.js";
|
||||
import { generateSecureToken } from "../../infra/secure-random.js";
|
||||
import { getMemorySearchManager } from "../../memory/index.js";
|
||||
import { resolveSignalReactionLevel } from "../../plugin-sdk-internal/signal.js";
|
||||
import {
|
||||
resolveTelegramInlineButtonsScope,
|
||||
resolveTelegramReactionLevel,
|
||||
} from "../../plugin-sdk-internal/telegram.js";
|
||||
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
|
||||
import { prepareProviderRuntimeAuth } from "../../plugins/provider-runtime.js";
|
||||
import { type enqueueCommand, enqueueCommandInLane } from "../../process/command-queue.js";
|
||||
|
||||
@ -7,9 +7,6 @@ import {
|
||||
DefaultResourceLoader,
|
||||
SessionManager,
|
||||
} from "@mariozechner/pi-coding-agent";
|
||||
import { resolveSignalReactionLevel } from "../../../../extensions/signal/src/reaction-level.js";
|
||||
import { resolveTelegramInlineButtonsScope } from "../../../../extensions/telegram/src/inline-buttons.js";
|
||||
import { resolveTelegramReactionLevel } from "../../../../extensions/telegram/src/reaction-level.js";
|
||||
import { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js";
|
||||
import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
@ -19,6 +16,11 @@ import {
|
||||
ensureGlobalUndiciStreamTimeouts,
|
||||
} from "../../../infra/net/undici-global-dispatcher.js";
|
||||
import { MAX_IMAGE_BYTES } from "../../../media/constants.js";
|
||||
import { resolveSignalReactionLevel } from "../../../plugin-sdk-internal/signal.js";
|
||||
import {
|
||||
resolveTelegramInlineButtonsScope,
|
||||
resolveTelegramReactionLevel,
|
||||
} from "../../../plugin-sdk-internal/telegram.js";
|
||||
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
|
||||
import type {
|
||||
PluginHookAgentContext,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { ImageContent } from "@mariozechner/pi-ai";
|
||||
import { loadWebMedia } from "../../../../extensions/whatsapp/src/media.js";
|
||||
import { loadWebMedia } from "../../../plugin-sdk/web-media.js";
|
||||
import { resolveUserPath } from "../../../utils.js";
|
||||
import type { ImageSanitizationLimits } from "../../image-sanitization.js";
|
||||
import {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { getPresence } from "../../../extensions/discord/src/monitor/presence-cache.js";
|
||||
import type { DiscordActionConfig } from "../../config/config.js";
|
||||
import {
|
||||
addRoleDiscord,
|
||||
createChannelDiscord,
|
||||
@ -19,8 +19,8 @@ import {
|
||||
setChannelPermissionDiscord,
|
||||
uploadEmojiDiscord,
|
||||
uploadStickerDiscord,
|
||||
} from "../../../extensions/discord/src/send.js";
|
||||
import type { DiscordActionConfig } from "../../config/config.js";
|
||||
} from "../../plugin-sdk-internal/discord.js";
|
||||
import { getPresence } from "../../plugin-sdk-internal/discord.js";
|
||||
import {
|
||||
type ActionGate,
|
||||
jsonResult,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { readDiscordComponentSpec } from "../../../extensions/discord/src/components.js";
|
||||
import type { DiscordActionConfig } from "../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
createThreadDiscord,
|
||||
deleteMessageDiscord,
|
||||
@ -21,14 +22,15 @@ import {
|
||||
sendStickerDiscord,
|
||||
sendVoiceMessageDiscord,
|
||||
unpinMessageDiscord,
|
||||
} from "../../../extensions/discord/src/send.js";
|
||||
} from "../../plugin-sdk-internal/discord.js";
|
||||
import type {
|
||||
DiscordSendComponents,
|
||||
DiscordSendEmbeds,
|
||||
} from "../../../extensions/discord/src/send.shared.js";
|
||||
import { resolveDiscordChannelId } from "../../../extensions/discord/src/targets.js";
|
||||
import type { DiscordActionConfig } from "../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
} from "../../plugin-sdk-internal/discord.js";
|
||||
import {
|
||||
readDiscordComponentSpec,
|
||||
resolveDiscordChannelId,
|
||||
} from "../../plugin-sdk-internal/discord.js";
|
||||
import { readBooleanParam } from "../../plugin-sdk/boolean-param.js";
|
||||
import { resolvePollMaxSelections } from "../../polls.js";
|
||||
import { withNormalizedTimestamp } from "../date-time.js";
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import type { DiscordActionConfig } from "../../config/config.js";
|
||||
import {
|
||||
banMemberDiscord,
|
||||
hasAnyGuildPermissionDiscord,
|
||||
kickMemberDiscord,
|
||||
timeoutMemberDiscord,
|
||||
} from "../../../extensions/discord/src/send.js";
|
||||
import type { DiscordActionConfig } from "../../config/config.js";
|
||||
} from "../../plugin-sdk-internal/discord.js";
|
||||
import { type ActionGate, jsonResult, readStringParam } from "./common.js";
|
||||
import {
|
||||
isDiscordModerationAction,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Activity, UpdatePresenceData } from "@buape/carbon/gateway";
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { getGateway } from "../../../extensions/discord/src/monitor/gateway-registry.js";
|
||||
import type { DiscordActionConfig } from "../../config/config.js";
|
||||
import { getGateway } from "../../plugin-sdk-internal/discord.js";
|
||||
import { type ActionGate, jsonResult, readStringParam } from "./common.js";
|
||||
|
||||
const ACTIVITY_TYPE_MAP: Record<string, number> = {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { createDiscordActionGate } from "../../../extensions/discord/src/accounts.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { createDiscordActionGate } from "../../plugin-sdk-internal/discord.js";
|
||||
import { readStringParam } from "./common.js";
|
||||
import { handleDiscordGuildAction } from "./discord-actions-guild.js";
|
||||
import { handleDiscordMessagingAction } from "./discord-actions-messaging.js";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { type Context, complete } from "@mariozechner/pi-ai";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { loadWebMedia } from "../../../extensions/whatsapp/src/media.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { loadWebMedia } from "../../plugin-sdk/web-media.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import { isMinimaxVlmModel, isMinimaxVlmProvider, minimaxUnderstandImage } from "../minimax-vlm.js";
|
||||
import {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { type Api, type Model } from "@mariozechner/pi-ai";
|
||||
import { getDefaultLocalRoots } from "../../../extensions/whatsapp/src/media.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { getDefaultLocalRoots } from "../../plugin-sdk/web-media.js";
|
||||
import type { ImageModelConfig } from "./image-tool.helpers.js";
|
||||
import { getApiKeyForModel, normalizeWorkspaceDir, requireApiKey } from "./tool-runtime.helpers.js";
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { type Context, complete } from "@mariozechner/pi-ai";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { loadWebMediaRaw } from "../../../extensions/whatsapp/src/media.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { extractPdfContent, type PdfExtractedContent } from "../../media/pdf-extract.js";
|
||||
import { loadWebMediaRaw } from "../../plugin-sdk/web-media.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import {
|
||||
coerceImageModelConfig,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { resolveSlackAccount } from "../../../extensions/slack/src/accounts.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
deleteSlackMessage,
|
||||
downloadSlackFile,
|
||||
@ -15,11 +15,14 @@ import {
|
||||
removeSlackReaction,
|
||||
sendSlackMessage,
|
||||
unpinSlackMessage,
|
||||
} from "../../../extensions/slack/src/actions.js";
|
||||
import { parseSlackBlocksInput } from "../../../extensions/slack/src/blocks-input.js";
|
||||
import { recordSlackThreadParticipation } from "../../../extensions/slack/src/sent-thread-cache.js";
|
||||
import { parseSlackTarget, resolveSlackChannelId } from "../../../extensions/slack/src/targets.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
} from "../../plugin-sdk-internal/slack.js";
|
||||
import {
|
||||
parseSlackBlocksInput,
|
||||
parseSlackTarget,
|
||||
recordSlackThreadParticipation,
|
||||
resolveSlackAccount,
|
||||
resolveSlackChannelId,
|
||||
} from "../../plugin-sdk-internal/slack.js";
|
||||
import { withNormalizedTimestamp } from "../date-time.js";
|
||||
import {
|
||||
createActionGate,
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import {
|
||||
createTelegramActionGate,
|
||||
resolveTelegramPollActionGateState,
|
||||
} from "../../../extensions/telegram/src/accounts.js";
|
||||
} from "../../plugin-sdk-internal/telegram.js";
|
||||
import type {
|
||||
TelegramButtonStyle,
|
||||
TelegramInlineButtons,
|
||||
} from "../../../extensions/telegram/src/button-types.js";
|
||||
} from "../../plugin-sdk-internal/telegram.js";
|
||||
import {
|
||||
resolveTelegramInlineButtonsScope,
|
||||
resolveTelegramTargetChatType,
|
||||
} from "../../../extensions/telegram/src/inline-buttons.js";
|
||||
import { resolveTelegramReactionLevel } from "../../../extensions/telegram/src/reaction-level.js";
|
||||
} from "../../plugin-sdk-internal/telegram.js";
|
||||
import {
|
||||
createForumTopicTelegram,
|
||||
deleteMessageTelegram,
|
||||
@ -21,10 +21,13 @@ import {
|
||||
sendMessageTelegram,
|
||||
sendPollTelegram,
|
||||
sendStickerTelegram,
|
||||
} from "../../../extensions/telegram/src/send.js";
|
||||
import { getCacheStats, searchStickers } from "../../../extensions/telegram/src/sticker-cache.js";
|
||||
import { resolveTelegramToken } from "../../../extensions/telegram/src/token.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
} from "../../plugin-sdk-internal/telegram.js";
|
||||
import {
|
||||
getCacheStats,
|
||||
resolveTelegramReactionLevel,
|
||||
resolveTelegramToken,
|
||||
searchStickers,
|
||||
} from "../../plugin-sdk-internal/telegram.js";
|
||||
import { readBooleanParam } from "../../plugin-sdk/boolean-param.js";
|
||||
import { resolvePollMaxSelections } from "../../polls.js";
|
||||
import {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { sendReactionWhatsApp } from "../../../extensions/whatsapp/src/send.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { sendReactionWhatsApp } from "../../plugin-sdk-internal/whatsapp.js";
|
||||
import { createActionGate, jsonResult, readReactionParams, readStringParam } from "./common.js";
|
||||
import { resolveAuthorizedWhatsAppOutboundTarget } from "./whatsapp-target-auth.js";
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { resolveWhatsAppAccount } from "../../../extensions/whatsapp/src/accounts.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { resolveWhatsAppAccount } from "../../plugin-sdk-internal/whatsapp.js";
|
||||
import { resolveWhatsAppOutboundTarget } from "../../whatsapp/resolve-outbound-target.js";
|
||||
import { ToolAuthorizationError } from "./common.js";
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { AcpRuntimeError } from "../../acp/runtime/errors.js";
|
||||
import { setDefaultChannelPluginRegistryForTests } from "../../commands/channel-test-helpers.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js";
|
||||
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
|
||||
@ -395,6 +396,7 @@ async function runInternalAcpCommand(params: {
|
||||
|
||||
describe("/acp command", () => {
|
||||
beforeEach(() => {
|
||||
setDefaultChannelPluginRegistryForTests();
|
||||
acpManagerTesting.resetAcpSessionManagerForTests();
|
||||
hoisted.listAcpSessionEntriesMock.mockReset().mockResolvedValue([]);
|
||||
hoisted.callGatewayMock.mockReset().mockResolvedValue({ ok: true });
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { buildFeishuConversationId } from "../../../../extensions/feishu/src/conversation-id.js";
|
||||
import {
|
||||
buildTelegramTopicConversationId,
|
||||
normalizeConversationText,
|
||||
@ -7,6 +6,7 @@ import {
|
||||
import { DISCORD_THREAD_BINDING_CHANNEL } from "../../../channels/thread-bindings-policy.js";
|
||||
import { resolveConversationIdFromTargets } from "../../../infra/outbound/conversation-id.js";
|
||||
import { getSessionBindingService } from "../../../infra/outbound/session-binding-service.js";
|
||||
import { buildFeishuConversationId } from "../../../plugin-sdk/feishu.js";
|
||||
import { parseAgentSessionKey } from "../../../routing/session-key.js";
|
||||
import type { HandleCommandsParams } from "../commands-types.js";
|
||||
import { parseDiscordParentChannelFromSessionKey } from "../discord-parent-channel.js";
|
||||
|
||||
@ -3,7 +3,7 @@ import { logVerbose } from "../../globals.js";
|
||||
import {
|
||||
isTelegramExecApprovalApprover,
|
||||
isTelegramExecApprovalClientEnabled,
|
||||
} from "../../plugin-sdk/telegram.js";
|
||||
} from "../../plugin-sdk-internal/telegram.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
|
||||
import { requireGatewayClientScopeForInternalChannel } from "./command-gates.js";
|
||||
import type { CommandHandler } from "./commands-types.js";
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
calculateTotalPages,
|
||||
getModelsPageSize,
|
||||
type ProviderInfo,
|
||||
} from "../../plugin-sdk/telegram.js";
|
||||
} from "../../plugin-sdk-internal/telegram.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
import { rejectUnauthorizedCommand } from "./command-gates.js";
|
||||
import type { CommandHandler } from "./commands-types.js";
|
||||
|
||||
@ -22,7 +22,12 @@ const SESSION_COMMAND_PREFIX = "/session";
|
||||
const SESSION_DURATION_OFF_VALUES = new Set(["off", "disable", "disabled", "none", "0"]);
|
||||
const SESSION_ACTION_IDLE = "idle";
|
||||
const SESSION_ACTION_MAX_AGE = "max-age";
|
||||
const channelRuntime = createPluginRuntime().channel;
|
||||
let cachedChannelRuntime: ReturnType<typeof createPluginRuntime>["channel"] | undefined;
|
||||
|
||||
function getChannelRuntime() {
|
||||
cachedChannelRuntime ??= createPluginRuntime().channel;
|
||||
return cachedChannelRuntime;
|
||||
}
|
||||
|
||||
function resolveSessionCommandUsage() {
|
||||
return "Usage: /session idle <duration|off> | /session max-age <duration|off> (example: /session idle 24h)";
|
||||
@ -373,6 +378,7 @@ export const handleSessionCommand: CommandHandler = async (params, allowTextComm
|
||||
const threadId =
|
||||
params.ctx.MessageThreadId != null ? String(params.ctx.MessageThreadId).trim() : "";
|
||||
const telegramConversationId = onTelegram ? resolveTelegramConversationId(params) : undefined;
|
||||
const channelRuntime = getChannelRuntime();
|
||||
|
||||
const discordManager = onDiscord
|
||||
? channelRuntime.discord.threadBindings.getManager(accountId)
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
} from "../../agents/model-selection.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { buildBrowseProvidersButton } from "../../plugin-sdk/telegram.js";
|
||||
import { buildBrowseProvidersButton } from "../../plugin-sdk-internal/telegram.js";
|
||||
import { shortenHomePath } from "../../utils.js";
|
||||
import { resolveSelectedAndActiveModel } from "../model-runtime.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { StickerMetadata } from "../../extensions/telegram/src/bot/types.js";
|
||||
import type { ChannelId } from "../channels/plugins/types.js";
|
||||
import type {
|
||||
MediaUnderstandingDecision,
|
||||
MediaUnderstandingOutput,
|
||||
} from "../media-understanding/types.js";
|
||||
import type { StickerMetadata } from "../plugin-sdk-internal/telegram.js";
|
||||
import type { InputProvenance } from "../sessions/input-provenance.js";
|
||||
import type { InternalMessageChannel } from "../utils/message-channel.js";
|
||||
import type { CommandArgs } from "./commands-registry.types.js";
|
||||
|
||||
226
src/auto-reply/thinking.shared.ts
Normal file
226
src/auto-reply/thinking.shared.ts
Normal file
@ -0,0 +1,226 @@
|
||||
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
|
||||
export type VerboseLevel = "off" | "on" | "full";
|
||||
export type NoticeLevel = "off" | "on" | "full";
|
||||
export type ElevatedLevel = "off" | "on" | "ask" | "full";
|
||||
export type ElevatedMode = "off" | "ask" | "full";
|
||||
export type ReasoningLevel = "off" | "on" | "stream";
|
||||
export type UsageDisplayLevel = "off" | "tokens" | "full";
|
||||
export type ThinkingCatalogEntry = {
|
||||
provider: string;
|
||||
id: string;
|
||||
reasoning?: boolean;
|
||||
};
|
||||
|
||||
const BASE_THINKING_LEVELS: ThinkLevel[] = ["off", "minimal", "low", "medium", "high", "adaptive"];
|
||||
|
||||
export function normalizeProviderId(provider?: string | null): string {
|
||||
if (!provider) {
|
||||
return "";
|
||||
}
|
||||
const normalized = provider.trim().toLowerCase();
|
||||
if (normalized === "z.ai" || normalized === "z-ai") {
|
||||
return "zai";
|
||||
}
|
||||
if (normalized === "bedrock" || normalized === "aws-bedrock") {
|
||||
return "amazon-bedrock";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function isBinaryThinkingProvider(provider?: string | null): boolean {
|
||||
return normalizeProviderId(provider) === "zai";
|
||||
}
|
||||
|
||||
// Normalize user-provided thinking level strings to the canonical enum.
|
||||
export function normalizeThinkLevel(raw?: string | null): ThinkLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.trim().toLowerCase();
|
||||
const collapsed = key.replace(/[\s_-]+/g, "");
|
||||
if (collapsed === "adaptive" || collapsed === "auto") {
|
||||
return "adaptive";
|
||||
}
|
||||
if (collapsed === "xhigh" || collapsed === "extrahigh") {
|
||||
return "xhigh";
|
||||
}
|
||||
if (["off"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["on", "enable", "enabled"].includes(key)) {
|
||||
return "low";
|
||||
}
|
||||
if (["min", "minimal"].includes(key)) {
|
||||
return "minimal";
|
||||
}
|
||||
if (["low", "thinkhard", "think-hard", "think_hard"].includes(key)) {
|
||||
return "low";
|
||||
}
|
||||
if (["mid", "med", "medium", "thinkharder", "think-harder", "harder"].includes(key)) {
|
||||
return "medium";
|
||||
}
|
||||
if (
|
||||
["high", "ultra", "ultrathink", "think-hard", "thinkhardest", "highest", "max"].includes(key)
|
||||
) {
|
||||
return "high";
|
||||
}
|
||||
if (["think"].includes(key)) {
|
||||
return "minimal";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function listThinkingLevels(
|
||||
_provider?: string | null,
|
||||
_model?: string | null,
|
||||
): ThinkLevel[] {
|
||||
return [...BASE_THINKING_LEVELS];
|
||||
}
|
||||
|
||||
export function listThinkingLevelLabels(provider?: string | null, model?: string | null): string[] {
|
||||
if (isBinaryThinkingProvider(provider)) {
|
||||
return ["off", "on"];
|
||||
}
|
||||
return listThinkingLevels(provider, model);
|
||||
}
|
||||
|
||||
export function formatThinkingLevels(
|
||||
provider?: string | null,
|
||||
model?: string | null,
|
||||
separator = ", ",
|
||||
): string {
|
||||
return listThinkingLevelLabels(provider, model).join(separator);
|
||||
}
|
||||
|
||||
export function formatXHighModelHint(): string {
|
||||
return "provider models that advertise xhigh reasoning";
|
||||
}
|
||||
|
||||
export function resolveThinkingDefaultForModel(params: {
|
||||
provider: string;
|
||||
model: string;
|
||||
catalog?: ThinkingCatalogEntry[];
|
||||
}): ThinkLevel {
|
||||
const candidate = params.catalog?.find(
|
||||
(entry) => entry.provider === params.provider && entry.id === params.model,
|
||||
);
|
||||
if (candidate?.reasoning) {
|
||||
return "low";
|
||||
}
|
||||
return "off";
|
||||
}
|
||||
|
||||
type OnOffFullLevel = "off" | "on" | "full";
|
||||
|
||||
function normalizeOnOffFullLevel(raw?: string | null): OnOffFullLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["full", "all", "everything"].includes(key)) {
|
||||
return "full";
|
||||
}
|
||||
if (["on", "minimal", "true", "yes", "1"].includes(key)) {
|
||||
return "on";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function normalizeVerboseLevel(raw?: string | null): VerboseLevel | undefined {
|
||||
return normalizeOnOffFullLevel(raw);
|
||||
}
|
||||
|
||||
export function normalizeNoticeLevel(raw?: string | null): NoticeLevel | undefined {
|
||||
return normalizeOnOffFullLevel(raw);
|
||||
}
|
||||
|
||||
export function normalizeUsageDisplay(raw?: string | null): UsageDisplayLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0", "disable", "disabled"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["on", "true", "yes", "1", "enable", "enabled"].includes(key)) {
|
||||
return "tokens";
|
||||
}
|
||||
if (["tokens", "token", "tok", "minimal", "min"].includes(key)) {
|
||||
return "tokens";
|
||||
}
|
||||
if (["full", "session"].includes(key)) {
|
||||
return "full";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveResponseUsageMode(raw?: string | null): UsageDisplayLevel {
|
||||
return normalizeUsageDisplay(raw) ?? "off";
|
||||
}
|
||||
|
||||
export function normalizeFastMode(raw?: string | boolean | null): boolean | undefined {
|
||||
if (typeof raw === "boolean") {
|
||||
return raw;
|
||||
}
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
|
||||
return false;
|
||||
}
|
||||
if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
|
||||
return true;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function normalizeElevatedLevel(raw?: string | null): ElevatedLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["full", "auto", "auto-approve", "autoapprove"].includes(key)) {
|
||||
return "full";
|
||||
}
|
||||
if (["ask", "prompt", "approval", "approve"].includes(key)) {
|
||||
return "ask";
|
||||
}
|
||||
if (["on", "true", "yes", "1"].includes(key)) {
|
||||
return "on";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveElevatedMode(level?: ElevatedLevel | null): ElevatedMode {
|
||||
if (!level || level === "off") {
|
||||
return "off";
|
||||
}
|
||||
if (level === "full") {
|
||||
return "full";
|
||||
}
|
||||
return "ask";
|
||||
}
|
||||
|
||||
export function normalizeReasoningLevel(raw?: string | null): ReasoningLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0", "hide", "hidden", "disable", "disabled"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["on", "true", "yes", "1", "show", "visible", "enable", "enabled"].includes(key)) {
|
||||
return "on";
|
||||
}
|
||||
if (["stream", "streaming", "draft", "live"].includes(key)) {
|
||||
return "stream";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@ -118,6 +118,10 @@ describe("listThinkingLevelLabels", () => {
|
||||
expect(listThinkingLevelLabels("zai", "glm-4.7")).toEqual(["off", "on"]);
|
||||
});
|
||||
|
||||
it("keeps built-in binary thinking fallback without provider runtime", () => {
|
||||
expect(listThinkingLevelLabels("zai", "glm-4.7")).toEqual(["off", "on"]);
|
||||
});
|
||||
|
||||
it("returns full levels for non-ZAI", () => {
|
||||
expect(listThinkingLevelLabels("openai", "gpt-4.1-mini")).toContain("low");
|
||||
expect(listThinkingLevelLabels("openai", "gpt-4.1-mini")).not.toContain("on");
|
||||
@ -144,7 +148,23 @@ describe("resolveThinkingDefaultForModel", () => {
|
||||
).toBe("adaptive");
|
||||
});
|
||||
|
||||
it("treats Bedrock Anthropic aliases as adaptive", () => {
|
||||
it("uses provider-advertised adaptive defaults for Bedrock aliases", () => {
|
||||
providerRuntimeMocks.resolveProviderDefaultThinkingLevel.mockImplementation(
|
||||
({ provider, context }) =>
|
||||
provider === "amazon-bedrock" && context.modelId === "claude-sonnet-4-6"
|
||||
? "adaptive"
|
||||
: undefined,
|
||||
);
|
||||
|
||||
expect(
|
||||
resolveThinkingDefaultForModel({ provider: "aws-bedrock", model: "claude-sonnet-4-6" }),
|
||||
).toBe("adaptive");
|
||||
});
|
||||
|
||||
it("keeps built-in adaptive defaults without provider runtime", () => {
|
||||
expect(
|
||||
resolveThinkingDefaultForModel({ provider: "anthropic", model: "claude-opus-4-6" }),
|
||||
).toBe("adaptive");
|
||||
expect(
|
||||
resolveThinkingDefaultForModel({ provider: "aws-bedrock", model: "claude-sonnet-4-6" }),
|
||||
).toBe("adaptive");
|
||||
|
||||
@ -1,38 +1,40 @@
|
||||
import {
|
||||
formatThinkingLevels as formatThinkingLevelsFallback,
|
||||
isBinaryThinkingProvider as isBinaryThinkingProviderFallback,
|
||||
listThinkingLevelLabels as listThinkingLevelLabelsFallback,
|
||||
listThinkingLevels as listThinkingLevelsFallback,
|
||||
normalizeProviderId,
|
||||
resolveThinkingDefaultForModel as resolveThinkingDefaultForModelFallback,
|
||||
} from "./thinking.shared.js";
|
||||
import type { ThinkLevel, ThinkingCatalogEntry } from "./thinking.shared.js";
|
||||
export {
|
||||
formatXHighModelHint,
|
||||
normalizeElevatedLevel,
|
||||
normalizeFastMode,
|
||||
normalizeNoticeLevel,
|
||||
normalizeReasoningLevel,
|
||||
normalizeThinkLevel,
|
||||
normalizeUsageDisplay,
|
||||
normalizeVerboseLevel,
|
||||
resolveResponseUsageMode,
|
||||
resolveElevatedMode,
|
||||
} from "./thinking.shared.js";
|
||||
export type {
|
||||
ElevatedLevel,
|
||||
ElevatedMode,
|
||||
NoticeLevel,
|
||||
ReasoningLevel,
|
||||
ThinkLevel,
|
||||
ThinkingCatalogEntry,
|
||||
UsageDisplayLevel,
|
||||
VerboseLevel,
|
||||
} from "./thinking.shared.js";
|
||||
import {
|
||||
resolveProviderBinaryThinking,
|
||||
resolveProviderDefaultThinkingLevel,
|
||||
resolveProviderXHighThinking,
|
||||
} from "../plugins/provider-runtime.js";
|
||||
|
||||
export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "adaptive";
|
||||
export type VerboseLevel = "off" | "on" | "full";
|
||||
export type NoticeLevel = "off" | "on" | "full";
|
||||
export type ElevatedLevel = "off" | "on" | "ask" | "full";
|
||||
export type ElevatedMode = "off" | "ask" | "full";
|
||||
export type ReasoningLevel = "off" | "on" | "stream";
|
||||
export type UsageDisplayLevel = "off" | "tokens" | "full";
|
||||
export type ThinkingCatalogEntry = {
|
||||
provider: string;
|
||||
id: string;
|
||||
reasoning?: boolean;
|
||||
};
|
||||
|
||||
const CLAUDE_46_MODEL_RE = /claude-(?:opus|sonnet)-4(?:\.|-)6(?:$|[-.])/i;
|
||||
|
||||
function normalizeProviderId(provider?: string | null): string {
|
||||
if (!provider) {
|
||||
return "";
|
||||
}
|
||||
const normalized = provider.trim().toLowerCase();
|
||||
if (normalized === "z.ai" || normalized === "z-ai") {
|
||||
return "zai";
|
||||
}
|
||||
if (normalized === "bedrock" || normalized === "aws-bedrock") {
|
||||
return "amazon-bedrock";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function isBinaryThinkingProvider(provider?: string | null, model?: string | null): boolean {
|
||||
const normalizedProvider = normalizeProviderId(provider);
|
||||
if (!normalizedProvider) {
|
||||
@ -49,46 +51,7 @@ export function isBinaryThinkingProvider(provider?: string | null, model?: strin
|
||||
if (typeof pluginDecision === "boolean") {
|
||||
return pluginDecision;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize user-provided thinking level strings to the canonical enum.
|
||||
export function normalizeThinkLevel(raw?: string | null): ThinkLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.trim().toLowerCase();
|
||||
const collapsed = key.replace(/[\s_-]+/g, "");
|
||||
if (collapsed === "adaptive" || collapsed === "auto") {
|
||||
return "adaptive";
|
||||
}
|
||||
if (collapsed === "xhigh" || collapsed === "extrahigh") {
|
||||
return "xhigh";
|
||||
}
|
||||
if (["off"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["on", "enable", "enabled"].includes(key)) {
|
||||
return "low";
|
||||
}
|
||||
if (["min", "minimal"].includes(key)) {
|
||||
return "minimal";
|
||||
}
|
||||
if (["low", "thinkhard", "think-hard", "think_hard"].includes(key)) {
|
||||
return "low";
|
||||
}
|
||||
if (["mid", "med", "medium", "thinkharder", "think-harder", "harder"].includes(key)) {
|
||||
return "medium";
|
||||
}
|
||||
if (
|
||||
["high", "ultra", "ultrathink", "think-hard", "thinkhardest", "highest", "max"].includes(key)
|
||||
) {
|
||||
return "high";
|
||||
}
|
||||
if (["think"].includes(key)) {
|
||||
return "minimal";
|
||||
}
|
||||
return undefined;
|
||||
return isBinaryThinkingProviderFallback(provider);
|
||||
}
|
||||
|
||||
export function supportsXHighThinking(provider?: string | null, model?: string | null): boolean {
|
||||
@ -113,11 +76,10 @@ export function supportsXHighThinking(provider?: string | null, model?: string |
|
||||
}
|
||||
|
||||
export function listThinkingLevels(provider?: string | null, model?: string | null): ThinkLevel[] {
|
||||
const levels: ThinkLevel[] = ["off", "minimal", "low", "medium", "high"];
|
||||
const levels = listThinkingLevelsFallback(provider, model);
|
||||
if (supportsXHighThinking(provider, model)) {
|
||||
levels.push("xhigh");
|
||||
levels.splice(levels.length - 1, 0, "xhigh");
|
||||
}
|
||||
levels.push("adaptive");
|
||||
return levels;
|
||||
}
|
||||
|
||||
@ -125,7 +87,7 @@ export function listThinkingLevelLabels(provider?: string | null, model?: string
|
||||
if (isBinaryThinkingProvider(provider, model)) {
|
||||
return ["off", "on"];
|
||||
}
|
||||
return listThinkingLevels(provider, model);
|
||||
return listThinkingLevelLabelsFallback(provider, model);
|
||||
}
|
||||
|
||||
export function formatThinkingLevels(
|
||||
@ -133,11 +95,9 @@ export function formatThinkingLevels(
|
||||
model?: string | null,
|
||||
separator = ", ",
|
||||
): string {
|
||||
return listThinkingLevelLabels(provider, model).join(separator);
|
||||
}
|
||||
|
||||
export function formatXHighModelHint(): string {
|
||||
return "provider models that advertise xhigh reasoning";
|
||||
return supportsXHighThinking(provider, model)
|
||||
? listThinkingLevelLabels(provider, model).join(separator)
|
||||
: formatThinkingLevelsFallback(provider, model, separator);
|
||||
}
|
||||
|
||||
export function resolveThinkingDefaultForModel(params: {
|
||||
@ -146,7 +106,6 @@ export function resolveThinkingDefaultForModel(params: {
|
||||
catalog?: ThinkingCatalogEntry[];
|
||||
}): ThinkLevel {
|
||||
const normalizedProvider = normalizeProviderId(params.provider);
|
||||
const modelLower = params.model.trim().toLowerCase();
|
||||
const candidate = params.catalog?.find(
|
||||
(entry) => entry.provider === params.provider && entry.id === params.model,
|
||||
);
|
||||
@ -161,133 +120,5 @@ export function resolveThinkingDefaultForModel(params: {
|
||||
if (pluginDecision) {
|
||||
return pluginDecision;
|
||||
}
|
||||
|
||||
if (normalizedProvider === "amazon-bedrock" && CLAUDE_46_MODEL_RE.test(modelLower)) {
|
||||
return "adaptive";
|
||||
}
|
||||
if (candidate?.reasoning) {
|
||||
return "low";
|
||||
}
|
||||
return "off";
|
||||
}
|
||||
|
||||
type OnOffFullLevel = "off" | "on" | "full";
|
||||
|
||||
function normalizeOnOffFullLevel(raw?: string | null): OnOffFullLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["full", "all", "everything"].includes(key)) {
|
||||
return "full";
|
||||
}
|
||||
if (["on", "minimal", "true", "yes", "1"].includes(key)) {
|
||||
return "on";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Normalize verbose flags used to toggle agent verbosity.
|
||||
export function normalizeVerboseLevel(raw?: string | null): VerboseLevel | undefined {
|
||||
return normalizeOnOffFullLevel(raw);
|
||||
}
|
||||
|
||||
// Normalize system notice flags used to toggle system notifications.
|
||||
export function normalizeNoticeLevel(raw?: string | null): NoticeLevel | undefined {
|
||||
return normalizeOnOffFullLevel(raw);
|
||||
}
|
||||
|
||||
// Normalize response-usage display modes used to toggle per-response usage footers.
|
||||
export function normalizeUsageDisplay(raw?: string | null): UsageDisplayLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0", "disable", "disabled"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["on", "true", "yes", "1", "enable", "enabled"].includes(key)) {
|
||||
return "tokens";
|
||||
}
|
||||
if (["tokens", "token", "tok", "minimal", "min"].includes(key)) {
|
||||
return "tokens";
|
||||
}
|
||||
if (["full", "session"].includes(key)) {
|
||||
return "full";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveResponseUsageMode(raw?: string | null): UsageDisplayLevel {
|
||||
return normalizeUsageDisplay(raw) ?? "off";
|
||||
}
|
||||
|
||||
// Normalize fast-mode flags used to toggle low-latency model behavior.
|
||||
export function normalizeFastMode(raw?: string | boolean | null): boolean | undefined {
|
||||
if (typeof raw === "boolean") {
|
||||
return raw;
|
||||
}
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0", "disable", "disabled", "normal"].includes(key)) {
|
||||
return false;
|
||||
}
|
||||
if (["on", "true", "yes", "1", "enable", "enabled", "fast"].includes(key)) {
|
||||
return true;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Normalize elevated flags used to toggle elevated bash permissions.
|
||||
export function normalizeElevatedLevel(raw?: string | null): ElevatedLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["full", "auto", "auto-approve", "autoapprove"].includes(key)) {
|
||||
return "full";
|
||||
}
|
||||
if (["ask", "prompt", "approval", "approve"].includes(key)) {
|
||||
return "ask";
|
||||
}
|
||||
if (["on", "true", "yes", "1"].includes(key)) {
|
||||
return "on";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function resolveElevatedMode(level?: ElevatedLevel | null): ElevatedMode {
|
||||
if (!level || level === "off") {
|
||||
return "off";
|
||||
}
|
||||
if (level === "full") {
|
||||
return "full";
|
||||
}
|
||||
return "ask";
|
||||
}
|
||||
|
||||
// Normalize reasoning visibility flags used to toggle reasoning exposure.
|
||||
export function normalizeReasoningLevel(raw?: string | null): ReasoningLevel | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const key = raw.toLowerCase();
|
||||
if (["off", "false", "no", "0", "hide", "hidden", "disable", "disabled"].includes(key)) {
|
||||
return "off";
|
||||
}
|
||||
if (["on", "true", "yes", "1", "show", "visible", "enable", "enabled"].includes(key)) {
|
||||
return "on";
|
||||
}
|
||||
if (["stream", "streaming", "draft", "live"].includes(key)) {
|
||||
return "stream";
|
||||
}
|
||||
return undefined;
|
||||
return resolveThinkingDefaultForModelFallback(params);
|
||||
}
|
||||
|
||||
@ -7,19 +7,15 @@ export {
|
||||
monitorWebChannel,
|
||||
resolveHeartbeatRecipients,
|
||||
runWebHeartbeatOnce,
|
||||
type WebChannelStatus,
|
||||
type WebMonitorTuning,
|
||||
} from "../extensions/whatsapp/src/auto-reply.js";
|
||||
} from "./plugin-sdk-internal/whatsapp.js";
|
||||
export {
|
||||
extractMediaPlaceholder,
|
||||
extractText,
|
||||
monitorWebInbox,
|
||||
type WebInboundMessage,
|
||||
type WebListenerCloseReason,
|
||||
} from "../extensions/whatsapp/src/inbound.js";
|
||||
export { loginWeb } from "../extensions/whatsapp/src/login.js";
|
||||
export { loadWebMedia, optimizeImageToJpeg } from "../extensions/whatsapp/src/media.js";
|
||||
export { sendMessageWhatsApp } from "../extensions/whatsapp/src/send.js";
|
||||
} from "./plugin-sdk-internal/whatsapp.js";
|
||||
export { loginWeb } from "./plugin-sdk-internal/whatsapp.js";
|
||||
export { loadWebMedia, optimizeImageToJpeg } from "./plugin-sdk-internal/whatsapp.js";
|
||||
export { sendMessageWhatsApp } from "./plugin-sdk-internal/whatsapp.js";
|
||||
export {
|
||||
createWaSocket,
|
||||
formatError,
|
||||
@ -30,4 +26,4 @@ export {
|
||||
WA_WEB_AUTH_DIR,
|
||||
waitForWaConnection,
|
||||
webAuthExists,
|
||||
} from "../extensions/whatsapp/src/session.js";
|
||||
} from "./plugin-sdk-internal/whatsapp.js";
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
// Shim: re-exports from extension
|
||||
export * from "../../../../extensions/discord/src/channel-actions.js";
|
||||
// Public entrypoint for the Discord channel action adapter.
|
||||
export * from "../../../plugin-sdk-internal/discord.js";
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
resolveSignalAccount,
|
||||
resolveSignalReactionLevel,
|
||||
sendReactionSignal,
|
||||
} from "../../../plugin-sdk/signal.js";
|
||||
} from "../../../plugin-sdk-internal/signal.js";
|
||||
import type { ChannelMessageActionAdapter, ChannelMessageActionName } from "../types.js";
|
||||
import { resolveReactionMessageId } from "./reaction-message-id.js";
|
||||
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "../../../../extensions/telegram/src/channel-actions.js";
|
||||
// Public entrypoint for the Telegram channel action adapter.
|
||||
export * from "../../../plugin-sdk-internal/telegram.js";
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
// Shim: re-exports from extensions/whatsapp/src/agent-tools-login.ts
|
||||
export * from "../../../../extensions/whatsapp/src/agent-tools-login.js";
|
||||
// Shim: keep legacy import path while the runtime loads the plugin SDK surface.
|
||||
export * from "../../../plugin-sdk-internal/whatsapp.js";
|
||||
|
||||
88
src/channels/plugins/bundled.ts
Normal file
88
src/channels/plugins/bundled.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { bluebubblesPlugin } from "../../../extensions/bluebubbles/src/channel.js";
|
||||
import { discordPlugin } from "../../../extensions/discord/src/channel.js";
|
||||
import { discordSetupPlugin } from "../../../extensions/discord/src/channel.setup.js";
|
||||
import { setDiscordRuntime } from "../../../extensions/discord/src/runtime.js";
|
||||
import { feishuPlugin } from "../../../extensions/feishu/src/channel.js";
|
||||
import { googlechatPlugin } from "../../../extensions/googlechat/src/channel.js";
|
||||
import { imessagePlugin } from "../../../extensions/imessage/src/channel.js";
|
||||
import { imessageSetupPlugin } from "../../../extensions/imessage/src/channel.setup.js";
|
||||
import { ircPlugin } from "../../../extensions/irc/src/channel.js";
|
||||
import { linePlugin } from "../../../extensions/line/src/channel.js";
|
||||
import { lineSetupPlugin } from "../../../extensions/line/src/channel.setup.js";
|
||||
import { setLineRuntime } from "../../../extensions/line/src/runtime.js";
|
||||
import { matrixPlugin } from "../../../extensions/matrix/src/channel.js";
|
||||
import { mattermostPlugin } from "../../../extensions/mattermost/src/channel.js";
|
||||
import { msteamsPlugin } from "../../../extensions/msteams/src/channel.js";
|
||||
import { nextcloudTalkPlugin } from "../../../extensions/nextcloud-talk/src/channel.js";
|
||||
import { nostrPlugin } from "../../../extensions/nostr/src/channel.js";
|
||||
import { signalPlugin } from "../../../extensions/signal/src/channel.js";
|
||||
import { signalSetupPlugin } from "../../../extensions/signal/src/channel.setup.js";
|
||||
import { slackPlugin } from "../../../extensions/slack/src/channel.js";
|
||||
import { slackSetupPlugin } from "../../../extensions/slack/src/channel.setup.js";
|
||||
import { synologyChatPlugin } from "../../../extensions/synology-chat/src/channel.js";
|
||||
import { telegramPlugin } from "../../../extensions/telegram/src/channel.js";
|
||||
import { telegramSetupPlugin } from "../../../extensions/telegram/src/channel.setup.js";
|
||||
import { setTelegramRuntime } from "../../../extensions/telegram/src/runtime.js";
|
||||
import { tlonPlugin } from "../../../extensions/tlon/src/channel.js";
|
||||
import { whatsappPlugin } from "../../../extensions/whatsapp/src/channel.js";
|
||||
import { whatsappSetupPlugin } from "../../../extensions/whatsapp/src/channel.setup.js";
|
||||
import { zaloPlugin } from "../../../extensions/zalo/src/channel.js";
|
||||
import { zalouserPlugin } from "../../../extensions/zalouser/src/channel.js";
|
||||
import type { ChannelId, ChannelPlugin } from "./types.js";
|
||||
|
||||
export const bundledChannelPlugins = [
|
||||
bluebubblesPlugin,
|
||||
discordPlugin,
|
||||
feishuPlugin,
|
||||
googlechatPlugin,
|
||||
imessagePlugin,
|
||||
ircPlugin,
|
||||
linePlugin,
|
||||
matrixPlugin,
|
||||
mattermostPlugin,
|
||||
msteamsPlugin,
|
||||
nextcloudTalkPlugin,
|
||||
nostrPlugin,
|
||||
signalPlugin,
|
||||
slackPlugin,
|
||||
synologyChatPlugin,
|
||||
telegramPlugin,
|
||||
tlonPlugin,
|
||||
whatsappPlugin,
|
||||
zaloPlugin,
|
||||
zalouserPlugin,
|
||||
] as ChannelPlugin[];
|
||||
|
||||
export const bundledChannelSetupPlugins = [
|
||||
telegramSetupPlugin,
|
||||
whatsappSetupPlugin,
|
||||
discordSetupPlugin,
|
||||
ircPlugin,
|
||||
googlechatPlugin,
|
||||
slackSetupPlugin,
|
||||
signalSetupPlugin,
|
||||
imessageSetupPlugin,
|
||||
lineSetupPlugin,
|
||||
] as ChannelPlugin[];
|
||||
|
||||
const bundledChannelPluginsById = new Map(
|
||||
bundledChannelPlugins.map((plugin) => [plugin.id, plugin] as const),
|
||||
);
|
||||
|
||||
export function getBundledChannelPlugin(id: ChannelId): ChannelPlugin | undefined {
|
||||
return bundledChannelPluginsById.get(id);
|
||||
}
|
||||
|
||||
export function requireBundledChannelPlugin(id: ChannelId): ChannelPlugin {
|
||||
const plugin = getBundledChannelPlugin(id);
|
||||
if (!plugin) {
|
||||
throw new Error(`missing bundled channel plugin: ${id}`);
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export const bundledChannelRuntimeSetters = {
|
||||
setDiscordRuntime,
|
||||
setLineRuntime,
|
||||
setTelegramRuntime,
|
||||
};
|
||||
@ -1,33 +1,11 @@
|
||||
import { expect, vi } from "vitest";
|
||||
import { bluebubblesPlugin } from "../../../../extensions/bluebubbles/src/channel.js";
|
||||
import { discordPlugin } from "../../../../extensions/discord/src/channel.js";
|
||||
import { setDiscordRuntime } from "../../../../extensions/discord/src/runtime.js";
|
||||
import { feishuPlugin } from "../../../../extensions/feishu/src/channel.js";
|
||||
import { googlechatPlugin } from "../../../../extensions/googlechat/src/channel.js";
|
||||
import { imessagePlugin } from "../../../../extensions/imessage/src/channel.js";
|
||||
import { ircPlugin } from "../../../../extensions/irc/src/channel.js";
|
||||
import { linePlugin } from "../../../../extensions/line/src/channel.js";
|
||||
import { setLineRuntime } from "../../../../extensions/line/src/runtime.js";
|
||||
import { matrixPlugin } from "../../../../extensions/matrix/src/channel.js";
|
||||
import { mattermostPlugin } from "../../../../extensions/mattermost/src/channel.js";
|
||||
import { msteamsPlugin } from "../../../../extensions/msteams/src/channel.js";
|
||||
import { nextcloudTalkPlugin } from "../../../../extensions/nextcloud-talk/src/channel.js";
|
||||
import { nostrPlugin } from "../../../../extensions/nostr/src/channel.js";
|
||||
import { signalPlugin } from "../../../../extensions/signal/src/channel.js";
|
||||
import { slackPlugin } from "../../../../extensions/slack/src/channel.js";
|
||||
import { synologyChatPlugin } from "../../../../extensions/synology-chat/src/channel.js";
|
||||
import { telegramPlugin } from "../../../../extensions/telegram/src/channel.js";
|
||||
import { setTelegramRuntime } from "../../../../extensions/telegram/src/runtime.js";
|
||||
import { tlonPlugin } from "../../../../extensions/tlon/src/channel.js";
|
||||
import { whatsappPlugin } from "../../../../extensions/whatsapp/src/channel.js";
|
||||
import { zaloPlugin } from "../../../../extensions/zalo/src/channel.js";
|
||||
import { zalouserPlugin } from "../../../../extensions/zalouser/src/channel.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import {
|
||||
resolveDefaultLineAccountId,
|
||||
resolveLineAccount,
|
||||
listLineAccountIds,
|
||||
} from "../../../line/accounts.js";
|
||||
import { bundledChannelRuntimeSetters, requireBundledChannelPlugin } from "../bundled.js";
|
||||
import type { ChannelPlugin } from "../types.js";
|
||||
|
||||
type PluginContractEntry = {
|
||||
@ -84,7 +62,7 @@ const telegramGetCapabilitiesMock = vi.fn();
|
||||
const discordListActionsMock = vi.fn();
|
||||
const discordGetCapabilitiesMock = vi.fn();
|
||||
|
||||
setTelegramRuntime({
|
||||
bundledChannelRuntimeSetters.setTelegramRuntime({
|
||||
channel: {
|
||||
telegram: {
|
||||
messageActions: {
|
||||
@ -95,7 +73,7 @@ setTelegramRuntime({
|
||||
},
|
||||
} as never);
|
||||
|
||||
setDiscordRuntime({
|
||||
bundledChannelRuntimeSetters.setDiscordRuntime({
|
||||
channel: {
|
||||
discord: {
|
||||
messageActions: {
|
||||
@ -106,7 +84,7 @@ setDiscordRuntime({
|
||||
},
|
||||
} as never);
|
||||
|
||||
setLineRuntime({
|
||||
bundledChannelRuntimeSetters.setLineRuntime({
|
||||
channel: {
|
||||
line: {
|
||||
listLineAccountIds,
|
||||
@ -118,32 +96,32 @@ setLineRuntime({
|
||||
} as never);
|
||||
|
||||
export const pluginContractRegistry: PluginContractEntry[] = [
|
||||
{ id: "bluebubbles", plugin: bluebubblesPlugin },
|
||||
{ id: "discord", plugin: discordPlugin },
|
||||
{ id: "feishu", plugin: feishuPlugin },
|
||||
{ id: "googlechat", plugin: googlechatPlugin },
|
||||
{ id: "imessage", plugin: imessagePlugin },
|
||||
{ id: "irc", plugin: ircPlugin },
|
||||
{ id: "line", plugin: linePlugin },
|
||||
{ id: "matrix", plugin: matrixPlugin },
|
||||
{ id: "mattermost", plugin: mattermostPlugin },
|
||||
{ id: "msteams", plugin: msteamsPlugin },
|
||||
{ id: "nextcloud-talk", plugin: nextcloudTalkPlugin },
|
||||
{ id: "nostr", plugin: nostrPlugin },
|
||||
{ id: "signal", plugin: signalPlugin },
|
||||
{ id: "slack", plugin: slackPlugin },
|
||||
{ id: "synology-chat", plugin: synologyChatPlugin },
|
||||
{ id: "telegram", plugin: telegramPlugin },
|
||||
{ id: "tlon", plugin: tlonPlugin },
|
||||
{ id: "whatsapp", plugin: whatsappPlugin },
|
||||
{ id: "zalo", plugin: zaloPlugin },
|
||||
{ id: "zalouser", plugin: zalouserPlugin },
|
||||
{ id: "bluebubbles", plugin: requireBundledChannelPlugin("bluebubbles") },
|
||||
{ id: "discord", plugin: requireBundledChannelPlugin("discord") },
|
||||
{ id: "feishu", plugin: requireBundledChannelPlugin("feishu") },
|
||||
{ id: "googlechat", plugin: requireBundledChannelPlugin("googlechat") },
|
||||
{ id: "imessage", plugin: requireBundledChannelPlugin("imessage") },
|
||||
{ id: "irc", plugin: requireBundledChannelPlugin("irc") },
|
||||
{ id: "line", plugin: requireBundledChannelPlugin("line") },
|
||||
{ id: "matrix", plugin: requireBundledChannelPlugin("matrix") },
|
||||
{ id: "mattermost", plugin: requireBundledChannelPlugin("mattermost") },
|
||||
{ id: "msteams", plugin: requireBundledChannelPlugin("msteams") },
|
||||
{ id: "nextcloud-talk", plugin: requireBundledChannelPlugin("nextcloud-talk") },
|
||||
{ id: "nostr", plugin: requireBundledChannelPlugin("nostr") },
|
||||
{ id: "signal", plugin: requireBundledChannelPlugin("signal") },
|
||||
{ id: "slack", plugin: requireBundledChannelPlugin("slack") },
|
||||
{ id: "synology-chat", plugin: requireBundledChannelPlugin("synology-chat") },
|
||||
{ id: "telegram", plugin: requireBundledChannelPlugin("telegram") },
|
||||
{ id: "tlon", plugin: requireBundledChannelPlugin("tlon") },
|
||||
{ id: "whatsapp", plugin: requireBundledChannelPlugin("whatsapp") },
|
||||
{ id: "zalo", plugin: requireBundledChannelPlugin("zalo") },
|
||||
{ id: "zalouser", plugin: requireBundledChannelPlugin("zalouser") },
|
||||
];
|
||||
|
||||
export const actionContractRegistry: ActionsContractEntry[] = [
|
||||
{
|
||||
id: "slack",
|
||||
plugin: slackPlugin,
|
||||
plugin: requireBundledChannelPlugin("slack"),
|
||||
unsupportedAction: "poll",
|
||||
cases: [
|
||||
{
|
||||
@ -217,7 +195,7 @@ export const actionContractRegistry: ActionsContractEntry[] = [
|
||||
},
|
||||
{
|
||||
id: "mattermost",
|
||||
plugin: mattermostPlugin,
|
||||
plugin: requireBundledChannelPlugin("mattermost"),
|
||||
unsupportedAction: "poll",
|
||||
cases: [
|
||||
{
|
||||
@ -265,7 +243,7 @@ export const actionContractRegistry: ActionsContractEntry[] = [
|
||||
},
|
||||
{
|
||||
id: "telegram",
|
||||
plugin: telegramPlugin,
|
||||
plugin: requireBundledChannelPlugin("telegram"),
|
||||
cases: [
|
||||
{
|
||||
name: "forwards runtime-backed Telegram actions and capabilities",
|
||||
@ -283,7 +261,7 @@ export const actionContractRegistry: ActionsContractEntry[] = [
|
||||
},
|
||||
{
|
||||
id: "discord",
|
||||
plugin: discordPlugin,
|
||||
plugin: requireBundledChannelPlugin("discord"),
|
||||
cases: [
|
||||
{
|
||||
name: "forwards runtime-backed Discord actions and capabilities",
|
||||
@ -304,7 +282,7 @@ export const actionContractRegistry: ActionsContractEntry[] = [
|
||||
export const setupContractRegistry: SetupContractEntry[] = [
|
||||
{
|
||||
id: "slack",
|
||||
plugin: slackPlugin,
|
||||
plugin: requireBundledChannelPlugin("slack"),
|
||||
cases: [
|
||||
{
|
||||
name: "default account stores tokens and enables the channel",
|
||||
@ -334,7 +312,7 @@ export const setupContractRegistry: SetupContractEntry[] = [
|
||||
},
|
||||
{
|
||||
id: "mattermost",
|
||||
plugin: mattermostPlugin,
|
||||
plugin: requireBundledChannelPlugin("mattermost"),
|
||||
cases: [
|
||||
{
|
||||
name: "default account stores token and normalized base URL",
|
||||
@ -363,7 +341,7 @@ export const setupContractRegistry: SetupContractEntry[] = [
|
||||
},
|
||||
{
|
||||
id: "line",
|
||||
plugin: linePlugin,
|
||||
plugin: requireBundledChannelPlugin("line"),
|
||||
cases: [
|
||||
{
|
||||
name: "default account stores token and secret",
|
||||
@ -396,7 +374,7 @@ export const setupContractRegistry: SetupContractEntry[] = [
|
||||
export const statusContractRegistry: StatusContractEntry[] = [
|
||||
{
|
||||
id: "slack",
|
||||
plugin: slackPlugin,
|
||||
plugin: requireBundledChannelPlugin("slack"),
|
||||
cases: [
|
||||
{
|
||||
name: "configured account produces a configured status snapshot",
|
||||
@ -424,7 +402,7 @@ export const statusContractRegistry: StatusContractEntry[] = [
|
||||
},
|
||||
{
|
||||
id: "mattermost",
|
||||
plugin: mattermostPlugin,
|
||||
plugin: requireBundledChannelPlugin("mattermost"),
|
||||
cases: [
|
||||
{
|
||||
name: "configured account preserves connectivity details in the snapshot",
|
||||
@ -455,7 +433,7 @@ export const statusContractRegistry: StatusContractEntry[] = [
|
||||
},
|
||||
{
|
||||
id: "line",
|
||||
plugin: linePlugin,
|
||||
plugin: requireBundledChannelPlugin("line"),
|
||||
cases: [
|
||||
{
|
||||
name: "configured account produces a webhook status snapshot",
|
||||
|
||||
@ -10,7 +10,7 @@ import type {
|
||||
GroupToolPolicyConfig,
|
||||
} from "../../config/types.tools.js";
|
||||
import { resolveExactLineGroupConfigKey } from "../../line/group-keys.js";
|
||||
import { inspectSlackAccount } from "../../plugin-sdk/slack.js";
|
||||
import { inspectSlackAccount } from "../../plugin-sdk-internal/slack.js";
|
||||
import { normalizeAtHashSlug, normalizeHyphenSlug } from "../../shared/string-normalization.js";
|
||||
import type { ChannelGroupContext } from "./types.js";
|
||||
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
// Shim: re-exports from extension
|
||||
export * from "../../../../extensions/discord/src/normalize.js";
|
||||
@ -1 +0,0 @@
|
||||
export * from "../../../../extensions/telegram/src/normalize.js";
|
||||
@ -1,2 +1,25 @@
|
||||
// Shim: re-exports from extensions/whatsapp/src/normalize.ts
|
||||
export * from "../../../../extensions/whatsapp/src/normalize.js";
|
||||
import { normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js";
|
||||
import { looksLikeHandleOrPhoneTarget, trimMessagingTarget } from "./shared.js";
|
||||
|
||||
export function normalizeWhatsAppMessagingTarget(raw: string): string | undefined {
|
||||
const trimmed = trimMessagingTarget(raw);
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
return normalizeWhatsAppTarget(trimmed) ?? undefined;
|
||||
}
|
||||
|
||||
export function normalizeWhatsAppAllowFromEntries(allowFrom: Array<string | number>): string[] {
|
||||
return allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map((entry) => (entry === "*" ? entry : normalizeWhatsAppTarget(entry)))
|
||||
.filter((entry): entry is string => Boolean(entry));
|
||||
}
|
||||
|
||||
export function looksLikeWhatsAppTargetId(raw: string): boolean {
|
||||
return looksLikeHandleOrPhoneTarget({
|
||||
raw,
|
||||
prefixPattern: /^whatsapp:/i,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,17 +1,9 @@
|
||||
import { discordSetupPlugin } from "../../../extensions/discord/src/channel.setup.js";
|
||||
import { googlechatPlugin } from "../../../extensions/googlechat/src/channel.js";
|
||||
import { imessageSetupPlugin } from "../../../extensions/imessage/src/channel.setup.js";
|
||||
import { ircPlugin } from "../../../extensions/irc/src/channel.js";
|
||||
import { lineSetupPlugin } from "../../../extensions/line/src/channel.setup.js";
|
||||
import { signalSetupPlugin } from "../../../extensions/signal/src/channel.setup.js";
|
||||
import { slackSetupPlugin } from "../../../extensions/slack/src/channel.setup.js";
|
||||
import { telegramSetupPlugin } from "../../../extensions/telegram/src/channel.setup.js";
|
||||
import { whatsappSetupPlugin } from "../../../extensions/whatsapp/src/channel.setup.js";
|
||||
import {
|
||||
getActivePluginRegistryVersion,
|
||||
requireActivePluginRegistry,
|
||||
} from "../../plugins/runtime.js";
|
||||
import { CHAT_CHANNEL_ORDER, type ChatChannelId } from "../registry.js";
|
||||
import { bundledChannelSetupPlugins } from "./bundled.js";
|
||||
import type { ChannelId, ChannelPlugin } from "./types.js";
|
||||
|
||||
type CachedChannelSetupPlugins = {
|
||||
@ -28,18 +20,6 @@ const EMPTY_CHANNEL_SETUP_CACHE: CachedChannelSetupPlugins = {
|
||||
|
||||
let cachedChannelSetupPlugins = EMPTY_CHANNEL_SETUP_CACHE;
|
||||
|
||||
const BUNDLED_CHANNEL_SETUP_PLUGINS = [
|
||||
telegramSetupPlugin,
|
||||
whatsappSetupPlugin,
|
||||
discordSetupPlugin,
|
||||
ircPlugin,
|
||||
googlechatPlugin,
|
||||
slackSetupPlugin,
|
||||
signalSetupPlugin,
|
||||
imessageSetupPlugin,
|
||||
lineSetupPlugin,
|
||||
] as ChannelPlugin[];
|
||||
|
||||
function dedupeSetupPlugins(plugins: ChannelPlugin[]): ChannelPlugin[] {
|
||||
const seen = new Set<string>();
|
||||
const resolved: ChannelPlugin[] = [];
|
||||
@ -77,7 +57,7 @@ function resolveCachedChannelSetupPlugins(): CachedChannelSetupPlugins {
|
||||
|
||||
const registryPlugins = (registry.channelSetups ?? []).map((entry) => entry.plugin);
|
||||
const sorted = sortChannelSetupPlugins(
|
||||
registryPlugins.length > 0 ? registryPlugins : BUNDLED_CHANNEL_SETUP_PLUGINS,
|
||||
registryPlugins.length > 0 ? registryPlugins : bundledChannelSetupPlugins,
|
||||
);
|
||||
const byId = new Map<string, ChannelPlugin>();
|
||||
for (const plugin of sorted) {
|
||||
|
||||
1
src/channels/plugins/setup-wizard-helpers.runtime.ts
Normal file
1
src/channels/plugins/setup-wizard-helpers.runtime.ts
Normal file
@ -0,0 +1 @@
|
||||
export { promptResolvedAllowFrom } from "./setup-wizard-helpers.js";
|
||||
@ -1,11 +1,11 @@
|
||||
import { handleSlackAction, type SlackActionContext } from "../../agents/tools/slack-actions.js";
|
||||
import { handleSlackMessageAction } from "../../plugin-sdk/slack-message-actions.js";
|
||||
import {
|
||||
extractSlackToolSend,
|
||||
isSlackInteractiveRepliesEnabled,
|
||||
listSlackMessageActions,
|
||||
resolveSlackChannelId,
|
||||
} from "../../plugin-sdk/slack.js";
|
||||
} from "../../plugin-sdk-internal/slack.js";
|
||||
import { handleSlackMessageAction } from "../../plugin-sdk/slack-message-actions.js";
|
||||
import type { ChannelMessageActionAdapter } from "./types.js";
|
||||
|
||||
export function createSlackActions(providerId: string): ChannelMessageActionAdapter {
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
// Shim: re-exports from extension
|
||||
export * from "../../../../extensions/discord/src/status-issues.js";
|
||||
@ -1 +0,0 @@
|
||||
export * from "../../../../extensions/telegram/src/status-issues.js";
|
||||
@ -1,2 +0,0 @@
|
||||
// Shim: re-exports from extensions/whatsapp/src/status-issues.ts
|
||||
export * from "../../../../extensions/whatsapp/src/status-issues.js";
|
||||
@ -1,4 +1,2 @@
|
||||
export {
|
||||
inspectDiscordAccount,
|
||||
type InspectedDiscordAccount,
|
||||
} from "../../extensions/discord/src/account-inspect.js";
|
||||
export { inspectDiscordAccount } from "../plugin-sdk-internal/discord.js";
|
||||
export type { InspectedDiscordAccount } from "../plugin-sdk-internal/discord.js";
|
||||
|
||||
@ -1,4 +1,2 @@
|
||||
export {
|
||||
inspectSlackAccount,
|
||||
type InspectedSlackAccount,
|
||||
} from "../../extensions/slack/src/account-inspect.js";
|
||||
export { inspectSlackAccount } from "../plugin-sdk-internal/slack.js";
|
||||
export type { InspectedSlackAccount } from "../plugin-sdk-internal/slack.js";
|
||||
|
||||
@ -1,4 +1,2 @@
|
||||
export {
|
||||
inspectTelegramAccount,
|
||||
type InspectedTelegramAccount,
|
||||
} from "../../extensions/telegram/src/account-inspect.js";
|
||||
export { inspectTelegramAccount } from "../plugin-sdk-internal/telegram.js";
|
||||
export type { InspectedTelegramAccount } from "../plugin-sdk-internal/telegram.js";
|
||||
|
||||
@ -19,32 +19,32 @@ const sendFns = vi.hoisted(() => ({
|
||||
imessage: vi.fn(async () => ({ messageId: "i1", chatId: "imessage:1" })),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/web/index.js", () => {
|
||||
vi.mock("../plugin-sdk-internal/whatsapp.js", () => {
|
||||
moduleLoads.whatsapp();
|
||||
return { sendMessageWhatsApp: sendFns.whatsapp };
|
||||
});
|
||||
|
||||
vi.mock("../../extensions/telegram/src/send.js", () => {
|
||||
vi.mock("../plugin-sdk-internal/telegram.js", () => {
|
||||
moduleLoads.telegram();
|
||||
return { sendMessageTelegram: sendFns.telegram };
|
||||
});
|
||||
|
||||
vi.mock("../../extensions/discord/src/send.js", () => {
|
||||
vi.mock("../plugin-sdk-internal/discord.js", () => {
|
||||
moduleLoads.discord();
|
||||
return { sendMessageDiscord: sendFns.discord };
|
||||
});
|
||||
|
||||
vi.mock("../../extensions/slack/src/send.js", () => {
|
||||
vi.mock("../plugin-sdk-internal/slack.js", () => {
|
||||
moduleLoads.slack();
|
||||
return { sendMessageSlack: sendFns.slack };
|
||||
});
|
||||
|
||||
vi.mock("../../extensions/signal/src/send.js", () => {
|
||||
vi.mock("../plugin-sdk-internal/signal.js", () => {
|
||||
moduleLoads.signal();
|
||||
return { sendMessageSignal: sendFns.signal };
|
||||
});
|
||||
|
||||
vi.mock("../../extensions/imessage/src/send.js", () => {
|
||||
vi.mock("../plugin-sdk-internal/imessage.js", () => {
|
||||
moduleLoads.imessage();
|
||||
return { sendMessageIMessage: sendFns.imessage };
|
||||
});
|
||||
|
||||
@ -35,32 +35,32 @@ export function createDefaultDeps(): CliDeps {
|
||||
return {
|
||||
whatsapp: createLazySender(
|
||||
"whatsapp",
|
||||
() => import("../channels/web/index.js") as Promise<Record<string, unknown>>,
|
||||
() => import("../plugin-sdk-internal/whatsapp.js") as Promise<Record<string, unknown>>,
|
||||
"sendMessageWhatsApp",
|
||||
),
|
||||
telegram: createLazySender(
|
||||
"telegram",
|
||||
() => import("../../extensions/telegram/src/send.js") as Promise<Record<string, unknown>>,
|
||||
() => import("../plugin-sdk-internal/telegram.js") as Promise<Record<string, unknown>>,
|
||||
"sendMessageTelegram",
|
||||
),
|
||||
discord: createLazySender(
|
||||
"discord",
|
||||
() => import("../../extensions/discord/src/send.js") as Promise<Record<string, unknown>>,
|
||||
() => import("../plugin-sdk-internal/discord.js") as Promise<Record<string, unknown>>,
|
||||
"sendMessageDiscord",
|
||||
),
|
||||
slack: createLazySender(
|
||||
"slack",
|
||||
() => import("../../extensions/slack/src/send.js") as Promise<Record<string, unknown>>,
|
||||
() => import("../plugin-sdk-internal/slack.js") as Promise<Record<string, unknown>>,
|
||||
"sendMessageSlack",
|
||||
),
|
||||
signal: createLazySender(
|
||||
"signal",
|
||||
() => import("../../extensions/signal/src/send.js") as Promise<Record<string, unknown>>,
|
||||
() => import("../plugin-sdk-internal/signal.js") as Promise<Record<string, unknown>>,
|
||||
"sendMessageSignal",
|
||||
),
|
||||
imessage: createLazySender(
|
||||
"imessage",
|
||||
() => import("../../extensions/imessage/src/send.js") as Promise<Record<string, unknown>>,
|
||||
() => import("../plugin-sdk-internal/imessage.js") as Promise<Record<string, unknown>>,
|
||||
"sendMessageIMessage",
|
||||
),
|
||||
};
|
||||
@ -70,4 +70,4 @@ export function createOutboundSendDeps(deps: CliDeps): OutboundSendDeps {
|
||||
return createOutboundSendDepsFromCliSource(deps);
|
||||
}
|
||||
|
||||
export { logWebSelfId } from "../../extensions/whatsapp/src/auth-store.js";
|
||||
export { logWebSelfId } from "../plugin-sdk-internal/whatsapp.js";
|
||||
|
||||
@ -11,6 +11,11 @@ import { enablePluginInConfig } from "../plugins/enable.js";
|
||||
import { installPluginFromNpmSpec, installPluginFromPath } from "../plugins/install.js";
|
||||
import { recordPluginInstall } from "../plugins/installs.js";
|
||||
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
|
||||
import {
|
||||
installPluginFromMarketplace,
|
||||
listMarketplacePlugins,
|
||||
resolveMarketplaceInstallShortcut,
|
||||
} from "../plugins/marketplace.js";
|
||||
import type { PluginRecord } from "../plugins/registry.js";
|
||||
import { applyExclusiveSlotSelection } from "../plugins/slots.js";
|
||||
import { resolvePluginSourceRoots, formatPluginSourceForTable } from "../plugins/source-display.js";
|
||||
@ -46,6 +51,10 @@ export type PluginUpdateOptions = {
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
export type PluginMarketplaceListOptions = {
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
export type PluginUninstallOptions = {
|
||||
keepFiles?: boolean;
|
||||
keepConfig?: boolean;
|
||||
@ -203,9 +212,65 @@ async function installBundledPluginSource(params: {
|
||||
|
||||
async function runPluginInstallCommand(params: {
|
||||
raw: string;
|
||||
opts: { link?: boolean; pin?: boolean };
|
||||
opts: { link?: boolean; pin?: boolean; marketplace?: string };
|
||||
}) {
|
||||
const { raw, opts } = params;
|
||||
const shorthand = !params.opts.marketplace
|
||||
? await resolveMarketplaceInstallShortcut(params.raw)
|
||||
: null;
|
||||
if (shorthand?.ok === false) {
|
||||
defaultRuntime.error(shorthand.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const raw = shorthand?.ok ? shorthand.plugin : params.raw;
|
||||
const opts = {
|
||||
...params.opts,
|
||||
marketplace:
|
||||
params.opts.marketplace ?? (shorthand?.ok ? shorthand.marketplaceSource : undefined),
|
||||
};
|
||||
|
||||
if (opts.marketplace) {
|
||||
if (opts.link) {
|
||||
defaultRuntime.error("`--link` is not supported with `--marketplace`.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (opts.pin) {
|
||||
defaultRuntime.error("`--pin` is not supported with `--marketplace`.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cfg = loadConfig();
|
||||
const result = await installPluginFromMarketplace({
|
||||
marketplace: opts.marketplace,
|
||||
plugin: raw,
|
||||
logger: createPluginInstallLogger(),
|
||||
});
|
||||
if (!result.ok) {
|
||||
defaultRuntime.error(result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
clearPluginManifestRegistryCache();
|
||||
|
||||
let next = enablePluginInConfig(cfg, result.pluginId).config;
|
||||
next = recordPluginInstall(next, {
|
||||
pluginId: result.pluginId,
|
||||
source: "marketplace",
|
||||
installPath: result.targetDir,
|
||||
version: result.version,
|
||||
marketplaceName: result.marketplaceName,
|
||||
marketplaceSource: result.marketplaceSource,
|
||||
marketplacePlugin: result.marketplacePlugin,
|
||||
});
|
||||
const slotResult = applySlotSelectionForPlugin(next, result.pluginId);
|
||||
next = slotResult.config;
|
||||
await writeConfigFile(next);
|
||||
logSlotWarnings(slotResult.warnings);
|
||||
defaultRuntime.log(`Installed plugin: ${result.pluginId}`);
|
||||
defaultRuntime.log(`Restart the gateway to load plugins.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileSpec = resolveFileNpmSpecToLocalPath(raw);
|
||||
if (fileSpec && !fileSpec.ok) {
|
||||
defaultRuntime.error(fileSpec.error);
|
||||
@ -734,17 +799,24 @@ export function registerPluginsCli(program: Command) {
|
||||
|
||||
plugins
|
||||
.command("install")
|
||||
.description("Install a plugin (path, archive, or npm spec)")
|
||||
.argument("<path-or-spec>", "Path (.ts/.js/.zip/.tgz/.tar.gz) or an npm package spec")
|
||||
.description("Install a plugin (path, archive, npm spec, or marketplace entry)")
|
||||
.argument(
|
||||
"<path-or-spec-or-plugin>",
|
||||
"Path (.ts/.js/.zip/.tgz/.tar.gz), npm package spec, or marketplace plugin name",
|
||||
)
|
||||
.option("-l, --link", "Link a local path instead of copying", false)
|
||||
.option("--pin", "Record npm installs as exact resolved <name>@<version>", false)
|
||||
.action(async (raw: string, opts: { link?: boolean; pin?: boolean }) => {
|
||||
.option(
|
||||
"--marketplace <source>",
|
||||
"Install a Claude marketplace plugin from a local repo/path or git/GitHub source",
|
||||
)
|
||||
.action(async (raw: string, opts: { link?: boolean; pin?: boolean; marketplace?: string }) => {
|
||||
await runPluginInstallCommand({ raw, opts });
|
||||
});
|
||||
|
||||
plugins
|
||||
.command("update")
|
||||
.description("Update installed plugins (npm installs only)")
|
||||
.description("Update installed plugins (npm and marketplace installs)")
|
||||
.argument("[id]", "Plugin id (omit with --all)")
|
||||
.option("--all", "Update all tracked plugins", false)
|
||||
.option("--dry-run", "Show what would change without writing", false)
|
||||
@ -755,7 +827,7 @@ export function registerPluginsCli(program: Command) {
|
||||
|
||||
if (targets.length === 0) {
|
||||
if (opts.all) {
|
||||
defaultRuntime.log("No npm-installed plugins to update.");
|
||||
defaultRuntime.log("No tracked plugins to update.");
|
||||
return;
|
||||
}
|
||||
defaultRuntime.error("Provide a plugin id or use --all.");
|
||||
@ -839,4 +911,54 @@ export function registerPluginsCli(program: Command) {
|
||||
lines.push(`${theme.muted("Docs:")} ${docs}`);
|
||||
defaultRuntime.log(lines.join("\n"));
|
||||
});
|
||||
|
||||
const marketplace = plugins
|
||||
.command("marketplace")
|
||||
.description("Inspect Claude-compatible plugin marketplaces");
|
||||
|
||||
marketplace
|
||||
.command("list")
|
||||
.description("List plugins published by a marketplace source")
|
||||
.argument("<source>", "Local marketplace path/repo or git/GitHub source")
|
||||
.option("--json", "Print JSON")
|
||||
.action(async (source: string, opts: PluginMarketplaceListOptions) => {
|
||||
const result = await listMarketplacePlugins({
|
||||
marketplace: source,
|
||||
logger: createPluginInstallLogger(),
|
||||
});
|
||||
if (!result.ok) {
|
||||
defaultRuntime.error(result.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
source: result.sourceLabel,
|
||||
name: result.manifest.name,
|
||||
version: result.manifest.version,
|
||||
plugins: result.manifest.plugins,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.manifest.plugins.length === 0) {
|
||||
defaultRuntime.log(`No plugins found in marketplace ${result.sourceLabel}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
defaultRuntime.log(
|
||||
`${theme.heading("Marketplace")} ${theme.muted(result.manifest.name ?? result.sourceLabel)}`,
|
||||
);
|
||||
for (const plugin of result.manifest.plugins) {
|
||||
const suffix = plugin.version ? theme.muted(` v${plugin.version}`) : "";
|
||||
const desc = plugin.description ? ` - ${theme.muted(plugin.description)}` : "";
|
||||
defaultRuntime.log(`${theme.command(plugin.name)}${suffix}${desc}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,85 +1,15 @@
|
||||
import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js";
|
||||
import type { SecretInput } from "../config/types.secrets.js";
|
||||
import { normalizeApiKeyInput, validateApiKeyInput } from "./auth-choice.api-key.js";
|
||||
import { ensureApiKeyFromOptionEnvOrPrompt } from "./auth-choice.apply-helpers.js";
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import { ensureModelAllowlistEntry } from "./model-allowlist.js";
|
||||
import { applyPrimaryModel } from "./model-picker.js";
|
||||
import type { ApiKeyStorageOptions } from "./onboard-auth.credentials.js";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
applyKilocodeConfig,
|
||||
applyKilocodeProviderConfig,
|
||||
applyKimiCodeConfig,
|
||||
applyKimiCodeProviderConfig,
|
||||
applyLitellmConfig,
|
||||
applyLitellmProviderConfig,
|
||||
applyMistralConfig,
|
||||
applyMistralProviderConfig,
|
||||
applyModelStudioConfig,
|
||||
applyModelStudioConfigCn,
|
||||
applyModelStudioProviderConfig,
|
||||
applyModelStudioProviderConfigCn,
|
||||
applyModelStudioStandardConfig,
|
||||
applyModelStudioStandardConfigCn,
|
||||
applyModelStudioStandardProviderConfig,
|
||||
applyModelStudioStandardProviderConfigCn,
|
||||
applyMoonshotConfig,
|
||||
applyMoonshotConfigCn,
|
||||
applyMoonshotProviderConfig,
|
||||
applyMoonshotProviderConfigCn,
|
||||
applyOpencodeGoConfig,
|
||||
applyOpencodeGoProviderConfig,
|
||||
applyOpencodeZenConfig,
|
||||
applyOpencodeZenProviderConfig,
|
||||
applyQianfanConfig,
|
||||
applyQianfanProviderConfig,
|
||||
applySyntheticConfig,
|
||||
applySyntheticProviderConfig,
|
||||
applyTogetherConfig,
|
||||
applyTogetherProviderConfig,
|
||||
applyVeniceConfig,
|
||||
applyVeniceProviderConfig,
|
||||
applyVercelAiGatewayConfig,
|
||||
applyVercelAiGatewayProviderConfig,
|
||||
applyXaiConfig,
|
||||
applyXaiProviderConfig,
|
||||
applyXiaomiConfig,
|
||||
applyXiaomiProviderConfig,
|
||||
KILOCODE_DEFAULT_MODEL_REF,
|
||||
KIMI_CODING_MODEL_REF,
|
||||
LITELLM_DEFAULT_MODEL_REF,
|
||||
MISTRAL_DEFAULT_MODEL_REF,
|
||||
MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
MOONSHOT_DEFAULT_MODEL_REF,
|
||||
QIANFAN_DEFAULT_MODEL_REF,
|
||||
setKilocodeApiKey,
|
||||
setKimiCodingApiKey,
|
||||
setLitellmApiKey,
|
||||
setMistralApiKey,
|
||||
setModelStudioApiKey,
|
||||
setMoonshotApiKey,
|
||||
setOpencodeGoApiKey,
|
||||
setOpencodeZenApiKey,
|
||||
setQianfanApiKey,
|
||||
setSyntheticApiKey,
|
||||
setTogetherApiKey,
|
||||
setVeniceApiKey,
|
||||
setVercelAiGatewayApiKey,
|
||||
setVolcengineApiKey,
|
||||
setByteplusApiKey,
|
||||
setXaiApiKey,
|
||||
setXiaomiApiKey,
|
||||
SYNTHETIC_DEFAULT_MODEL_REF,
|
||||
TOGETHER_DEFAULT_MODEL_REF,
|
||||
VENICE_DEFAULT_MODEL_REF,
|
||||
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||
XAI_DEFAULT_MODEL_REF,
|
||||
XIAOMI_DEFAULT_MODEL_REF,
|
||||
} from "./onboard-auth.js";
|
||||
import type { AuthChoice, SecretInputMode } from "./onboard-types.js";
|
||||
import { OPENCODE_GO_DEFAULT_MODEL_REF } from "./opencode-go-model-default.js";
|
||||
import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
|
||||
import type { SecretInputMode } from "./onboard-types.js";
|
||||
|
||||
type ApiKeyProviderConfigApplier = (
|
||||
config: ApplyAuthChoiceParams["config"],
|
||||
@ -94,7 +24,7 @@ type ApplyProviderDefaultModel = (args: {
|
||||
|
||||
type ApplyApiKeyProviderParams = {
|
||||
params: ApplyAuthChoiceParams;
|
||||
authChoice: AuthChoice;
|
||||
authChoice: string;
|
||||
config: ApplyAuthChoiceParams["config"];
|
||||
setConfig: (config: ApplyAuthChoiceParams["config"]) => void;
|
||||
getConfig: () => ApplyAuthChoiceParams["config"];
|
||||
@ -104,426 +34,6 @@ type ApplyApiKeyProviderParams = {
|
||||
getAgentModelOverride: () => string | undefined;
|
||||
};
|
||||
|
||||
type SimpleApiKeyProviderFlow = {
|
||||
provider: Parameters<typeof ensureApiKeyFromOptionEnvOrPrompt>[0]["provider"];
|
||||
profileId: string;
|
||||
expectedProviders: string[];
|
||||
envLabel: string;
|
||||
promptMessage: string;
|
||||
setCredential: (
|
||||
apiKey: SecretInput,
|
||||
agentDir?: string,
|
||||
options?: ApiKeyStorageOptions,
|
||||
) => void | Promise<void>;
|
||||
defaultModel: string;
|
||||
applyDefaultConfig: ApiKeyProviderConfigApplier;
|
||||
applyProviderConfig: ApiKeyProviderConfigApplier;
|
||||
tokenProvider?: string;
|
||||
normalize?: (value: string) => string;
|
||||
validate?: (value: string) => string | undefined;
|
||||
noteDefault?: string;
|
||||
noteMessage?: string;
|
||||
noteTitle?: string;
|
||||
};
|
||||
|
||||
const VOLCENGINE_DEFAULT_MODEL_REF = "volcengine-plan/ark-code-latest";
|
||||
const BYTEPLUS_DEFAULT_MODEL_REF = "byteplus-plan/ark-code-latest";
|
||||
|
||||
const SIMPLE_API_KEY_PROVIDER_FLOWS: Partial<Record<AuthChoice, SimpleApiKeyProviderFlow>> = {
|
||||
"ai-gateway-api-key": {
|
||||
provider: "vercel-ai-gateway",
|
||||
profileId: "vercel-ai-gateway:default",
|
||||
expectedProviders: ["vercel-ai-gateway"],
|
||||
envLabel: "AI_GATEWAY_API_KEY",
|
||||
promptMessage: "Enter Vercel AI Gateway API key",
|
||||
setCredential: setVercelAiGatewayApiKey,
|
||||
defaultModel: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyVercelAiGatewayConfig,
|
||||
applyProviderConfig: applyVercelAiGatewayProviderConfig,
|
||||
noteDefault: VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||
},
|
||||
"moonshot-api-key": {
|
||||
provider: "moonshot",
|
||||
profileId: "moonshot:default",
|
||||
expectedProviders: ["moonshot"],
|
||||
envLabel: "MOONSHOT_API_KEY",
|
||||
promptMessage: "Enter Moonshot API key",
|
||||
setCredential: setMoonshotApiKey,
|
||||
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyMoonshotConfig,
|
||||
applyProviderConfig: applyMoonshotProviderConfig,
|
||||
},
|
||||
"moonshot-api-key-cn": {
|
||||
provider: "moonshot",
|
||||
profileId: "moonshot:default",
|
||||
expectedProviders: ["moonshot"],
|
||||
envLabel: "MOONSHOT_API_KEY",
|
||||
promptMessage: "Enter Moonshot API key (.cn)",
|
||||
setCredential: setMoonshotApiKey,
|
||||
defaultModel: MOONSHOT_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyMoonshotConfigCn,
|
||||
applyProviderConfig: applyMoonshotProviderConfigCn,
|
||||
},
|
||||
"kimi-code-api-key": {
|
||||
provider: "kimi-coding",
|
||||
profileId: "kimi-coding:default",
|
||||
expectedProviders: ["kimi-code", "kimi-coding"],
|
||||
envLabel: "KIMI_API_KEY",
|
||||
promptMessage: "Enter Kimi Coding API key",
|
||||
setCredential: setKimiCodingApiKey,
|
||||
defaultModel: KIMI_CODING_MODEL_REF,
|
||||
applyDefaultConfig: applyKimiCodeConfig,
|
||||
applyProviderConfig: applyKimiCodeProviderConfig,
|
||||
noteDefault: KIMI_CODING_MODEL_REF,
|
||||
noteMessage: [
|
||||
"Kimi Coding uses a dedicated endpoint and API key.",
|
||||
"Get your API key at: https://www.kimi.com/code/en",
|
||||
].join("\n"),
|
||||
noteTitle: "Kimi Coding",
|
||||
},
|
||||
"xiaomi-api-key": {
|
||||
provider: "xiaomi",
|
||||
profileId: "xiaomi:default",
|
||||
expectedProviders: ["xiaomi"],
|
||||
envLabel: "XIAOMI_API_KEY",
|
||||
promptMessage: "Enter Xiaomi API key",
|
||||
setCredential: setXiaomiApiKey,
|
||||
defaultModel: XIAOMI_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyXiaomiConfig,
|
||||
applyProviderConfig: applyXiaomiProviderConfig,
|
||||
noteDefault: XIAOMI_DEFAULT_MODEL_REF,
|
||||
},
|
||||
"xai-api-key": {
|
||||
provider: "xai",
|
||||
profileId: "xai:default",
|
||||
expectedProviders: ["xai"],
|
||||
envLabel: "XAI_API_KEY",
|
||||
promptMessage: "Enter xAI API key",
|
||||
setCredential: setXaiApiKey,
|
||||
defaultModel: XAI_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyXaiConfig,
|
||||
applyProviderConfig: applyXaiProviderConfig,
|
||||
noteDefault: XAI_DEFAULT_MODEL_REF,
|
||||
},
|
||||
"mistral-api-key": {
|
||||
provider: "mistral",
|
||||
profileId: "mistral:default",
|
||||
expectedProviders: ["mistral"],
|
||||
envLabel: "MISTRAL_API_KEY",
|
||||
promptMessage: "Enter Mistral API key",
|
||||
setCredential: setMistralApiKey,
|
||||
defaultModel: MISTRAL_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyMistralConfig,
|
||||
applyProviderConfig: applyMistralProviderConfig,
|
||||
noteDefault: MISTRAL_DEFAULT_MODEL_REF,
|
||||
},
|
||||
"venice-api-key": {
|
||||
provider: "venice",
|
||||
profileId: "venice:default",
|
||||
expectedProviders: ["venice"],
|
||||
envLabel: "VENICE_API_KEY",
|
||||
promptMessage: "Enter Venice AI API key",
|
||||
setCredential: setVeniceApiKey,
|
||||
defaultModel: VENICE_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyVeniceConfig,
|
||||
applyProviderConfig: applyVeniceProviderConfig,
|
||||
noteDefault: VENICE_DEFAULT_MODEL_REF,
|
||||
noteMessage: [
|
||||
"Venice AI provides privacy-focused inference with uncensored models.",
|
||||
"Get your API key at: https://venice.ai/settings/api",
|
||||
"Supports 'private' (fully private) and 'anonymized' (proxy) modes.",
|
||||
].join("\n"),
|
||||
noteTitle: "Venice AI",
|
||||
},
|
||||
"opencode-zen": {
|
||||
provider: "opencode",
|
||||
profileId: "opencode:default",
|
||||
expectedProviders: ["opencode", "opencode-go"],
|
||||
envLabel: "OPENCODE_API_KEY",
|
||||
promptMessage: "Enter OpenCode API key",
|
||||
setCredential: setOpencodeZenApiKey,
|
||||
defaultModel: OPENCODE_ZEN_DEFAULT_MODEL,
|
||||
applyDefaultConfig: applyOpencodeZenConfig,
|
||||
applyProviderConfig: applyOpencodeZenProviderConfig,
|
||||
noteDefault: OPENCODE_ZEN_DEFAULT_MODEL,
|
||||
noteMessage: [
|
||||
"OpenCode uses one API key across the Zen and Go catalogs.",
|
||||
"Zen provides access to Claude, GPT, Gemini, and more models.",
|
||||
"Get your API key at: https://opencode.ai/auth",
|
||||
"Choose the Zen catalog when you want the curated multi-model proxy.",
|
||||
].join("\n"),
|
||||
noteTitle: "OpenCode",
|
||||
},
|
||||
"opencode-go": {
|
||||
provider: "opencode-go",
|
||||
profileId: "opencode-go:default",
|
||||
expectedProviders: ["opencode", "opencode-go"],
|
||||
envLabel: "OPENCODE_API_KEY",
|
||||
promptMessage: "Enter OpenCode API key",
|
||||
setCredential: setOpencodeGoApiKey,
|
||||
defaultModel: OPENCODE_GO_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyOpencodeGoConfig,
|
||||
applyProviderConfig: applyOpencodeGoProviderConfig,
|
||||
noteDefault: OPENCODE_GO_DEFAULT_MODEL_REF,
|
||||
noteMessage: [
|
||||
"OpenCode uses one API key across the Zen and Go catalogs.",
|
||||
"Go provides access to Kimi, GLM, and MiniMax models through the Go catalog.",
|
||||
"Get your API key at: https://opencode.ai/auth",
|
||||
"Choose the Go catalog when you want the OpenCode-hosted Kimi/GLM/MiniMax lineup.",
|
||||
].join("\n"),
|
||||
noteTitle: "OpenCode",
|
||||
},
|
||||
"together-api-key": {
|
||||
provider: "together",
|
||||
profileId: "together:default",
|
||||
expectedProviders: ["together"],
|
||||
envLabel: "TOGETHER_API_KEY",
|
||||
promptMessage: "Enter Together AI API key",
|
||||
setCredential: setTogetherApiKey,
|
||||
defaultModel: TOGETHER_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyTogetherConfig,
|
||||
applyProviderConfig: applyTogetherProviderConfig,
|
||||
noteDefault: TOGETHER_DEFAULT_MODEL_REF,
|
||||
noteMessage: [
|
||||
"Together AI provides access to leading open-source models including Llama, DeepSeek, Qwen, and more.",
|
||||
"Get your API key at: https://api.together.xyz/settings/api-keys",
|
||||
].join("\n"),
|
||||
noteTitle: "Together AI",
|
||||
},
|
||||
"qianfan-api-key": {
|
||||
provider: "qianfan",
|
||||
profileId: "qianfan:default",
|
||||
expectedProviders: ["qianfan"],
|
||||
envLabel: "QIANFAN_API_KEY",
|
||||
promptMessage: "Enter QIANFAN API key",
|
||||
setCredential: setQianfanApiKey,
|
||||
defaultModel: QIANFAN_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyQianfanConfig,
|
||||
applyProviderConfig: applyQianfanProviderConfig,
|
||||
noteDefault: QIANFAN_DEFAULT_MODEL_REF,
|
||||
noteMessage: [
|
||||
"Get your API key at: https://console.bce.baidu.com/qianfan/ais/console/apiKey",
|
||||
"API key format: bce-v3/ALTAK-...",
|
||||
].join("\n"),
|
||||
noteTitle: "QIANFAN",
|
||||
},
|
||||
"kilocode-api-key": {
|
||||
provider: "kilocode",
|
||||
profileId: "kilocode:default",
|
||||
expectedProviders: ["kilocode"],
|
||||
envLabel: "KILOCODE_API_KEY",
|
||||
promptMessage: "Enter Kilo Gateway API key",
|
||||
setCredential: setKilocodeApiKey,
|
||||
defaultModel: KILOCODE_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyKilocodeConfig,
|
||||
applyProviderConfig: applyKilocodeProviderConfig,
|
||||
noteDefault: KILOCODE_DEFAULT_MODEL_REF,
|
||||
},
|
||||
"modelstudio-api-key-cn": {
|
||||
provider: "modelstudio",
|
||||
profileId: "modelstudio:default",
|
||||
expectedProviders: ["modelstudio"],
|
||||
envLabel: "MODELSTUDIO_API_KEY",
|
||||
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (China)",
|
||||
setCredential: setModelStudioApiKey,
|
||||
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyModelStudioConfigCn,
|
||||
applyProviderConfig: applyModelStudioProviderConfigCn,
|
||||
noteDefault: MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
noteMessage: [
|
||||
"Get your API key at: https://bailian.console.aliyun.com/",
|
||||
"Endpoint: coding.dashscope.aliyuncs.com",
|
||||
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
|
||||
].join("\n"),
|
||||
noteTitle: "Alibaba Cloud Model Studio Coding Plan (China)",
|
||||
normalize: (value) => String(value ?? "").trim(),
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
},
|
||||
"modelstudio-api-key": {
|
||||
provider: "modelstudio",
|
||||
profileId: "modelstudio:default",
|
||||
expectedProviders: ["modelstudio"],
|
||||
envLabel: "MODELSTUDIO_API_KEY",
|
||||
promptMessage: "Enter Alibaba Cloud Model Studio Coding Plan API key (Global/Intl)",
|
||||
setCredential: setModelStudioApiKey,
|
||||
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyModelStudioConfig,
|
||||
applyProviderConfig: applyModelStudioProviderConfig,
|
||||
noteDefault: MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
noteMessage: [
|
||||
"Get your API key at: https://bailian.console.aliyun.com/",
|
||||
"Endpoint: coding-intl.dashscope.aliyuncs.com",
|
||||
"Models: qwen3.5-plus, glm-4.7, kimi-k2.5, MiniMax-M2.5, etc.",
|
||||
].join("\n"),
|
||||
noteTitle: "Alibaba Cloud Model Studio Coding Plan (Global/Intl)",
|
||||
normalize: (value) => String(value ?? "").trim(),
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
},
|
||||
"modelstudio-standard-api-key-cn": {
|
||||
provider: "modelstudio",
|
||||
profileId: "modelstudio:default",
|
||||
expectedProviders: ["modelstudio"],
|
||||
envLabel: "MODELSTUDIO_API_KEY",
|
||||
promptMessage: "Enter Alibaba Cloud Model Studio API key (China)",
|
||||
setCredential: setModelStudioApiKey,
|
||||
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyModelStudioStandardConfigCn,
|
||||
applyProviderConfig: applyModelStudioStandardProviderConfigCn,
|
||||
noteDefault: MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
noteMessage: [
|
||||
"Get your API key at: https://bailian.console.aliyun.com/",
|
||||
"Endpoint: dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"Models: qwen3.5-plus, qwen3.5-flash, qwen3-coder-plus, etc.",
|
||||
].join("\n"),
|
||||
noteTitle: "Alibaba Cloud Model Studio (China)",
|
||||
normalize: (value) => String(value ?? "").trim(),
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
},
|
||||
"modelstudio-standard-api-key": {
|
||||
provider: "modelstudio",
|
||||
profileId: "modelstudio:default",
|
||||
expectedProviders: ["modelstudio"],
|
||||
envLabel: "MODELSTUDIO_API_KEY",
|
||||
promptMessage: "Enter Alibaba Cloud Model Studio API key (Global/Intl)",
|
||||
setCredential: setModelStudioApiKey,
|
||||
defaultModel: MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyModelStudioStandardConfig,
|
||||
applyProviderConfig: applyModelStudioStandardProviderConfig,
|
||||
noteDefault: MODELSTUDIO_DEFAULT_MODEL_REF,
|
||||
noteMessage: [
|
||||
"Get your API key at: https://modelstudio.console.alibabacloud.com/",
|
||||
"Endpoint: dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||
"Models: qwen3.5-plus, qwen3.5-flash, qwen3-coder-plus, etc.",
|
||||
].join("\n"),
|
||||
noteTitle: "Alibaba Cloud Model Studio (Global/Intl)",
|
||||
normalize: (value) => String(value ?? "").trim(),
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
},
|
||||
"volcengine-api-key": {
|
||||
provider: "volcengine",
|
||||
profileId: "volcengine:default",
|
||||
expectedProviders: ["volcengine"],
|
||||
envLabel: "VOLCANO_ENGINE_API_KEY",
|
||||
promptMessage: "Enter Volcano Engine API key",
|
||||
setCredential: setVolcengineApiKey,
|
||||
defaultModel: VOLCENGINE_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: (cfg) => applyPrimaryModel(cfg, VOLCENGINE_DEFAULT_MODEL_REF),
|
||||
applyProviderConfig: (cfg) =>
|
||||
ensureModelAllowlistEntry({
|
||||
cfg,
|
||||
modelRef: VOLCENGINE_DEFAULT_MODEL_REF,
|
||||
}),
|
||||
noteDefault: VOLCENGINE_DEFAULT_MODEL_REF,
|
||||
},
|
||||
"byteplus-api-key": {
|
||||
provider: "byteplus",
|
||||
profileId: "byteplus:default",
|
||||
expectedProviders: ["byteplus"],
|
||||
envLabel: "BYTEPLUS_API_KEY",
|
||||
promptMessage: "Enter BytePlus API key",
|
||||
setCredential: setByteplusApiKey,
|
||||
defaultModel: BYTEPLUS_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: (cfg) => applyPrimaryModel(cfg, BYTEPLUS_DEFAULT_MODEL_REF),
|
||||
applyProviderConfig: (cfg) =>
|
||||
ensureModelAllowlistEntry({
|
||||
cfg,
|
||||
modelRef: BYTEPLUS_DEFAULT_MODEL_REF,
|
||||
}),
|
||||
noteDefault: BYTEPLUS_DEFAULT_MODEL_REF,
|
||||
},
|
||||
"synthetic-api-key": {
|
||||
provider: "synthetic",
|
||||
profileId: "synthetic:default",
|
||||
expectedProviders: ["synthetic"],
|
||||
envLabel: "SYNTHETIC_API_KEY",
|
||||
promptMessage: "Enter Synthetic API key",
|
||||
setCredential: setSyntheticApiKey,
|
||||
defaultModel: SYNTHETIC_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applySyntheticConfig,
|
||||
applyProviderConfig: applySyntheticProviderConfig,
|
||||
normalize: (value) => String(value ?? "").trim(),
|
||||
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
||||
},
|
||||
};
|
||||
|
||||
async function applyApiKeyProviderWithDefaultModel({
|
||||
params,
|
||||
config,
|
||||
setConfig,
|
||||
getConfig,
|
||||
normalizedTokenProvider,
|
||||
requestedSecretInputMode,
|
||||
applyProviderDefaultModel,
|
||||
getAgentModelOverride,
|
||||
provider,
|
||||
profileId,
|
||||
expectedProviders,
|
||||
envLabel,
|
||||
promptMessage,
|
||||
setCredential,
|
||||
defaultModel,
|
||||
applyDefaultConfig,
|
||||
applyProviderConfig,
|
||||
noteMessage,
|
||||
noteTitle,
|
||||
tokenProvider = normalizedTokenProvider,
|
||||
normalize = normalizeApiKeyInput,
|
||||
validate = validateApiKeyInput,
|
||||
noteDefault = defaultModel,
|
||||
}: ApplyApiKeyProviderParams & {
|
||||
provider: Parameters<typeof ensureApiKeyFromOptionEnvOrPrompt>[0]["provider"];
|
||||
profileId: string;
|
||||
expectedProviders: string[];
|
||||
envLabel: string;
|
||||
promptMessage: string;
|
||||
setCredential: (apiKey: SecretInput, mode?: SecretInputMode) => void | Promise<void>;
|
||||
defaultModel: string;
|
||||
applyDefaultConfig: ApiKeyProviderConfigApplier;
|
||||
applyProviderConfig: ApiKeyProviderConfigApplier;
|
||||
noteMessage?: string;
|
||||
noteTitle?: string;
|
||||
tokenProvider?: string;
|
||||
normalize?: (value: string) => string;
|
||||
validate?: (value: string) => string | undefined;
|
||||
noteDefault?: string;
|
||||
}): Promise<ApplyAuthChoiceResult> {
|
||||
let nextConfig = config;
|
||||
|
||||
await ensureApiKeyFromOptionEnvOrPrompt({
|
||||
token: params.opts?.token,
|
||||
provider,
|
||||
tokenProvider,
|
||||
secretInputMode: requestedSecretInputMode,
|
||||
config: nextConfig,
|
||||
expectedProviders,
|
||||
envLabel,
|
||||
promptMessage,
|
||||
setCredential: async (apiKey, mode) => {
|
||||
await setCredential(apiKey, mode);
|
||||
},
|
||||
noteMessage,
|
||||
noteTitle,
|
||||
normalize,
|
||||
validate,
|
||||
prompter: params.prompter,
|
||||
});
|
||||
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId,
|
||||
provider,
|
||||
mode: "api_key",
|
||||
});
|
||||
setConfig(nextConfig);
|
||||
await applyProviderDefaultModel({
|
||||
defaultModel,
|
||||
applyDefaultConfig,
|
||||
applyProviderConfig,
|
||||
noteDefault,
|
||||
});
|
||||
|
||||
return { config: getConfig(), agentModelOverride: getAgentModelOverride() };
|
||||
}
|
||||
|
||||
export async function applyLiteLlmApiKeyProvider({
|
||||
params,
|
||||
authChoice,
|
||||
@ -588,50 +98,3 @@ export async function applyLiteLlmApiKeyProvider({
|
||||
});
|
||||
return { config: getConfig(), agentModelOverride: getAgentModelOverride() };
|
||||
}
|
||||
|
||||
export async function applySimpleAuthChoiceApiProvider({
|
||||
params,
|
||||
authChoice,
|
||||
config,
|
||||
setConfig,
|
||||
getConfig,
|
||||
normalizedTokenProvider,
|
||||
requestedSecretInputMode,
|
||||
applyProviderDefaultModel,
|
||||
getAgentModelOverride,
|
||||
}: ApplyApiKeyProviderParams): Promise<ApplyAuthChoiceResult | null> {
|
||||
const simpleApiKeyProviderFlow = SIMPLE_API_KEY_PROVIDER_FLOWS[authChoice];
|
||||
if (!simpleApiKeyProviderFlow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await applyApiKeyProviderWithDefaultModel({
|
||||
params,
|
||||
authChoice,
|
||||
config,
|
||||
setConfig,
|
||||
getConfig,
|
||||
normalizedTokenProvider,
|
||||
requestedSecretInputMode,
|
||||
applyProviderDefaultModel,
|
||||
getAgentModelOverride,
|
||||
provider: simpleApiKeyProviderFlow.provider,
|
||||
profileId: simpleApiKeyProviderFlow.profileId,
|
||||
expectedProviders: simpleApiKeyProviderFlow.expectedProviders,
|
||||
envLabel: simpleApiKeyProviderFlow.envLabel,
|
||||
promptMessage: simpleApiKeyProviderFlow.promptMessage,
|
||||
setCredential: async (apiKey, mode) =>
|
||||
simpleApiKeyProviderFlow.setCredential(apiKey, params.agentDir, {
|
||||
secretInputMode: mode ?? requestedSecretInputMode,
|
||||
}),
|
||||
defaultModel: simpleApiKeyProviderFlow.defaultModel,
|
||||
applyDefaultConfig: simpleApiKeyProviderFlow.applyDefaultConfig,
|
||||
applyProviderConfig: simpleApiKeyProviderFlow.applyProviderConfig,
|
||||
noteDefault: simpleApiKeyProviderFlow.noteDefault,
|
||||
noteMessage: simpleApiKeyProviderFlow.noteMessage,
|
||||
noteTitle: simpleApiKeyProviderFlow.noteTitle,
|
||||
tokenProvider: simpleApiKeyProviderFlow.tokenProvider,
|
||||
normalize: simpleApiKeyProviderFlow.normalize,
|
||||
validate: simpleApiKeyProviderFlow.validate,
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user