mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-11 00:11:53 +08:00
Compare commits
78 Commits
fix/plugin
...
docs/plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
647eb8a95f | ||
|
|
a20e6a7824 | ||
|
|
8941820e41 | ||
|
|
1f70fbf936 | ||
|
|
6601123382 | ||
|
|
64ec02f118 | ||
|
|
e9481fdeae | ||
|
|
cc2fbd7e10 | ||
|
|
e1ff24903f | ||
|
|
b9e71240ed | ||
|
|
d3a0a623a3 | ||
|
|
d1b080eac5 | ||
|
|
49091ebcbd | ||
|
|
4685fc7e77 | ||
|
|
52a0aa0672 | ||
|
|
a07dcfde84 | ||
|
|
7066316db8 | ||
|
|
ad24fccff5 | ||
|
|
c70ae1c96e | ||
|
|
67e61acac7 | ||
|
|
b70b7b0d94 | ||
|
|
3382ef2724 | ||
|
|
aa6b962a3a | ||
|
|
8ac3e41cdf | ||
|
|
574cc9de64 | ||
|
|
3cd4978fc2 | ||
|
|
2d492ab534 | ||
|
|
4becbc8b25 | ||
|
|
b4656f193a | ||
|
|
f537ea90ed | ||
|
|
037fa2f8fb | ||
|
|
94ec0d6aeb | ||
|
|
537115bbdc | ||
|
|
ec0e4ff218 | ||
|
|
b2f9ab9a1f | ||
|
|
041f0b87ec | ||
|
|
c96a12aeb9 | ||
|
|
f783101735 | ||
|
|
707eb8e1b3 | ||
|
|
e1854dfbf6 | ||
|
|
8727338372 | ||
|
|
2b210703a3 | ||
|
|
432e8943ad | ||
|
|
7a0dacbfba | ||
|
|
8790c54635 | ||
|
|
0f6dbb4390 | ||
|
|
ec59974a46 | ||
|
|
60f559e217 | ||
|
|
4c9f411f6d | ||
|
|
7ac312b8fe | ||
|
|
e7e4c68caf | ||
|
|
a2472dc31b | ||
|
|
177136c964 | ||
|
|
b2380b3ab1 | ||
|
|
89bc66feef | ||
|
|
36feecf018 | ||
|
|
1e98dbcad3 | ||
|
|
2909d8cd12 | ||
|
|
88da51d91b | ||
|
|
506861efd0 | ||
|
|
f7866c1c15 | ||
|
|
6c4eced494 | ||
|
|
eea84bc6ec | ||
|
|
d38561acbe | ||
|
|
d6346aaf63 | ||
|
|
e7814f7ba0 | ||
|
|
b67baae1f6 | ||
|
|
6db72746fb | ||
|
|
518d2dd6a9 | ||
|
|
14237aa6c0 | ||
|
|
8e6a4c2d82 | ||
|
|
b1ab7ba3ac | ||
|
|
4f210e98a5 | ||
|
|
7b344b8a8a | ||
|
|
d81772dbc7 | ||
|
|
2cc777539a | ||
|
|
1ad3893b39 | ||
|
|
17713ec988 |
42
.github/workflows/ci.yml
vendored
42
.github/workflows/ci.yml
vendored
@@ -185,8 +185,8 @@ jobs:
|
||||
run: pnpm release:check
|
||||
|
||||
checks:
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true'
|
||||
needs: [docs-scope, changed-scope, build-artifacts]
|
||||
if: always() && needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_node == 'true' && (github.event_name != 'push' || needs.build-artifacts.result == 'success')
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -263,6 +263,17 @@ jobs:
|
||||
echo "OPENCLAW_TEST_SHARD_INDEX=$SHARD_INDEX" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Download dist artifact
|
||||
if: github.event_name == 'push' && matrix.task == 'test'
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: dist-build
|
||||
path: dist/
|
||||
|
||||
- name: Build dist
|
||||
if: github.event_name != 'push' && matrix.task == 'test' && matrix.runtime == 'node'
|
||||
run: pnpm build
|
||||
|
||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||
if: github.event_name != 'pull_request' || (matrix.runtime != 'bun' && matrix.task != 'compat-node22')
|
||||
run: ${{ matrix.command }}
|
||||
@@ -368,6 +379,11 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: pnpm config:docs:check
|
||||
|
||||
- name: Check plugin SDK API baseline drift
|
||||
id: plugin_sdk_api_drift
|
||||
continue-on-error: true
|
||||
run: pnpm plugin-sdk:api:check
|
||||
|
||||
- name: Upload gateway watch regression artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -386,6 +402,7 @@ jobs:
|
||||
NO_RAW_WINDOW_OPEN_OUTCOME: ${{ steps.no_raw_window_open.outcome }}
|
||||
GATEWAY_WATCH_REGRESSION_OUTCOME: ${{ steps.gateway_watch_regression.outcome }}
|
||||
CONFIG_DOCS_DRIFT_OUTCOME: ${{ steps.config_docs_drift.outcome }}
|
||||
PLUGIN_SDK_API_DRIFT_OUTCOME: ${{ steps.plugin_sdk_api_drift.outcome }}
|
||||
run: |
|
||||
failures=0
|
||||
for result in \
|
||||
@@ -395,7 +412,8 @@ jobs:
|
||||
"extension-plugin-sdk-internal-boundary|$EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME" \
|
||||
"lint:ui:no-raw-window-open|$NO_RAW_WINDOW_OPEN_OUTCOME" \
|
||||
"gateway-watch-regression|$GATEWAY_WATCH_REGRESSION_OUTCOME" \
|
||||
"config-docs-drift|$CONFIG_DOCS_DRIFT_OUTCOME"; do
|
||||
"config-docs-drift|$CONFIG_DOCS_DRIFT_OUTCOME" \
|
||||
"plugin-sdk-api-drift|$PLUGIN_SDK_API_DRIFT_OUTCOME"; do
|
||||
name="${result%%|*}"
|
||||
outcome="${result#*|}"
|
||||
if [ "$outcome" != "success" ]; then
|
||||
@@ -570,8 +588,8 @@ jobs:
|
||||
run: pre-commit run --all-files pnpm-audit-prod
|
||||
|
||||
checks-windows:
|
||||
needs: [docs-scope, changed-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_windows == 'true'
|
||||
needs: [docs-scope, changed-scope, build-artifacts]
|
||||
if: always() && needs.docs-scope.outputs.docs_only != 'true' && needs.changed-scope.outputs.run_windows == 'true' && (github.event_name != 'push' || needs.build-artifacts.result == 'success')
|
||||
runs-on: blacksmith-32vcpu-windows-2025
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
@@ -691,6 +709,17 @@ jobs:
|
||||
if: matrix.task == 'test'
|
||||
run: pnpm canvas:a2ui:bundle
|
||||
|
||||
- name: Download dist artifact
|
||||
if: github.event_name == 'push' && matrix.task == 'test'
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: dist-build
|
||||
path: dist/
|
||||
|
||||
- name: Build dist (Windows)
|
||||
if: github.event_name != 'push' && matrix.task == 'test'
|
||||
run: pnpm build
|
||||
|
||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||
run: ${{ matrix.command }}
|
||||
|
||||
@@ -713,6 +742,9 @@ jobs:
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Build dist (macOS)
|
||||
run: pnpm build
|
||||
|
||||
# --- Run all checks sequentially (fast gates first) ---
|
||||
- name: TS tests (macOS)
|
||||
env:
|
||||
|
||||
5
.github/workflows/workflow-sanity.yml
vendored
5
.github/workflows/workflow-sanity.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Disallow direct inputs interpolation in composite run blocks
|
||||
run: python3 scripts/check-composite-action-input-interpolation.py
|
||||
|
||||
config-docs-drift:
|
||||
generated-doc-baselines:
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
@@ -87,3 +87,6 @@ jobs:
|
||||
|
||||
- name: Check config docs drift statefile
|
||||
run: pnpm config:docs:check
|
||||
|
||||
- name: Check plugin SDK API baseline drift
|
||||
run: pnpm plugin-sdk:api:check
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
- Format check: `pnpm format` (oxfmt --check)
|
||||
- Format fix: `pnpm format:fix` (oxfmt --write)
|
||||
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
||||
- Generated baseline artifacts live together under `docs/.generated/`.
|
||||
- Config schema drift uses `pnpm config:docs:gen` / `pnpm config:docs:check`.
|
||||
- Plugin SDK API drift uses `pnpm plugin-sdk:api:gen` / `pnpm plugin-sdk:api:check`.
|
||||
- If you change config schema/help or the public Plugin SDK surface, update the matching baseline artifact and keep the two drift-check flows adjacent in scripts/workflows/docs guidance rather than inventing a third pattern.
|
||||
- For narrowly scoped changes, prefer narrowly scoped tests that directly validate the touched behavior. If no meaningful scoped test exists, say so explicitly and use the next most direct validation available.
|
||||
- Preferred landing bar for pushes to `main`: `pnpm check` and `pnpm test`, with a green result when feasible.
|
||||
- Scoped tests prove the change itself. `pnpm test` remains the default `main` landing bar; scoped tests do not replace full-suite gates by default.
|
||||
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -63,11 +63,14 @@ Docs: https://docs.openclaw.ai
|
||||
- Models/OpenAI: switch the default OpenAI setup model to `openai/gpt-5.4`, keep Codex on `openai-codex/gpt-5.4`, and centralize OpenAI chat, image, TTS, transcription, and embedding defaults in one shared module so future default-model updates stay low-churn. Thanks @vincentkoc.
|
||||
- Memory/plugins: let the active memory plugin register its own system-prompt section while preserving cache-clear and snapshot-load prompt isolation. (#40126) Thanks @jarimustonen.
|
||||
- Control UI/usage: improve usage overview styling, localization, and responsive chat/context-notice presentation, including safer theme color handling and unclipped usage-header menus. (#51951) Thanks @BunsDev.
|
||||
- Agents: add per-agent thinking/reasoning/fast defaults and auto-revert disallowed model overrides to the agent's default selection. Thanks @xuanmingguo and @vincentkoc.
|
||||
- Control UI/usage: drop the empty session-detail placeholder card so the usage view stays single-column until a real session detail panel is selected. (#52013) Thanks @BunsDev.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/default timeout: raise the shared default agent timeout from `600s` to `48h` so long-running ACP and agent sessions do not fail unless you configure a shorter limit.
|
||||
- Gateway/Linux: auto-detect nvm-managed Node TLS CA bundle needs before CLI startup and refresh installed services that are missing `NODE_EXTRA_CA_CERTS`. (#51146) Thanks @GodsBoy.
|
||||
- Android/pairing: resolve portless secure setup URLs to `443` while preserving direct cleartext gateway defaults and explicit `:80` manual endpoints in onboarding. (#43540) Thanks @fmercurio.
|
||||
- CLI/config: make `config set --strict-json` enforce real JSON, prefer `JSON.parse` with JSON5 fallback for machine-written cron/subagent stores, and relabel raw config surfaces as `JSON/JSON5` to match actual compatibility. Related: #48415, #43127, #14529, #21332. Thanks @adhitShet and @vincentkoc.
|
||||
- CLI/Ollama onboarding: keep the interactive model picker for explicit `openclaw onboard --auth-choice ollama` runs so setup still selects a default model without reintroducing pre-picker auto-pulls. (#49249) Thanks @BruceMacD.
|
||||
- Plugins/bundler TDZ: fix `RESERVED_COMMANDS` temporal dead zone error that prevented device-pair, phone-control, and talk-voice plugins from registering when the bundler placed the commands module after call sites in the same output chunk. Thanks @BunsDev.
|
||||
@@ -89,8 +92,10 @@ Docs: https://docs.openclaw.ai
|
||||
- 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: avoid loading provider discovery during startup model normalization. (#46522) Thanks @ItsAditya-xyz and @vincentkoc.
|
||||
- Agents/Telegram: avoid rebuilding the full model catalog on ordinary inbound replies so Telegram message handling no longer pays multi-second core startup latency before reply generation. Thanks @vincentkoc.
|
||||
- Gateway/Discord startup: load only configured channel plugins during gateway boot, and lazy-load Discord provider/session runtime setup so startup stops importing unrelated providers and trims cold-start delay. Thanks @vincentkoc.
|
||||
- Agents/inbound: lazy-load media and link understanding for plain-text turns and cache synced auth stores by auth-file state so ordinary inbound replies avoid unnecessary startup churn. Thanks @vincentkoc.
|
||||
- Telegram/polling: hard-timeout stuck `getUpdates` requests so wedged network paths fail over sooner instead of waiting for the polling stall watchdog. Thanks @vincentkoc.
|
||||
- Agents/models: cache `models.json` readiness by config and auth-file state so embedded runner turns stop paying repeated model-catalog startup work before replies. Thanks @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. (#46802) Thanks @vincentkoc.
|
||||
@@ -107,6 +112,8 @@ Docs: https://docs.openclaw.ai
|
||||
- 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.
|
||||
- Telegram/setup: warn when setup leaves DMs on pairing without an allowlist, and show valid account-scoped remediation commands. (#50710) Thanks @ernestodeoliveira.
|
||||
- Doctor/Telegram: replace the fresh-install empty group-allowlist false positive with first-run guidance that explains DM pairing approval and the next group setup steps, so new Telegram installs get actionable setup help instead of a broken-config warning. Thanks @vincentkoc.
|
||||
- Doctor/extensions: keep Matrix DM `allowFrom` repairs on the canonical `dm.allowFrom` path and stop treating Zalouser group sender gating as if it fell back to `allowFrom`, so doctor warnings and `--fix` stay aligned with runtime access control. Thanks @vincentkoc.
|
||||
- Doctor/refactor: centralize built-in channel doctor semantics in one static capability registry with conservative fallback behavior for unknown/external channels, so future extension changes stop depending on scattered shared string checks. Thanks @vincentkoc.
|
||||
- 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.
|
||||
@@ -117,6 +124,8 @@ Docs: https://docs.openclaw.ai
|
||||
- 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.
|
||||
- Android/canvas: serialize A2UI action-status event strings before evaluating WebView JS, so action ids and multiline errors do not break the callback dispatch. (#43784) Thanks @Kaneki-x.
|
||||
- Android/camera: recycle intermediate and final snap bitmaps in `camera.snap` so repeated captures do not leak native image memory. (#41902) Thanks @Kaneki-x.
|
||||
- Telegram/message chunking: preserve spaces, paragraph separators, and word boundaries when HTML overflow rechunking splits formatted replies. (#47274) Thanks @obviyus.
|
||||
- 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) Thanks @obviyus.
|
||||
- CI/onboarding smoke: surface `ensure-base-commit` fetch failures as workflow warnings and fail the onboarding Docker smoke when expected setup prompts drift instead of continuing silently. Thanks @Takhoffman.
|
||||
@@ -197,6 +206,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/security: add regression coverage proving pinned fallback host overrides stay bound to Telegram and delegate non-matching hostnames back to the original lookup path. Thanks @vincentkoc.
|
||||
- Secrets/exec refs: require explicit `--allow-exec` for `secrets apply` write plans that contain exec SecretRefs/providers, and align audit/configure/apply dry-run behavior to skip exec checks unless opted in to prevent unexpected command side effects. (#49417) Thanks @restriction and @joshavant.
|
||||
- Tools/image generation: add bundled fal image generation support so `image_generate` can target `fal/*` models with `FAL_KEY`, including single-image edit flows via FLUX image-to-image. Thanks @vincentkoc.
|
||||
- Messages/polls: treat zero-valued poll params on `message.send` as unset defaults while keeping non-zero poll params on the poll validation path. (#52150) Fixes #52118. Thanks @Bartok9.
|
||||
- xAI/web search: add missing Grok credential metadata so the bundled provider registration type-checks again. (#49472) thanks @scoootscooob.
|
||||
- Signal/runtime API: re-export `SignalAccountConfig` so Signal account resolution type-checks again. (#49470) Thanks @scoootscooob.
|
||||
- Google Chat/runtime API: thin the private runtime barrel onto the curated public SDK surface while keeping public Google Chat exports intact. (#49504) Thanks @scoootscooob.
|
||||
@@ -225,6 +235,10 @@ Docs: https://docs.openclaw.ai
|
||||
- Discord/pickers: keep `/codex_resume --browse-projects` picker callbacks alive in Discord by sharing component callback state across duplicate module graphs, preserving callback fallbacks, and acknowledging matched plugin interactions before dispatch. (#51260) Thanks @huntharo.
|
||||
- Agents/memory flush: keep transcript-hash dedup active across memory-flush fallback retries so a write-then-throw flush attempt cannot append duplicate `MEMORY.md` entries before the fallback cycle completes. (#34222) Thanks @lml2468.
|
||||
- make `openclaw update status` explicitly say `up to date` when the local version already matches npm latest, while keeping the availability logic unchanged. (#51409) Thanks @dongzhenye.
|
||||
- Android/canvas: recycle captured and scaled snapshot bitmaps so repeated canvas snapshots do not leak native image memory. (#41889) Thanks @Kaneki-x.
|
||||
- Android/theme: switch status bar icon contrast with the active system theme so Android light mode no longer leaves unreadable light icons over the app header. (#51098) Thanks @goweii.
|
||||
- Discord/ACP: forward worker abort signals into ACP turns so timed-out Discord jobs cancel the running turn instead of silently leaving the bound ACP session working in the background.
|
||||
- Gateway/openresponses: preserve assistant commentary and session continuity across hosted-tool `/v1/responses` turns, and emit streamed tool-call payloads before finalization so client tool loops stay resumable. (#52171) Thanks @CharZhou.
|
||||
|
||||
### Breaking
|
||||
|
||||
@@ -239,6 +253,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/Matrix: add a new Matrix plugin backed by the official `matrix-js-sdk`. If you are upgrading from the previous public Matrix plugin, follow the migration guide: https://docs.openclaw.ai/install/migrating-matrix Thanks @gumadeiras.
|
||||
- Discord/commands: switch native command deployment to Carbon reconcile by default so Discord restarts stop churning slash commands through OpenClaw’s local deploy path. (#46597) Thanks @huntharo and @thewilloftheshadow.
|
||||
- Plugins/Matrix: durably dedupe inbound room events across gateway restarts so previously handled Matrix messages are not replayed as new, while preserving clean-restart backlog delivery for unseen events. (#50922) thanks @gumadeiras
|
||||
- Agents/media replies: migrate the remaining browser, canvas, and nodes snapshot outputs onto `details.media` so generated media keeps attaching to assistant replies after the collect-then-attach refactor. (#51731) Thanks @christianklotz.
|
||||
- Android/contacts search: escape literal `%` and `_` in contact-name queries so searches like `100%` or `_id` no longer match unrelated contacts through SQL `LIKE` wildcards. (#41891) Thanks @Kaneki-x.
|
||||
|
||||
## 2026.3.13
|
||||
|
||||
@@ -555,6 +571,8 @@ Docs: https://docs.openclaw.ai
|
||||
- macOS/remote gateway: stop PortGuardian from killing Docker Desktop and other external listeners on the gateway port in remote mode, so containerized and tunneled gateway setups no longer lose their port-forward owner on app startup. (#6755) Thanks @teslamint.
|
||||
- Feishu/streaming recovery: clear stale `streamingStartPromise` when card creation fails (HTTP 400) so subsequent messages can retry streaming instead of silently dropping all future replies. Fixes #43322.
|
||||
- Exec/env sandbox: block JVM agent injection (`JAVA_TOOL_OPTIONS`, `_JAVA_OPTIONS`, `JDK_JAVA_OPTIONS`), Python breakpoint hijack (`PYTHONBREAKPOINT`), and .NET startup hooks (`DOTNET_STARTUP_HOOKS`) from the host exec environment. (#49025)
|
||||
- Android/camera clip cleanup: delete temporary clip files even when `readBytes()` fails so failed clip captures do not leak cache storage. (#41890) Thanks @Kaneki-x.
|
||||
- Android/photos: recycle decoded and intermediate bitmaps in `photos.latest` so repeated photo fetches stop leaking native memory. (#41888) Thanks @Kaneki-x.
|
||||
|
||||
### Security
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.content.pm.PackageManager
|
||||
import android.content.Intent
|
||||
import android.Manifest
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -11,17 +13,21 @@ import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
class PermissionRequester(private val activity: ComponentActivity) {
|
||||
private val mutex = Mutex()
|
||||
private var pending: CompletableDeferred<Map<String, Boolean>>? = null
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
private val launcher: ActivityResultLauncher<Array<String>> =
|
||||
activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
|
||||
@@ -86,32 +92,84 @@ class PermissionRequester(private val activity: ComponentActivity) {
|
||||
|
||||
private suspend fun showRationaleDialog(permissions: List<String>): Boolean =
|
||||
withContext(Dispatchers.Main) {
|
||||
if (activity.isFinishing || activity.isDestroyed) {
|
||||
return@withContext false
|
||||
}
|
||||
suspendCancellableCoroutine { cont ->
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle("Permission required")
|
||||
.setMessage(buildRationaleMessage(permissions))
|
||||
.setPositiveButton("Continue") { _, _ -> cont.resume(true) }
|
||||
.setNegativeButton("Not now") { _, _ -> cont.resume(false) }
|
||||
.setOnCancelListener { cont.resume(false) }
|
||||
.show()
|
||||
val lifecycle = activity.lifecycle
|
||||
var dialog: AlertDialog? = null
|
||||
var observer: LifecycleEventObserver? = null
|
||||
val finished = AtomicBoolean(false)
|
||||
val removeObserver = {
|
||||
observer?.let(lifecycle::removeObserver)
|
||||
observer = null
|
||||
}
|
||||
fun finish(result: Boolean?) {
|
||||
if (!finished.compareAndSet(false, true)) return
|
||||
removeObserver()
|
||||
dialog?.dismiss()
|
||||
if (result != null) {
|
||||
cont.resume(result)
|
||||
}
|
||||
}
|
||||
val actualObserver =
|
||||
LifecycleEventObserver { _, event ->
|
||||
if (event != Lifecycle.Event.ON_DESTROY) return@LifecycleEventObserver
|
||||
finish(false)
|
||||
}
|
||||
observer = actualObserver
|
||||
lifecycle.addObserver(actualObserver)
|
||||
cont.invokeOnCancellation {
|
||||
mainHandler.post {
|
||||
finish(null)
|
||||
}
|
||||
}
|
||||
dialog =
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle("Permission required")
|
||||
.setMessage(buildRationaleMessage(permissions))
|
||||
.setPositiveButton("Continue") { _, _ -> finish(true) }
|
||||
.setNegativeButton("Not now") { _, _ -> finish(false) }
|
||||
.setOnCancelListener { finish(false) }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSettingsDialog(permissions: List<String>) {
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle("Enable permission in Settings")
|
||||
.setMessage(buildSettingsMessage(permissions))
|
||||
.setPositiveButton("Open Settings") { _, _ ->
|
||||
val intent =
|
||||
Intent(
|
||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
Uri.fromParts("package", activity.packageName, null),
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
private suspend fun showSettingsDialog(permissions: List<String>) =
|
||||
withContext(Dispatchers.Main) {
|
||||
if (activity.isFinishing || activity.isDestroyed) return@withContext
|
||||
val lifecycle = activity.lifecycle
|
||||
var dialog: AlertDialog? = null
|
||||
var observer: LifecycleEventObserver? = null
|
||||
val removeObserver = {
|
||||
observer?.let(lifecycle::removeObserver)
|
||||
observer = null
|
||||
}
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show()
|
||||
}
|
||||
val actualObserver =
|
||||
LifecycleEventObserver { _, event ->
|
||||
if (event != Lifecycle.Event.ON_DESTROY) return@LifecycleEventObserver
|
||||
removeObserver()
|
||||
dialog?.dismiss()
|
||||
}
|
||||
observer = actualObserver
|
||||
lifecycle.addObserver(actualObserver)
|
||||
dialog =
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle("Enable permission in Settings")
|
||||
.setMessage(buildSettingsMessage(permissions))
|
||||
.setPositiveButton("Open Settings") { _, _ ->
|
||||
if (activity.isFinishing || activity.isDestroyed) return@setPositiveButton
|
||||
val intent =
|
||||
Intent(
|
||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
Uri.fromParts("package", activity.packageName, null),
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
.setNegativeButton("Cancel", null)
|
||||
.setOnDismissListener { removeObserver() }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun buildRationaleMessage(permissions: List<String>): String {
|
||||
val labels = permissions.map { permissionLabel(it) }
|
||||
|
||||
@@ -121,42 +121,48 @@ class CameraCaptureManager(private val context: Context) {
|
||||
(rotated.height.toDouble() * (maxWidth.toDouble() / rotated.width.toDouble()))
|
||||
.toInt()
|
||||
.coerceAtLeast(1)
|
||||
rotated.scale(maxWidth, h)
|
||||
val s = rotated.scale(maxWidth, h)
|
||||
if (s !== rotated) rotated.recycle()
|
||||
s
|
||||
} else {
|
||||
rotated
|
||||
}
|
||||
|
||||
val maxPayloadBytes = 5 * 1024 * 1024
|
||||
// Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
|
||||
val maxEncodedBytes = (maxPayloadBytes / 4) * 3
|
||||
val result =
|
||||
JpegSizeLimiter.compressToLimit(
|
||||
initialWidth = scaled.width,
|
||||
initialHeight = scaled.height,
|
||||
startQuality = (quality * 100.0).roundToInt().coerceIn(10, 100),
|
||||
maxBytes = maxEncodedBytes,
|
||||
encode = { width, height, q ->
|
||||
val bitmap =
|
||||
if (width == scaled.width && height == scaled.height) {
|
||||
scaled
|
||||
} else {
|
||||
scaled.scale(width, height)
|
||||
try {
|
||||
val maxPayloadBytes = 5 * 1024 * 1024
|
||||
// Base64 inflates payloads by ~4/3; cap encoded bytes so the payload stays under 5MB (API limit).
|
||||
val maxEncodedBytes = (maxPayloadBytes / 4) * 3
|
||||
val result =
|
||||
JpegSizeLimiter.compressToLimit(
|
||||
initialWidth = scaled.width,
|
||||
initialHeight = scaled.height,
|
||||
startQuality = (quality * 100.0).roundToInt().coerceIn(10, 100),
|
||||
maxBytes = maxEncodedBytes,
|
||||
encode = { width, height, q ->
|
||||
val bitmap =
|
||||
if (width == scaled.width && height == scaled.height) {
|
||||
scaled
|
||||
} else {
|
||||
scaled.scale(width, height)
|
||||
}
|
||||
val out = ByteArrayOutputStream()
|
||||
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, q, out)) {
|
||||
if (bitmap !== scaled) bitmap.recycle()
|
||||
throw IllegalStateException("UNAVAILABLE: failed to encode JPEG")
|
||||
}
|
||||
val out = ByteArrayOutputStream()
|
||||
if (!bitmap.compress(Bitmap.CompressFormat.JPEG, q, out)) {
|
||||
if (bitmap !== scaled) bitmap.recycle()
|
||||
throw IllegalStateException("UNAVAILABLE: failed to encode JPEG")
|
||||
}
|
||||
if (bitmap !== scaled) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
out.toByteArray()
|
||||
},
|
||||
if (bitmap !== scaled) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
out.toByteArray()
|
||||
},
|
||||
)
|
||||
val base64 = Base64.encodeToString(result.bytes, Base64.NO_WRAP)
|
||||
Payload(
|
||||
"""{"format":"jpg","base64":"$base64","width":${result.width},"height":${result.height}}""",
|
||||
)
|
||||
val base64 = Base64.encodeToString(result.bytes, Base64.NO_WRAP)
|
||||
Payload(
|
||||
"""{"format":"jpg","base64":"$base64","width":${result.width},"height":${result.height}}""",
|
||||
)
|
||||
} finally {
|
||||
scaled.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
|
||||
@@ -134,9 +134,11 @@ class CameraHandler(
|
||||
}
|
||||
|
||||
val bytes = withContext(Dispatchers.IO) {
|
||||
val b = filePayload.file.readBytes()
|
||||
filePayload.file.delete()
|
||||
b
|
||||
try {
|
||||
filePayload.file.readBytes()
|
||||
} finally {
|
||||
filePayload.file.delete()
|
||||
}
|
||||
}
|
||||
val base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
|
||||
clipLog("returning base64 payload")
|
||||
|
||||
@@ -180,27 +180,41 @@ class CanvasController {
|
||||
withContext(Dispatchers.Main) {
|
||||
val wv = webView ?: throw IllegalStateException("no webview")
|
||||
val bmp = wv.captureBitmap()
|
||||
val scaled = bmp.scaleForMaxWidth(maxWidth)
|
||||
|
||||
val out = ByteArrayOutputStream()
|
||||
scaled.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
|
||||
try {
|
||||
val scaled = bmp.scaleForMaxWidth(maxWidth)
|
||||
try {
|
||||
val out = ByteArrayOutputStream()
|
||||
scaled.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
|
||||
} finally {
|
||||
if (scaled !== bmp) scaled.recycle()
|
||||
}
|
||||
} finally {
|
||||
bmp.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun snapshotBase64(format: SnapshotFormat, quality: Double?, maxWidth: Int?): String =
|
||||
withContext(Dispatchers.Main) {
|
||||
val wv = webView ?: throw IllegalStateException("no webview")
|
||||
val bmp = wv.captureBitmap()
|
||||
val scaled = bmp.scaleForMaxWidth(maxWidth)
|
||||
|
||||
val out = ByteArrayOutputStream()
|
||||
val (compressFormat, compressQuality) =
|
||||
when (format) {
|
||||
SnapshotFormat.Png -> Bitmap.CompressFormat.PNG to 100
|
||||
SnapshotFormat.Jpeg -> Bitmap.CompressFormat.JPEG to clampJpegQuality(quality)
|
||||
try {
|
||||
val scaled = bmp.scaleForMaxWidth(maxWidth)
|
||||
try {
|
||||
val out = ByteArrayOutputStream()
|
||||
val (compressFormat, compressQuality) =
|
||||
when (format) {
|
||||
SnapshotFormat.Png -> Bitmap.CompressFormat.PNG to 100
|
||||
SnapshotFormat.Jpeg -> Bitmap.CompressFormat.JPEG to clampJpegQuality(quality)
|
||||
}
|
||||
scaled.compress(compressFormat, compressQuality, out)
|
||||
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
|
||||
} finally {
|
||||
if (scaled !== bmp) scaled.recycle()
|
||||
}
|
||||
scaled.compress(compressFormat, compressQuality, out)
|
||||
Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
|
||||
} finally {
|
||||
bmp.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun WebView.captureBitmap(): Bitmap =
|
||||
|
||||
@@ -76,8 +76,8 @@ private object SystemContactsDataSource : ContactsDataSource {
|
||||
selection = null
|
||||
selectionArgs = null
|
||||
} else {
|
||||
selection = "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
|
||||
selectionArgs = arrayOf("%${request.query}%")
|
||||
selection = "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ? ESCAPE '\\'"
|
||||
selectionArgs = arrayOf("%${escapeLikePattern(request.query)}%")
|
||||
}
|
||||
val sortOrder = "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} COLLATE NOCASE ASC LIMIT ${request.limit}"
|
||||
resolver.query(
|
||||
@@ -247,6 +247,9 @@ private object SystemContactsDataSource : ContactsDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
private fun escapeLikePattern(pattern: String): String =
|
||||
pattern.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
||||
|
||||
private fun loadPhones(resolver: ContentResolver, contactId: Long): List<String> {
|
||||
return queryContactValues(
|
||||
resolver = resolver,
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.os.SystemClock
|
||||
import androidx.core.content.ContextCompat
|
||||
import ai.openclaw.app.gateway.GatewaySession
|
||||
import java.time.Instant
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -18,7 +19,6 @@ import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sqrt
|
||||
@@ -142,19 +142,18 @@ private object SystemMotionDataSource : MotionDataSource {
|
||||
val averageDelta: Double,
|
||||
)
|
||||
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
private suspend fun readStepCounter(sensorManager: SensorManager, sensor: Sensor): Int? {
|
||||
val sample =
|
||||
withTimeoutOrNull(1200L) {
|
||||
suspendCancellableCoroutine<Float?> { cont ->
|
||||
var resumed = false
|
||||
val listener =
|
||||
object : SensorEventListener {
|
||||
override fun onSensorChanged(event: SensorEvent?) {
|
||||
if (resumed) return
|
||||
val value = event?.values?.firstOrNull()
|
||||
resumed = true
|
||||
val token = cont.tryResume(value) ?: return
|
||||
cont.completeResume(token)
|
||||
sensorManager.unregisterListener(this)
|
||||
cont.resume(value)
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
|
||||
@@ -162,8 +161,7 @@ private object SystemMotionDataSource : MotionDataSource {
|
||||
val registered = sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
|
||||
if (!registered) {
|
||||
sensorManager.unregisterListener(listener)
|
||||
resumed = true
|
||||
cont.resume(null)
|
||||
cont.resume(null) { _, _, _ -> }
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
cont.invokeOnCancellation { sensorManager.unregisterListener(listener) }
|
||||
@@ -172,6 +170,7 @@ private object SystemMotionDataSource : MotionDataSource {
|
||||
return sample?.toInt()?.takeIf { it >= 0 }
|
||||
}
|
||||
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
private suspend fun readAccelerometerSample(
|
||||
sensorManager: SensorManager,
|
||||
sensor: Sensor,
|
||||
@@ -181,7 +180,6 @@ private object SystemMotionDataSource : MotionDataSource {
|
||||
suspendCancellableCoroutine<AccelerometerSample?> { cont ->
|
||||
var count = 0
|
||||
var sumDelta = 0.0
|
||||
var resumed = false
|
||||
val listener =
|
||||
object : SensorEventListener {
|
||||
override fun onSensorChanged(event: SensorEvent?) {
|
||||
@@ -195,15 +193,14 @@ private object SystemMotionDataSource : MotionDataSource {
|
||||
).toDouble()
|
||||
sumDelta += abs(magnitude - SensorManager.GRAVITY_EARTH.toDouble())
|
||||
count += 1
|
||||
if (count >= ACCELEROMETER_SAMPLE_TARGET && !resumed) {
|
||||
resumed = true
|
||||
sensorManager.unregisterListener(this)
|
||||
cont.resume(
|
||||
AccelerometerSample(
|
||||
samples = count,
|
||||
averageDelta = if (count == 0) 0.0 else sumDelta / count,
|
||||
),
|
||||
if (count >= ACCELEROMETER_SAMPLE_TARGET) {
|
||||
val result = AccelerometerSample(
|
||||
samples = count,
|
||||
averageDelta = sumDelta / count,
|
||||
)
|
||||
val token = cont.tryResume(result) ?: return
|
||||
cont.completeResume(token)
|
||||
sensorManager.unregisterListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,8 +208,7 @@ private object SystemMotionDataSource : MotionDataSource {
|
||||
}
|
||||
val registered = sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
|
||||
if (!registered) {
|
||||
resumed = true
|
||||
cont.resume(null)
|
||||
cont.resume(null) { _, _, _ -> }
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
cont.invokeOnCancellation { sensorManager.unregisterListener(listener) }
|
||||
|
||||
@@ -71,17 +71,22 @@ private object SystemPhotosDataSource : PhotosDataSource {
|
||||
for (row in rows) {
|
||||
if (remainingBudget <= 0) break
|
||||
val bitmap = decodeScaledBitmap(resolver, row.uri, request.maxWidth) ?: continue
|
||||
val encoded = encodeJpegUnderBudget(bitmap, request.quality, MAX_PER_PHOTO_BASE64_CHARS) ?: continue
|
||||
if (encoded.base64.length > remainingBudget) break
|
||||
remainingBudget -= encoded.base64.length
|
||||
out +=
|
||||
EncodedPhotoPayload(
|
||||
format = "jpeg",
|
||||
base64 = encoded.base64,
|
||||
width = encoded.width,
|
||||
height = encoded.height,
|
||||
createdAt = row.createdAtMs?.let { Instant.ofEpochMilli(it).toString() },
|
||||
)
|
||||
try {
|
||||
val encoded = encodeJpegUnderBudget(bitmap, request.quality, MAX_PER_PHOTO_BASE64_CHARS)
|
||||
if (encoded == null) continue
|
||||
if (encoded.base64.length > remainingBudget) break
|
||||
remainingBudget -= encoded.base64.length
|
||||
out +=
|
||||
EncodedPhotoPayload(
|
||||
format = "jpeg",
|
||||
base64 = encoded.base64,
|
||||
width = encoded.width,
|
||||
height = encoded.height,
|
||||
createdAt = row.createdAtMs?.let { Instant.ofEpochMilli(it).toString() },
|
||||
)
|
||||
} finally {
|
||||
bitmap.recycle()
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -159,7 +164,11 @@ private object SystemPhotosDataSource : PhotosDataSource {
|
||||
|
||||
if (decoded.width <= maxWidth) return decoded
|
||||
val targetHeight = max(1, ((decoded.height.toDouble() * maxWidth) / decoded.width).roundToInt())
|
||||
return decoded.scale(maxWidth, targetHeight, true)
|
||||
return try {
|
||||
decoded.scale(maxWidth, targetHeight, true)
|
||||
} finally {
|
||||
decoded.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeInSampleSize(width: Int, maxWidth: Int): Int {
|
||||
@@ -178,30 +187,36 @@ private object SystemPhotosDataSource : PhotosDataSource {
|
||||
maxBase64Chars: Int,
|
||||
): EncodedJpeg? {
|
||||
var working = bitmap
|
||||
var jpegQuality = (quality.coerceIn(0.1, 1.0) * 100.0).roundToInt().coerceIn(10, 100)
|
||||
repeat(10) {
|
||||
val out = ByteArrayOutputStream()
|
||||
val ok = working.compress(Bitmap.CompressFormat.JPEG, jpegQuality, out)
|
||||
if (!ok) return null
|
||||
val bytes = out.toByteArray()
|
||||
val base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
|
||||
if (base64.length <= maxBase64Chars) {
|
||||
return EncodedJpeg(
|
||||
base64 = base64,
|
||||
width = working.width,
|
||||
height = working.height,
|
||||
)
|
||||
try {
|
||||
var jpegQuality = (quality.coerceIn(0.1, 1.0) * 100.0).roundToInt().coerceIn(10, 100)
|
||||
repeat(10) {
|
||||
val out = ByteArrayOutputStream()
|
||||
val ok = working.compress(Bitmap.CompressFormat.JPEG, jpegQuality, out)
|
||||
if (!ok) return null
|
||||
val bytes = out.toByteArray()
|
||||
val base64 = android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP)
|
||||
if (base64.length <= maxBase64Chars) {
|
||||
return EncodedJpeg(
|
||||
base64 = base64,
|
||||
width = working.width,
|
||||
height = working.height,
|
||||
)
|
||||
}
|
||||
if (jpegQuality > 35) {
|
||||
jpegQuality = max(25, jpegQuality - 15)
|
||||
return@repeat
|
||||
}
|
||||
val nextWidth = max(240, (working.width * 0.75f).roundToInt())
|
||||
if (nextWidth >= working.width) return null
|
||||
val nextHeight = max(1, ((working.height.toDouble() * nextWidth) / working.width).roundToInt())
|
||||
val previous = working
|
||||
working = working.scale(nextWidth, nextHeight, true)
|
||||
if (previous !== bitmap) previous.recycle()
|
||||
}
|
||||
if (jpegQuality > 35) {
|
||||
jpegQuality = max(25, jpegQuality - 15)
|
||||
return@repeat
|
||||
}
|
||||
val nextWidth = max(240, (working.width * 0.75f).roundToInt())
|
||||
if (nextWidth >= working.width) return null
|
||||
val nextHeight = max(1, ((working.height.toDouble() * nextWidth) / working.width).roundToInt())
|
||||
working = working.scale(nextWidth, nextHeight, true)
|
||||
return null
|
||||
} finally {
|
||||
if (working !== bitmap) working.recycle()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,9 +58,12 @@ object OpenClawCanvasA2UIAction {
|
||||
}
|
||||
|
||||
fun jsDispatchA2UIActionStatus(actionId: String, ok: Boolean, error: String?): String {
|
||||
val err = (error ?: "").replace("\\", "\\\\").replace("\"", "\\\"")
|
||||
val err = jsonStringLiteral(error ?: "")
|
||||
val okLiteral = if (ok) "true" else "false"
|
||||
val idEscaped = actionId.replace("\\", "\\\\").replace("\"", "\\\"")
|
||||
return "window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail: { id: \"${idEscaped}\", ok: ${okLiteral}, error: \"${err}\" } }));"
|
||||
val idLiteral = jsonStringLiteral(actionId)
|
||||
return "window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail: { id: ${idLiteral}, ok: ${okLiteral}, error: ${err} } }));"
|
||||
}
|
||||
|
||||
private fun jsonStringLiteral(raw: String): String =
|
||||
JsonPrimitive(raw).toString().replace("\u2028", "\\u2028").replace("\u2029", "\\u2029")
|
||||
}
|
||||
|
||||
@@ -97,8 +97,25 @@ internal fun parseGatewayEndpoint(rawInput: String): GatewayEndpointConfig? {
|
||||
"wss", "https" -> true
|
||||
else -> true
|
||||
}
|
||||
val port = uri.port.takeIf { it in 1..65535 } ?: if (tls) 443 else 18789
|
||||
val displayUrl = "${if (tls) "https" else "http"}://$host:$port"
|
||||
val defaultPort =
|
||||
when (scheme) {
|
||||
"wss", "https" -> 443
|
||||
"ws", "http" -> 18789
|
||||
else -> 443
|
||||
}
|
||||
val displayPort =
|
||||
when (scheme) {
|
||||
"wss", "https" -> 443
|
||||
"ws", "http" -> 80
|
||||
else -> 443
|
||||
}
|
||||
val port = uri.port.takeIf { it in 1..65535 } ?: defaultPort
|
||||
val displayUrl =
|
||||
if (port == displayPort && defaultPort == displayPort) {
|
||||
"${if (tls) "https" else "http"}://$host"
|
||||
} else {
|
||||
"${if (tls) "https" else "http"}://$host:$port"
|
||||
}
|
||||
|
||||
return GatewayEndpointConfig(host = host, port = port, tls = tls, displayUrl = displayUrl)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package ai.openclaw.app.ui
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
@Composable
|
||||
fun OpenClawTheme(content: @Composable () -> Unit) {
|
||||
@@ -16,6 +20,15 @@ fun OpenClawTheme(content: @Composable () -> Unit) {
|
||||
val colorScheme = if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
val mobileColors = if (isDark) darkMobileColors() else lightMobileColors()
|
||||
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
WindowCompat.getInsetsController(window, window.decorView)
|
||||
.isAppearanceLightStatusBars = !isDark
|
||||
}
|
||||
}
|
||||
|
||||
CompositionLocalProvider(LocalMobileColors provides mobileColors) {
|
||||
MaterialTheme(colorScheme = colorScheme, content = content)
|
||||
}
|
||||
|
||||
@@ -46,4 +46,18 @@ class OpenClawCanvasA2UIActionTest {
|
||||
js,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun jsDispatchA2uiStatusQuotesControlCharacters() {
|
||||
val js =
|
||||
OpenClawCanvasA2UIAction.jsDispatchA2UIActionStatus(
|
||||
actionId = "a1\n\u2028\"",
|
||||
ok = false,
|
||||
error = "parse failed\n\t\u2029\\",
|
||||
)
|
||||
assertEquals(
|
||||
"window.dispatchEvent(new CustomEvent('openclaw:a2ui-action-status', { detail: { id: \"a1\\n\\u2028\\\"\", ok: false, error: \"parse failed\\n\\t\\u2029\\\\\" } }));",
|
||||
js,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,86 @@ import java.util.Base64
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class GatewayConfigResolverTest {
|
||||
@Test
|
||||
fun parseGatewayEndpointUsesDefaultTlsPortForBareWssUrls() {
|
||||
val parsed = parseGatewayEndpoint("wss://gateway.example")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "gateway.example",
|
||||
port = 443,
|
||||
tls = true,
|
||||
displayUrl = "https://gateway.example",
|
||||
),
|
||||
parsed,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointUsesDefaultCleartextPortForBareWsUrls() {
|
||||
val parsed = parseGatewayEndpoint("ws://gateway.example")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "gateway.example",
|
||||
port = 18789,
|
||||
tls = false,
|
||||
displayUrl = "http://gateway.example:18789",
|
||||
),
|
||||
parsed,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointOmitsExplicitDefaultTlsPortFromDisplayUrl() {
|
||||
val parsed = parseGatewayEndpoint("https://gateway.example:443")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "gateway.example",
|
||||
port = 443,
|
||||
tls = true,
|
||||
displayUrl = "https://gateway.example",
|
||||
),
|
||||
parsed,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointKeepsExplicitNonDefaultPortInDisplayUrl() {
|
||||
val parsed = parseGatewayEndpoint("http://gateway.example:8080")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "gateway.example",
|
||||
port = 8080,
|
||||
tls = false,
|
||||
displayUrl = "http://gateway.example:8080",
|
||||
),
|
||||
parsed,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseGatewayEndpointKeepsExplicitCleartextPort80InDisplayUrl() {
|
||||
val parsed = parseGatewayEndpoint("http://gateway.example:80")
|
||||
|
||||
assertEquals(
|
||||
GatewayEndpointConfig(
|
||||
host = "gateway.example",
|
||||
port = 80,
|
||||
tls = false,
|
||||
displayUrl = "http://gateway.example:80",
|
||||
),
|
||||
parsed,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveScannedSetupCodeAcceptsRawSetupCode() {
|
||||
val setupCode =
|
||||
|
||||
@@ -4,5 +4,9 @@ These baseline artifacts are generated from the repo-owned OpenClaw config schem
|
||||
|
||||
- Do not edit `config-baseline.json` by hand.
|
||||
- Do not edit `config-baseline.jsonl` by hand.
|
||||
- Regenerate it with `pnpm config:docs:gen`.
|
||||
- Validate it in CI or locally with `pnpm config:docs:check`.
|
||||
- Do not edit `plugin-sdk-api-baseline.json` by hand.
|
||||
- Do not edit `plugin-sdk-api-baseline.jsonl` by hand.
|
||||
- Regenerate config baseline artifacts with `pnpm config:docs:gen`.
|
||||
- Validate config baseline artifacts in CI or locally with `pnpm config:docs:check`.
|
||||
- Regenerate Plugin SDK API baseline artifacts with `pnpm plugin-sdk:api:gen`.
|
||||
- Validate Plugin SDK API baseline artifacts in CI or locally with `pnpm plugin-sdk:api:check`.
|
||||
|
||||
@@ -4111,6 +4111,20 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "agents.list.*.fastModeDefault",
|
||||
"kind": "core",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Agent Fast Mode Default",
|
||||
"help": "Optional per-agent default for fast mode. Applies when no per-message or session fast-mode override is set.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "agents.list.*.groupChat",
|
||||
"kind": "core",
|
||||
@@ -5226,6 +5240,25 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "agents.list.*.reasoningDefault",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"enumValues": [
|
||||
"on",
|
||||
"off",
|
||||
"stream"
|
||||
],
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Agent Reasoning Default",
|
||||
"help": "Optional per-agent default reasoning visibility (on|off|stream). Applies when no per-message or session reasoning override is set.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "agents.list.*.runtime",
|
||||
"kind": "core",
|
||||
@@ -6287,6 +6320,29 @@
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "agents.list.*.thinkingDefault",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"enumValues": [
|
||||
"off",
|
||||
"minimal",
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh",
|
||||
"adaptive"
|
||||
],
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Agent Thinking Default",
|
||||
"help": "Optional per-agent default thinking level. Overrides agents.defaults.thinkingDefault for this agent when no per-message or session override is set.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "agents.list.*.tools",
|
||||
"kind": "core",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5563}
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5566}
|
||||
{"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}
|
||||
@@ -354,6 +354,7 @@
|
||||
{"recordType":"path","path":"agents.list.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.agentDir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.default","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.fastModeDefault","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Fast Mode Default","help":"Optional per-agent default for fast mode. Applies when no per-message or session fast-mode override is set.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.groupChat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.groupChat.historyLimit","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.groupChat.mentionPatterns","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -463,6 +464,7 @@
|
||||
{"recordType":"path","path":"agents.list.*.name","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.params","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.params.*","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.reasoningDefault","kind":"core","type":"string","required":false,"enumValues":["on","off","stream"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Reasoning Default","help":"Optional per-agent default reasoning visibility (on|off|stream). Applies when no per-message or session reasoning override is set.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.runtime","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Runtime","help":"Optional runtime descriptor for this agent. Use embedded for default OpenClaw execution or acp for external ACP harness defaults.","hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.runtime.acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Runtime","help":"ACP runtime defaults for this agent when runtime.type=acp. Binding-level ACP overrides still take precedence per conversation.","hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.runtime.acp.agent","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Harness Agent","help":"Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude).","hasChildren":false}
|
||||
@@ -561,6 +563,7 @@
|
||||
{"recordType":"path","path":"agents.list.*.subagents.model.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.subagents.model.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.subagents.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.thinkingDefault","kind":"core","type":"string","required":false,"enumValues":["off","minimal","low","medium","high","xhigh","adaptive"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Thinking Default","help":"Optional per-agent default thinking level. Overrides agents.defaults.thinkingDefault for this agent when no per-message or session override is set.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.tools.allow","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.tools.allow.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
|
||||
5126
docs/.generated/plugin-sdk-api-baseline.json
Normal file
5126
docs/.generated/plugin-sdk-api-baseline.json
Normal file
File diff suppressed because it is too large
Load Diff
565
docs/.generated/plugin-sdk-api-baseline.jsonl
Normal file
565
docs/.generated/plugin-sdk-api-baseline.jsonl
Normal file
@@ -0,0 +1,565 @@
|
||||
{"category":"legacy","entrypoint":"index","importSpecifier":"openclaw/plugin-sdk","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/index.ts"}
|
||||
{"declaration":"export function buildFalImageGenerationProvider(): ImageGenerationProvider;","entrypoint":"index","exportName":"buildFalImageGenerationProvider","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":188,"sourcePath":"src/image-generation/providers/fal.ts"}
|
||||
{"declaration":"export function buildGoogleImageGenerationProvider(): ImageGenerationProvider;","entrypoint":"index","exportName":"buildGoogleImageGenerationProvider","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":96,"sourcePath":"src/image-generation/providers/google.ts"}
|
||||
{"declaration":"export function buildOpenAIImageGenerationProvider(): ImageGenerationProvider;","entrypoint":"index","exportName":"buildOpenAIImageGenerationProvider","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":22,"sourcePath":"src/image-generation/providers/openai.ts"}
|
||||
{"declaration":"export function delegateCompactionToRuntime(params: { sessionId: string; sessionKey?: string | undefined; sessionFile: string; tokenBudget?: number | undefined; force?: boolean | undefined; currentTokenCount?: number | undefined; compactionTarget?: \"budget\" | ... 1 more ... | undefined; customInstructions?: string | undefined; runtimeContext?: ContextEngineRuntimeContext | undefined; }): Promise<...>;","entrypoint":"index","exportName":"delegateCompactionToRuntime","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/context-engine/delegate.ts"}
|
||||
{"declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"index","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":13,"sourcePath":"src/plugins/config-schema.ts"}
|
||||
{"declaration":"export function onDiagnosticEvent(listener: (evt: DiagnosticEventPayload) => void): () => void;","entrypoint":"index","exportName":"onDiagnosticEvent","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":229,"sourcePath":"src/infra/diagnostic-events.ts"}
|
||||
{"declaration":"export function registerContextEngine(id: string, factory: ContextEngineFactory): ContextEngineRegistrationResult;","entrypoint":"index","exportName":"registerContextEngine","importSpecifier":"openclaw/plugin-sdk","kind":"function","recordType":"export","sourceLine":377,"sourcePath":"src/context-engine/registry.ts"}
|
||||
{"declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"index","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/agents/tools/common.ts"}
|
||||
{"declaration":"export type ChannelAccountSnapshot = ChannelAccountSnapshot;","entrypoint":"index","exportName":"ChannelAccountSnapshot","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":139,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAgentTool = ChannelAgentTool;","entrypoint":"index","exportName":"ChannelAgentTool","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":17,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAgentToolFactory = ChannelAgentToolFactory;","entrypoint":"index","exportName":"ChannelAgentToolFactory","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":21,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelCapabilities = ChannelCapabilities;","entrypoint":"index","exportName":"ChannelCapabilities","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":223,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelConfigSchema = ChannelConfigSchema;","entrypoint":"index","exportName":"ChannelConfigSchema","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":47,"sourcePath":"src/channels/plugins/types.plugin.ts"}
|
||||
{"declaration":"export type ChannelConfiguredBindingConversationRef = ChannelConfiguredBindingConversationRef;","entrypoint":"index","exportName":"ChannelConfiguredBindingConversationRef","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":553,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelConfiguredBindingMatch = ChannelConfiguredBindingMatch;","entrypoint":"index","exportName":"ChannelConfiguredBindingMatch","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":558,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelConfiguredBindingProvider = ChannelConfiguredBindingProvider;","entrypoint":"index","exportName":"ChannelConfiguredBindingProvider","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":562,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext<ResolvedAccount>;","entrypoint":"index","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":238,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelId = ChannelId;","entrypoint":"index","exportName":"ChannelId","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionAdapter = ChannelMessageActionAdapter;","entrypoint":"index","exportName":"ChannelMessageActionAdapter","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":506,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionContext = ChannelMessageActionContext;","entrypoint":"index","exportName":"ChannelMessageActionContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":473,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionName = \"send\" | \"broadcast\" | \"poll\" | \"poll-vote\" | \"react\" | \"reactions\" | \"read\" | \"edit\" | \"unsend\" | \"reply\" | \"sendWithEffect\" | \"renameGroup\" | \"setGroupIcon\" | \"addParticipant\" | \"removeParticipant\" | \"leaveGroup\" | \"sendAttachment\" | \"delete\" | \"pin\" | \"unpin\" | \"list-pins\" | \"permissions\" | \"thread-create\" | \"thread-list\" | \"thread-reply\" | \"search\" | \"sticker\" | \"sticker-search\" | \"member-info\" | \"role-info\" | \"emoji-list\" | \"emoji-upload\" | \"sticker-upload\" | \"role-add\" | \"role-remove\" | \"channel-info\" | \"channel-list\" | \"channel-create\" | \"channel-edit\" | \"channel-delete\" | \"channel-move\" | \"category-create\" | \"category-edit\" | \"category-delete\" | \"topic-create\" | \"topic-edit\" | \"voice-status\" | \"event-list\" | \"event-create\" | \"timeout\" | \"kick\" | \"ban\" | \"set-profile\" | \"set-presence\" | \"download-file\";","entrypoint":"index","exportName":"ChannelMessageActionName","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/channels/plugins/types.ts"}
|
||||
{"declaration":"export type ChannelPlugin = ChannelPlugin<ResolvedAccount, Probe, Audit>;","entrypoint":"index","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/channels/plugins/types.plugin.ts"}
|
||||
{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"index","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":56,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelSetupInput = ChannelSetupInput;","entrypoint":"index","exportName":"ChannelSetupInput","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelSetupWizard = ChannelSetupWizard;","entrypoint":"index","exportName":"ChannelSetupWizard","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":247,"sourcePath":"src/channels/plugins/setup-wizard.ts"}
|
||||
{"declaration":"export type ChannelSetupWizardAllowFromEntry = ChannelSetupWizardAllowFromEntry;","entrypoint":"index","exportName":"ChannelSetupWizardAllowFromEntry","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":154,"sourcePath":"src/channels/plugins/setup-wizard.ts"}
|
||||
{"declaration":"export type ChannelStatusIssue = ChannelStatusIssue;","entrypoint":"index","exportName":"ChannelStatusIssue","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":97,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"index","exportName":"ClawdbotConfig","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"declaration":"export type CompiledConfiguredBinding = CompiledConfiguredBinding;","entrypoint":"index","exportName":"CompiledConfiguredBinding","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":38,"sourcePath":"src/channels/plugins/binding-types.ts"}
|
||||
{"declaration":"export type ConfiguredBindingConversation = ConversationRef;","entrypoint":"index","exportName":"ConfiguredBindingConversation","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/binding-types.ts"}
|
||||
{"declaration":"export type ConfiguredBindingResolution = ConfiguredBindingResolution;","entrypoint":"index","exportName":"ConfiguredBindingResolution","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":49,"sourcePath":"src/channels/plugins/binding-types.ts"}
|
||||
{"declaration":"export type ContextEngineFactory = ContextEngineFactory;","entrypoint":"index","exportName":"ContextEngineFactory","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/context-engine/registry.ts"}
|
||||
{"declaration":"export type ContextEngineInfo = ContextEngineInfo;","entrypoint":"index","exportName":"ContextEngineInfo","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":46,"sourcePath":"src/context-engine/types.ts"}
|
||||
{"declaration":"export type ContextEngineMaintenanceResult = TranscriptRewriteResult;","entrypoint":"index","exportName":"ContextEngineMaintenanceResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":84,"sourcePath":"src/context-engine/types.ts"}
|
||||
{"declaration":"export type ContextEngineRuntimeContext = ContextEngineRuntimeContext;","entrypoint":"index","exportName":"ContextEngineRuntimeContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":86,"sourcePath":"src/context-engine/types.ts"}
|
||||
{"declaration":"export type DiagnosticEventPayload = DiagnosticEventPayload;","entrypoint":"index","exportName":"DiagnosticEventPayload","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":150,"sourcePath":"src/infra/diagnostic-events.ts"}
|
||||
{"declaration":"export type GeneratedImageAsset = GeneratedImageAsset;","entrypoint":"index","exportName":"GeneratedImageAsset","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/image-generation/types.ts"}
|
||||
{"declaration":"export type HookEntry = HookEntry;","entrypoint":"index","exportName":"HookEntry","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":47,"sourcePath":"src/hooks/types.ts"}
|
||||
{"declaration":"export type ImageGenerationProvider = ImageGenerationProvider;","entrypoint":"index","exportName":"ImageGenerationProvider","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":66,"sourcePath":"src/image-generation/types.ts"}
|
||||
{"declaration":"export type ImageGenerationRequest = ImageGenerationRequest;","entrypoint":"index","exportName":"ImageGenerationRequest","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":21,"sourcePath":"src/image-generation/types.ts"}
|
||||
{"declaration":"export type ImageGenerationResolution = ImageGenerationResolution;","entrypoint":"index","exportName":"ImageGenerationResolution","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":12,"sourcePath":"src/image-generation/types.ts"}
|
||||
{"declaration":"export type ImageGenerationResult = ImageGenerationResult;","entrypoint":"index","exportName":"ImageGenerationResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":36,"sourcePath":"src/image-generation/types.ts"}
|
||||
{"declaration":"export type ImageGenerationSourceImage = ImageGenerationSourceImage;","entrypoint":"index","exportName":"ImageGenerationSourceImage","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":14,"sourcePath":"src/image-generation/types.ts"}
|
||||
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"index","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":935,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"index","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"index","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1292,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"index","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":80,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"index","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":58,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"index","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/plugins/runtime/types.ts"}
|
||||
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"index","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":144,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"index","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":130,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"index","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":284,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ReplyPayload = ReplyPayload;","entrypoint":"index","exportName":"ReplyPayload","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/auto-reply/types.ts"}
|
||||
{"declaration":"export type RuntimeEnv = RuntimeEnv;","entrypoint":"index","exportName":"RuntimeEnv","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/runtime.ts"}
|
||||
{"declaration":"export type RuntimeLogger = RuntimeLogger;","entrypoint":"index","exportName":"RuntimeLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":3,"sourcePath":"src/plugins/runtime/types-core.ts"}
|
||||
{"declaration":"export type SecretInput = SecretInput;","entrypoint":"index","exportName":"SecretInput","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":16,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"declaration":"export type SecretRef = SecretRef;","entrypoint":"index","exportName":"SecretRef","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":10,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"index","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":917,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type StatefulBindingTargetDescriptor = StatefulBindingTargetDescriptor;","entrypoint":"index","exportName":"StatefulBindingTargetDescriptor","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":17,"sourcePath":"src/channels/plugins/binding-types.ts"}
|
||||
{"declaration":"export type StatefulBindingTargetDriver = StatefulBindingTargetDriver;","entrypoint":"index","exportName":"StatefulBindingTargetDriver","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":15,"sourcePath":"src/channels/plugins/stateful-target-drivers.ts"}
|
||||
{"declaration":"export type StatefulBindingTargetReadyResult = StatefulBindingTargetReadyResult;","entrypoint":"index","exportName":"StatefulBindingTargetReadyResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":7,"sourcePath":"src/channels/plugins/stateful-target-drivers.ts"}
|
||||
{"declaration":"export type StatefulBindingTargetResetResult = StatefulBindingTargetResetResult;","entrypoint":"index","exportName":"StatefulBindingTargetResetResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":11,"sourcePath":"src/channels/plugins/stateful-target-drivers.ts"}
|
||||
{"declaration":"export type StatefulBindingTargetSessionResult = StatefulBindingTargetSessionResult;","entrypoint":"index","exportName":"StatefulBindingTargetSessionResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":8,"sourcePath":"src/channels/plugins/stateful-target-drivers.ts"}
|
||||
{"declaration":"export type SubagentRunParams = SubagentRunParams;","entrypoint":"index","exportName":"SubagentRunParams","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":8,"sourcePath":"src/plugins/runtime/types.ts"}
|
||||
{"declaration":"export type SubagentRunResult = SubagentRunResult;","entrypoint":"index","exportName":"SubagentRunResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":19,"sourcePath":"src/plugins/runtime/types.ts"}
|
||||
{"declaration":"export type TranscriptRewriteReplacement = TranscriptRewriteReplacement;","entrypoint":"index","exportName":"TranscriptRewriteReplacement","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":61,"sourcePath":"src/context-engine/types.ts"}
|
||||
{"declaration":"export type TranscriptRewriteRequest = TranscriptRewriteRequest;","entrypoint":"index","exportName":"TranscriptRewriteRequest","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":68,"sourcePath":"src/context-engine/types.ts"}
|
||||
{"declaration":"export type TranscriptRewriteResult = TranscriptRewriteResult;","entrypoint":"index","exportName":"TranscriptRewriteResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":73,"sourcePath":"src/context-engine/types.ts"}
|
||||
{"declaration":"export type WizardPrompter = WizardPrompter;","entrypoint":"index","exportName":"WizardPrompter","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":37,"sourcePath":"src/wizard/prompts.ts"}
|
||||
{"declaration":"export interface ContextEngine","entrypoint":"index","exportName":"ContextEngine","importSpecifier":"openclaw/plugin-sdk","kind":"interface","recordType":"export","sourceLine":104,"sourcePath":"src/context-engine/types.ts"}
|
||||
{"category":"utilities","entrypoint":"allow-from","importSpecifier":"openclaw/plugin-sdk/allow-from","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/allow-from.ts"}
|
||||
{"declaration":"export function addAllowlistUserEntriesFromConfigEntry(target: Set<string>, entry: unknown): void;","entrypoint":"allow-from","exportName":"addAllowlistUserEntriesFromConfigEntry","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":130,"sourcePath":"src/channels/allowlists/resolve-utils.ts"}
|
||||
{"declaration":"export function buildAllowlistResolutionSummary<T extends AllowlistUserResolutionLike>(resolvedUsers: T[], opts?: { formatResolved?: ((entry: T) => string) | undefined; formatUnresolved?: ((entry: T) => string) | undefined; } | undefined): { ...; };","entrypoint":"allow-from","exportName":"buildAllowlistResolutionSummary","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":36,"sourcePath":"src/channels/allowlists/resolve-utils.ts"}
|
||||
{"declaration":"export function canonicalizeAllowlistWithResolvedIds<T extends AllowlistUserResolutionLike>(params: { existing?: (string | number)[] | undefined; resolvedMap: Map<string, T>; }): string[];","entrypoint":"allow-from","exportName":"canonicalizeAllowlistWithResolvedIds","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":73,"sourcePath":"src/channels/allowlists/resolve-utils.ts"}
|
||||
{"declaration":"export function compileAllowlist(entries: readonly string[]): CompiledAllowlist;","entrypoint":"allow-from","exportName":"compileAllowlist","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":30,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"declaration":"export function firstDefined<T>(...values: (T | undefined)[]): (T & ({} | null)) | undefined;","entrypoint":"allow-from","exportName":"firstDefined","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":29,"sourcePath":"src/channels/allow-from.ts"}
|
||||
{"declaration":"export function formatAllowFromLowercase(params: { allowFrom: (string | number)[]; stripPrefixRe?: RegExp | undefined; }): string[];","entrypoint":"allow-from","exportName":"formatAllowFromLowercase","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":31,"sourcePath":"src/plugin-sdk/allow-from.ts"}
|
||||
{"declaration":"export function formatAllowlistMatchMeta(match?: { matchKey?: string | undefined; matchSource?: string | undefined; } | null | undefined): string;","entrypoint":"allow-from","exportName":"formatAllowlistMatchMeta","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":24,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"declaration":"export function formatNormalizedAllowFromEntries(params: { allowFrom: (string | number)[]; normalizeEntry: (entry: string) => string | null | undefined; }): string[];","entrypoint":"allow-from","exportName":"formatNormalizedAllowFromEntries","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":43,"sourcePath":"src/plugin-sdk/allow-from.ts"}
|
||||
{"declaration":"export function isAllowedParsedChatSender<TParsed extends ParsedChatAllowTarget>(params: { allowFrom: (string | number)[]; sender: string; chatId?: number | null | undefined; chatGuid?: string | null | undefined; chatIdentifier?: string | null | undefined; normalizeSender: (sender: string) => string; parseAllowTarget: (entry: string) => TParsed; }): boolean;","entrypoint":"allow-from","exportName":"isAllowedParsedChatSender","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":81,"sourcePath":"src/plugin-sdk/allow-from.ts"}
|
||||
{"declaration":"export function isNormalizedSenderAllowed(params: { senderId: string | number; allowFrom: (string | number)[]; stripPrefixRe?: RegExp | undefined; }): boolean;","entrypoint":"allow-from","exportName":"isNormalizedSenderAllowed","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":55,"sourcePath":"src/plugin-sdk/allow-from.ts"}
|
||||
{"declaration":"export function isSenderIdAllowed(allow: { entries: string[]; hasWildcard: boolean; hasEntries: boolean; }, senderId: string | undefined, allowWhenEmpty: boolean): boolean;","entrypoint":"allow-from","exportName":"isSenderIdAllowed","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":38,"sourcePath":"src/channels/allow-from.ts"}
|
||||
{"declaration":"export function mapAllowlistResolutionInputs<T>(params: { inputs: string[]; mapInput: (input: string) => T | Promise<T>; }): Promise<T[]>;","entrypoint":"allow-from","exportName":"mapAllowlistResolutionInputs","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":151,"sourcePath":"src/plugin-sdk/allow-from.ts"}
|
||||
{"declaration":"export function mapBasicAllowlistResolutionEntries(entries: BasicAllowlistResolutionEntry[]): BasicAllowlistResolutionEntry[];","entrypoint":"allow-from","exportName":"mapBasicAllowlistResolutionEntries","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":138,"sourcePath":"src/plugin-sdk/allow-from.ts"}
|
||||
{"declaration":"export function mergeAllowlist(params: { existing?: (string | number)[] | undefined; additions: string[]; }): string[];","entrypoint":"allow-from","exportName":"mergeAllowlist","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":29,"sourcePath":"src/channels/allowlists/resolve-utils.ts"}
|
||||
{"declaration":"export function mergeDmAllowFromSources(params: { allowFrom?: (string | number)[] | undefined; storeAllowFrom?: (string | number)[] | undefined; dmPolicy?: string | undefined; }): string[];","entrypoint":"allow-from","exportName":"mergeDmAllowFromSources","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":1,"sourcePath":"src/channels/allow-from.ts"}
|
||||
{"declaration":"export function patchAllowlistUsersInConfigEntries<T extends AllowlistUserResolutionLike, TEntries extends Record<string, unknown>>(params: { entries: TEntries; resolvedMap: Map<string, T>; strategy?: \"merge\" | \"canonicalize\" | undefined; }): TEntries;","entrypoint":"allow-from","exportName":"patchAllowlistUsersInConfigEntries","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":92,"sourcePath":"src/channels/allowlists/resolve-utils.ts"}
|
||||
{"declaration":"export function resolveAllowlistCandidates<TSource extends string>(params: { compiledAllowlist: CompiledAllowlist; candidates: { value?: string | undefined; source: TSource; }[]; }): AllowlistMatch<TSource>;","entrypoint":"allow-from","exportName":"resolveAllowlistCandidates","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":44,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"declaration":"export function resolveAllowlistMatchByCandidates<TSource extends string>(params: { allowList: readonly string[]; candidates: { value?: string | undefined; source: TSource; }[]; }): AllowlistMatch<TSource>;","entrypoint":"allow-from","exportName":"resolveAllowlistMatchByCandidates","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":76,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"declaration":"export function resolveAllowlistMatchSimple(params: { allowFrom: readonly (string | number)[]; senderId: string; senderName?: string | null | undefined; allowNameMatching?: boolean | undefined; }): AllowlistMatch<\"name\" | \"id\" | \"wildcard\">;","entrypoint":"allow-from","exportName":"resolveAllowlistMatchSimple","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":86,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"declaration":"export function resolveCompiledAllowlistMatch<TSource extends string>(params: { compiledAllowlist: CompiledAllowlist; candidates: { value?: string | undefined; source: TSource; }[]; }): AllowlistMatch<TSource>;","entrypoint":"allow-from","exportName":"resolveCompiledAllowlistMatch","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":63,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"declaration":"export function resolveGroupAllowFromSources(params: { allowFrom?: (string | number)[] | undefined; groupAllowFrom?: (string | number)[] | undefined; fallbackToAllowFrom?: boolean | undefined; }): string[];","entrypoint":"allow-from","exportName":"resolveGroupAllowFromSources","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":12,"sourcePath":"src/channels/allow-from.ts"}
|
||||
{"declaration":"export function summarizeMapping(label: string, mapping: string[], unresolved: string[], runtime: RuntimeEnv): void;","entrypoint":"allow-from","exportName":"summarizeMapping","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"function","recordType":"export","sourceLine":146,"sourcePath":"src/channels/allowlists/resolve-utils.ts"}
|
||||
{"declaration":"export type AllowlistMatch = AllowlistMatch<TSource>;","entrypoint":"allow-from","exportName":"AllowlistMatch","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"declaration":"export type AllowlistMatchSource = AllowlistMatchSource;","entrypoint":"allow-from","exportName":"AllowlistMatchSource","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"declaration":"export type AllowlistUserResolutionLike = AllowlistUserResolutionLike;","entrypoint":"allow-from","exportName":"AllowlistUserResolutionLike","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"type","recordType":"export","sourceLine":5,"sourcePath":"src/channels/allowlists/resolve-utils.ts"}
|
||||
{"declaration":"export type BasicAllowlistResolutionEntry = BasicAllowlistResolutionEntry;","entrypoint":"allow-from","exportName":"BasicAllowlistResolutionEntry","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"type","recordType":"export","sourceLine":129,"sourcePath":"src/plugin-sdk/allow-from.ts"}
|
||||
{"declaration":"export type CompiledAllowlist = CompiledAllowlist;","entrypoint":"allow-from","exportName":"CompiledAllowlist","importSpecifier":"openclaw/plugin-sdk/allow-from","kind":"type","recordType":"export","sourceLine":19,"sourcePath":"src/channels/allowlist-match.ts"}
|
||||
{"category":"channel","entrypoint":"channel-actions","importSpecifier":"openclaw/plugin-sdk/channel-actions","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/channel-actions.ts"}
|
||||
{"declaration":"export function createMessageToolButtonsSchema(): TSchema;","entrypoint":"channel-actions","exportName":"createMessageToolButtonsSchema","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":10,"sourcePath":"src/plugin-sdk/channel-actions.ts"}
|
||||
{"declaration":"export function createMessageToolCardSchema(): TSchema;","entrypoint":"channel-actions","exportName":"createMessageToolCardSchema","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":25,"sourcePath":"src/plugin-sdk/channel-actions.ts"}
|
||||
{"declaration":"export function createUnionActionGate<TAccount, TKey extends string>(accounts: readonly TAccount[], createGate: (account: TAccount) => OptionalDefaultGate<TKey>): OptionalDefaultGate<TKey>;","entrypoint":"channel-actions","exportName":"createUnionActionGate","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/actions/shared.ts"}
|
||||
{"declaration":"export function listTokenSourcedAccounts<TAccount extends TokenSourcedAccount>(accounts: readonly TAccount[]): TAccount[];","entrypoint":"channel-actions","exportName":"listTokenSourcedAccounts","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":7,"sourcePath":"src/channels/plugins/actions/shared.ts"}
|
||||
{"declaration":"export function resolveReactionMessageId(params: { args: Record<string, unknown>; toolContext?: ReactionToolContext | undefined; }): string | number | undefined;","entrypoint":"channel-actions","exportName":"resolveReactionMessageId","importSpecifier":"openclaw/plugin-sdk/channel-actions","kind":"function","recordType":"export","sourceLine":7,"sourcePath":"src/channels/plugins/actions/reaction-message-id.ts"}
|
||||
{"category":"channel","entrypoint":"channel-config-schema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/channel-config-schema.ts"}
|
||||
{"declaration":"export function buildCatchallMultiAccountChannelSchema<T extends ExtendableZodObject>(accountSchema: T): T;","entrypoint":"channel-config-schema","exportName":"buildCatchallMultiAccountChannelSchema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","kind":"function","recordType":"export","sourceLine":26,"sourcePath":"src/channels/plugins/config-schema.ts"}
|
||||
{"declaration":"export function buildChannelConfigSchema(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): ChannelConfigSchema;","entrypoint":"channel-config-schema","exportName":"buildChannelConfigSchema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","kind":"function","recordType":"export","sourceLine":35,"sourcePath":"src/channels/plugins/config-schema.ts"}
|
||||
{"declaration":"export function buildNestedDmConfigSchema(): ZodOptional<ZodObject<{ enabled: ZodOptional<ZodBoolean>; policy: ZodOptional<ZodEnum<{ disabled: \"disabled\"; pairing: \"pairing\"; allowlist: \"allowlist\"; open: \"open\"; }>>; allowFrom: ZodOptional<...>; }, $strip>>;","entrypoint":"channel-config-schema","exportName":"buildNestedDmConfigSchema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/channels/plugins/config-schema.ts"}
|
||||
{"declaration":"export const AllowFromListSchema: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;","entrypoint":"channel-config-schema","exportName":"AllowFromListSchema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","kind":"const","recordType":"export","sourceLine":14,"sourcePath":"src/channels/plugins/config-schema.ts"}
|
||||
{"declaration":"export const DmPolicySchema: z.ZodEnum<{ disabled: \"disabled\"; pairing: \"pairing\"; allowlist: \"allowlist\"; open: \"open\"; }>;","entrypoint":"channel-config-schema","exportName":"DmPolicySchema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","kind":"const","recordType":"export","sourceLine":337,"sourcePath":"src/config/zod-schema.core.ts"}
|
||||
{"declaration":"export const GroupPolicySchema: z.ZodEnum<{ disabled: \"disabled\"; allowlist: \"allowlist\"; open: \"open\"; }>;","entrypoint":"channel-config-schema","exportName":"GroupPolicySchema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","kind":"const","recordType":"export","sourceLine":335,"sourcePath":"src/config/zod-schema.core.ts"}
|
||||
{"declaration":"export const MarkdownConfigSchema: z.ZodOptional<z.ZodObject<{ tables: z.ZodOptional<z.ZodEnum<{ off: \"off\"; bullets: \"bullets\"; code: \"code\"; }>>; }, z.core.$strict>>;","entrypoint":"channel-config-schema","exportName":"MarkdownConfigSchema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","kind":"const","recordType":"export","sourceLine":371,"sourcePath":"src/config/zod-schema.core.ts"}
|
||||
{"declaration":"export const ToolPolicySchema: z.ZodOptional<z.ZodObject<{ allow: z.ZodOptional<z.ZodArray<z.ZodString>>; alsoAllow: z.ZodOptional<z.ZodArray<z.ZodString>>; deny: z.ZodOptional<z.ZodArray<z.ZodString>>; }, z.core.$strict>>;","entrypoint":"channel-config-schema","exportName":"ToolPolicySchema","importSpecifier":"openclaw/plugin-sdk/channel-config-schema","kind":"const","recordType":"export","sourceLine":253,"sourcePath":"src/config/zod-schema.agent-runtime.ts"}
|
||||
{"category":"channel","entrypoint":"channel-contract","importSpecifier":"openclaw/plugin-sdk/channel-contract","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/channel-contract.ts"}
|
||||
{"declaration":"export type BaseProbeResult = BaseProbeResult<TError>;","entrypoint":"channel-contract","exportName":"BaseProbeResult","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":547,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type BaseTokenResolution = BaseTokenResolution;","entrypoint":"channel-contract","exportName":"BaseTokenResolution","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":553,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAccountSnapshot = ChannelAccountSnapshot;","entrypoint":"channel-contract","exportName":"ChannelAccountSnapshot","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":139,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAgentTool = ChannelAgentTool;","entrypoint":"channel-contract","exportName":"ChannelAgentTool","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":17,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelGroupContext = ChannelGroupContext;","entrypoint":"channel-contract","exportName":"ChannelGroupContext","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":210,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionAdapter = ChannelMessageActionAdapter;","entrypoint":"channel-contract","exportName":"ChannelMessageActionAdapter","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":506,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionContext = ChannelMessageActionContext;","entrypoint":"channel-contract","exportName":"ChannelMessageActionContext","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":473,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionDiscoveryContext = ChannelMessageActionDiscoveryContext;","entrypoint":"channel-contract","exportName":"ChannelMessageActionDiscoveryContext","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":29,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionName = \"send\" | \"broadcast\" | \"poll\" | \"poll-vote\" | \"react\" | \"reactions\" | \"read\" | \"edit\" | \"unsend\" | \"reply\" | \"sendWithEffect\" | \"renameGroup\" | \"setGroupIcon\" | \"addParticipant\" | \"removeParticipant\" | \"leaveGroup\" | \"sendAttachment\" | \"delete\" | \"pin\" | \"unpin\" | \"list-pins\" | \"permissions\" | \"thread-create\" | \"thread-list\" | \"thread-reply\" | \"search\" | \"sticker\" | \"sticker-search\" | \"member-info\" | \"role-info\" | \"emoji-list\" | \"emoji-upload\" | \"sticker-upload\" | \"role-add\" | \"role-remove\" | \"channel-info\" | \"channel-list\" | \"channel-create\" | \"channel-edit\" | \"channel-delete\" | \"channel-move\" | \"category-create\" | \"category-edit\" | \"category-delete\" | \"topic-create\" | \"topic-edit\" | \"voice-status\" | \"event-list\" | \"event-create\" | \"timeout\" | \"kick\" | \"ban\" | \"set-profile\" | \"set-presence\" | \"download-file\";","entrypoint":"channel-contract","exportName":"ChannelMessageActionName","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/channels/plugins/types.ts"}
|
||||
{"declaration":"export type ChannelMessageToolDiscovery = ChannelMessageToolDiscovery;","entrypoint":"channel-contract","exportName":"ChannelMessageToolDiscovery","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageToolSchemaContribution = ChannelMessageToolSchemaContribution;","entrypoint":"channel-contract","exportName":"ChannelMessageToolSchemaContribution","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":49,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelStatusIssue = ChannelStatusIssue;","entrypoint":"channel-contract","exportName":"ChannelStatusIssue","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":97,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelThreadingContext = ChannelThreadingContext;","entrypoint":"channel-contract","exportName":"ChannelThreadingContext","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":358,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelThreadingToolContext = ChannelThreadingToolContext;","entrypoint":"channel-contract","exportName":"ChannelThreadingToolContext","importSpecifier":"openclaw/plugin-sdk/channel-contract","kind":"type","recordType":"export","sourceLine":372,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"category":"channel","entrypoint":"channel-pairing","importSpecifier":"openclaw/plugin-sdk/channel-pairing","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/channel-pairing.ts"}
|
||||
{"declaration":"export function createChannelPairingChallengeIssuer(params: { channel: ChannelId; upsertPairingRequest: (params: { id: string; meta?: PairingMeta | undefined; }) => Promise<{ code: string; created: boolean; }>; }): (challenge: Omit<...>) => Promise<...>;","entrypoint":"channel-pairing","exportName":"createChannelPairingChallengeIssuer","importSpecifier":"openclaw/plugin-sdk/channel-pairing","kind":"function","recordType":"export","sourceLine":19,"sourcePath":"src/plugin-sdk/channel-pairing.ts"}
|
||||
{"declaration":"export function createChannelPairingController(params: { core: PluginRuntime; channel: ChannelId; accountId: string; }): ChannelPairingController;","entrypoint":"channel-pairing","exportName":"createChannelPairingController","importSpecifier":"openclaw/plugin-sdk/channel-pairing","kind":"function","recordType":"export","sourceLine":36,"sourcePath":"src/plugin-sdk/channel-pairing.ts"}
|
||||
{"declaration":"export function createLoggedPairingApprovalNotifier(format: string | ((params: { cfg: OpenClawConfig; id: string; accountId?: string | undefined; runtime?: RuntimeEnv | undefined; }) => string), log?: (message: string) => void): (params: { ...; }) => Promise<...>;","entrypoint":"channel-pairing","exportName":"createLoggedPairingApprovalNotifier","importSpecifier":"openclaw/plugin-sdk/channel-pairing","kind":"function","recordType":"export","sourceLine":12,"sourcePath":"src/channels/plugins/pairing-adapters.ts"}
|
||||
{"declaration":"export function createPairingPrefixStripper(prefixRe: RegExp, map?: (entry: string) => string): (entry: string) => string;","entrypoint":"channel-pairing","exportName":"createPairingPrefixStripper","importSpecifier":"openclaw/plugin-sdk/channel-pairing","kind":"function","recordType":"export","sourceLine":5,"sourcePath":"src/channels/plugins/pairing-adapters.ts"}
|
||||
{"declaration":"export function createTextPairingAdapter(params: { idLabel: string; message: string; normalizeAllowEntry?: ((entry: string) => string) | undefined; notify: (params: { cfg: OpenClawConfig; id: string; accountId?: string | undefined; runtime?: RuntimeEnv | undefined; } & { ...; }) => void | Promise<...>; }): ChannelPairingAdapter;","entrypoint":"channel-pairing","exportName":"createTextPairingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-pairing","kind":"function","recordType":"export","sourceLine":21,"sourcePath":"src/channels/plugins/pairing-adapters.ts"}
|
||||
{"declaration":"export type ChannelPairingController = ChannelPairingController;","entrypoint":"channel-pairing","exportName":"ChannelPairingController","importSpecifier":"openclaw/plugin-sdk/channel-pairing","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/plugin-sdk/channel-pairing.ts"}
|
||||
{"category":"channel","entrypoint":"channel-reply-pipeline","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/channel-reply-pipeline.ts"}
|
||||
{"declaration":"export function createChannelReplyPipeline(params: { cfg: OpenClawConfig; agentId: string; channel?: string | undefined; accountId?: string | undefined; typing?: CreateTypingCallbacksParams | undefined; typingCallbacks?: TypingCallbacks | undefined; }): ChannelReplyPipeline;","entrypoint":"channel-reply-pipeline","exportName":"createChannelReplyPipeline","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline","kind":"function","recordType":"export","sourceLine":20,"sourcePath":"src/plugin-sdk/channel-reply-pipeline.ts"}
|
||||
{"declaration":"export type ChannelReplyPipeline = ChannelReplyPipeline;","entrypoint":"channel-reply-pipeline","exportName":"ChannelReplyPipeline","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline","kind":"type","recordType":"export","sourceLine":16,"sourcePath":"src/plugin-sdk/channel-reply-pipeline.ts"}
|
||||
{"declaration":"export type CreateTypingCallbacksParams = CreateTypingCallbacksParams;","entrypoint":"channel-reply-pipeline","exportName":"CreateTypingCallbacksParams","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline","kind":"type","recordType":"export","sourceLine":11,"sourcePath":"src/channels/typing.ts"}
|
||||
{"declaration":"export type ReplyPrefixContext = import(\"/Users/vincentkoc/GIT/_Perso/openclaw/.worktrees/pr-51877/src/auto-reply/reply/response-prefix-template\").ResponsePrefixContext;","entrypoint":"channel-reply-pipeline","exportName":"ReplyPrefixContext","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline","kind":"type","recordType":"export","sourceLine":12,"sourcePath":"src/plugin-sdk/channel-reply-pipeline.ts"}
|
||||
{"declaration":"export type ReplyPrefixContextBundle = ReplyPrefixContextBundle;","entrypoint":"channel-reply-pipeline","exportName":"ReplyPrefixContextBundle","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline","kind":"type","recordType":"export","sourceLine":12,"sourcePath":"src/channels/reply-prefix.ts"}
|
||||
{"declaration":"export type ReplyPrefixOptions = ReplyPrefixOptions;","entrypoint":"channel-reply-pipeline","exportName":"ReplyPrefixOptions","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline","kind":"type","recordType":"export","sourceLine":20,"sourcePath":"src/channels/reply-prefix.ts"}
|
||||
{"declaration":"export type TypingCallbacks = TypingCallbacks;","entrypoint":"channel-reply-pipeline","exportName":"TypingCallbacks","importSpecifier":"openclaw/plugin-sdk/channel-reply-pipeline","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/channels/typing.ts"}
|
||||
{"category":"legacy","entrypoint":"channel-runtime","importSpecifier":"openclaw/plugin-sdk/channel-runtime","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/channel-runtime.ts"}
|
||||
{"declaration":"export function createAccountStatusSink(params: { accountId: string; setStatus: (next: ChannelAccountSnapshot) => void; }): (patch: Omit<ChannelAccountSnapshot, \"accountId\">) => void;","entrypoint":"channel-runtime","exportName":"createAccountStatusSink","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":23,"sourcePath":"src/plugin-sdk/channel-lifecycle.ts"}
|
||||
{"declaration":"export function createReplyPrefixContext(params: { cfg: OpenClawConfig; agentId: string; channel?: string | undefined; accountId?: string | undefined; }): ReplyPrefixContextBundle;","entrypoint":"channel-runtime","exportName":"createReplyPrefixContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":28,"sourcePath":"src/channels/reply-prefix.ts"}
|
||||
{"declaration":"export function createReplyPrefixOptions(params: { cfg: OpenClawConfig; agentId: string; channel?: string | undefined; accountId?: string | undefined; }): ReplyPrefixOptions;","entrypoint":"channel-runtime","exportName":"createReplyPrefixOptions","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":64,"sourcePath":"src/channels/reply-prefix.ts"}
|
||||
{"declaration":"export function createTypingCallbacks(params: CreateTypingCallbacksParams): TypingCallbacks;","entrypoint":"channel-runtime","exportName":"createTypingCallbacks","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":23,"sourcePath":"src/channels/typing.ts"}
|
||||
{"declaration":"export function isWhatsAppGroupJid(value: string): boolean;","entrypoint":"channel-runtime","exportName":"isWhatsAppGroupJid","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":17,"sourcePath":"src/whatsapp/normalize.ts"}
|
||||
{"declaration":"export function isWhatsAppUserTarget(value: string): boolean;","entrypoint":"channel-runtime","exportName":"isWhatsAppUserTarget","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":33,"sourcePath":"src/whatsapp/normalize.ts"}
|
||||
{"declaration":"export function keepHttpServerTaskAlive(params: { server: CloseAwareServer; abortSignal?: AbortSignal | undefined; onAbort?: (() => void | Promise<void>) | undefined; }): Promise<void>;","entrypoint":"channel-runtime","exportName":"keepHttpServerTaskAlive","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":79,"sourcePath":"src/plugin-sdk/channel-lifecycle.ts"}
|
||||
{"declaration":"export function looksLikeSignalTargetId(raw: string, normalized?: string | undefined): boolean;","entrypoint":"channel-runtime","exportName":"looksLikeSignalTargetId","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":38,"sourcePath":"src/channels/plugins/normalize/signal.ts"}
|
||||
{"declaration":"export function looksLikeWhatsAppTargetId(raw: string): boolean;","entrypoint":"channel-runtime","exportName":"looksLikeWhatsAppTargetId","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":20,"sourcePath":"src/channels/plugins/normalize/whatsapp.ts"}
|
||||
{"declaration":"export function normalizeChatType(raw?: string | undefined): ChatType | undefined;","entrypoint":"channel-runtime","exportName":"normalizeChatType","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":3,"sourcePath":"src/channels/chat-type.ts"}
|
||||
{"declaration":"export function normalizePollDurationHours(value: number | undefined, options: { defaultHours: number; maxHours: number; }): number;","entrypoint":"channel-runtime","exportName":"normalizePollDurationHours","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":93,"sourcePath":"src/polls.ts"}
|
||||
{"declaration":"export function normalizePollInput(input: PollInput, options?: NormalizePollOptions): NormalizedPollInput;","entrypoint":"channel-runtime","exportName":"normalizePollInput","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":36,"sourcePath":"src/polls.ts"}
|
||||
{"declaration":"export function normalizeSignalMessagingTarget(raw: string): string | undefined;","entrypoint":"channel-runtime","exportName":"normalizeSignalMessagingTarget","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":1,"sourcePath":"src/channels/plugins/normalize/signal.ts"}
|
||||
{"declaration":"export function normalizeWhatsAppAllowFromEntries(allowFrom: (string | number)[]): string[];","entrypoint":"channel-runtime","exportName":"normalizeWhatsAppAllowFromEntries","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":12,"sourcePath":"src/channels/plugins/normalize/whatsapp.ts"}
|
||||
{"declaration":"export function normalizeWhatsAppMessagingTarget(raw: string): string | undefined;","entrypoint":"channel-runtime","exportName":"normalizeWhatsAppMessagingTarget","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":4,"sourcePath":"src/channels/plugins/normalize/whatsapp.ts"}
|
||||
{"declaration":"export function normalizeWhatsAppTarget(value: string): string | null;","entrypoint":"channel-runtime","exportName":"normalizeWhatsAppTarget","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":55,"sourcePath":"src/whatsapp/normalize.ts"}
|
||||
{"declaration":"export function reduceInteractiveReply<TState>(interactive: InteractiveReply | undefined, initialState: TState, reduce: (state: TState, block: InteractiveReplyBlock, index: number) => TState): TState;","entrypoint":"channel-runtime","exportName":"reduceInteractiveReply","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":3,"sourcePath":"src/channels/plugins/outbound/interactive.ts"}
|
||||
{"declaration":"export function resolvePollMaxSelections(optionCount: number, allowMultiselect: boolean | undefined): number;","entrypoint":"channel-runtime","exportName":"resolvePollMaxSelections","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":29,"sourcePath":"src/polls.ts"}
|
||||
{"declaration":"export function resolveWhatsAppHeartbeatRecipients(cfg: OpenClawConfig, opts?: HeartbeatRecipientsOpts): HeartbeatRecipientsResult;","entrypoint":"channel-runtime","exportName":"resolveWhatsAppHeartbeatRecipients","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":48,"sourcePath":"src/channels/plugins/whatsapp-heartbeat.ts"}
|
||||
{"declaration":"export function waitUntilAbort(signal?: AbortSignal | undefined, onAbort?: (() => void | Promise<void>) | undefined): Promise<void>;","entrypoint":"channel-runtime","exportName":"waitUntilAbort","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"function","recordType":"export","sourceLine":38,"sourcePath":"src/plugin-sdk/channel-lifecycle.ts"}
|
||||
{"declaration":"export const CHANNEL_MESSAGE_ACTION_NAMES: readonly [\"send\", \"broadcast\", \"poll\", \"poll-vote\", \"react\", \"reactions\", \"read\", \"edit\", \"unsend\", \"reply\", \"sendWithEffect\", \"renameGroup\", \"setGroupIcon\", \"addParticipant\", \"removeParticipant\", \"leaveGroup\", \"sendAttachment\", \"delete\", \"pin\", \"unpin\", \"list-pins\", \"permissions\", \"thread-create\", \"thread-list\", \"thread-reply\", \"search\", \"sticker\", \"sticker-search\", \"member-info\", \"role-info\", \"emoji-list\", \"emoji-upload\", \"sticker-upload\", \"role-add\", \"role-remove\", \"channel-info\", \"channel-list\", \"channel-create\", \"channel-edit\", \"channel-delete\", \"channel-move\", \"category-create\", \"category-edit\", \"category-delete\", \"topic-create\", \"topic-edit\", \"voice-status\", \"event-list\", \"event-create\", \"timeout\", \"kick\", \"ban\", \"set-profile\", \"set-presence\", \"set-profile\", \"download-file\"];","entrypoint":"channel-runtime","exportName":"CHANNEL_MESSAGE_ACTION_NAMES","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"const","recordType":"export","sourceLine":1,"sourcePath":"src/channels/plugins/message-action-names.ts"}
|
||||
{"declaration":"export const CHANNEL_MESSAGE_CAPABILITIES: readonly [\"interactive\", \"buttons\", \"cards\", \"components\", \"blocks\"];","entrypoint":"channel-runtime","exportName":"CHANNEL_MESSAGE_CAPABILITIES","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"const","recordType":"export","sourceLine":1,"sourcePath":"src/channels/plugins/message-capabilities.ts"}
|
||||
{"declaration":"export type BaseProbeResult = BaseProbeResult<TError>;","entrypoint":"channel-runtime","exportName":"BaseProbeResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":547,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type BaseTokenResolution = BaseTokenResolution;","entrypoint":"channel-runtime","exportName":"BaseTokenResolution","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":553,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAccountSnapshot = ChannelAccountSnapshot;","entrypoint":"channel-runtime","exportName":"ChannelAccountSnapshot","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":139,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAccountState = ChannelAccountState;","entrypoint":"channel-runtime","exportName":"ChannelAccountState","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":105,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAgentPromptAdapter = ChannelAgentPromptAdapter;","entrypoint":"channel-runtime","exportName":"ChannelAgentPromptAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":455,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAgentTool = ChannelAgentTool;","entrypoint":"channel-runtime","exportName":"ChannelAgentTool","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":17,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAgentToolFactory = ChannelAgentToolFactory;","entrypoint":"channel-runtime","exportName":"ChannelAgentToolFactory","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":21,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelAllowlistAdapter = ChannelAllowlistAdapter;","entrypoint":"channel-runtime","exportName":"ChannelAllowlistAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":497,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelAuthAdapter = ChannelAuthAdapter;","entrypoint":"channel-runtime","exportName":"ChannelAuthAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":362,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelCapabilities = ChannelCapabilities;","entrypoint":"channel-runtime","exportName":"ChannelCapabilities","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":223,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelCapabilitiesDiagnostics = ChannelCapabilitiesDiagnostics;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDiagnostics","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":47,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelCapabilitiesDisplayLine = ChannelCapabilitiesDisplayLine;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDisplayLine","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":42,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelCapabilitiesDisplayTone = ChannelCapabilitiesDisplayTone;","entrypoint":"channel-runtime","exportName":"ChannelCapabilitiesDisplayTone","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":40,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelCommandAdapter = ChannelCommandAdapter;","entrypoint":"channel-runtime","exportName":"ChannelCommandAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":444,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelConfigAdapter = ChannelConfigAdapter<ResolvedAccount>;","entrypoint":"channel-runtime","exportName":"ChannelConfigAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":91,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelConfiguredBindingConversationRef = ChannelConfiguredBindingConversationRef;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingConversationRef","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":553,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelConfiguredBindingMatch = ChannelConfiguredBindingMatch;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingMatch","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":558,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelConfiguredBindingProvider = ChannelConfiguredBindingProvider;","entrypoint":"channel-runtime","exportName":"ChannelConfiguredBindingProvider","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":562,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelDirectoryAdapter = ChannelDirectoryAdapter;","entrypoint":"channel-runtime","exportName":"ChannelDirectoryAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":406,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelDirectoryEntry = ChannelDirectoryEntry;","entrypoint":"channel-runtime","exportName":"ChannelDirectoryEntry","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":461,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelDirectoryEntryKind = ChannelDirectoryEntryKind;","entrypoint":"channel-runtime","exportName":"ChannelDirectoryEntryKind","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":459,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelElevatedAdapter = ChannelElevatedAdapter;","entrypoint":"channel-runtime","exportName":"ChannelElevatedAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":437,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelExecApprovalAdapter = ChannelExecApprovalAdapter;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":463,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelExecApprovalForwardTarget = ChannelExecApprovalForwardTarget;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalForwardTarget","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelExecApprovalInitiatingSurfaceState = ChannelExecApprovalInitiatingSurfaceState;","entrypoint":"channel-runtime","exportName":"ChannelExecApprovalInitiatingSurfaceState","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":27,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelGatewayAdapter = ChannelGatewayAdapter<ResolvedAccount>;","entrypoint":"channel-runtime","exportName":"ChannelGatewayAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":346,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext<ResolvedAccount>;","entrypoint":"channel-runtime","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":238,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelGroupAdapter = ChannelGroupAdapter;","entrypoint":"channel-runtime","exportName":"ChannelGroupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":122,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelGroupContext = ChannelGroupContext;","entrypoint":"channel-runtime","exportName":"ChannelGroupContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":210,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelHeartbeatAdapter = ChannelHeartbeatAdapter;","entrypoint":"channel-runtime","exportName":"ChannelHeartbeatAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":372,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelHeartbeatDeps = ChannelHeartbeatDeps;","entrypoint":"channel-runtime","exportName":"ChannelHeartbeatDeps","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":113,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelId = ChannelId;","entrypoint":"channel-runtime","exportName":"ChannelId","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelLifecycleAdapter = ChannelLifecycleAdapter;","entrypoint":"channel-runtime","exportName":"ChannelLifecycleAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":449,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelLoginWithQrStartResult = ChannelLoginWithQrStartResult;","entrypoint":"channel-runtime","exportName":"ChannelLoginWithQrStartResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":317,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelLoginWithQrWaitResult = ChannelLoginWithQrWaitResult;","entrypoint":"channel-runtime","exportName":"ChannelLoginWithQrWaitResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":322,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelLogoutContext = ChannelLogoutContext<ResolvedAccount>;","entrypoint":"channel-runtime","exportName":"ChannelLogoutContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":327,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelLogoutResult = ChannelLogoutResult;","entrypoint":"channel-runtime","exportName":"ChannelLogoutResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":311,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelLogSink = ChannelLogSink;","entrypoint":"channel-runtime","exportName":"ChannelLogSink","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":203,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMentionAdapter = ChannelMentionAdapter;","entrypoint":"channel-runtime","exportName":"ChannelMentionAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":253,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionAdapter = ChannelMessageActionAdapter;","entrypoint":"channel-runtime","exportName":"ChannelMessageActionAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":506,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionContext = ChannelMessageActionContext;","entrypoint":"channel-runtime","exportName":"ChannelMessageActionContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":473,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionDiscoveryContext = ChannelMessageActionDiscoveryContext;","entrypoint":"channel-runtime","exportName":"ChannelMessageActionDiscoveryContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":29,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageActionName = \"send\" | \"broadcast\" | \"poll\" | \"poll-vote\" | \"react\" | \"reactions\" | \"read\" | \"edit\" | \"unsend\" | \"reply\" | \"sendWithEffect\" | \"renameGroup\" | \"setGroupIcon\" | \"addParticipant\" | \"removeParticipant\" | \"leaveGroup\" | \"sendAttachment\" | \"delete\" | \"pin\" | \"unpin\" | \"list-pins\" | \"permissions\" | \"thread-create\" | \"thread-list\" | \"thread-reply\" | \"search\" | \"sticker\" | \"sticker-search\" | \"member-info\" | \"role-info\" | \"emoji-list\" | \"emoji-upload\" | \"sticker-upload\" | \"role-add\" | \"role-remove\" | \"channel-info\" | \"channel-list\" | \"channel-create\" | \"channel-edit\" | \"channel-delete\" | \"channel-move\" | \"category-create\" | \"category-edit\" | \"category-delete\" | \"topic-create\" | \"topic-edit\" | \"voice-status\" | \"event-list\" | \"event-create\" | \"timeout\" | \"kick\" | \"ban\" | \"set-profile\" | \"set-presence\" | \"download-file\";","entrypoint":"channel-runtime","exportName":"ChannelMessageActionName","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/channels/plugins/types.ts"}
|
||||
{"declaration":"export type ChannelMessageCapability = \"interactive\" | \"buttons\" | \"cards\" | \"components\" | \"blocks\";","entrypoint":"channel-runtime","exportName":"ChannelMessageCapability","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/channels/plugins/message-capabilities.ts"}
|
||||
{"declaration":"export type ChannelMessageToolDiscovery = ChannelMessageToolDiscovery;","entrypoint":"channel-runtime","exportName":"ChannelMessageToolDiscovery","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessageToolSchemaContribution = ChannelMessageToolSchemaContribution;","entrypoint":"channel-runtime","exportName":"ChannelMessageToolSchemaContribution","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":49,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessagingAdapter = ChannelMessagingAdapter;","entrypoint":"channel-runtime","exportName":"ChannelMessagingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":387,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMeta = ChannelMeta;","entrypoint":"channel-runtime","exportName":"ChannelMeta","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":118,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelOutboundAdapter = ChannelOutboundAdapter;","entrypoint":"channel-runtime","exportName":"ChannelOutboundAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":154,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelOutboundContext = ChannelOutboundContext;","entrypoint":"channel-runtime","exportName":"ChannelOutboundContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":128,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelOutboundTargetMode = ChannelOutboundTargetMode;","entrypoint":"channel-runtime","exportName":"ChannelOutboundTargetMode","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":15,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelPairingAdapter = ChannelPairingAdapter;","entrypoint":"channel-runtime","exportName":"ChannelPairingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":335,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelPlugin = ChannelPlugin<ResolvedAccount, Probe, Audit>;","entrypoint":"channel-runtime","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/channels/plugins/types.plugin.ts"}
|
||||
{"declaration":"export type ChannelPollContext = ChannelPollContext;","entrypoint":"channel-runtime","exportName":"ChannelPollContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":536,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelPollResult = ChannelPollResult;","entrypoint":"channel-runtime","exportName":"ChannelPollResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":528,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelResolveKind = ChannelResolveKind;","entrypoint":"channel-runtime","exportName":"ChannelResolveKind","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":417,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelResolverAdapter = ChannelResolverAdapter;","entrypoint":"channel-runtime","exportName":"ChannelResolverAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":427,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelResolveResult = ChannelResolveResult;","entrypoint":"channel-runtime","exportName":"ChannelResolveResult","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":419,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelSecurityAdapter = ChannelSecurityAdapter<ResolvedAccount>;","entrypoint":"channel-runtime","exportName":"ChannelSecurityAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":575,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelSecurityContext = ChannelSecurityContext<ResolvedAccount>;","entrypoint":"channel-runtime","exportName":"ChannelSecurityContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":247,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelSecurityDmPolicy = ChannelSecurityDmPolicy;","entrypoint":"channel-runtime","exportName":"ChannelSecurityDmPolicy","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":238,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"channel-runtime","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":56,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelSetupInput = ChannelSetupInput;","entrypoint":"channel-runtime","exportName":"ChannelSetupInput","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelStatusAdapter = ChannelStatusAdapter<ResolvedAccount, Probe, Audit>;","entrypoint":"channel-runtime","exportName":"ChannelStatusAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":184,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelStatusIssue = ChannelStatusIssue;","entrypoint":"channel-runtime","exportName":"ChannelStatusIssue","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":97,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelStreamingAdapter = ChannelStreamingAdapter;","entrypoint":"channel-runtime","exportName":"ChannelStreamingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":272,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelStructuredComponents = ChannelStructuredComponents;","entrypoint":"channel-runtime","exportName":"ChannelStructuredComponents","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":281,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelThreadingAdapter = ChannelThreadingAdapter;","entrypoint":"channel-runtime","exportName":"ChannelThreadingAdapter","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":315,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelThreadingContext = ChannelThreadingContext;","entrypoint":"channel-runtime","exportName":"ChannelThreadingContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":358,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelThreadingToolContext = ChannelThreadingToolContext;","entrypoint":"channel-runtime","exportName":"ChannelThreadingToolContext","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":372,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelToolSend = ChannelToolSend;","entrypoint":"channel-runtime","exportName":"ChannelToolSend","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":500,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChatType = ChatType;","entrypoint":"channel-runtime","exportName":"ChatType","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/channels/chat-type.ts"}
|
||||
{"declaration":"export type CreateTypingCallbacksParams = CreateTypingCallbacksParams;","entrypoint":"channel-runtime","exportName":"CreateTypingCallbacksParams","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":11,"sourcePath":"src/channels/typing.ts"}
|
||||
{"declaration":"export type NormalizedPollInput = NormalizedPollInput;","entrypoint":"channel-runtime","exportName":"NormalizedPollInput","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":17,"sourcePath":"src/polls.ts"}
|
||||
{"declaration":"export type PollInput = PollInput;","entrypoint":"channel-runtime","exportName":"PollInput","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/polls.ts"}
|
||||
{"declaration":"export type ReplyPrefixContextBundle = ReplyPrefixContextBundle;","entrypoint":"channel-runtime","exportName":"ReplyPrefixContextBundle","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":12,"sourcePath":"src/channels/reply-prefix.ts"}
|
||||
{"declaration":"export type ReplyPrefixOptions = ReplyPrefixOptions;","entrypoint":"channel-runtime","exportName":"ReplyPrefixOptions","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":20,"sourcePath":"src/channels/reply-prefix.ts"}
|
||||
{"declaration":"export type TypingCallbacks = TypingCallbacks;","entrypoint":"channel-runtime","exportName":"TypingCallbacks","importSpecifier":"openclaw/plugin-sdk/channel-runtime","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/channels/typing.ts"}
|
||||
{"category":"channel","entrypoint":"channel-setup","importSpecifier":"openclaw/plugin-sdk/channel-setup","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/channel-setup.ts"}
|
||||
{"declaration":"export function createOptionalChannelSetupAdapter(params: OptionalChannelSetupParams): ChannelSetupAdapter;","entrypoint":"channel-setup","exportName":"createOptionalChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":22,"sourcePath":"src/plugin-sdk/optional-channel-setup.ts"}
|
||||
{"declaration":"export function createOptionalChannelSetupSurface(params: OptionalChannelSetupParams): OptionalChannelSetupSurface;","entrypoint":"channel-setup","exportName":"createOptionalChannelSetupSurface","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":37,"sourcePath":"src/plugin-sdk/channel-setup.ts"}
|
||||
{"declaration":"export function createOptionalChannelSetupWizard(params: OptionalChannelSetupParams): ChannelSetupWizard;","entrypoint":"channel-setup","exportName":"createOptionalChannelSetupWizard","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":35,"sourcePath":"src/plugin-sdk/optional-channel-setup.ts"}
|
||||
{"declaration":"export function createTopLevelChannelDmPolicy(params: { label: string; channel: string; policyKey: string; allowFromKey: string; getCurrent: (cfg: OpenClawConfig) => DmPolicy; promptAllowFrom?: ((params: { cfg: OpenClawConfig; prompter: WizardPrompter; accountId?: string | undefined; }) => Promise<...>) | undefined; getAllowFrom?: ((cfg: OpenClawConfig) => (string | number)[] | undefined) | undefined; }): ChannelSetupDmPolicy;","entrypoint":"channel-setup","exportName":"createTopLevelChannelDmPolicy","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":363,"sourcePath":"src/channels/plugins/setup-wizard-helpers.ts"}
|
||||
{"declaration":"export function formatDocsLink(path: string, label?: string | undefined, opts?: { fallback?: string | undefined; force?: boolean | undefined; } | undefined): string;","entrypoint":"channel-setup","exportName":"formatDocsLink","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":5,"sourcePath":"src/terminal/links.ts"}
|
||||
{"declaration":"export function setSetupChannelEnabled(cfg: OpenClawConfig, channel: string, enabled: boolean): OpenClawConfig;","entrypoint":"channel-setup","exportName":"setSetupChannelEnabled","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":765,"sourcePath":"src/channels/plugins/setup-wizard-helpers.ts"}
|
||||
{"declaration":"export function splitSetupEntries(raw: string): string[];","entrypoint":"channel-setup","exportName":"splitSetupEntries","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"function","recordType":"export","sourceLine":76,"sourcePath":"src/channels/plugins/setup-wizard-helpers.ts"}
|
||||
{"declaration":"export const DEFAULT_ACCOUNT_ID: \"default\";","entrypoint":"channel-setup","exportName":"DEFAULT_ACCOUNT_ID","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"const","recordType":"export","sourceLine":3,"sourcePath":"src/routing/account-id.ts"}
|
||||
{"declaration":"export type ChannelSetupAdapter = ChannelSetupAdapter;","entrypoint":"channel-setup","exportName":"ChannelSetupAdapter","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":56,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type ChannelSetupDmPolicy = ChannelSetupDmPolicy;","entrypoint":"channel-setup","exportName":"ChannelSetupDmPolicy","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":93,"sourcePath":"src/channels/plugins/setup-wizard-types.ts"}
|
||||
{"declaration":"export type ChannelSetupInput = ChannelSetupInput;","entrypoint":"channel-setup","exportName":"ChannelSetupInput","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelSetupWizard = ChannelSetupWizard;","entrypoint":"channel-setup","exportName":"ChannelSetupWizard","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":247,"sourcePath":"src/channels/plugins/setup-wizard.ts"}
|
||||
{"declaration":"export type OptionalChannelSetupSurface = OptionalChannelSetupSurface;","entrypoint":"channel-setup","exportName":"OptionalChannelSetupSurface","importSpecifier":"openclaw/plugin-sdk/channel-setup","kind":"type","recordType":"export","sourceLine":27,"sourcePath":"src/plugin-sdk/channel-setup.ts"}
|
||||
{"category":"channel","entrypoint":"command-auth","importSpecifier":"openclaw/plugin-sdk/command-auth","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/command-auth.ts"}
|
||||
{"declaration":"export function buildCommandsMessage(cfg?: OpenClawConfig | undefined, skillCommands?: SkillCommandSpec[] | undefined, options?: CommandsMessageOptions | undefined): string;","entrypoint":"command-auth","exportName":"buildCommandsMessage","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":847,"sourcePath":"src/auto-reply/status.ts"}
|
||||
{"declaration":"export function buildCommandsMessagePaginated(cfg?: OpenClawConfig | undefined, skillCommands?: SkillCommandSpec[] | undefined, options?: CommandsMessageOptions | undefined): CommandsMessageResult;","entrypoint":"command-auth","exportName":"buildCommandsMessagePaginated","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":856,"sourcePath":"src/auto-reply/status.ts"}
|
||||
{"declaration":"export function buildCommandsPaginationKeyboard(currentPage: number, totalPages: number, agentId?: string | undefined): { text: string; callback_data: string; }[][];","entrypoint":"command-auth","exportName":"buildCommandsPaginationKeyboard","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":89,"sourcePath":"src/auto-reply/reply/commands-info.ts"}
|
||||
{"declaration":"export function buildCommandText(commandName: string, args?: string | undefined): string;","entrypoint":"command-auth","exportName":"buildCommandText","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":199,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function buildCommandTextFromArgs(command: ChatCommandDefinition, args?: CommandArgs | undefined): string;","entrypoint":"command-auth","exportName":"buildCommandTextFromArgs","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":291,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function buildHelpMessage(cfg?: OpenClawConfig | undefined): string;","entrypoint":"command-auth","exportName":"buildHelpMessage","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":727,"sourcePath":"src/auto-reply/status.ts"}
|
||||
{"declaration":"export function buildModelsProviderData(cfg: OpenClawConfig, agentId?: string | undefined): Promise<ModelsProviderData>;","entrypoint":"command-auth","exportName":"buildModelsProviderData","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":37,"sourcePath":"src/auto-reply/reply/commands-models.ts"}
|
||||
{"declaration":"export function findCommandByNativeName(name: string, provider?: string | undefined): ChatCommandDefinition | undefined;","entrypoint":"command-auth","exportName":"findCommandByNativeName","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":187,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function formatModelsAvailableHeader(params: { provider: string; total: number; cfg: OpenClawConfig; agentDir?: string | undefined; sessionEntry?: SessionEntry | undefined; }): string;","entrypoint":"command-auth","exportName":"formatModelsAvailableHeader","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":204,"sourcePath":"src/auto-reply/reply/commands-models.ts"}
|
||||
{"declaration":"export function getCommandDetection(_cfg?: OpenClawConfig | undefined): CommandDetection;","entrypoint":"command-auth","exportName":"getCommandDetection","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":440,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function hasControlCommand(text?: string | undefined, cfg?: OpenClawConfig | undefined, options?: CommandNormalizeOptions | undefined): boolean;","entrypoint":"command-auth","exportName":"hasControlCommand","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":10,"sourcePath":"src/auto-reply/command-detection.ts"}
|
||||
{"declaration":"export function hasInlineCommandTokens(text?: string | undefined): boolean;","entrypoint":"command-auth","exportName":"hasInlineCommandTokens","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":74,"sourcePath":"src/auto-reply/command-detection.ts"}
|
||||
{"declaration":"export function isCommandEnabled(cfg: OpenClawConfig, commandKey: string): boolean;","entrypoint":"command-auth","exportName":"isCommandEnabled","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":98,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function isCommandMessage(raw: string): boolean;","entrypoint":"command-auth","exportName":"isCommandMessage","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":435,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function isControlCommandMessage(text?: string | undefined, cfg?: OpenClawConfig | undefined, options?: CommandNormalizeOptions | undefined): boolean;","entrypoint":"command-auth","exportName":"isControlCommandMessage","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":48,"sourcePath":"src/auto-reply/command-detection.ts"}
|
||||
{"declaration":"export function isNativeCommandSurface(surface?: string | undefined): boolean;","entrypoint":"command-auth","exportName":"isNativeCommandSurface","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":521,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function listChatCommands(params?: { skillCommands?: SkillCommandSpec[] | undefined; } | undefined): ChatCommandDefinition[];","entrypoint":"command-auth","exportName":"listChatCommands","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":88,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function listChatCommandsForConfig(cfg: OpenClawConfig, params?: { skillCommands?: SkillCommandSpec[] | undefined; } | undefined): ChatCommandDefinition[];","entrypoint":"command-auth","exportName":"listChatCommandsForConfig","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":117,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function listNativeCommandSpecs(params?: { skillCommands?: SkillCommandSpec[] | undefined; provider?: string | undefined; } | undefined): NativeCommandSpec[];","entrypoint":"command-auth","exportName":"listNativeCommandSpecs","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":170,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function listNativeCommandSpecsForConfig(cfg: OpenClawConfig, params?: { skillCommands?: SkillCommandSpec[] | undefined; provider?: string | undefined; } | undefined): NativeCommandSpec[];","entrypoint":"command-auth","exportName":"listNativeCommandSpecsForConfig","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":180,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function listReservedChatSlashCommandNames(extraNames?: string[]): Set<string>;","entrypoint":"command-auth","exportName":"listReservedChatSlashCommandNames","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":4,"sourcePath":"src/auto-reply/skill-commands-base.ts"}
|
||||
{"declaration":"export function listSkillCommandsForAgents(params: { cfg: OpenClawConfig; agentIds?: string[] | undefined; }): SkillCommandSpec[];","entrypoint":"command-auth","exportName":"listSkillCommandsForAgents","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":46,"sourcePath":"src/auto-reply/skill-commands.ts"}
|
||||
{"declaration":"export function listSkillCommandsForWorkspace(params: { workspaceDir: string; cfg: OpenClawConfig; skillFilter?: string[] | undefined; }): SkillCommandSpec[];","entrypoint":"command-auth","exportName":"listSkillCommandsForWorkspace","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":17,"sourcePath":"src/auto-reply/skill-commands.ts"}
|
||||
{"declaration":"export function maybeResolveTextAlias(raw: string, cfg?: OpenClawConfig | undefined): string | null;","entrypoint":"command-auth","exportName":"maybeResolveTextAlias","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":473,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function normalizeCommandBody(raw: string, options?: CommandNormalizeOptions | undefined): string;","entrypoint":"command-auth","exportName":"normalizeCommandBody","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":384,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function parseCommandArgs(command: ChatCommandDefinition, raw?: string | undefined): CommandArgs | undefined;","entrypoint":"command-auth","exportName":"parseCommandArgs","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":254,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function resolveCommandArgChoices(params: { command: ChatCommandDefinition; arg: CommandArgDefinition; cfg?: OpenClawConfig | undefined; provider?: string | undefined; model?: string | undefined; }): ResolvedCommandArgChoice[];","entrypoint":"command-auth","exportName":"resolveCommandArgChoices","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":316,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function resolveCommandArgMenu(params: { command: ChatCommandDefinition; args?: CommandArgs | undefined; cfg?: OpenClawConfig | undefined; }): { arg: CommandArgDefinition; choices: ResolvedCommandArgChoice[]; title?: string | undefined; } | null;","entrypoint":"command-auth","exportName":"resolveCommandArgMenu","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":346,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function resolveCommandAuthorization(params: { ctx: MsgContext; cfg: OpenClawConfig; commandAuthorized: boolean; }): CommandAuthorization;","entrypoint":"command-auth","exportName":"resolveCommandAuthorization","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":303,"sourcePath":"src/auto-reply/command-auth.ts"}
|
||||
{"declaration":"export function resolveCommandAuthorizedFromAuthorizers(params: { useAccessGroups: boolean; authorizers: CommandAuthorizer[]; modeWhenAccessGroupsOff?: CommandGatingModeWhenAccessGroupsOff | undefined; }): boolean;","entrypoint":"command-auth","exportName":"resolveCommandAuthorizedFromAuthorizers","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":8,"sourcePath":"src/channels/command-gating.ts"}
|
||||
{"declaration":"export function resolveControlCommandGate(params: { useAccessGroups: boolean; authorizers: CommandAuthorizer[]; allowTextCommands: boolean; hasControlCommand: boolean; modeWhenAccessGroupsOff?: CommandGatingModeWhenAccessGroupsOff | undefined; }): { ...; };","entrypoint":"command-auth","exportName":"resolveControlCommandGate","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":31,"sourcePath":"src/channels/command-gating.ts"}
|
||||
{"declaration":"export function resolveDirectDmAuthorizationOutcome(params: { isGroup: boolean; dmPolicy: string; senderAllowedForCommands: boolean; }): \"disabled\" | \"unauthorized\" | \"allowed\";","entrypoint":"command-auth","exportName":"resolveDirectDmAuthorizationOutcome","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":114,"sourcePath":"src/plugin-sdk/command-auth.ts"}
|
||||
{"declaration":"export function resolveDualTextControlCommandGate(params: { useAccessGroups: boolean; primaryConfigured: boolean; primaryAllowed: boolean; secondaryConfigured: boolean; secondaryAllowed: boolean; hasControlCommand: boolean; modeWhenAccessGroupsOff?: CommandGatingModeWhenAccessGroupsOff | undefined; }): { ...; };","entrypoint":"command-auth","exportName":"resolveDualTextControlCommandGate","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":47,"sourcePath":"src/channels/command-gating.ts"}
|
||||
{"declaration":"export function resolveModelsCommandReply(params: { cfg: OpenClawConfig; commandBodyNormalized: string; surface?: string | undefined; currentModel?: string | undefined; agentId?: string | undefined; agentDir?: string | undefined; sessionEntry?: SessionEntry | undefined; }): Promise<...>;","entrypoint":"command-auth","exportName":"resolveModelsCommandReply","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":220,"sourcePath":"src/auto-reply/reply/commands-models.ts"}
|
||||
{"declaration":"export function resolveNativeCommandSessionTargets(params: ResolveNativeCommandSessionTargetsParams): { sessionKey: string; commandTargetSessionKey: string; };","entrypoint":"command-auth","exportName":"resolveNativeCommandSessionTargets","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":10,"sourcePath":"src/channels/native-command-session-targets.ts"}
|
||||
{"declaration":"export function resolveSenderCommandAuthorization(params: ResolveSenderCommandAuthorizationParams): Promise<{ shouldComputeAuth: boolean; effectiveAllowFrom: string[]; effectiveGroupAllowFrom: string[]; senderAllowedForCommands: boolean; commandAuthorized: boolean | undefined; }>;","entrypoint":"command-auth","exportName":"resolveSenderCommandAuthorization","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":143,"sourcePath":"src/plugin-sdk/command-auth.ts"}
|
||||
{"declaration":"export function resolveSenderCommandAuthorizationWithRuntime(params: ResolveSenderCommandAuthorizationWithRuntimeParams): Promise<{ shouldComputeAuth: boolean; effectiveAllowFrom: string[]; effectiveGroupAllowFrom: string[]; senderAllowedForCommands: boolean; commandAuthorized: boolean | undefined; }>;","entrypoint":"command-auth","exportName":"resolveSenderCommandAuthorizationWithRuntime","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":132,"sourcePath":"src/plugin-sdk/command-auth.ts"}
|
||||
{"declaration":"export function resolveSkillCommandInvocation(params: { commandBodyNormalized: string; skillCommands: SkillCommandSpec[]; }): { command: SkillCommandSpec; args?: string | undefined; } | null;","entrypoint":"command-auth","exportName":"resolveSkillCommandInvocation","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":58,"sourcePath":"src/auto-reply/skill-commands-base.ts"}
|
||||
{"declaration":"export function resolveStoredModelOverride(params: { sessionEntry?: SessionEntry | undefined; sessionStore?: Record<string, SessionEntry> | undefined; sessionKey?: string | undefined; parentSessionKey?: string | undefined; }): StoredModelOverride | null;","entrypoint":"command-auth","exportName":"resolveStoredModelOverride","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":156,"sourcePath":"src/auto-reply/reply/model-selection.ts"}
|
||||
{"declaration":"export function resolveTextCommand(raw: string, cfg?: OpenClawConfig | undefined): { command: ChatCommandDefinition; args?: string | undefined; } | null;","entrypoint":"command-auth","exportName":"resolveTextCommand","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":494,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function serializeCommandArgs(command: ChatCommandDefinition, args?: CommandArgs | undefined): string | undefined;","entrypoint":"command-auth","exportName":"serializeCommandArgs","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":271,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export function shouldComputeCommandAuthorized(text?: string | undefined, cfg?: OpenClawConfig | undefined, options?: CommandNormalizeOptions | undefined): boolean;","entrypoint":"command-auth","exportName":"shouldComputeCommandAuthorized","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":82,"sourcePath":"src/auto-reply/command-detection.ts"}
|
||||
{"declaration":"export function shouldHandleTextCommands(params: ShouldHandleTextCommandsParams): boolean;","entrypoint":"command-auth","exportName":"shouldHandleTextCommands","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"function","recordType":"export","sourceLine":528,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export type ChatCommandDefinition = ChatCommandDefinition;","entrypoint":"command-auth","exportName":"ChatCommandDefinition","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type CommandArgChoiceContext = CommandArgChoiceContext;","entrypoint":"command-auth","exportName":"CommandArgChoiceContext","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":16,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type CommandArgDefinition = CommandArgDefinition;","entrypoint":"command-auth","exportName":"CommandArgDefinition","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":28,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type CommandArgMenuSpec = CommandArgMenuSpec;","entrypoint":"command-auth","exportName":"CommandArgMenuSpec","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":38,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type CommandArgs = CommandArgs;","entrypoint":"command-auth","exportName":"CommandArgs","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":46,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type CommandArgValues = CommandArgValues;","entrypoint":"command-auth","exportName":"CommandArgValues","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":44,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type CommandAuthorization = CommandAuthorization;","entrypoint":"command-auth","exportName":"CommandAuthorization","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/auto-reply/command-auth.ts"}
|
||||
{"declaration":"export type CommandAuthorizationRuntime = CommandAuthorizationRuntime;","entrypoint":"command-auth","exportName":"CommandAuthorizationRuntime","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":98,"sourcePath":"src/plugin-sdk/command-auth.ts"}
|
||||
{"declaration":"export type CommandAuthorizer = CommandAuthorizer;","entrypoint":"command-auth","exportName":"CommandAuthorizer","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/channels/command-gating.ts"}
|
||||
{"declaration":"export type CommandDetection = CommandDetection;","entrypoint":"command-auth","exportName":"CommandDetection","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":78,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type CommandGatingModeWhenAccessGroupsOff = CommandGatingModeWhenAccessGroupsOff;","entrypoint":"command-auth","exportName":"CommandGatingModeWhenAccessGroupsOff","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/channels/command-gating.ts"}
|
||||
{"declaration":"export type CommandNormalizeOptions = CommandNormalizeOptions;","entrypoint":"command-auth","exportName":"CommandNormalizeOptions","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":74,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type CommandScope = CommandScope;","entrypoint":"command-auth","exportName":"CommandScope","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":3,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type ModelsProviderData = ModelsProviderData;","entrypoint":"command-auth","exportName":"ModelsProviderData","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":27,"sourcePath":"src/auto-reply/reply/commands-models.ts"}
|
||||
{"declaration":"export type NativeCommandSpec = NativeCommandSpec;","entrypoint":"command-auth","exportName":"NativeCommandSpec","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":67,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type ResolvedCommandArgChoice = ResolvedCommandArgChoice;","entrypoint":"command-auth","exportName":"ResolvedCommandArgChoice","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":314,"sourcePath":"src/auto-reply/commands-registry.ts"}
|
||||
{"declaration":"export type ResolveNativeCommandSessionTargetsParams = ResolveNativeCommandSessionTargetsParams;","entrypoint":"command-auth","exportName":"ResolveNativeCommandSessionTargetsParams","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/channels/native-command-session-targets.ts"}
|
||||
{"declaration":"export type ResolveSenderCommandAuthorizationParams = ResolveSenderCommandAuthorizationParams;","entrypoint":"command-auth","exportName":"ResolveSenderCommandAuthorizationParams","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":81,"sourcePath":"src/plugin-sdk/command-auth.ts"}
|
||||
{"declaration":"export type ResolveSenderCommandAuthorizationWithRuntimeParams = ResolveSenderCommandAuthorizationWithRuntimeParams;","entrypoint":"command-auth","exportName":"ResolveSenderCommandAuthorizationWithRuntimeParams","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":106,"sourcePath":"src/plugin-sdk/command-auth.ts"}
|
||||
{"declaration":"export type ShouldHandleTextCommandsParams = ShouldHandleTextCommandsParams;","entrypoint":"command-auth","exportName":"ShouldHandleTextCommandsParams","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":83,"sourcePath":"src/auto-reply/commands-registry.types.ts"}
|
||||
{"declaration":"export type StoredModelOverride = StoredModelOverride;","entrypoint":"command-auth","exportName":"StoredModelOverride","importSpecifier":"openclaw/plugin-sdk/command-auth","kind":"type","recordType":"export","sourceLine":123,"sourcePath":"src/auto-reply/reply/model-selection.ts"}
|
||||
{"category":"core","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export function applyAccountNameToChannelSection(params: { cfg: OpenClawConfig; channelKey: string; accountId: string; name?: string | undefined; alwaysUseAccounts?: boolean | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"applyAccountNameToChannelSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":33,"sourcePath":"src/channels/plugins/setup-helpers.ts"}
|
||||
{"declaration":"export function buildAgentSessionKey(params: { agentId: string; channel: string; accountId?: string | null | undefined; peer?: RoutePeer | null | undefined; dmScope?: \"main\" | \"per-peer\" | \"per-channel-peer\" | \"per-account-channel-peer\" | undefined; identityLinks?: Record<...> | undefined; }): string;","entrypoint":"core","exportName":"buildAgentSessionKey","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":91,"sourcePath":"src/routing/resolve-route.ts"}
|
||||
{"declaration":"export function buildChannelConfigSchema(schema: ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>): ChannelConfigSchema;","entrypoint":"core","exportName":"buildChannelConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":35,"sourcePath":"src/channels/plugins/config-schema.ts"}
|
||||
{"declaration":"export function buildChannelOutboundSessionRoute(params: { cfg: OpenClawConfig; agentId: string; channel: string; accountId?: string | null | undefined; peer: { kind: \"direct\" | \"group\" | \"channel\"; id: string; }; chatType: \"direct\" | \"group\" | \"channel\"; from: string; to: string; threadId?: string | ... 1 more ... | undefined; }): ChannelOutboundSessionRoute;","entrypoint":"core","exportName":"buildChannelOutboundSessionRoute","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":155,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export function channelTargetSchema(options?: { description?: string | undefined; } | undefined): TString;","entrypoint":"core","exportName":"channelTargetSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":33,"sourcePath":"src/agents/schema/typebox.ts"}
|
||||
{"declaration":"export function channelTargetsSchema(options?: { description?: string | undefined; } | undefined): TArray<TString>;","entrypoint":"core","exportName":"channelTargetsSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":39,"sourcePath":"src/agents/schema/typebox.ts"}
|
||||
{"declaration":"export function clearAccountEntryFields<TAccountEntry extends object>(params: { accounts?: Record<string, TAccountEntry> | undefined; accountId: string; fields: string[]; isValueSet?: ((value: unknown) => boolean) | undefined; markClearedOnFieldPresence?: boolean | undefined; }): { ...; };","entrypoint":"core","exportName":"clearAccountEntryFields","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":122,"sourcePath":"src/channels/plugins/config-helpers.ts"}
|
||||
{"declaration":"export function createChannelPluginBase<TResolvedAccount>(params: CreateChannelPluginBaseOptions<TResolvedAccount>): CreatedChannelPluginBase<TResolvedAccount>;","entrypoint":"core","exportName":"createChannelPluginBase","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":417,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export function createChatChannelPlugin<TResolvedAccount extends { accountId?: string | null; }, Probe = unknown, Audit = unknown>(params: { base: ChatChannelPluginBase<TResolvedAccount, Probe, Audit>; security?: ChannelSecurityAdapter<TResolvedAccount> | ChatChannelSecurityOptions<...> | undefined; pairing?: ChannelPairingAdapter | ... 1 more ... | undefined; threading?: ChannelThreadingAdapter | ... 1 more ... | undefined; outbound?: ChannelOutboundAdapter | ... 1 more ... | undefined; }): ChannelPlugin<...>;","entrypoint":"core","exportName":"createChatChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":394,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export function defineChannelPluginEntry<TPlugin extends ChannelPlugin>({ id, name, description, plugin, configSchema, setRuntime, registerFull, }: DefineChannelPluginEntryOptions<TPlugin>): DefinedPluginEntry;","entrypoint":"core","exportName":"defineChannelPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":231,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"core","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":79,"sourcePath":"src/plugin-sdk/plugin-entry.ts"}
|
||||
{"declaration":"export function defineSetupPluginEntry<TPlugin>(plugin: TPlugin): { plugin: TPlugin; };","entrypoint":"core","exportName":"defineSetupPluginEntry","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":257,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export function delegateCompactionToRuntime(params: { sessionId: string; sessionKey?: string | undefined; sessionFile: string; tokenBudget?: number | undefined; force?: boolean | undefined; currentTokenCount?: number | undefined; compactionTarget?: \"budget\" | ... 1 more ... | undefined; customInstructions?: string | undefined; runtimeContext?: ContextEngineRuntimeContext | undefined; }): Promise<...>;","entrypoint":"core","exportName":"delegateCompactionToRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/context-engine/delegate.ts"}
|
||||
{"declaration":"export function deleteAccountFromConfigSection(params: { cfg: OpenClawConfig; sectionKey: string; accountId: string; clearBaseFields?: string[] | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"deleteAccountFromConfigSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":60,"sourcePath":"src/channels/plugins/config-helpers.ts"}
|
||||
{"declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":13,"sourcePath":"src/plugins/config-schema.ts"}
|
||||
{"declaration":"export function formatPairingApproveHint(channelId: string): string;","entrypoint":"core","exportName":"formatPairingApproveHint","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":17,"sourcePath":"src/channels/plugins/helpers.ts"}
|
||||
{"declaration":"export function getChatChannelMeta(id: \"telegram\" | \"whatsapp\" | \"discord\" | \"irc\" | \"googlechat\" | \"slack\" | \"signal\" | \"imessage\" | \"line\"): ChannelMeta;","entrypoint":"core","exportName":"getChatChannelMeta","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":142,"sourcePath":"src/channels/registry.ts"}
|
||||
{"declaration":"export function isSecretRef(value: unknown): value is SecretRef;","entrypoint":"core","exportName":"isSecretRef","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":34,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"declaration":"export function loadSecretFileSync(filePath: string, label: string, options?: SecretFileReadOptions): SecretFileReadResult;","entrypoint":"core","exportName":"loadSecretFileSync","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":29,"sourcePath":"src/infra/secret-file.ts"}
|
||||
{"declaration":"export function migrateBaseNameToDefaultAccount(params: { cfg: OpenClawConfig; channelKey: string; alwaysUseAccounts?: boolean | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"migrateBaseNameToDefaultAccount","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":92,"sourcePath":"src/channels/plugins/setup-helpers.ts"}
|
||||
{"declaration":"export function normalizeAccountId(value: string | null | undefined): string;","entrypoint":"core","exportName":"normalizeAccountId","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":34,"sourcePath":"src/routing/account-id.ts"}
|
||||
{"declaration":"export function normalizeAtHashSlug(raw?: string | null | undefined): string;","entrypoint":"core","exportName":"normalizeAtHashSlug","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":19,"sourcePath":"src/shared/string-normalization.ts"}
|
||||
{"declaration":"export function normalizeHyphenSlug(raw?: string | null | undefined): string;","entrypoint":"core","exportName":"normalizeHyphenSlug","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":9,"sourcePath":"src/shared/string-normalization.ts"}
|
||||
{"declaration":"export function optionalStringEnum<T extends readonly string[]>(values: T, options?: StringEnumOptions<T>): TOptional<TUnsafe<T[number]>>;","entrypoint":"core","exportName":"optionalStringEnum","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":26,"sourcePath":"src/agents/schema/typebox.ts"}
|
||||
{"declaration":"export function parseOptionalDelimitedEntries(value?: string | undefined): string[] | undefined;","entrypoint":"core","exportName":"parseOptionalDelimitedEntries","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":23,"sourcePath":"src/channels/plugins/helpers.ts"}
|
||||
{"declaration":"export function readSecretFileSync(filePath: string, label: string, options?: SecretFileReadOptions): string;","entrypoint":"core","exportName":"readSecretFileSync","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":118,"sourcePath":"src/infra/secret-file.ts"}
|
||||
{"declaration":"export function resolveGatewayBindUrl(params: { bind?: string | undefined; customBindHost?: string | undefined; scheme: \"ws\" | \"wss\"; port: number; pickTailnetHost: () => string | null; pickLanHost: () => string | null; }): GatewayBindUrlResult;","entrypoint":"core","exportName":"resolveGatewayBindUrl","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":11,"sourcePath":"src/shared/gateway-bind-url.ts"}
|
||||
{"declaration":"export function resolveTailnetHostWithRunner(runCommandWithTimeout?: TailscaleStatusCommandRunner | undefined): Promise<string | null>;","entrypoint":"core","exportName":"resolveTailnetHostWithRunner","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":43,"sourcePath":"src/shared/tailscale-status.ts"}
|
||||
{"declaration":"export function resolveThreadSessionKeys(params: { baseSessionKey: string; threadId?: string | null | undefined; parentSessionKey?: string | undefined; useSuffix?: boolean | undefined; normalizeThreadId?: ((threadId: string) => string) | undefined; }): { ...; };","entrypoint":"core","exportName":"resolveThreadSessionKeys","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":234,"sourcePath":"src/routing/session-key.ts"}
|
||||
{"declaration":"export function setAccountEnabledInConfigSection(params: { cfg: OpenClawConfig; sectionKey: string; accountId: string; enabled: boolean; allowTopLevel?: boolean | undefined; }): OpenClawConfig;","entrypoint":"core","exportName":"setAccountEnabledInConfigSection","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/channels/plugins/config-helpers.ts"}
|
||||
{"declaration":"export function stringEnum<T extends readonly string[]>(values: T, options?: StringEnumOptions<T>): TUnsafe<T[number]>;","entrypoint":"core","exportName":"stringEnum","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":15,"sourcePath":"src/agents/schema/typebox.ts"}
|
||||
{"declaration":"export function stripChannelTargetPrefix(raw: string, ...providers: string[]): string;","entrypoint":"core","exportName":"stripChannelTargetPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":140,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export function stripTargetKindPrefix(raw: string): string;","entrypoint":"core","exportName":"stripTargetKindPrefix","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":151,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export function tryReadSecretFileSync(filePath: string | undefined, label: string, options?: SecretFileReadOptions): string | undefined;","entrypoint":"core","exportName":"tryReadSecretFileSync","importSpecifier":"openclaw/plugin-sdk/core","kind":"function","recordType":"export","sourceLine":130,"sourcePath":"src/infra/secret-file.ts"}
|
||||
{"declaration":"export const DEFAULT_ACCOUNT_ID: \"default\";","entrypoint":"core","exportName":"DEFAULT_ACCOUNT_ID","importSpecifier":"openclaw/plugin-sdk/core","kind":"const","recordType":"export","sourceLine":3,"sourcePath":"src/routing/account-id.ts"}
|
||||
{"declaration":"export const DEFAULT_SECRET_FILE_MAX_BYTES: number;","entrypoint":"core","exportName":"DEFAULT_SECRET_FILE_MAX_BYTES","importSpecifier":"openclaw/plugin-sdk/core","kind":"const","recordType":"export","sourceLine":5,"sourcePath":"src/infra/secret-file.ts"}
|
||||
{"declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"core","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/agents/tools/common.ts"}
|
||||
{"declaration":"export type ChannelMessageActionContext = ChannelMessageActionContext;","entrypoint":"core","exportName":"ChannelMessageActionContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":473,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelMessagingAdapter = ChannelMessagingAdapter;","entrypoint":"core","exportName":"ChannelMessagingAdapter","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":387,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelOutboundSessionRoute = ChannelOutboundSessionRoute;","entrypoint":"core","exportName":"ChannelOutboundSessionRoute","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":302,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelOutboundSessionRouteParams = { cfg: OpenClawConfig; agentId: string; accountId?: string | null; target: string; resolvedTarget?: { to: string; kind: import(\"/Users/vincentkoc/GIT/_Perso/openclaw/.worktrees/pr-51877/src/channels/plugins/types.core\").ChannelDirectoryEntryKind | \"channel\"; display?: string; source: \"normalized\" | \"directory\"; }; replyToId?: string | null; threadId?: string | number | null;};","entrypoint":"core","exportName":"ChannelOutboundSessionRouteParams","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":136,"sourcePath":"src/plugin-sdk/core.ts"}
|
||||
{"declaration":"export type ChannelPlugin = ChannelPlugin<ResolvedAccount, Probe, Audit>;","entrypoint":"core","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/channels/plugins/types.plugin.ts"}
|
||||
{"declaration":"export type GatewayBindUrlResult = GatewayBindUrlResult;","entrypoint":"core","exportName":"GatewayBindUrlResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/shared/gateway-bind-url.ts"}
|
||||
{"declaration":"export type GatewayRequestHandlerOptions = GatewayRequestHandlerOptions;","entrypoint":"core","exportName":"GatewayRequestHandlerOptions","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":111,"sourcePath":"src/gateway/server-methods/types.ts"}
|
||||
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"core","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":935,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"core","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1292,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"core","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1050,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":80,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"core","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1275,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"core","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1265,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"core","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1258,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginToolContext = OpenClawPluginToolContext;","entrypoint":"core","exportName":"OpenClawPluginToolContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":94,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginToolFactory = OpenClawPluginToolFactory;","entrypoint":"core","exportName":"OpenClawPluginToolFactory","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":111,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"core","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":950,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"core","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1079,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"core","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":58,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"core","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/plugins/runtime/types.ts"}
|
||||
{"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"core","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":560,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"core","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":144,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthDoctorHintContext = ProviderAuthDoctorHintContext;","entrypoint":"core","exportName":"ProviderAuthDoctorHintContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":435,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthMethod = ProviderAuthMethod;","entrypoint":"core","exportName":"ProviderAuthMethod","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":222,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthMethodNonInteractiveContext = ProviderAuthMethodNonInteractiveContext;","entrypoint":"core","exportName":"ProviderAuthMethodNonInteractiveContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":206,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"core","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":130,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderBuildMissingAuthMessageContext = ProviderBuildMissingAuthMessageContext;","entrypoint":"core","exportName":"ProviderBuildMissingAuthMessageContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":488,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderBuiltInModelSuppressionContext = ProviderBuiltInModelSuppressionContext;","entrypoint":"core","exportName":"ProviderBuiltInModelSuppressionContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":504,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderBuiltInModelSuppressionResult = ProviderBuiltInModelSuppressionResult;","entrypoint":"core","exportName":"ProviderBuiltInModelSuppressionResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":513,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderCacheTtlEligibilityContext = ProviderCacheTtlEligibilityContext;","entrypoint":"core","exportName":"ProviderCacheTtlEligibilityContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":476,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderCatalogContext = ProviderCatalogContext;","entrypoint":"core","exportName":"ProviderCatalogContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":243,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderCatalogResult = ProviderCatalogResult;","entrypoint":"core","exportName":"ProviderCatalogResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":266,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderDefaultThinkingPolicyContext = ProviderDefaultThinkingPolicyContext;","entrypoint":"core","exportName":"ProviderDefaultThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":537,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderDiscoveryContext = ProviderCatalogContext;","entrypoint":"core","exportName":"ProviderDiscoveryContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":576,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderFetchUsageSnapshotContext = ProviderFetchUsageSnapshotContext;","entrypoint":"core","exportName":"ProviderFetchUsageSnapshotContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":416,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderModernModelPolicyContext = ProviderModernModelPolicyContext;","entrypoint":"core","exportName":"ProviderModernModelPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":547,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderNormalizeResolvedModelContext = ProviderNormalizeResolvedModelContext;","entrypoint":"core","exportName":"ProviderNormalizeResolvedModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":327,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderPreparedRuntimeAuth = ProviderPreparedRuntimeAuth;","entrypoint":"core","exportName":"ProviderPreparedRuntimeAuth","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":363,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderPrepareDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"core","exportName":"ProviderPrepareDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":318,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderPrepareExtraParamsContext = ProviderPrepareExtraParamsContext;","entrypoint":"core","exportName":"ProviderPrepareExtraParamsContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":449,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderPrepareRuntimeAuthContext = ProviderPrepareRuntimeAuthContext;","entrypoint":"core","exportName":"ProviderPrepareRuntimeAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":342,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderResolvedUsageAuth = ProviderResolvedUsageAuth;","entrypoint":"core","exportName":"ProviderResolvedUsageAuth","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":403,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderResolveDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"core","exportName":"ProviderResolveDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":301,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderResolveUsageAuthContext = ProviderResolveUsageAuthContext;","entrypoint":"core","exportName":"ProviderResolveUsageAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":384,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"core","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":284,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderThinkingPolicyContext = ProviderThinkingPolicyContext;","entrypoint":"core","exportName":"ProviderThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":525,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderUsageSnapshot = ProviderUsageSnapshot;","entrypoint":"core","exportName":"ProviderUsageSnapshot","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":7,"sourcePath":"src/infra/provider-usage.types.ts"}
|
||||
{"declaration":"export type ProviderWrapStreamFnContext = ProviderWrapStreamFnContext;","entrypoint":"core","exportName":"ProviderWrapStreamFnContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":466,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type RoutePeer = RoutePeer;","entrypoint":"core","exportName":"RoutePeer","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":21,"sourcePath":"src/routing/resolve-route.ts"}
|
||||
{"declaration":"export type RoutePeerKind = ChatType;","entrypoint":"core","exportName":"RoutePeerKind","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":19,"sourcePath":"src/routing/resolve-route.ts"}
|
||||
{"declaration":"export type SecretFileReadOptions = SecretFileReadOptions;","entrypoint":"core","exportName":"SecretFileReadOptions","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":7,"sourcePath":"src/infra/secret-file.ts"}
|
||||
{"declaration":"export type SecretFileReadResult = SecretFileReadResult;","entrypoint":"core","exportName":"SecretFileReadResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":12,"sourcePath":"src/infra/secret-file.ts"}
|
||||
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"core","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":917,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type TailscaleStatusCommandResult = TailscaleStatusCommandResult;","entrypoint":"core","exportName":"TailscaleStatusCommandResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/shared/tailscale-status.ts"}
|
||||
{"declaration":"export type TailscaleStatusCommandRunner = TailscaleStatusCommandRunner;","entrypoint":"core","exportName":"TailscaleStatusCommandRunner","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/shared/tailscale-status.ts"}
|
||||
{"declaration":"export type UsageProviderId = UsageProviderId;","entrypoint":"core","exportName":"UsageProviderId","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":20,"sourcePath":"src/infra/provider-usage.types.ts"}
|
||||
{"declaration":"export type UsageWindow = UsageWindow;","entrypoint":"core","exportName":"UsageWindow","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/infra/provider-usage.types.ts"}
|
||||
{"category":"core","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/plugin-entry.ts"}
|
||||
{"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"plugin-entry","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export","sourceLine":79,"sourcePath":"src/plugin-sdk/plugin-entry.ts"}
|
||||
{"declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export","sourceLine":13,"sourcePath":"src/plugins/config-schema.ts"}
|
||||
{"declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"plugin-entry","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/agents/tools/common.ts"}
|
||||
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"plugin-entry","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":935,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"plugin-entry","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-entry","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1292,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1050,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":80,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1275,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"plugin-entry","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1265,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"plugin-entry","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1258,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"plugin-entry","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":950,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"plugin-entry","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1079,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"plugin-entry","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":58,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":560,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":144,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthDoctorHintContext = ProviderAuthDoctorHintContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthDoctorHintContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":435,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthMethod = ProviderAuthMethod;","entrypoint":"plugin-entry","exportName":"ProviderAuthMethod","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":222,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthMethodNonInteractiveContext = ProviderAuthMethodNonInteractiveContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthMethodNonInteractiveContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":206,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"plugin-entry","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":130,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderBuildMissingAuthMessageContext = ProviderBuildMissingAuthMessageContext;","entrypoint":"plugin-entry","exportName":"ProviderBuildMissingAuthMessageContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":488,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderBuiltInModelSuppressionContext = ProviderBuiltInModelSuppressionContext;","entrypoint":"plugin-entry","exportName":"ProviderBuiltInModelSuppressionContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":504,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderBuiltInModelSuppressionResult = ProviderBuiltInModelSuppressionResult;","entrypoint":"plugin-entry","exportName":"ProviderBuiltInModelSuppressionResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":513,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderCacheTtlEligibilityContext = ProviderCacheTtlEligibilityContext;","entrypoint":"plugin-entry","exportName":"ProviderCacheTtlEligibilityContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":476,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderCatalogContext = ProviderCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderCatalogContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":243,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderCatalogResult = ProviderCatalogResult;","entrypoint":"plugin-entry","exportName":"ProviderCatalogResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":266,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderDefaultThinkingPolicyContext = ProviderDefaultThinkingPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderDefaultThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":537,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderDiscoveryContext = ProviderCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderDiscoveryContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":576,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderFetchUsageSnapshotContext = ProviderFetchUsageSnapshotContext;","entrypoint":"plugin-entry","exportName":"ProviderFetchUsageSnapshotContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":416,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderModernModelPolicyContext = ProviderModernModelPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderModernModelPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":547,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderNormalizeResolvedModelContext = ProviderNormalizeResolvedModelContext;","entrypoint":"plugin-entry","exportName":"ProviderNormalizeResolvedModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":327,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderPreparedRuntimeAuth = ProviderPreparedRuntimeAuth;","entrypoint":"plugin-entry","exportName":"ProviderPreparedRuntimeAuth","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":363,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderPrepareDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":318,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderPrepareExtraParamsContext = ProviderPrepareExtraParamsContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareExtraParamsContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":449,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderPrepareRuntimeAuthContext = ProviderPrepareRuntimeAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareRuntimeAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":342,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderResolvedUsageAuth = ProviderResolvedUsageAuth;","entrypoint":"plugin-entry","exportName":"ProviderResolvedUsageAuth","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":403,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderResolveDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"plugin-entry","exportName":"ProviderResolveDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":301,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderResolveUsageAuthContext = ProviderResolveUsageAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderResolveUsageAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":384,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"plugin-entry","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":284,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderThinkingPolicyContext = ProviderThinkingPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":525,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type ProviderWrapStreamFnContext = ProviderWrapStreamFnContext;","entrypoint":"plugin-entry","exportName":"ProviderWrapStreamFnContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":466,"sourcePath":"src/plugins/types.ts"}
|
||||
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"plugin-entry","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":917,"sourcePath":"src/plugins/types.ts"}
|
||||
{"category":"provider","entrypoint":"provider-onboard","importSpecifier":"openclaw/plugin-sdk/provider-onboard","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/provider-onboard.ts"}
|
||||
{"declaration":"export function applyAgentDefaultModelPrimary(cfg: OpenClawConfig, primary: string): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyAgentDefaultModelPrimary","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":76,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export function applyOnboardAuthAgentModelsAndProviders(cfg: OpenClawConfig, params: { agentModels: Record<string, AgentModelEntryConfig>; providers: Record<string, ModelProviderConfig>; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyOnboardAuthAgentModelsAndProviders","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":53,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export function applyProviderConfigWithDefaultModel(cfg: OpenClawConfig, params: { agentModels: Record<string, AgentModelEntryConfig>; providerId: string; api: \"github-copilot\" | \"openai-completions\" | ... 5 more ... | \"ollama\"; baseUrl: string; defaultModel: ModelDefinitionConfig; defaultModelId?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithDefaultModel","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":131,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export function applyProviderConfigWithDefaultModelPreset(cfg: OpenClawConfig, params: { providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | \"openai-codex-responses\" | \"anthropic-messages\" | \"google-generative-ai\" | \"bedrock-converse-stream\" | \"ollama\"; ... 4 more ...; primaryModelRef?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithDefaultModelPreset","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":152,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export function applyProviderConfigWithDefaultModels(cfg: OpenClawConfig, params: { agentModels: Record<string, AgentModelEntryConfig>; providerId: string; api: \"github-copilot\" | \"openai-completions\" | ... 5 more ... | \"ollama\"; baseUrl: string; defaultModels: ModelDefinitionConfig[]; defaultModelId?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithDefaultModels","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":96,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export function applyProviderConfigWithDefaultModelsPreset(cfg: OpenClawConfig, params: { providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | \"openai-codex-responses\" | \"anthropic-messages\" | \"google-generative-ai\" | \"bedrock-converse-stream\" | \"ollama\"; ... 4 more ...; primaryModelRef?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithDefaultModelsPreset","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":177,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export function applyProviderConfigWithModelCatalog(cfg: OpenClawConfig, params: { agentModels: Record<string, AgentModelEntryConfig>; providerId: string; api: \"github-copilot\" | \"openai-completions\" | ... 5 more ... | \"ollama\"; baseUrl: string; catalogModels: ModelDefinitionConfig[]; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithModelCatalog","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":202,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export function applyProviderConfigWithModelCatalogPreset(cfg: OpenClawConfig, params: { providerId: string; api: \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | \"openai-codex-responses\" | \"anthropic-messages\" | \"google-generative-ai\" | \"bedrock-converse-stream\" | \"ollama\"; baseUrl: string; catalogModels: ModelDefinitionConfig[]; aliases?: readonly AgentModelAliasEntry[] | undefined; primaryModelRef?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyProviderConfigWithModelCatalogPreset","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":234,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export function ensureModelAllowlistEntry(params: { cfg: OpenClawConfig; modelRef: string; defaultProvider?: string | undefined; }): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"ensureModelAllowlistEntry","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":5,"sourcePath":"src/plugins/provider-model-allowlist.ts"}
|
||||
{"declaration":"export function withAgentModelAliases(existing: Record<string, AgentModelEntryConfig> | undefined, aliases: readonly AgentModelAliasEntry[]): Record<string, AgentModelEntryConfig>;","entrypoint":"provider-onboard","exportName":"withAgentModelAliases","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":38,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export type AgentModelAliasEntry = AgentModelAliasEntry;","entrypoint":"provider-onboard","exportName":"AgentModelAliasEntry","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"type","recordType":"export","sourceLine":21,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
|
||||
{"declaration":"export type ModelApi = \"github-copilot\" | \"openai-completions\" | \"openai-responses\" | \"openai-codex-responses\" | \"anthropic-messages\" | \"google-generative-ai\" | \"bedrock-converse-stream\" | \"ollama\";","entrypoint":"provider-onboard","exportName":"ModelApi","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"type","recordType":"export","sourceLine":15,"sourcePath":"src/config/types.models.ts"}
|
||||
{"declaration":"export type ModelDefinitionConfig = ModelDefinitionConfig;","entrypoint":"provider-onboard","exportName":"ModelDefinitionConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"type","recordType":"export","sourceLine":46,"sourcePath":"src/config/types.models.ts"}
|
||||
{"declaration":"export type ModelProviderConfig = ModelProviderConfig;","entrypoint":"provider-onboard","exportName":"ModelProviderConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"type","recordType":"export","sourceLine":64,"sourcePath":"src/config/types.models.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"provider-onboard","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"category":"utilities","entrypoint":"reply-payload","importSpecifier":"openclaw/plugin-sdk/reply-payload","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function buildMediaPayload(mediaList: MediaPayloadInput[], opts?: { preserveMediaTypeCardinality?: boolean | undefined; } | undefined): MediaPayload;","entrypoint":"reply-payload","exportName":"buildMediaPayload","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":15,"sourcePath":"src/channels/plugins/media-payload.ts"}
|
||||
{"declaration":"export function countOutboundMedia(payload: { mediaUrls?: string[] | undefined; mediaUrl?: string | undefined; }): number;","entrypoint":"reply-payload","exportName":"countOutboundMedia","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":83,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function createNormalizedOutboundDeliverer(handler: (payload: OutboundReplyPayload) => Promise<void>): (payload: unknown) => Promise<void>;","entrypoint":"reply-payload","exportName":"createNormalizedOutboundDeliverer","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":51,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function deliverFormattedTextWithAttachments(params: { payload: OutboundReplyPayload; send: (params: { text: string; replyToId?: string | undefined; }) => Promise<void>; }): Promise<boolean>;","entrypoint":"reply-payload","exportName":"deliverFormattedTextWithAttachments","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":386,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function deliverTextOrMediaReply(params: { payload: OutboundReplyPayload; text: string; chunkText?: ((text: string) => readonly string[]) | undefined; sendText: (text: string) => Promise<void>; sendMedia: (payload: { ...; }) => Promise<...>; onMediaError?: ((params: { ...; }) => void | Promise<...>) | undefined; }): Promise<...>;","entrypoint":"reply-payload","exportName":"deliverTextOrMediaReply","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":345,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function formatTextWithAttachmentLinks(text: string | undefined, mediaUrls: string[]): string;","entrypoint":"reply-payload","exportName":"formatTextWithAttachmentLinks","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":286,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function hasOutboundMedia(payload: { mediaUrls?: string[] | undefined; mediaUrl?: string | undefined; }): boolean;","entrypoint":"reply-payload","exportName":"hasOutboundMedia","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":88,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function hasOutboundReplyContent(payload: { text?: string | undefined; mediaUrls?: string[] | undefined; mediaUrl?: string | undefined; }, options?: { trimText?: boolean | undefined; } | undefined): boolean;","entrypoint":"reply-payload","exportName":"hasOutboundReplyContent","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":99,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function hasOutboundText(payload: { text?: string | undefined; }, options?: { trim?: boolean | undefined; } | undefined): boolean;","entrypoint":"reply-payload","exportName":"hasOutboundText","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":93,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function isNumericTargetId(raw: string): boolean;","entrypoint":"reply-payload","exportName":"isNumericTargetId","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":277,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function normalizeOutboundReplyPayload(payload: Record<string, unknown>): OutboundReplyPayload;","entrypoint":"reply-payload","exportName":"normalizeOutboundReplyPayload","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":31,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function resolveOutboundMediaUrls(payload: { mediaUrls?: string[] | undefined; mediaUrl?: string | undefined; }): string[];","entrypoint":"reply-payload","exportName":"resolveOutboundMediaUrls","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":64,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function resolvePayloadMediaUrls(payload: ReplyPayload): string[];","entrypoint":"reply-payload","exportName":"resolvePayloadMediaUrls","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":78,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function resolveSendableOutboundReplyParts(payload: { text?: string | undefined; mediaUrls?: string[] | undefined; mediaUrl?: string | undefined; }, options?: { text?: string | undefined; } | undefined): SendableOutboundReplyParts;","entrypoint":"reply-payload","exportName":"resolveSendableOutboundReplyParts","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":107,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function resolveTextChunksWithFallback(text: string, chunks: readonly string[]): string[];","entrypoint":"reply-payload","exportName":"resolveTextChunksWithFallback","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":131,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function sendMediaWithLeadingCaption(params: { mediaUrls: string[]; caption: string; send: (payload: { mediaUrl: string; caption?: string | undefined; }) => Promise<void>; onError?: ((params: { error: unknown; mediaUrl: string; caption?: string | undefined; index: number; isFirst: boolean; }) => void | Promise<...>) | undefined; }): Promise<...>;","entrypoint":"reply-payload","exportName":"sendMediaWithLeadingCaption","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":307,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function sendPayloadMediaSequence<TResult>(params: { text: string; mediaUrls: readonly string[]; send: (input: { text: string; mediaUrl: string; index: number; isFirst: boolean; }) => Promise<TResult>; }): Promise<TResult | undefined>;","entrypoint":"reply-payload","exportName":"sendPayloadMediaSequence","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":183,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function sendPayloadMediaSequenceAndFinalize<TMediaResult, TResult>(params: { text: string; mediaUrls: readonly string[]; send: (input: { text: string; mediaUrl: string; index: number; isFirst: boolean; }) => Promise<TMediaResult>; finalize: () => Promise<TResult>; }): Promise<...>;","entrypoint":"reply-payload","exportName":"sendPayloadMediaSequenceAndFinalize","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":227,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function sendPayloadMediaSequenceOrFallback<TResult>(params: { text: string; mediaUrls: readonly string[]; send: (input: { text: string; mediaUrl: string; index: number; isFirst: boolean; }) => Promise<TResult>; fallbackResult: TResult; sendNoMedia?: (() => Promise<...>) | undefined; }): Promise<...>;","entrypoint":"reply-payload","exportName":"sendPayloadMediaSequenceOrFallback","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":209,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function sendPayloadWithChunkedTextAndMedia<TContext extends { payload: object; }, TResult>(params: { ctx: TContext; textChunkLimit?: number | undefined; chunker?: ((text: string, limit: number) => string[]) | null | undefined; sendText: (ctx: TContext & { ...; }) => Promise<...>; sendMedia: (ctx: TContext & { ...; }) => Promise<...>; emptyResult: TResult; }): Promise<...>;","entrypoint":"reply-payload","exportName":"sendPayloadWithChunkedTextAndMedia","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":142,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export function sendTextMediaPayload(params: { channel: string; ctx: ChannelOutboundPayloadContext; adapter: SendPayloadAdapter; }): Promise<OutboundDeliveryResult>;","entrypoint":"reply-payload","exportName":"sendTextMediaPayload","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"function","recordType":"export","sourceLine":244,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export type MediaPayload = MediaPayload;","entrypoint":"reply-payload","exportName":"MediaPayload","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/channels/plugins/media-payload.ts"}
|
||||
{"declaration":"export type MediaPayloadInput = MediaPayloadInput;","entrypoint":"reply-payload","exportName":"MediaPayloadInput","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/channels/plugins/media-payload.ts"}
|
||||
{"declaration":"export type OutboundReplyPayload = OutboundReplyPayload;","entrypoint":"reply-payload","exportName":"OutboundReplyPayload","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"declaration":"export type SendableOutboundReplyParts = SendableOutboundReplyParts;","entrypoint":"reply-payload","exportName":"SendableOutboundReplyParts","importSpecifier":"openclaw/plugin-sdk/reply-payload","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/plugin-sdk/reply-payload.ts"}
|
||||
{"category":"runtime","entrypoint":"runtime-store","importSpecifier":"openclaw/plugin-sdk/runtime-store","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/runtime-store.ts"}
|
||||
{"declaration":"export function createPluginRuntimeStore<T>(errorMessage: string): { setRuntime: (next: T) => void; clearRuntime: () => void; tryGetRuntime: () => T | null; getRuntime: () => T; };","entrypoint":"runtime-store","exportName":"createPluginRuntimeStore","importSpecifier":"openclaw/plugin-sdk/runtime-store","kind":"function","recordType":"export","sourceLine":4,"sourcePath":"src/plugin-sdk/runtime-store.ts"}
|
||||
{"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"runtime-store","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/runtime-store","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/plugins/runtime/types.ts"}
|
||||
{"category":"channel","entrypoint":"secret-input","importSpecifier":"openclaw/plugin-sdk/secret-input","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/secret-input.ts"}
|
||||
{"declaration":"export function buildOptionalSecretInputSchema(): ZodOptional<ZodUnion<readonly [ZodString, ZodDiscriminatedUnion<[ZodObject<{ source: ZodLiteral<\"env\">; provider: ZodString; id: ZodString; }, $strip>, ZodObject<...>, ZodObject<...>], \"source\">]>>;","entrypoint":"secret-input","exportName":"buildOptionalSecretInputSchema","importSpecifier":"openclaw/plugin-sdk/secret-input","kind":"function","recordType":"export","sourceLine":17,"sourcePath":"src/plugin-sdk/secret-input.ts"}
|
||||
{"declaration":"export function buildSecretInputArraySchema(): ZodArray<ZodUnion<readonly [ZodString, ZodDiscriminatedUnion<[ZodObject<{ source: ZodLiteral<\"env\">; provider: ZodString; id: ZodString; }, $strip>, ZodObject<...>, ZodObject<...>], \"source\">]>>;","entrypoint":"secret-input","exportName":"buildSecretInputArraySchema","importSpecifier":"openclaw/plugin-sdk/secret-input","kind":"function","recordType":"export","sourceLine":21,"sourcePath":"src/plugin-sdk/secret-input.ts"}
|
||||
{"declaration":"export function buildSecretInputSchema(): ZodUnion<readonly [ZodString, ZodDiscriminatedUnion<[ZodObject<{ source: ZodLiteral<\"env\">; provider: ZodString; id: ZodString; }, $strip>, ZodObject<...>, ZodObject<...>], \"source\">]>;","entrypoint":"secret-input","exportName":"buildSecretInputSchema","importSpecifier":"openclaw/plugin-sdk/secret-input","kind":"function","recordType":"export","sourceLine":11,"sourcePath":"src/plugin-sdk/secret-input-schema.ts"}
|
||||
{"declaration":"export function hasConfiguredSecretInput(value: unknown, defaults?: SecretDefaults | undefined): boolean;","entrypoint":"secret-input","exportName":"hasConfiguredSecretInput","importSpecifier":"openclaw/plugin-sdk/secret-input","kind":"function","recordType":"export","sourceLine":106,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"declaration":"export function normalizeResolvedSecretInputString(params: { value: unknown; refValue?: unknown; defaults?: SecretDefaults | undefined; path: string; }): string | undefined;","entrypoint":"secret-input","exportName":"normalizeResolvedSecretInputString","importSpecifier":"openclaw/plugin-sdk/secret-input","kind":"function","recordType":"export","sourceLine":144,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"declaration":"export function normalizeSecretInputString(value: unknown): string | undefined;","entrypoint":"secret-input","exportName":"normalizeSecretInputString","importSpecifier":"openclaw/plugin-sdk/secret-input","kind":"function","recordType":"export","sourceLine":113,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"declaration":"export type SecretInput = SecretInput;","entrypoint":"secret-input","exportName":"SecretInput","importSpecifier":"openclaw/plugin-sdk/secret-input","kind":"type","recordType":"export","sourceLine":16,"sourcePath":"src/config/types.secrets.ts"}
|
||||
{"category":"utilities","entrypoint":"testing","importSpecifier":"openclaw/plugin-sdk/testing","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/testing.ts"}
|
||||
{"declaration":"export function createWindowsCmdShimFixture(params: { shimPath: string; scriptPath: string; shimLine: string; }): Promise<void>;","entrypoint":"testing","exportName":"createWindowsCmdShimFixture","importSpecifier":"openclaw/plugin-sdk/testing","kind":"function","recordType":"export","sourceLine":15,"sourcePath":"src/plugin-sdk/testing.ts"}
|
||||
{"declaration":"export function installCommonResolveTargetErrorCases(params: { resolveTarget: ResolveTargetFn; implicitAllowFrom: string[]; }): void;","entrypoint":"testing","exportName":"installCommonResolveTargetErrorCases","importSpecifier":"openclaw/plugin-sdk/testing","kind":"function","recordType":"export","sourceLine":40,"sourcePath":"src/plugin-sdk/testing.ts"}
|
||||
{"declaration":"export function removeAckReactionAfterReply(params: { removeAfterReply: boolean; ackReactionPromise: Promise<boolean> | null; ackReactionValue: string | null; remove: () => Promise<void>; onError?: ((err: unknown) => void) | undefined; }): void;","entrypoint":"testing","exportName":"removeAckReactionAfterReply","importSpecifier":"openclaw/plugin-sdk/testing","kind":"function","recordType":"export","sourceLine":81,"sourcePath":"src/channels/ack-reactions.ts"}
|
||||
{"declaration":"export function shouldAckReaction(params: AckReactionGateParams): boolean;","entrypoint":"testing","exportName":"shouldAckReaction","importSpecifier":"openclaw/plugin-sdk/testing","kind":"function","recordType":"export","sourceLine":16,"sourcePath":"src/channels/ack-reactions.ts"}
|
||||
{"declaration":"export type ChannelAccountSnapshot = ChannelAccountSnapshot;","entrypoint":"testing","exportName":"ChannelAccountSnapshot","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":139,"sourcePath":"src/channels/plugins/types.core.ts"}
|
||||
{"declaration":"export type ChannelGatewayContext = ChannelGatewayContext<ResolvedAccount>;","entrypoint":"testing","exportName":"ChannelGatewayContext","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":238,"sourcePath":"src/channels/plugins/types.adapters.ts"}
|
||||
{"declaration":"export type MockFn = MockFn<T>;","entrypoint":"testing","exportName":"MockFn","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":5,"sourcePath":"src/test-utils/vitest-mock-fn.ts"}
|
||||
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"testing","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
|
||||
{"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"testing","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":53,"sourcePath":"src/plugins/runtime/types.ts"}
|
||||
{"declaration":"export type RuntimeEnv = RuntimeEnv;","entrypoint":"testing","exportName":"RuntimeEnv","importSpecifier":"openclaw/plugin-sdk/testing","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/runtime.ts"}
|
||||
{"category":"channel","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/webhook-ingress.ts"}
|
||||
{"declaration":"export function applyBasicWebhookRequestGuards(params: { req: IncomingMessage; res: ServerResponse<IncomingMessage>; allowMethods?: readonly string[] | undefined; rateLimiter?: FixedWindowRateLimiter | undefined; rateLimitKey?: string | undefined; nowMs?: number | undefined; requireJsonContentType?: boolean | undefined; }): boolean;","entrypoint":"webhook-ingress","exportName":"applyBasicWebhookRequestGuards","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":148,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export function beginWebhookRequestPipelineOrReject(params: { req: IncomingMessage; res: ServerResponse<IncomingMessage>; allowMethods?: readonly string[] | undefined; rateLimiter?: FixedWindowRateLimiter | undefined; ... 6 more ...; inFlightLimitMessage?: string | undefined; }): { ...; } | { ...; };","entrypoint":"webhook-ingress","exportName":"beginWebhookRequestPipelineOrReject","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":189,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export function createBoundedCounter(options: { maxTrackedKeys: number; ttlMs?: number | undefined; pruneIntervalMs?: number | undefined; }): BoundedCounter;","entrypoint":"webhook-ingress","exportName":"createBoundedCounter","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":109,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export function createFixedWindowRateLimiter(options: { windowMs: number; maxRequests: number; maxTrackedKeys: number; pruneIntervalMs?: number | undefined; }): FixedWindowRateLimiter;","entrypoint":"webhook-ingress","exportName":"createFixedWindowRateLimiter","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":52,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export function createWebhookAnomalyTracker(options?: { maxTrackedKeys?: number | undefined; ttlMs?: number | undefined; logEvery?: number | undefined; trackedStatusCodes?: readonly number[] | undefined; } | undefined): WebhookAnomalyTracker;","entrypoint":"webhook-ingress","exportName":"createWebhookAnomalyTracker","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":167,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export function createWebhookInFlightLimiter(options?: { maxInFlightPerKey?: number | undefined; maxTrackedKeys?: number | undefined; } | undefined): WebhookInFlightLimiter;","entrypoint":"webhook-ingress","exportName":"createWebhookInFlightLimiter","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":91,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export function isJsonContentType(value: string | string[] | undefined): boolean;","entrypoint":"webhook-ingress","exportName":"isJsonContentType","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":138,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export function isRequestBodyLimitError(error: unknown, code?: RequestBodyLimitErrorCode | undefined): error is RequestBodyLimitError;","entrypoint":"webhook-ingress","exportName":"isRequestBodyLimitError","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":46,"sourcePath":"src/infra/http-body.ts"}
|
||||
{"declaration":"export function normalizeWebhookPath(raw: string): string;","entrypoint":"webhook-ingress","exportName":"normalizeWebhookPath","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":2,"sourcePath":"src/plugin-sdk/webhook-path.ts"}
|
||||
{"declaration":"export function readJsonWebhookBodyOrReject(params: { req: IncomingMessage; res: ServerResponse<IncomingMessage>; maxBytes?: number | undefined; timeoutMs?: number | undefined; profile?: WebhookBodyReadProfile | undefined; emptyObjectOnEmpty?: boolean | undefined; invalidJsonMessage?: string | undefined; }): Promise<...>;","entrypoint":"webhook-ingress","exportName":"readJsonWebhookBodyOrReject","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":275,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export function readRequestBodyWithLimit(req: IncomingMessage, options: ReadRequestBodyOptions): Promise<string>;","entrypoint":"webhook-ingress","exportName":"readRequestBodyWithLimit","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":121,"sourcePath":"src/infra/http-body.ts"}
|
||||
{"declaration":"export function readWebhookBodyOrReject(params: { req: IncomingMessage; res: ServerResponse<IncomingMessage>; maxBytes?: number | undefined; timeoutMs?: number | undefined; profile?: WebhookBodyReadProfile | undefined; invalidBodyMessage?: string | undefined; }): Promise<...>;","entrypoint":"webhook-ingress","exportName":"readWebhookBodyOrReject","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":240,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export function registerPluginHttpRoute(params: { path?: string | null | undefined; fallbackPath?: string | null | undefined; handler: PluginHttpRouteHandler; auth: OpenClawPluginHttpRouteAuth; ... 6 more ...; registry?: PluginRegistry | undefined; }): () => void;","entrypoint":"webhook-ingress","exportName":"registerPluginHttpRoute","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":12,"sourcePath":"src/plugins/http-registry.ts"}
|
||||
{"declaration":"export function registerWebhookTarget<T extends { path: string; }>(targetsByPath: Map<string, T[]>, target: T, opts?: RegisterWebhookTargetOptions<T> | undefined): RegisteredWebhookTarget<T>;","entrypoint":"webhook-ingress","exportName":"registerWebhookTarget","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":61,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export function registerWebhookTargetWithPluginRoute<T extends { path: string; }>(params: { targetsByPath: Map<string, T[]>; target: T; route: RegisterWebhookPluginRouteOptions; onLastPathTargetRemoved?: ((params: { ...; }) => void) | undefined; }): RegisteredWebhookTarget<...>;","entrypoint":"webhook-ingress","exportName":"registerWebhookTargetWithPluginRoute","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":30,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export function requestBodyErrorToText(code: RequestBodyLimitErrorCode): string;","entrypoint":"webhook-ingress","exportName":"requestBodyErrorToText","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":59,"sourcePath":"src/infra/http-body.ts"}
|
||||
{"declaration":"export function resolveSingleWebhookTarget<T>(targets: readonly T[], isMatch: (target: T) => boolean): WebhookTargetMatchResult<T>;","entrypoint":"webhook-ingress","exportName":"resolveSingleWebhookTarget","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":193,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export function resolveSingleWebhookTargetAsync<T>(targets: readonly T[], isMatch: (target: T) => Promise<boolean>): Promise<WebhookTargetMatchResult<T>>;","entrypoint":"webhook-ingress","exportName":"resolveSingleWebhookTargetAsync","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":212,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export function resolveWebhookPath(params: { webhookPath?: string | undefined; webhookUrl?: string | undefined; defaultPath?: string | null | undefined; }): string | null;","entrypoint":"webhook-ingress","exportName":"resolveWebhookPath","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":15,"sourcePath":"src/plugin-sdk/webhook-path.ts"}
|
||||
{"declaration":"export function resolveWebhookTargets<T>(req: IncomingMessage, targetsByPath: Map<string, T[]>): { path: string; targets: T[]; } | null;","entrypoint":"webhook-ingress","exportName":"resolveWebhookTargets","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":107,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export function resolveWebhookTargetWithAuthOrReject<T>(params: { targets: readonly T[]; res: ServerResponse<IncomingMessage>; isMatch: (target: T) => boolean | Promise<boolean>; unauthorizedStatusCode?: number | undefined; unauthorizedMessage?: string | undefined; ambiguousStatusCode?: number | undefined; ambiguousMessage?: string | undefined; }): Promise<...>;","entrypoint":"webhook-ingress","exportName":"resolveWebhookTargetWithAuthOrReject","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":231,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export function resolveWebhookTargetWithAuthOrRejectSync<T>(params: { targets: readonly T[]; res: ServerResponse<IncomingMessage>; isMatch: (target: T) => boolean; unauthorizedStatusCode?: number | undefined; unauthorizedMessage?: string | undefined; ambiguousStatusCode?: number | undefined; ambiguousMessage?: string | undefined; }): T | null;","entrypoint":"webhook-ingress","exportName":"resolveWebhookTargetWithAuthOrRejectSync","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":247,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export function withResolvedWebhookRequestPipeline<T>(params: { req: IncomingMessage; res: ServerResponse<IncomingMessage>; targetsByPath: Map<string, T[]>; allowMethods?: readonly string[] | undefined; ... 8 more ...; handle: (args: { ...; }) => boolean | ... 1 more ... | Promise<...>; }): Promise<...>;","entrypoint":"webhook-ingress","exportName":"withResolvedWebhookRequestPipeline","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"function","recordType":"export","sourceLine":121,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export const WEBHOOK_ANOMALY_COUNTER_DEFAULTS: Readonly<{ maxTrackedKeys: 4096; ttlMs: number; logEvery: 25; }>;","entrypoint":"webhook-ingress","exportName":"WEBHOOK_ANOMALY_COUNTER_DEFAULTS","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"const","recordType":"export","sourceLine":31,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export const WEBHOOK_ANOMALY_STATUS_CODES: readonly number[];","entrypoint":"webhook-ingress","exportName":"WEBHOOK_ANOMALY_STATUS_CODES","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"const","recordType":"export","sourceLine":37,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export const WEBHOOK_BODY_READ_DEFAULTS: Readonly<{ preAuth: { maxBytes: number; timeoutMs: number; }; postAuth: { maxBytes: number; timeoutMs: number; }; }>;","entrypoint":"webhook-ingress","exportName":"WEBHOOK_BODY_READ_DEFAULTS","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"const","recordType":"export","sourceLine":19,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export const WEBHOOK_IN_FLIGHT_DEFAULTS: Readonly<{ maxInFlightPerKey: 8; maxTrackedKeys: 4096; }>;","entrypoint":"webhook-ingress","exportName":"WEBHOOK_IN_FLIGHT_DEFAULTS","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"const","recordType":"export","sourceLine":30,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export const WEBHOOK_RATE_LIMIT_DEFAULTS: Readonly<{ windowMs: 60000; maxRequests: 120; maxTrackedKeys: 4096; }>;","entrypoint":"webhook-ingress","exportName":"WEBHOOK_RATE_LIMIT_DEFAULTS","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"const","recordType":"export","sourceLine":25,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export type BoundedCounter = BoundedCounter;","entrypoint":"webhook-ingress","exportName":"BoundedCounter","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":19,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export type FixedWindowRateLimiter = FixedWindowRateLimiter;","entrypoint":"webhook-ingress","exportName":"FixedWindowRateLimiter","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export type RegisteredWebhookTarget = RegisteredWebhookTarget<T>;","entrypoint":"webhook-ingress","exportName":"RegisteredWebhookTarget","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":10,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export type RegisterWebhookPluginRouteOptions = RegisterWebhookPluginRouteOptions;","entrypoint":"webhook-ingress","exportName":"RegisterWebhookPluginRouteOptions","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":24,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export type RegisterWebhookTargetOptions = RegisterWebhookTargetOptions<T>;","entrypoint":"webhook-ingress","exportName":"RegisterWebhookTargetOptions","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":15,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
{"declaration":"export type WebhookAnomalyTracker = WebhookAnomalyTracker;","entrypoint":"webhook-ingress","exportName":"WebhookAnomalyTracker","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":39,"sourcePath":"src/plugin-sdk/webhook-memory-guards.ts"}
|
||||
{"declaration":"export type WebhookBodyReadProfile = WebhookBodyReadProfile;","entrypoint":"webhook-ingress","exportName":"WebhookBodyReadProfile","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":11,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export type WebhookInFlightLimiter = WebhookInFlightLimiter;","entrypoint":"webhook-ingress","exportName":"WebhookInFlightLimiter","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":35,"sourcePath":"src/plugin-sdk/webhook-request-guards.ts"}
|
||||
{"declaration":"export type WebhookTargetMatchResult = WebhookTargetMatchResult<T>;","entrypoint":"webhook-ingress","exportName":"WebhookTargetMatchResult","importSpecifier":"openclaw/plugin-sdk/webhook-ingress","kind":"type","recordType":"export","sourceLine":170,"sourcePath":"src/plugin-sdk/webhook-targets.ts"}
|
||||
@@ -303,6 +303,20 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
},
|
||||
},
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
default: true,
|
||||
thinkingDefault: "high", // per-agent thinking override
|
||||
reasoningDefault: "on", // per-agent reasoning visibility
|
||||
fastModeDefault: false, // per-agent fast mode
|
||||
},
|
||||
{
|
||||
id: "quick",
|
||||
fastModeDefault: true, // this agent always runs fast
|
||||
thinkingDefault: "off",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
tools: {
|
||||
|
||||
@@ -1372,6 +1372,9 @@ scripts/sandbox-browser-setup.sh # optional browser image
|
||||
workspace: "~/.openclaw/workspace",
|
||||
agentDir: "~/.openclaw/agents/main/agent",
|
||||
model: "anthropic/claude-opus-4-6", // or { primary, fallbacks }
|
||||
thinkingDefault: "high", // per-agent thinking level override
|
||||
reasoningDefault: "on", // per-agent reasoning visibility override
|
||||
fastModeDefault: false, // per-agent fast mode override
|
||||
params: { cacheRetention: "none" }, // overrides matching defaults.models params by key
|
||||
identity: {
|
||||
name: "Samantha",
|
||||
@@ -1407,6 +1410,9 @@ scripts/sandbox-browser-setup.sh # optional browser image
|
||||
- `default`: when multiple are set, first wins (warning logged). If none set, first list entry is default.
|
||||
- `model`: string form overrides `primary` only; object form `{ primary, fallbacks }` overrides both (`[]` disables global fallbacks). Cron jobs that only override `primary` still inherit default fallbacks unless you set `fallbacks: []`.
|
||||
- `params`: per-agent stream params merged over the selected model entry in `agents.defaults.models`. Use this for agent-specific overrides like `cacheRetention`, `temperature`, or `maxTokens` without duplicating the whole model catalog.
|
||||
- `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set.
|
||||
- `reasoningDefault`: optional per-agent default reasoning visibility (`on | off | stream`). Applies when no per-message or session reasoning override is set.
|
||||
- `fastModeDefault`: optional per-agent default for fast mode (`true | false`). Applies when no per-message or session fast-mode override is set.
|
||||
- `runtime`: optional per-agent runtime descriptor. Use `type: "acp"` with `runtime.acp` defaults (`agent`, `backend`, `mode`, `cwd`) when the agent should default to ACP harness sessions.
|
||||
- `identity.avatar`: workspace-relative path, `http(s)` URL, or `data:` URI.
|
||||
- `identity` derives defaults: `ackReaction` from `emoji`, `mentionPatterns` from `name`/`emoji`.
|
||||
|
||||
@@ -28,8 +28,9 @@ title: "Thinking Levels"
|
||||
|
||||
1. Inline directive on the message (applies only to that message).
|
||||
2. Session override (set by sending a directive-only message).
|
||||
3. Global default (`agents.defaults.thinkingDefault` in config).
|
||||
4. Fallback: `adaptive` for Anthropic Claude 4.6 models, `low` for other reasoning-capable models, `off` otherwise.
|
||||
3. Per-agent default (`agents.list[].thinkingDefault` in config).
|
||||
4. Global default (`agents.defaults.thinkingDefault` in config).
|
||||
5. Fallback: `adaptive` for Anthropic Claude 4.6 models, `low` for other reasoning-capable models, `off` otherwise.
|
||||
|
||||
## Setting a session default
|
||||
|
||||
@@ -50,8 +51,9 @@ title: "Thinking Levels"
|
||||
- OpenClaw resolves fast mode in this order:
|
||||
1. Inline/directive-only `/fast on|off`
|
||||
2. Session override
|
||||
3. Per-model config: `agents.defaults.models["<provider>/<model>"].params.fastMode`
|
||||
4. Fallback: `off`
|
||||
3. Per-agent default (`agents.list[].fastModeDefault`)
|
||||
4. Per-model config: `agents.defaults.models["<provider>/<model>"].params.fastMode`
|
||||
5. Fallback: `off`
|
||||
- For `openai/*`, fast mode applies the OpenAI fast profile: `service_tier=priority` when supported, plus low reasoning effort and low text verbosity.
|
||||
- For `openai-codex/*`, fast mode applies the same low-latency profile on Codex Responses. OpenClaw keeps one shared `/fast` toggle across both auth paths.
|
||||
- For direct `anthropic/*` API-key requests, fast mode maps to Anthropic service tiers: `/fast on` sets `service_tier=auto`, `/fast off` sets `service_tier=standard_only`.
|
||||
@@ -76,6 +78,7 @@ title: "Thinking Levels"
|
||||
- `stream` (Telegram only): streams reasoning into the Telegram draft bubble while the reply is generating, then sends the final answer without reasoning.
|
||||
- Alias: `/reason`.
|
||||
- Send `/reasoning` (or `/reasoning:`) with no argument to see the current reasoning level.
|
||||
- Resolution order: inline directive, then session override, then per-agent default (`agents.list[].reasoningDefault`), then fallback (`off`).
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -390,9 +390,9 @@ Notes:
|
||||
|
||||
## Agent tool
|
||||
|
||||
The `tts` tool converts text to speech and returns a `MEDIA:` path. When the
|
||||
result is Telegram-compatible, the tool includes `[[audio_as_voice]]` so
|
||||
Telegram sends a voice bubble.
|
||||
The `tts` tool converts text to speech and returns an audio attachment for
|
||||
reply delivery. When the result is Telegram-compatible, OpenClaw marks it for
|
||||
voice-bubble delivery.
|
||||
|
||||
## Gateway RPC
|
||||
|
||||
|
||||
@@ -390,9 +390,9 @@ Notes:
|
||||
|
||||
## Agent tool
|
||||
|
||||
The `tts` tool converts text to speech and returns a `MEDIA:` path. When the
|
||||
result is Telegram-compatible, the tool includes `[[audio_as_voice]]` so
|
||||
Telegram sends a voice bubble.
|
||||
The `tts` tool converts text to speech and returns an audio attachment for
|
||||
reply delivery. When the result is Telegram-compatible, OpenClaw marks it for
|
||||
voice-bubble delivery.
|
||||
|
||||
## Gateway RPC
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "OpenClaw ACP runtime backend via acpx",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"acpx": "0.3.0"
|
||||
"acpx": "0.3.1"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,32 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createPluginRuntimeMock } from "../../../test/helpers/extensions/plugin-runtime-mock.js";
|
||||
import {
|
||||
createBlueBubblesMonitorTestRuntime,
|
||||
EMPTY_DISPATCH_RESULT,
|
||||
resetBlueBubblesMonitorTestState,
|
||||
type DispatchReplyParams,
|
||||
} from "../../../test/helpers/extensions/bluebubbles-monitor.js";
|
||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||
import { fetchBlueBubblesHistory } from "./history.js";
|
||||
import { handleBlueBubblesWebhookRequest, resolveBlueBubblesMessageId } from "./monitor.js";
|
||||
import {
|
||||
handleBlueBubblesWebhookRequest,
|
||||
registerBlueBubblesWebhookTarget,
|
||||
resolveBlueBubblesMessageId,
|
||||
_resetBlueBubblesShortIdState,
|
||||
} from "./monitor.js";
|
||||
LOOPBACK_REMOTE_ADDRESSES_FOR_TEST,
|
||||
createWebhookDispatchForTest,
|
||||
createMockAccount,
|
||||
createHangingWebhookRequestForTest,
|
||||
createLoopbackWebhookRequestParamsForTest,
|
||||
createPasswordQueryRequestParamsForTest,
|
||||
createProtectedWebhookAccountForTest,
|
||||
createRemoteWebhookRequestParamsForTest,
|
||||
createTimestampedNewMessagePayloadForTest,
|
||||
dispatchWebhookPayloadForTest,
|
||||
expectWebhookRequestStatusForTest,
|
||||
expectWebhookStatusForTest,
|
||||
setupWebhookTargetForTest,
|
||||
setupWebhookTargetsForTest,
|
||||
trackWebhookRegistrationForTest,
|
||||
type WebhookRequestParams,
|
||||
} from "./monitor.webhook.test-helpers.js";
|
||||
import type { OpenClawConfig, PluginRuntime } from "./runtime-api.js";
|
||||
import { setBlueBubblesRuntime } from "./runtime.js";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("./send.js", () => ({
|
||||
@@ -70,13 +85,6 @@ const mockMatchesMentionWithExplicit = vi.fn(
|
||||
);
|
||||
const mockResolveRequireMention = vi.fn(() => false);
|
||||
const mockResolveGroupPolicy = vi.fn(() => "open" as const);
|
||||
type DispatchReplyParams = Parameters<
|
||||
PluginRuntime["channel"]["reply"]["dispatchReplyWithBufferedBlockDispatcher"]
|
||||
>[0];
|
||||
const EMPTY_DISPATCH_RESULT = {
|
||||
queuedFinal: false,
|
||||
counts: { tool: 0, block: 0, final: 0 },
|
||||
} as const;
|
||||
const mockDispatchReplyWithBufferedBlockDispatcher = vi.fn(
|
||||
async (_params: DispatchReplyParams) => EMPTY_DISPATCH_RESULT,
|
||||
);
|
||||
@@ -99,162 +107,51 @@ const mockChunkTextWithMode = vi.fn((text: string) => (text ? [text] : []));
|
||||
const mockChunkMarkdownTextWithMode = vi.fn((text: string) => (text ? [text] : []));
|
||||
const mockResolveChunkMode = vi.fn(() => "length" as const);
|
||||
const mockFetchBlueBubblesHistory = vi.mocked(fetchBlueBubblesHistory);
|
||||
const TEST_WEBHOOK_PASSWORD = "secret-token";
|
||||
|
||||
function createMockRuntime(): PluginRuntime {
|
||||
return createPluginRuntimeMock({
|
||||
system: {
|
||||
enqueueSystemEvent: mockEnqueueSystemEvent,
|
||||
},
|
||||
channel: {
|
||||
text: {
|
||||
chunkMarkdownText: mockChunkMarkdownText,
|
||||
chunkByNewline: mockChunkByNewline,
|
||||
chunkMarkdownTextWithMode: mockChunkMarkdownTextWithMode,
|
||||
chunkTextWithMode: mockChunkTextWithMode,
|
||||
resolveChunkMode:
|
||||
mockResolveChunkMode as unknown as PluginRuntime["channel"]["text"]["resolveChunkMode"],
|
||||
hasControlCommand: mockHasControlCommand,
|
||||
},
|
||||
reply: {
|
||||
dispatchReplyWithBufferedBlockDispatcher:
|
||||
mockDispatchReplyWithBufferedBlockDispatcher as unknown as PluginRuntime["channel"]["reply"]["dispatchReplyWithBufferedBlockDispatcher"],
|
||||
formatAgentEnvelope: mockFormatAgentEnvelope,
|
||||
formatInboundEnvelope: mockFormatInboundEnvelope,
|
||||
resolveEnvelopeFormatOptions:
|
||||
mockResolveEnvelopeFormatOptions as unknown as PluginRuntime["channel"]["reply"]["resolveEnvelopeFormatOptions"],
|
||||
},
|
||||
routing: {
|
||||
resolveAgentRoute:
|
||||
mockResolveAgentRoute as unknown as PluginRuntime["channel"]["routing"]["resolveAgentRoute"],
|
||||
},
|
||||
pairing: {
|
||||
buildPairingReply: mockBuildPairingReply,
|
||||
readAllowFromStore: mockReadAllowFromStore,
|
||||
upsertPairingRequest: mockUpsertPairingRequest,
|
||||
},
|
||||
media: {
|
||||
saveMediaBuffer:
|
||||
mockSaveMediaBuffer as unknown as PluginRuntime["channel"]["media"]["saveMediaBuffer"],
|
||||
},
|
||||
session: {
|
||||
resolveStorePath: mockResolveStorePath,
|
||||
readSessionUpdatedAt: mockReadSessionUpdatedAt,
|
||||
},
|
||||
mentions: {
|
||||
buildMentionRegexes: mockBuildMentionRegexes,
|
||||
matchesMentionPatterns: mockMatchesMentionPatterns,
|
||||
matchesMentionWithExplicit: mockMatchesMentionWithExplicit,
|
||||
},
|
||||
groups: {
|
||||
resolveGroupPolicy:
|
||||
mockResolveGroupPolicy as unknown as PluginRuntime["channel"]["groups"]["resolveGroupPolicy"],
|
||||
resolveRequireMention: mockResolveRequireMention,
|
||||
},
|
||||
commands: {
|
||||
resolveCommandAuthorizedFromAuthorizers: mockResolveCommandAuthorizedFromAuthorizers,
|
||||
},
|
||||
},
|
||||
return createBlueBubblesMonitorTestRuntime({
|
||||
enqueueSystemEvent: mockEnqueueSystemEvent,
|
||||
chunkMarkdownText: mockChunkMarkdownText,
|
||||
chunkByNewline: mockChunkByNewline,
|
||||
chunkMarkdownTextWithMode: mockChunkMarkdownTextWithMode,
|
||||
chunkTextWithMode: mockChunkTextWithMode,
|
||||
resolveChunkMode: mockResolveChunkMode,
|
||||
hasControlCommand: mockHasControlCommand,
|
||||
dispatchReplyWithBufferedBlockDispatcher: mockDispatchReplyWithBufferedBlockDispatcher,
|
||||
formatAgentEnvelope: mockFormatAgentEnvelope,
|
||||
formatInboundEnvelope: mockFormatInboundEnvelope,
|
||||
resolveEnvelopeFormatOptions: mockResolveEnvelopeFormatOptions,
|
||||
resolveAgentRoute: mockResolveAgentRoute,
|
||||
buildPairingReply: mockBuildPairingReply,
|
||||
readAllowFromStore: mockReadAllowFromStore,
|
||||
upsertPairingRequest: mockUpsertPairingRequest,
|
||||
saveMediaBuffer: mockSaveMediaBuffer,
|
||||
resolveStorePath: mockResolveStorePath,
|
||||
readSessionUpdatedAt: mockReadSessionUpdatedAt,
|
||||
buildMentionRegexes: mockBuildMentionRegexes,
|
||||
matchesMentionPatterns: mockMatchesMentionPatterns,
|
||||
matchesMentionWithExplicit: mockMatchesMentionWithExplicit,
|
||||
resolveGroupPolicy: mockResolveGroupPolicy,
|
||||
resolveRequireMention: mockResolveRequireMention,
|
||||
resolveCommandAuthorizedFromAuthorizers: mockResolveCommandAuthorizedFromAuthorizers,
|
||||
});
|
||||
}
|
||||
|
||||
function createMockAccount(
|
||||
overrides: Partial<ResolvedBlueBubblesAccount["config"]> = {},
|
||||
): ResolvedBlueBubblesAccount {
|
||||
return {
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
config: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test-password", // pragma: allowlist secret
|
||||
dmPolicy: "open",
|
||||
groupPolicy: "open",
|
||||
allowFrom: [],
|
||||
groupAllowFrom: [],
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createMockRequest(
|
||||
method: string,
|
||||
url: string,
|
||||
body: unknown,
|
||||
headers: Record<string, string> = {},
|
||||
): IncomingMessage {
|
||||
if (headers.host === undefined) {
|
||||
headers.host = "localhost";
|
||||
}
|
||||
const parsedUrl = new URL(url, "http://localhost");
|
||||
const hasAuthQuery = parsedUrl.searchParams.has("guid") || parsedUrl.searchParams.has("password");
|
||||
const hasAuthHeader =
|
||||
headers["x-guid"] !== undefined ||
|
||||
headers["x-password"] !== undefined ||
|
||||
headers["x-bluebubbles-guid"] !== undefined ||
|
||||
headers.authorization !== undefined;
|
||||
if (!hasAuthQuery && !hasAuthHeader) {
|
||||
parsedUrl.searchParams.set("password", "test-password");
|
||||
}
|
||||
|
||||
const req = new EventEmitter() as IncomingMessage;
|
||||
req.method = method;
|
||||
req.url = `${parsedUrl.pathname}${parsedUrl.search}`;
|
||||
req.headers = headers;
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = { remoteAddress: "127.0.0.1" };
|
||||
|
||||
// Emit body data after a microtask
|
||||
// oxlint-disable-next-line no-floating-promises
|
||||
Promise.resolve().then(() => {
|
||||
const bodyStr = typeof body === "string" ? body : JSON.stringify(body);
|
||||
req.emit("data", Buffer.from(bodyStr));
|
||||
req.emit("end");
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
function createMockResponse(): ServerResponse & { body: string; statusCode: number } {
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
body: "",
|
||||
setHeader: vi.fn(),
|
||||
end: vi.fn((data?: string) => {
|
||||
res.body = data ?? "";
|
||||
}),
|
||||
} as unknown as ServerResponse & { body: string; statusCode: number };
|
||||
return res;
|
||||
}
|
||||
|
||||
const flushAsync = async () => {
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
}
|
||||
};
|
||||
|
||||
function getFirstDispatchCall(): DispatchReplyParams {
|
||||
const callArgs = mockDispatchReplyWithBufferedBlockDispatcher.mock.calls[0]?.[0];
|
||||
if (!callArgs) {
|
||||
throw new Error("expected dispatch call arguments");
|
||||
}
|
||||
return callArgs;
|
||||
}
|
||||
|
||||
describe("BlueBubbles webhook monitor", () => {
|
||||
let unregister: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Reset short ID state between tests for predictable behavior
|
||||
_resetBlueBubblesShortIdState();
|
||||
mockFetchBlueBubblesHistory.mockResolvedValue({ entries: [], resolved: true });
|
||||
mockReadAllowFromStore.mockResolvedValue([]);
|
||||
mockUpsertPairingRequest.mockResolvedValue({ code: "TESTCODE", created: true });
|
||||
mockResolveRequireMention.mockReturnValue(false);
|
||||
mockHasControlCommand.mockReturnValue(false);
|
||||
mockResolveCommandAuthorizedFromAuthorizers.mockReturnValue(false);
|
||||
mockBuildMentionRegexes.mockReturnValue([/\bbert\b/i]);
|
||||
|
||||
setBlueBubblesRuntime(createMockRuntime());
|
||||
resetBlueBubblesMonitorTestState({
|
||||
createRuntime: createMockRuntime,
|
||||
fetchHistoryMock: mockFetchBlueBubblesHistory,
|
||||
readAllowFromStoreMock: mockReadAllowFromStore,
|
||||
upsertPairingRequestMock: mockUpsertPairingRequest,
|
||||
resolveRequireMentionMock: mockResolveRequireMention,
|
||||
hasControlCommandMock: mockHasControlCommand,
|
||||
resolveCommandAuthorizedFromAuthorizersMock: mockResolveCommandAuthorizedFromAuthorizers,
|
||||
buildMentionRegexesMock: mockBuildMentionRegexes,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -267,75 +164,145 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
core?: PluginRuntime;
|
||||
statusSink?: (event: unknown) => void;
|
||||
}) {
|
||||
const account = params?.account ?? createMockAccount();
|
||||
const config = params?.config ?? {};
|
||||
const core = params?.core ?? createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
statusSink: params?.statusSink,
|
||||
});
|
||||
return { account, config, core };
|
||||
}
|
||||
|
||||
function createNewMessagePayload(dataOverrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
...dataOverrides,
|
||||
const registration = trackWebhookRegistrationForTest(
|
||||
setupWebhookTargetForTest({
|
||||
createCore: createMockRuntime,
|
||||
core: params?.core,
|
||||
account: params?.account,
|
||||
config: params?.config,
|
||||
statusSink: params?.statusSink,
|
||||
}),
|
||||
(nextUnregister) => {
|
||||
unregister = nextUnregister;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function setRequestRemoteAddress(req: IncomingMessage, remoteAddress: string) {
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = {
|
||||
remoteAddress,
|
||||
};
|
||||
}
|
||||
|
||||
async function dispatchWebhook(req: IncomingMessage) {
|
||||
const res = createMockResponse();
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
return { handled, res };
|
||||
}
|
||||
|
||||
function createWebhookRequestForTest(params?: {
|
||||
method?: string;
|
||||
url?: string;
|
||||
body?: unknown;
|
||||
headers?: Record<string, string>;
|
||||
remoteAddress?: string;
|
||||
}) {
|
||||
const req = createMockRequest(
|
||||
params?.method ?? "POST",
|
||||
params?.url ?? "/bluebubbles-webhook",
|
||||
params?.body ?? {},
|
||||
params?.headers,
|
||||
);
|
||||
if (params?.remoteAddress) {
|
||||
setRequestRemoteAddress(req, params.remoteAddress);
|
||||
}
|
||||
return req;
|
||||
return {
|
||||
account: registration.account,
|
||||
config: registration.config,
|
||||
core: registration.core,
|
||||
};
|
||||
}
|
||||
|
||||
function createHangingWebhookRequest(url = "/bluebubbles-webhook?password=test-password") {
|
||||
const req = new EventEmitter() as IncomingMessage;
|
||||
const destroyMock = vi.fn();
|
||||
req.method = "POST";
|
||||
req.url = url;
|
||||
req.headers = {};
|
||||
req.destroy = destroyMock as unknown as IncomingMessage["destroy"];
|
||||
setRequestRemoteAddress(req, "127.0.0.1");
|
||||
return { req, destroyMock };
|
||||
function setupProtectedWebhookTarget(password = TEST_WEBHOOK_PASSWORD) {
|
||||
return setupWebhookTargetAccount(createProtectedWebhookTarget(password).account);
|
||||
}
|
||||
|
||||
function setupPasswordlessWebhookTarget() {
|
||||
return setupWebhookTargetAccount(createPasswordlessWebhookTarget().account);
|
||||
}
|
||||
|
||||
function setupWebhookTargetAccount(account: ResolvedBlueBubblesAccount) {
|
||||
setupWebhookTarget({ account });
|
||||
return account;
|
||||
}
|
||||
|
||||
function createWebhookTarget(
|
||||
account: ResolvedBlueBubblesAccount,
|
||||
statusSink: (event: unknown) => void = vi.fn(),
|
||||
) {
|
||||
return { account, statusSink };
|
||||
}
|
||||
|
||||
function createProtectedWebhookTarget(password = TEST_WEBHOOK_PASSWORD) {
|
||||
return createWebhookTarget(createProtectedWebhookAccountForTest(password));
|
||||
}
|
||||
|
||||
function createPasswordlessWebhookTarget() {
|
||||
return createWebhookTarget(createMockAccount({ password: undefined }));
|
||||
}
|
||||
|
||||
function createProtectedPasswordQueryRequestParams(password = TEST_WEBHOOK_PASSWORD) {
|
||||
return createPasswordQueryRequestParamsForTest({ password });
|
||||
}
|
||||
|
||||
async function expectWebhookRequestStatusWithSetup(
|
||||
setup: () => void,
|
||||
params: WebhookRequestParams,
|
||||
expectedStatus: number,
|
||||
expectedBody?: string,
|
||||
) {
|
||||
setup();
|
||||
return expectWebhookRequestStatusForTest(params, expectedStatus, expectedBody);
|
||||
}
|
||||
|
||||
async function dispatchWebhookPayloadWithSetup(setup: () => void, payload: unknown) {
|
||||
setup();
|
||||
return dispatchWebhookPayloadForTest({ body: payload });
|
||||
}
|
||||
|
||||
async function expectProtectedPasswordQueryRequestStatus(
|
||||
expectedStatus: number,
|
||||
password = TEST_WEBHOOK_PASSWORD,
|
||||
) {
|
||||
return expectWebhookRequestStatusForTest(
|
||||
createProtectedPasswordQueryRequestParams(password),
|
||||
expectedStatus,
|
||||
);
|
||||
}
|
||||
|
||||
async function expectProtectedWebhookRequestStatus(
|
||||
params: WebhookRequestParams,
|
||||
expectedStatus: number,
|
||||
expectedBody?: string,
|
||||
) {
|
||||
return expectWebhookRequestStatusWithSetup(
|
||||
() => {
|
||||
setupProtectedWebhookTarget();
|
||||
},
|
||||
params,
|
||||
expectedStatus,
|
||||
expectedBody,
|
||||
);
|
||||
}
|
||||
|
||||
async function expectRegisteredWebhookRequestStatus(
|
||||
params: WebhookRequestParams,
|
||||
expectedStatus: number,
|
||||
expectedBody?: string,
|
||||
) {
|
||||
return expectWebhookRequestStatusWithSetup(
|
||||
() => {
|
||||
setupWebhookTarget();
|
||||
},
|
||||
params,
|
||||
expectedStatus,
|
||||
expectedBody,
|
||||
);
|
||||
}
|
||||
|
||||
async function dispatchRegisteredWebhookPayload(payload: unknown) {
|
||||
return dispatchWebhookPayloadWithSetup(() => {
|
||||
setupWebhookTarget();
|
||||
}, payload);
|
||||
}
|
||||
|
||||
async function expectLoopbackWebhookRequestStatus(
|
||||
remoteAddress: (typeof LOOPBACK_REMOTE_ADDRESSES_FOR_TEST)[number],
|
||||
expectedStatus: number,
|
||||
overrides?: Omit<WebhookRequestParams, "remoteAddress">,
|
||||
) {
|
||||
return expectWebhookRequestStatusForTest(
|
||||
createLoopbackWebhookRequestParamsForTest(remoteAddress, { overrides }),
|
||||
expectedStatus,
|
||||
);
|
||||
}
|
||||
|
||||
async function expectProtectedLoopbackWebhookRequestStatus(
|
||||
remoteAddress: (typeof LOOPBACK_REMOTE_ADDRESSES_FOR_TEST)[number],
|
||||
expectedStatus: number,
|
||||
overrides?: Omit<WebhookRequestParams, "remoteAddress">,
|
||||
) {
|
||||
setupProtectedWebhookTarget();
|
||||
return expectLoopbackWebhookRequestStatus(remoteAddress, expectedStatus, overrides);
|
||||
}
|
||||
|
||||
async function expectPasswordlessLoopbackWebhookRequestStatus(
|
||||
remoteAddress: (typeof LOOPBACK_REMOTE_ADDRESSES_FOR_TEST)[number],
|
||||
expectedStatus: number,
|
||||
overrides?: Omit<WebhookRequestParams, "remoteAddress">,
|
||||
) {
|
||||
setupPasswordlessWebhookTarget();
|
||||
return expectLoopbackWebhookRequestStatus(remoteAddress, expectedStatus, overrides);
|
||||
}
|
||||
|
||||
function registerWebhookTargets(
|
||||
@@ -344,70 +311,37 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
statusSink?: (event: unknown) => void;
|
||||
}>,
|
||||
) {
|
||||
const config: OpenClawConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
const unregisterFns = params.map(({ account, statusSink }) =>
|
||||
registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
statusSink,
|
||||
trackWebhookRegistrationForTest(
|
||||
setupWebhookTargetsForTest({
|
||||
createCore: createMockRuntime,
|
||||
accounts: params,
|
||||
}),
|
||||
(nextUnregister) => {
|
||||
unregister = nextUnregister;
|
||||
},
|
||||
);
|
||||
|
||||
unregister = () => {
|
||||
for (const unregisterFn of unregisterFns) {
|
||||
unregisterFn();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function expectWebhookStatus(
|
||||
req: IncomingMessage,
|
||||
expectedStatus: number,
|
||||
expectedBody?: string,
|
||||
) {
|
||||
const { handled, res } = await dispatchWebhook(req);
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(expectedStatus);
|
||||
if (expectedBody !== undefined) {
|
||||
expect(res.body).toBe(expectedBody);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
describe("webhook parsing + auth handling", () => {
|
||||
it("rejects non-POST requests", async () => {
|
||||
setupWebhookTarget();
|
||||
const req = createWebhookRequestForTest({ method: "GET" });
|
||||
await expectWebhookStatus(req, 405);
|
||||
await expectRegisteredWebhookRequestStatus({ method: "GET" }, 405);
|
||||
});
|
||||
|
||||
it("accepts POST requests with valid JSON payload", async () => {
|
||||
setupWebhookTarget();
|
||||
const payload = createNewMessagePayload({ date: Date.now() });
|
||||
const req = createWebhookRequestForTest({ body: payload });
|
||||
await expectWebhookStatus(req, 200, "ok");
|
||||
const payload = createTimestampedNewMessagePayloadForTest();
|
||||
await expectRegisteredWebhookRequestStatus({ body: payload }, 200, "ok");
|
||||
});
|
||||
|
||||
it("rejects requests with invalid JSON", async () => {
|
||||
setupWebhookTarget();
|
||||
const req = createWebhookRequestForTest({ body: "invalid json {{" });
|
||||
await expectWebhookStatus(req, 400);
|
||||
await expectRegisteredWebhookRequestStatus({ body: "invalid json {{" }, 400);
|
||||
});
|
||||
|
||||
it("accepts URL-encoded payload wrappers", async () => {
|
||||
setupWebhookTarget();
|
||||
const payload = createNewMessagePayload({ date: Date.now() });
|
||||
const payload = createTimestampedNewMessagePayloadForTest();
|
||||
const encodedBody = new URLSearchParams({
|
||||
payload: JSON.stringify(payload),
|
||||
}).toString();
|
||||
const req = createWebhookRequestForTest({ body: encodedBody });
|
||||
await expectWebhookStatus(req, 200, "ok");
|
||||
await expectRegisteredWebhookRequestStatus({ body: encodedBody }, 200, "ok");
|
||||
});
|
||||
|
||||
it("returns 408 when request body times out (Slow-Loris protection)", async () => {
|
||||
@@ -416,11 +350,9 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
setupWebhookTarget();
|
||||
|
||||
// Create a request that never sends data or ends (simulates slow-loris)
|
||||
const { req, destroyMock } = createHangingWebhookRequest();
|
||||
const { req, destroyMock } = createHangingWebhookRequestForTest();
|
||||
|
||||
const res = createMockResponse();
|
||||
|
||||
const handledPromise = handleBlueBubblesWebhookRequest(req, res);
|
||||
const { res, handledPromise } = createWebhookDispatchForTest(req);
|
||||
|
||||
// Advance past the 30s timeout
|
||||
await vi.advanceTimersByTimeAsync(31_000);
|
||||
@@ -435,123 +367,78 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
|
||||
it("rejects unauthorized requests before reading the body", async () => {
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
setupWebhookTarget({ account });
|
||||
const { req } = createHangingWebhookRequest("/bluebubbles-webhook?password=wrong-token");
|
||||
setupProtectedWebhookTarget();
|
||||
const { req } = createHangingWebhookRequestForTest(
|
||||
"/bluebubbles-webhook?password=wrong-token",
|
||||
);
|
||||
const onSpy = vi.spyOn(req, "on");
|
||||
await expectWebhookStatus(req, 401);
|
||||
await expectWebhookStatusForTest(req, 401);
|
||||
expect(onSpy).not.toHaveBeenCalledWith("data", expect.any(Function));
|
||||
});
|
||||
|
||||
it("authenticates via password query parameter", async () => {
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
setupWebhookTarget({ account });
|
||||
const req = createWebhookRequestForTest({
|
||||
url: "/bluebubbles-webhook?password=secret-token",
|
||||
body: createNewMessagePayload(),
|
||||
remoteAddress: "192.168.1.100",
|
||||
});
|
||||
await expectWebhookStatus(req, 200);
|
||||
await expectProtectedWebhookRequestStatus(createProtectedPasswordQueryRequestParams(), 200);
|
||||
});
|
||||
|
||||
it("authenticates via x-password header", async () => {
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
setupWebhookTarget({ account });
|
||||
const req = createWebhookRequestForTest({
|
||||
body: createNewMessagePayload(),
|
||||
headers: { "x-password": "secret-token" }, // pragma: allowlist secret
|
||||
remoteAddress: "192.168.1.100",
|
||||
});
|
||||
await expectWebhookStatus(req, 200);
|
||||
await expectProtectedWebhookRequestStatus(
|
||||
createRemoteWebhookRequestParamsForTest({
|
||||
overrides: {
|
||||
headers: { "x-password": TEST_WEBHOOK_PASSWORD }, // pragma: allowlist secret
|
||||
},
|
||||
}),
|
||||
200,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects unauthorized requests with wrong password", async () => {
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
setupWebhookTarget({ account });
|
||||
const req = createWebhookRequestForTest({
|
||||
url: "/bluebubbles-webhook?password=wrong-token",
|
||||
body: createNewMessagePayload(),
|
||||
remoteAddress: "192.168.1.100",
|
||||
});
|
||||
await expectWebhookStatus(req, 401);
|
||||
await expectProtectedWebhookRequestStatus(
|
||||
createProtectedPasswordQueryRequestParams("wrong-token"),
|
||||
401,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects ambiguous routing when multiple targets match the same password", async () => {
|
||||
const accountA = createMockAccount({ password: "secret-token" });
|
||||
const accountB = createMockAccount({ password: "secret-token" });
|
||||
const sinkA = vi.fn();
|
||||
const sinkB = vi.fn();
|
||||
registerWebhookTargets([
|
||||
{ account: accountA, statusSink: sinkA },
|
||||
{ account: accountB, statusSink: sinkB },
|
||||
]);
|
||||
const targetA = createProtectedWebhookTarget();
|
||||
const targetB = createProtectedWebhookTarget();
|
||||
registerWebhookTargets([targetA, targetB]);
|
||||
|
||||
const req = createWebhookRequestForTest({
|
||||
url: "/bluebubbles-webhook?password=secret-token",
|
||||
body: createNewMessagePayload(),
|
||||
remoteAddress: "192.168.1.100",
|
||||
});
|
||||
await expectWebhookStatus(req, 401);
|
||||
expect(sinkA).not.toHaveBeenCalled();
|
||||
expect(sinkB).not.toHaveBeenCalled();
|
||||
await expectProtectedPasswordQueryRequestStatus(401);
|
||||
expect(targetA.statusSink).not.toHaveBeenCalled();
|
||||
expect(targetB.statusSink).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores targets without passwords when a password-authenticated target matches", async () => {
|
||||
const accountStrict = createMockAccount({ password: "secret-token" });
|
||||
const accountWithoutPassword = createMockAccount({ password: undefined });
|
||||
const sinkStrict = vi.fn();
|
||||
const sinkWithoutPassword = vi.fn();
|
||||
registerWebhookTargets([
|
||||
{ account: accountStrict, statusSink: sinkStrict },
|
||||
{ account: accountWithoutPassword, statusSink: sinkWithoutPassword },
|
||||
]);
|
||||
const strictTarget = createProtectedWebhookTarget();
|
||||
const passwordlessTarget = createPasswordlessWebhookTarget();
|
||||
registerWebhookTargets([strictTarget, passwordlessTarget]);
|
||||
|
||||
const req = createWebhookRequestForTest({
|
||||
url: "/bluebubbles-webhook?password=secret-token",
|
||||
body: createNewMessagePayload(),
|
||||
remoteAddress: "192.168.1.100",
|
||||
});
|
||||
await expectWebhookStatus(req, 200);
|
||||
expect(sinkStrict).toHaveBeenCalledTimes(1);
|
||||
expect(sinkWithoutPassword).not.toHaveBeenCalled();
|
||||
await expectProtectedPasswordQueryRequestStatus(200);
|
||||
expect(strictTarget.statusSink).toHaveBeenCalledTimes(1);
|
||||
expect(passwordlessTarget.statusSink).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("requires authentication for loopback requests when password is configured", async () => {
|
||||
const account = createMockAccount({ password: "secret-token" });
|
||||
setupWebhookTarget({ account });
|
||||
for (const remoteAddress of ["127.0.0.1", "::1", "::ffff:127.0.0.1"]) {
|
||||
const req = createWebhookRequestForTest({
|
||||
body: createNewMessagePayload(),
|
||||
remoteAddress,
|
||||
});
|
||||
await expectWebhookStatus(req, 401);
|
||||
for (const remoteAddress of LOOPBACK_REMOTE_ADDRESSES_FOR_TEST) {
|
||||
await expectProtectedLoopbackWebhookRequestStatus(remoteAddress, 401);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects targets without passwords for loopback and proxied-looking requests", async () => {
|
||||
const account = createMockAccount({ password: undefined });
|
||||
setupWebhookTarget({ account });
|
||||
|
||||
const headerVariants: Record<string, string>[] = [
|
||||
{ host: "localhost" },
|
||||
{ host: "localhost", "x-forwarded-for": "203.0.113.10" },
|
||||
{ host: "localhost", forwarded: "for=203.0.113.10;proto=https;host=example.com" },
|
||||
];
|
||||
for (const headers of headerVariants) {
|
||||
const req = createWebhookRequestForTest({
|
||||
body: createNewMessagePayload(),
|
||||
headers,
|
||||
remoteAddress: "127.0.0.1",
|
||||
});
|
||||
await expectWebhookStatus(req, 401);
|
||||
await expectPasswordlessLoopbackWebhookRequestStatus("127.0.0.1", 401, { headers });
|
||||
}
|
||||
});
|
||||
|
||||
it("ignores unregistered webhook paths", async () => {
|
||||
const req = createMockRequest("POST", "/unregistered-path", {});
|
||||
const res = createMockResponse();
|
||||
|
||||
const handled = await handleBlueBubblesWebhookRequest(req, res);
|
||||
const { handled } = await dispatchWebhookPayloadForTest({
|
||||
url: "/unregistered-path",
|
||||
});
|
||||
|
||||
expect(handled).toBe(false);
|
||||
});
|
||||
@@ -560,19 +447,13 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
const { resolveChatGuidForTarget } = await import("./send.js");
|
||||
vi.mocked(resolveChatGuidForTarget).mockClear();
|
||||
|
||||
setupWebhookTarget({ account: createMockAccount({ groupPolicy: "open" }) });
|
||||
const payload = createNewMessagePayload({
|
||||
const payload = createTimestampedNewMessagePayloadForTest({
|
||||
text: "hello from group",
|
||||
isGroup: true,
|
||||
chatId: "123",
|
||||
date: Date.now(),
|
||||
});
|
||||
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
||||
const res = createMockResponse();
|
||||
|
||||
await handleBlueBubblesWebhookRequest(req, res);
|
||||
await flushAsync();
|
||||
await dispatchRegisteredWebhookPayload(payload);
|
||||
|
||||
expect(resolveChatGuidForTarget).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -591,19 +472,13 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
return EMPTY_DISPATCH_RESULT;
|
||||
});
|
||||
|
||||
setupWebhookTarget({ account: createMockAccount({ groupPolicy: "open" }) });
|
||||
const payload = createNewMessagePayload({
|
||||
const payload = createTimestampedNewMessagePayloadForTest({
|
||||
text: "hello from group",
|
||||
isGroup: true,
|
||||
chat: { chatGuid: "iMessage;+;chat123456" },
|
||||
date: Date.now(),
|
||||
});
|
||||
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
||||
const res = createMockResponse();
|
||||
|
||||
await handleBlueBubblesWebhookRequest(req, res);
|
||||
await flushAsync();
|
||||
await dispatchRegisteredWebhookPayload(payload);
|
||||
|
||||
expect(resolveChatGuidForTarget).not.toHaveBeenCalled();
|
||||
expect(sendMessageBlueBubbles).toHaveBeenCalledWith(
|
||||
|
||||
369
extensions/bluebubbles/src/monitor.webhook.test-helpers.ts
Normal file
369
extensions/bluebubbles/src/monitor.webhook.test-helpers.ts
Normal file
@@ -0,0 +1,369 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { expect, vi } from "vitest";
|
||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||
import { handleBlueBubblesWebhookRequest } from "./monitor.js";
|
||||
import { registerBlueBubblesWebhookTarget } from "./monitor.js";
|
||||
import type { OpenClawConfig, PluginRuntime } from "./runtime-api.js";
|
||||
import { setBlueBubblesRuntime } from "./runtime.js";
|
||||
|
||||
export type WebhookRequestParams = {
|
||||
method?: string;
|
||||
url?: string;
|
||||
body?: unknown;
|
||||
headers?: Record<string, string>;
|
||||
remoteAddress?: string;
|
||||
};
|
||||
|
||||
export const LOOPBACK_REMOTE_ADDRESSES_FOR_TEST = ["127.0.0.1", "::1", "::ffff:127.0.0.1"] as const;
|
||||
|
||||
export function createMockAccount(
|
||||
overrides: Partial<ResolvedBlueBubblesAccount["config"]> = {},
|
||||
): ResolvedBlueBubblesAccount {
|
||||
return {
|
||||
accountId: "default",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
config: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test-password",
|
||||
dmPolicy: "open",
|
||||
groupPolicy: "open",
|
||||
allowFrom: [],
|
||||
groupAllowFrom: [],
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createProtectedWebhookAccountForTest(password = "test-password") {
|
||||
return createMockAccount({ password });
|
||||
}
|
||||
|
||||
export function createNewMessagePayloadForTest(dataOverrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
...dataOverrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createTimestampedNewMessagePayloadForTest(
|
||||
dataOverrides: Record<string, unknown> = {},
|
||||
) {
|
||||
return createNewMessagePayloadForTest({
|
||||
...dataOverrides,
|
||||
date: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createMessageReactionPayloadForTest(dataOverrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
type: "message-reaction",
|
||||
data: {
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
associatedMessageGuid: "msg-original-123",
|
||||
associatedMessageType: 2000,
|
||||
...dataOverrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createTimestampedMessageReactionPayloadForTest(
|
||||
dataOverrides: Record<string, unknown> = {},
|
||||
) {
|
||||
return createMessageReactionPayloadForTest({
|
||||
...dataOverrides,
|
||||
date: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
export function createMockRequest(
|
||||
method: string,
|
||||
url: string,
|
||||
body: unknown,
|
||||
headers: Record<string, string> = {},
|
||||
remoteAddress = "127.0.0.1",
|
||||
): IncomingMessage {
|
||||
if (headers.host === undefined) {
|
||||
headers.host = "localhost";
|
||||
}
|
||||
const parsedUrl = new URL(url, "http://localhost");
|
||||
const hasAuthQuery = parsedUrl.searchParams.has("guid") || parsedUrl.searchParams.has("password");
|
||||
const hasAuthHeader =
|
||||
headers["x-guid"] !== undefined ||
|
||||
headers["x-password"] !== undefined ||
|
||||
headers["x-bluebubbles-guid"] !== undefined ||
|
||||
headers.authorization !== undefined;
|
||||
if (!hasAuthQuery && !hasAuthHeader) {
|
||||
parsedUrl.searchParams.set("password", "test-password");
|
||||
}
|
||||
|
||||
const req = new EventEmitter() as IncomingMessage;
|
||||
req.method = method;
|
||||
req.url = `${parsedUrl.pathname}${parsedUrl.search}`;
|
||||
req.headers = headers;
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = { remoteAddress };
|
||||
|
||||
// Emit body data after a microtask.
|
||||
void Promise.resolve().then(() => {
|
||||
const bodyStr = typeof body === "string" ? body : JSON.stringify(body);
|
||||
req.emit("data", Buffer.from(bodyStr));
|
||||
req.emit("end");
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
export function createMockRequestForTest(params: WebhookRequestParams = {}): IncomingMessage {
|
||||
return createMockRequest(
|
||||
params.method ?? "POST",
|
||||
params.url ?? "/bluebubbles-webhook",
|
||||
params.body ?? {},
|
||||
params.headers,
|
||||
params.remoteAddress,
|
||||
);
|
||||
}
|
||||
|
||||
export function createRemoteWebhookRequestParamsForTest(
|
||||
params: {
|
||||
body?: unknown;
|
||||
remoteAddress?: string;
|
||||
overrides?: WebhookRequestParams;
|
||||
} = {},
|
||||
): WebhookRequestParams {
|
||||
return {
|
||||
body: params.body ?? createNewMessagePayloadForTest(),
|
||||
remoteAddress: params.remoteAddress ?? "192.168.1.100",
|
||||
...params.overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createPasswordQueryRequestParamsForTest(
|
||||
params: {
|
||||
body?: unknown;
|
||||
password?: string;
|
||||
remoteAddress?: string;
|
||||
overrides?: Omit<WebhookRequestParams, "url">;
|
||||
} = {},
|
||||
): WebhookRequestParams {
|
||||
return createRemoteWebhookRequestParamsForTest({
|
||||
body: params.body,
|
||||
remoteAddress: params.remoteAddress,
|
||||
overrides: {
|
||||
url: `/bluebubbles-webhook?password=${params.password ?? "test-password"}`,
|
||||
...params.overrides,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createLoopbackWebhookRequestParamsForTest(
|
||||
remoteAddress: (typeof LOOPBACK_REMOTE_ADDRESSES_FOR_TEST)[number],
|
||||
params: {
|
||||
body?: unknown;
|
||||
overrides?: Omit<WebhookRequestParams, "remoteAddress">;
|
||||
} = {},
|
||||
): WebhookRequestParams {
|
||||
return {
|
||||
body: params.body ?? createNewMessagePayloadForTest(),
|
||||
remoteAddress,
|
||||
...params.overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createHangingWebhookRequestForTest(
|
||||
url = "/bluebubbles-webhook?password=test-password",
|
||||
remoteAddress = "127.0.0.1",
|
||||
) {
|
||||
const req = new EventEmitter() as IncomingMessage;
|
||||
const destroyMock = vi.fn();
|
||||
req.method = "POST";
|
||||
req.url = url;
|
||||
req.headers = {};
|
||||
req.destroy = destroyMock as unknown as IncomingMessage["destroy"];
|
||||
(req as unknown as { socket: { remoteAddress: string } }).socket = { remoteAddress };
|
||||
return { req, destroyMock };
|
||||
}
|
||||
|
||||
export function createMockResponse(): ServerResponse & { body: string; statusCode: number } {
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
body: "",
|
||||
setHeader: vi.fn(),
|
||||
end: vi.fn((data?: string) => {
|
||||
res.body = data ?? "";
|
||||
}),
|
||||
} as unknown as ServerResponse & { body: string; statusCode: number };
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function flushAsync() {
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
}
|
||||
}
|
||||
|
||||
export function createWebhookDispatchForTest(req: IncomingMessage) {
|
||||
const res = createMockResponse();
|
||||
const handledPromise = handleBlueBubblesWebhookRequest(req, res);
|
||||
return { res, handledPromise };
|
||||
}
|
||||
|
||||
export async function dispatchWebhookRequestForTest(
|
||||
req: IncomingMessage,
|
||||
options: { flushAsyncAfter?: boolean } = {},
|
||||
) {
|
||||
const { res, handledPromise } = createWebhookDispatchForTest(req);
|
||||
const handled = await handledPromise;
|
||||
if (options.flushAsyncAfter) {
|
||||
await flushAsync();
|
||||
}
|
||||
return { handled, res };
|
||||
}
|
||||
|
||||
export async function dispatchWebhookPayloadForTest(params: WebhookRequestParams = {}) {
|
||||
const req = createMockRequestForTest(params);
|
||||
return dispatchWebhookRequestForTest(req, { flushAsyncAfter: true });
|
||||
}
|
||||
|
||||
export async function expectWebhookStatusForTest(
|
||||
req: IncomingMessage,
|
||||
expectedStatus: number,
|
||||
expectedBody?: string,
|
||||
) {
|
||||
const { res, handled } = await dispatchWebhookRequestForTest(req);
|
||||
expect(handled).toBe(true);
|
||||
expect(res.statusCode).toBe(expectedStatus);
|
||||
if (expectedBody !== undefined) {
|
||||
expect(res.body).toBe(expectedBody);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function expectWebhookRequestStatusForTest(
|
||||
params: WebhookRequestParams,
|
||||
expectedStatus: number,
|
||||
expectedBody?: string,
|
||||
) {
|
||||
return expectWebhookStatusForTest(createMockRequestForTest(params), expectedStatus, expectedBody);
|
||||
}
|
||||
|
||||
export function trackWebhookRegistrationForTest<T extends { unregister: () => void }>(
|
||||
registration: T,
|
||||
setUnregister: (unregister: () => void) => void,
|
||||
) {
|
||||
setUnregister(registration.unregister);
|
||||
return registration;
|
||||
}
|
||||
|
||||
export function registerWebhookTargetForTest(params: {
|
||||
core: PluginRuntime;
|
||||
account?: ResolvedBlueBubblesAccount;
|
||||
config?: OpenClawConfig;
|
||||
path?: string;
|
||||
statusSink?: (event: unknown) => void;
|
||||
runtime?: {
|
||||
log: (...args: unknown[]) => unknown;
|
||||
error: (...args: unknown[]) => unknown;
|
||||
};
|
||||
}) {
|
||||
setBlueBubblesRuntime(params.core);
|
||||
|
||||
return registerBlueBubblesWebhookTarget({
|
||||
account: params.account ?? createMockAccount(),
|
||||
config: params.config ?? {},
|
||||
runtime: params.runtime ?? { log: vi.fn(), error: vi.fn() },
|
||||
core: params.core,
|
||||
path: params.path ?? "/bluebubbles-webhook",
|
||||
statusSink: params.statusSink,
|
||||
});
|
||||
}
|
||||
|
||||
export function registerWebhookTargetsForTest(params: {
|
||||
core: PluginRuntime;
|
||||
accounts: Array<{
|
||||
account: ResolvedBlueBubblesAccount;
|
||||
statusSink?: (event: unknown) => void;
|
||||
}>;
|
||||
config?: OpenClawConfig;
|
||||
path?: string;
|
||||
runtime?: {
|
||||
log: (...args: unknown[]) => unknown;
|
||||
error: (...args: unknown[]) => unknown;
|
||||
};
|
||||
}) {
|
||||
return params.accounts.map(({ account, statusSink }) =>
|
||||
registerWebhookTargetForTest({
|
||||
core: params.core,
|
||||
account,
|
||||
config: params.config,
|
||||
path: params.path,
|
||||
runtime: params.runtime,
|
||||
statusSink,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function setupWebhookTargetForTest(params: {
|
||||
createCore: () => PluginRuntime;
|
||||
core?: PluginRuntime;
|
||||
account?: ResolvedBlueBubblesAccount;
|
||||
config?: OpenClawConfig;
|
||||
path?: string;
|
||||
statusSink?: (event: unknown) => void;
|
||||
runtime?: {
|
||||
log: (...args: unknown[]) => unknown;
|
||||
error: (...args: unknown[]) => unknown;
|
||||
};
|
||||
}) {
|
||||
const account = params.account ?? createMockAccount();
|
||||
const config = params.config ?? {};
|
||||
const core = params.core ?? params.createCore();
|
||||
const unregister = registerWebhookTargetForTest({
|
||||
core,
|
||||
account,
|
||||
config,
|
||||
path: params.path,
|
||||
statusSink: params.statusSink,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
return { account, config, core, unregister };
|
||||
}
|
||||
|
||||
export function setupWebhookTargetsForTest(params: {
|
||||
createCore: () => PluginRuntime;
|
||||
core?: PluginRuntime;
|
||||
accounts: Array<{
|
||||
account: ResolvedBlueBubblesAccount;
|
||||
statusSink?: (event: unknown) => void;
|
||||
}>;
|
||||
config?: OpenClawConfig;
|
||||
path?: string;
|
||||
runtime?: {
|
||||
log: (...args: unknown[]) => unknown;
|
||||
error: (...args: unknown[]) => unknown;
|
||||
};
|
||||
}) {
|
||||
const core = params.core ?? params.createCore();
|
||||
const unregisterFns = registerWebhookTargetsForTest({
|
||||
core,
|
||||
accounts: params.accounts,
|
||||
config: params.config,
|
||||
path: params.path,
|
||||
runtime: params.runtime,
|
||||
});
|
||||
const unregister = () => {
|
||||
for (const unregisterFn of unregisterFns) {
|
||||
unregisterFn();
|
||||
}
|
||||
};
|
||||
return { core, unregister };
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"build:viewer": "bun build src/viewer-client.ts --target browser --format esm --minify --outfile assets/viewer-runtime.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pierre/diffs": "1.1.1",
|
||||
"@pierre/diffs": "1.1.3",
|
||||
"@sinclair/typebox": "0.34.48",
|
||||
"playwright-core": "1.58.2"
|
||||
},
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { PluginRuntime } from "../../../src/plugins/runtime/types.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import { createStartAccountContext } from "../../../test/helpers/extensions/start-account-context.js";
|
||||
import type { ResolvedDiscordAccount } from "./accounts.js";
|
||||
import { discordPlugin } from "./channel.js";
|
||||
@@ -19,8 +18,8 @@ vi.mock("./probe.js", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./monitor.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./monitor.js")>();
|
||||
vi.mock("./monitor/provider.runtime.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./monitor/provider.runtime.js")>();
|
||||
return {
|
||||
...actual,
|
||||
monitorDiscordProvider: monitorDiscordProviderMock,
|
||||
@@ -46,6 +45,30 @@ function createCfg(): OpenClawConfig {
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
function resolveAccount(cfg: OpenClawConfig): ResolvedDiscordAccount {
|
||||
return discordPlugin.config.resolveAccount(cfg, "default") as ResolvedDiscordAccount;
|
||||
}
|
||||
|
||||
function startDiscordAccount(cfg: OpenClawConfig) {
|
||||
return discordPlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: resolveAccount(cfg),
|
||||
cfg,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function installDiscordRuntime(discord: Record<string, unknown>) {
|
||||
setDiscordRuntime({
|
||||
channel: {
|
||||
discord,
|
||||
},
|
||||
logging: {
|
||||
shouldLogVerbose: () => false,
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
probeDiscordMock.mockReset();
|
||||
monitorDiscordProviderMock.mockReset();
|
||||
@@ -55,13 +78,9 @@ afterEach(() => {
|
||||
describe("discordPlugin outbound", () => {
|
||||
it("forwards mediaLocalRoots to sendMessageDiscord", async () => {
|
||||
const sendMessageDiscord = vi.fn(async () => ({ messageId: "m1" }));
|
||||
setDiscordRuntime({
|
||||
channel: {
|
||||
discord: {
|
||||
sendMessageDiscord,
|
||||
},
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
installDiscordRuntime({
|
||||
sendMessageDiscord,
|
||||
});
|
||||
|
||||
const result = await discordPlugin.outbound!.sendMedia!({
|
||||
cfg: {} as OpenClawConfig,
|
||||
@@ -87,16 +106,9 @@ describe("discordPlugin outbound", () => {
|
||||
const runtimeProbeDiscord = vi.fn(async () => {
|
||||
throw new Error("runtime Discord probe should not be used");
|
||||
});
|
||||
setDiscordRuntime({
|
||||
channel: {
|
||||
discord: {
|
||||
probeDiscord: runtimeProbeDiscord,
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
shouldLogVerbose: () => false,
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
installDiscordRuntime({
|
||||
probeDiscord: runtimeProbeDiscord,
|
||||
});
|
||||
probeDiscordMock.mockResolvedValue({
|
||||
ok: true,
|
||||
bot: { username: "Bob" },
|
||||
@@ -111,7 +123,7 @@ describe("discordPlugin outbound", () => {
|
||||
});
|
||||
|
||||
const cfg = createCfg();
|
||||
const account = discordPlugin.config.resolveAccount(cfg, "default");
|
||||
const account = resolveAccount(cfg);
|
||||
|
||||
await discordPlugin.status!.probeAccount!({
|
||||
account,
|
||||
@@ -132,17 +144,10 @@ describe("discordPlugin outbound", () => {
|
||||
const runtimeMonitorDiscordProvider = vi.fn(async () => {
|
||||
throw new Error("runtime Discord monitor should not be used");
|
||||
});
|
||||
setDiscordRuntime({
|
||||
channel: {
|
||||
discord: {
|
||||
probeDiscord: runtimeProbeDiscord,
|
||||
monitorDiscordProvider: runtimeMonitorDiscordProvider,
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
shouldLogVerbose: () => false,
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
installDiscordRuntime({
|
||||
probeDiscord: runtimeProbeDiscord,
|
||||
monitorDiscordProvider: runtimeMonitorDiscordProvider,
|
||||
});
|
||||
probeDiscordMock.mockResolvedValue({
|
||||
ok: true,
|
||||
bot: { username: "Bob" },
|
||||
@@ -158,13 +163,7 @@ describe("discordPlugin outbound", () => {
|
||||
monitorDiscordProviderMock.mockResolvedValue(undefined);
|
||||
|
||||
const cfg = createCfg();
|
||||
await discordPlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: discordPlugin.config.resolveAccount(cfg, "default") as ResolvedDiscordAccount,
|
||||
cfg,
|
||||
runtime: createRuntimeEnv(),
|
||||
}),
|
||||
);
|
||||
await startDiscordAccount(cfg);
|
||||
|
||||
expect(probeDiscordMock).toHaveBeenCalledWith("discord-token", 2500, {
|
||||
includeApplication: true,
|
||||
@@ -180,6 +179,37 @@ describe("discordPlugin outbound", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("discordPlugin security", () => {
|
||||
it("normalizes dm allowlist entries with trimmed prefixes and mentions", () => {
|
||||
const resolveDmPolicy = discordPlugin.security?.resolveDmPolicy;
|
||||
if (!resolveDmPolicy) {
|
||||
throw new Error("resolveDmPolicy unavailable");
|
||||
}
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
discord: {
|
||||
token: "discord-token",
|
||||
dm: { policy: "allowlist", allowFrom: [" discord:<@!123456789> "] },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = resolveDmPolicy({
|
||||
cfg,
|
||||
account: discordPlugin.config.resolveAccount(cfg, "default") as ResolvedDiscordAccount,
|
||||
});
|
||||
if (!result) {
|
||||
throw new Error("discord resolveDmPolicy returned null");
|
||||
}
|
||||
|
||||
expect(result.policy).toBe("allowlist");
|
||||
expect(result.allowFrom).toEqual([" discord:<@!123456789> "]);
|
||||
expect(result.normalizeEntry?.(" discord:<@!123456789> ")).toBe("123456789");
|
||||
expect(result.normalizeEntry?.(" user:987654321 ")).toBe("987654321");
|
||||
});
|
||||
});
|
||||
|
||||
describe("discordPlugin groups", () => {
|
||||
it("uses plugin-owned group policy resolvers", () => {
|
||||
const cfg = {
|
||||
|
||||
@@ -46,7 +46,6 @@ import {
|
||||
resolveDiscordGroupRequireMention,
|
||||
resolveDiscordGroupToolPolicy,
|
||||
} from "./group-policy.js";
|
||||
import { monitorDiscordProvider } from "./monitor.js";
|
||||
import {
|
||||
looksLikeDiscordTargetId,
|
||||
normalizeDiscordMessagingTarget,
|
||||
@@ -78,6 +77,15 @@ type DiscordSendFn = ReturnType<
|
||||
typeof getDiscordRuntime
|
||||
>["channel"]["discord"]["sendMessageDiscord"];
|
||||
|
||||
let discordProviderRuntimePromise:
|
||||
| Promise<typeof import("./monitor/provider.runtime.js")>
|
||||
| undefined;
|
||||
|
||||
async function loadDiscordProviderRuntime() {
|
||||
discordProviderRuntimePromise ??= import("./monitor/provider.runtime.js");
|
||||
return await discordProviderRuntimePromise;
|
||||
}
|
||||
|
||||
const meta = getChatChannelMeta("discord");
|
||||
const REQUIRED_DISCORD_PERMISSIONS = ["ViewChannel", "SendMessages"] as const;
|
||||
|
||||
@@ -679,7 +687,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
}
|
||||
}
|
||||
ctx.log?.info(`[${account.accountId}] starting provider${discordBotLabel}`);
|
||||
return monitorDiscordProvider({
|
||||
return (await loadDiscordProviderRuntime()).monitorDiscordProvider({
|
||||
token,
|
||||
accountId: account.accountId,
|
||||
config: ctx.cfg,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ChannelType, MessageType } from "@buape/carbon";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
dispatchMock,
|
||||
loadConfigMock,
|
||||
readAllowFromStoreMock,
|
||||
sendMock,
|
||||
updateLastRouteMock,
|
||||
@@ -24,6 +25,7 @@ beforeEach(() => {
|
||||
});
|
||||
readAllowFromStoreMock.mockClear().mockResolvedValue([]);
|
||||
upsertPairingRequestMock.mockClear().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
loadConfigMock.mockClear().mockReturnValue(BASE_CFG);
|
||||
});
|
||||
|
||||
const BASE_CFG: Config = {
|
||||
@@ -33,6 +35,9 @@ const BASE_CFG: Config = {
|
||||
workspace: "/tmp/openclaw",
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
inbound: { debounceMs: 0 },
|
||||
},
|
||||
session: { store: "/tmp/openclaw-sessions.json" },
|
||||
};
|
||||
|
||||
@@ -80,6 +85,7 @@ function createHandlerBaseConfig(
|
||||
}
|
||||
|
||||
async function createDmHandler(opts: { cfg: Config; runtimeError?: (err: unknown) => void }) {
|
||||
loadConfigMock.mockReturnValue(opts.cfg);
|
||||
return createDiscordMessageHandler(createHandlerBaseConfig(opts.cfg, opts.runtimeError));
|
||||
}
|
||||
|
||||
@@ -92,9 +98,10 @@ function createDmClient() {
|
||||
} as unknown as Client;
|
||||
}
|
||||
|
||||
async function createCategoryGuildHandler() {
|
||||
async function createCategoryGuildHandler(runtimeError?: (err: unknown) => void) {
|
||||
loadConfigMock.mockReturnValue(CATEGORY_GUILD_CFG);
|
||||
return createDiscordMessageHandler({
|
||||
...createHandlerBaseConfig(CATEGORY_GUILD_CFG),
|
||||
...createHandlerBaseConfig(CATEGORY_GUILD_CFG, runtimeError),
|
||||
guildEntries: {
|
||||
"*": { requireMention: false, channels: { c1: { allow: true } } },
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ export const updateLastRouteMock: MockFn = vi.fn();
|
||||
export const dispatchMock: MockFn = vi.fn();
|
||||
export const readAllowFromStoreMock: MockFn = vi.fn();
|
||||
export const upsertPairingRequestMock: MockFn = vi.fn();
|
||||
export const loadConfigMock: MockFn = vi.fn();
|
||||
|
||||
vi.mock("./send.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./send.js")>();
|
||||
@@ -52,6 +53,8 @@ vi.mock("openclaw/plugin-sdk/config-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/config-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
loadConfig: (...args: unknown[]) => loadConfigMock(...args),
|
||||
readSessionUpdatedAt: vi.fn(() => undefined),
|
||||
resolveStorePath: vi.fn(() => "/tmp/openclaw-sessions.json"),
|
||||
updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args),
|
||||
resolveSessionKey: vi.fn(),
|
||||
|
||||
@@ -156,6 +156,22 @@ describe("createDiscordMessageHandler queue behavior", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("drops duplicate inbound message deliveries before they reach preflight", async () => {
|
||||
preflightDiscordMessageMock.mockReset();
|
||||
processDiscordMessageMock.mockReset();
|
||||
|
||||
const handler = createHandlerWithDefaultPreflight();
|
||||
const duplicate = createMessageData("m-dup");
|
||||
|
||||
await expect(handler(duplicate as never, {} as never)).resolves.toBeUndefined();
|
||||
await expect(handler(duplicate as never, {} as never)).resolves.toBeUndefined();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(processDiscordMessageMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(preflightDiscordMessageMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("applies explicit inbound worker timeout to queued runs so stalled runs do not block the queue", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
shouldDebounceTextInbound,
|
||||
} from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createDedupeCache } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { danger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { buildDiscordInboundJob } from "./inbound-job.js";
|
||||
import { createDiscordInboundWorker } from "./inbound-worker.js";
|
||||
@@ -30,6 +31,27 @@ export type DiscordMessageHandlerWithLifecycle = DiscordMessageHandler & {
|
||||
deactivate: () => void;
|
||||
};
|
||||
|
||||
const RECENT_DISCORD_MESSAGE_TTL_MS = 5 * 60_000;
|
||||
const RECENT_DISCORD_MESSAGE_MAX = 5000;
|
||||
|
||||
function buildDiscordInboundDedupeKey(params: {
|
||||
accountId: string;
|
||||
data: DiscordMessageEvent;
|
||||
}): string | null {
|
||||
const messageId = params.data.message?.id?.trim();
|
||||
if (!messageId) {
|
||||
return null;
|
||||
}
|
||||
const channelId = resolveDiscordMessageChannelId({
|
||||
message: params.data.message,
|
||||
eventChannelId: params.data.channel_id,
|
||||
});
|
||||
if (!channelId) {
|
||||
return null;
|
||||
}
|
||||
return `${params.accountId}:${channelId}:${messageId}`;
|
||||
}
|
||||
|
||||
export function createDiscordMessageHandler(
|
||||
params: DiscordMessageHandlerParams,
|
||||
): DiscordMessageHandlerWithLifecycle {
|
||||
@@ -48,6 +70,10 @@ export function createDiscordMessageHandler(
|
||||
abortSignal: params.abortSignal,
|
||||
runTimeoutMs: params.workerRunTimeoutMs,
|
||||
});
|
||||
const recentInboundMessages = createDedupeCache({
|
||||
ttlMs: RECENT_DISCORD_MESSAGE_TTL_MS,
|
||||
maxSize: RECENT_DISCORD_MESSAGE_MAX,
|
||||
});
|
||||
|
||||
const { debouncer } = createChannelInboundDebouncer<{
|
||||
data: DiscordMessageEvent;
|
||||
@@ -173,6 +199,13 @@ export function createDiscordMessageHandler(
|
||||
if (params.botUserId && msgAuthorId === params.botUserId) {
|
||||
return;
|
||||
}
|
||||
const dedupeKey = buildDiscordInboundDedupeKey({
|
||||
accountId: params.accountId,
|
||||
data,
|
||||
});
|
||||
if (dedupeKey && recentInboundMessages.check(dedupeKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await debouncer.enqueue({ data, client, abortSignal: options?.abortSignal });
|
||||
} catch (err) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { buildPluginBindingApprovalCustomId } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { peekSystemEvents, resetSystemEventsForTest } from "../../../../src/infra/system-events.js";
|
||||
import {
|
||||
clearDiscordComponentEntries,
|
||||
registerDiscordComponentEntries,
|
||||
@@ -50,7 +51,6 @@ import {
|
||||
|
||||
const readAllowFromStoreMock = vi.hoisted(() => vi.fn());
|
||||
const upsertPairingRequestMock = vi.hoisted(() => vi.fn());
|
||||
const enqueueSystemEventMock = vi.hoisted(() => vi.fn());
|
||||
const dispatchReplyMock = vi.hoisted(() => vi.fn());
|
||||
const recordInboundSessionMock = vi.hoisted(() => vi.fn());
|
||||
const readSessionUpdatedAtMock = vi.hoisted(() => vi.fn());
|
||||
@@ -90,14 +90,6 @@ vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/infra-runtime")>();
|
||||
return {
|
||||
...actual,
|
||||
enqueueSystemEvent: (...args: unknown[]) => enqueueSystemEventMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/reply-runtime", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("openclaw/plugin-sdk/reply-runtime")>();
|
||||
return {
|
||||
@@ -147,6 +139,12 @@ vi.mock("openclaw/plugin-sdk/plugin-runtime", async (importOriginal) => {
|
||||
|
||||
describe("agent components", () => {
|
||||
const createCfg = (): OpenClawConfig => ({}) as OpenClawConfig;
|
||||
const dmSessionKey = buildAgentSessionKey({
|
||||
agentId: "main",
|
||||
channel: "discord",
|
||||
accountId: "default",
|
||||
peer: { kind: "direct", id: "123456789" },
|
||||
});
|
||||
|
||||
const createBaseDmInteraction = (overrides: Record<string, unknown> = {}) => {
|
||||
const reply = vi.fn().mockResolvedValue(undefined);
|
||||
@@ -187,7 +185,7 @@ describe("agent components", () => {
|
||||
beforeEach(() => {
|
||||
readAllowFromStoreMock.mockClear().mockResolvedValue([]);
|
||||
upsertPairingRequestMock.mockClear().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
enqueueSystemEventMock.mockClear();
|
||||
resetSystemEventsForTest();
|
||||
});
|
||||
|
||||
it("sends pairing reply when DM sender is not allowlisted", async () => {
|
||||
@@ -207,7 +205,7 @@ describe("agent components", () => {
|
||||
const code = pairingText.match(/Pairing code:\s*([A-Z2-9]{8})/)?.[1];
|
||||
expect(code).toBeDefined();
|
||||
expect(pairingText).toContain(`openclaw pairing approve discord ${code}`);
|
||||
expect(enqueueSystemEventMock).not.toHaveBeenCalled();
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
|
||||
expect(readAllowFromStoreMock).toHaveBeenCalledWith("discord", "default");
|
||||
});
|
||||
|
||||
@@ -226,7 +224,7 @@ describe("agent components", () => {
|
||||
content: "You are not authorized to use this button.",
|
||||
ephemeral: true,
|
||||
});
|
||||
expect(enqueueSystemEventMock).not.toHaveBeenCalled();
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -243,7 +241,9 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(enqueueSystemEventMock).toHaveBeenCalled();
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
"[Discord component: hello clicked by Alice#1234 (123456789)]",
|
||||
]);
|
||||
expect(upsertPairingRequestMock).not.toHaveBeenCalled();
|
||||
expect(readAllowFromStoreMock).toHaveBeenCalledWith("discord", "default");
|
||||
});
|
||||
@@ -261,7 +261,9 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(enqueueSystemEventMock).toHaveBeenCalled();
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
"[Discord component: hello clicked by Alice#1234 (123456789)]",
|
||||
]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -281,7 +283,7 @@ describe("agent components", () => {
|
||||
content: "DM interactions are disabled.",
|
||||
ephemeral: true,
|
||||
});
|
||||
expect(enqueueSystemEventMock).not.toHaveBeenCalled();
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -299,7 +301,9 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(enqueueSystemEventMock).toHaveBeenCalled();
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
"[Discord select menu: hello interacted by Alice#1234 (123456789) (selected: alpha)]",
|
||||
]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -316,10 +320,9 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(enqueueSystemEventMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining("hello_cid"),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
"[Discord component: hello_cid clicked by Alice#1234 (123456789)]",
|
||||
]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -336,10 +339,9 @@ describe("agent components", () => {
|
||||
|
||||
expect(defer).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith({ content: "✓", ephemeral: true });
|
||||
expect(enqueueSystemEventMock).toHaveBeenCalledWith(
|
||||
expect.stringContaining("hello%2G"),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(peekSystemEvents(dmSessionKey)).toEqual([
|
||||
"[Discord component: hello%2G clicked by Alice#1234 (123456789)]",
|
||||
]);
|
||||
expect(readAllowFromStoreMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -498,7 +500,7 @@ describe("discord component interactions", () => {
|
||||
lastDispatchCtx = undefined;
|
||||
readAllowFromStoreMock.mockClear().mockResolvedValue([]);
|
||||
upsertPairingRequestMock.mockClear().mockResolvedValue({ code: "PAIRCODE", created: true });
|
||||
enqueueSystemEventMock.mockClear();
|
||||
resetSystemEventsForTest();
|
||||
dispatchReplyMock.mockClear().mockImplementation(async (params: DispatchParams) => {
|
||||
lastDispatchCtx = params.ctx;
|
||||
await params.dispatcherOptions.deliver({ text: "ok" });
|
||||
|
||||
12
extensions/discord/src/monitor/provider-session.runtime.ts
Normal file
12
extensions/discord/src/monitor/provider-session.runtime.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export { getAcpSessionManager, isAcpRuntimeError } from "openclaw/plugin-sdk/acp-runtime";
|
||||
export {
|
||||
resolveThreadBindingIdleTimeoutMs,
|
||||
resolveThreadBindingMaxAgeMs,
|
||||
resolveThreadBindingsEnabled,
|
||||
} from "openclaw/plugin-sdk/conversation-runtime";
|
||||
export { createDiscordMessageHandler } from "./message-handler.js";
|
||||
export {
|
||||
createNoopThreadBindingManager,
|
||||
createThreadBindingManager,
|
||||
reconcileAcpThreadBindingsOnStartup,
|
||||
} from "./thread-bindings.js";
|
||||
1
extensions/discord/src/monitor/provider.runtime.ts
Normal file
1
extensions/discord/src/monitor/provider.runtime.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { monitorDiscordProvider } from "./provider.js";
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
import { GatewayCloseCodes, type GatewayPlugin } from "@buape/carbon/gateway";
|
||||
import { VoicePlugin } from "@buape/carbon/voice";
|
||||
import { Routes } from "discord-api-types/v10";
|
||||
import { getAcpSessionManager } from "openclaw/plugin-sdk/acp-runtime";
|
||||
import { isAcpRuntimeError } from "openclaw/plugin-sdk/acp-runtime";
|
||||
import {
|
||||
listNativeCommandSpecsForConfig,
|
||||
listSkillCommandsForAgents,
|
||||
@@ -32,15 +30,9 @@ import {
|
||||
resolveDefaultGroupPolicy,
|
||||
warnMissingProviderGroupPolicyFallbackOnce,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
resolveThreadBindingIdleTimeoutMs,
|
||||
resolveThreadBindingMaxAgeMs,
|
||||
resolveThreadBindingsEnabled,
|
||||
} from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { createConnectedChannelStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { getPluginCommandSpecs } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import type { HistoryEntry } from "openclaw/plugin-sdk/reply-history";
|
||||
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import {
|
||||
danger,
|
||||
@@ -81,7 +73,6 @@ import {
|
||||
DiscordThreadUpdateListener,
|
||||
registerDiscordListener,
|
||||
} from "./listeners.js";
|
||||
import { createDiscordMessageHandler } from "./message-handler.js";
|
||||
import {
|
||||
createDiscordCommandArgFallbackButton,
|
||||
createDiscordModelPickerFallbackButton,
|
||||
@@ -94,11 +85,6 @@ import { runDiscordGatewayLifecycle } from "./provider.lifecycle.js";
|
||||
import { resolveDiscordRestFetch } from "./rest-fetch.js";
|
||||
import { formatDiscordStartupStatusMessage } from "./startup-status.js";
|
||||
import type { DiscordMonitorStatusSink } from "./status.js";
|
||||
import {
|
||||
createNoopThreadBindingManager,
|
||||
createThreadBindingManager,
|
||||
reconcileAcpThreadBindingsOnStartup,
|
||||
} from "./thread-bindings.js";
|
||||
import { formatThreadBindingDurationLabel } from "./thread-bindings.messages.js";
|
||||
|
||||
export type MonitorDiscordOpts = {
|
||||
@@ -116,14 +102,39 @@ export type MonitorDiscordOpts = {
|
||||
type DiscordVoiceManager = import("../voice/manager.js").DiscordVoiceManager;
|
||||
|
||||
type DiscordVoiceRuntimeModule = typeof import("../voice/manager.runtime.js");
|
||||
type DiscordProviderSessionRuntimeModule = typeof import("./provider-session.runtime.js");
|
||||
|
||||
let discordVoiceRuntimePromise: Promise<DiscordVoiceRuntimeModule> | undefined;
|
||||
let discordProviderSessionRuntimePromise: Promise<DiscordProviderSessionRuntimeModule> | undefined;
|
||||
|
||||
async function loadDiscordVoiceRuntime(): Promise<DiscordVoiceRuntimeModule> {
|
||||
discordVoiceRuntimePromise ??= import("../voice/manager.runtime.js");
|
||||
return await discordVoiceRuntimePromise;
|
||||
}
|
||||
|
||||
async function loadDiscordProviderSessionRuntime(): Promise<DiscordProviderSessionRuntimeModule> {
|
||||
discordProviderSessionRuntimePromise ??= import("./provider-session.runtime.js");
|
||||
return await discordProviderSessionRuntimePromise;
|
||||
}
|
||||
|
||||
function normalizeBooleanForTesting(value: unknown): boolean | undefined {
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveThreadBindingsEnabledForTesting(params: {
|
||||
channelEnabledRaw: unknown;
|
||||
sessionEnabledRaw: unknown;
|
||||
}): boolean {
|
||||
return (
|
||||
normalizeBooleanForTesting(params.channelEnabledRaw) ??
|
||||
normalizeBooleanForTesting(params.sessionEnabledRaw) ??
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
function formatThreadBindingDurationForConfigLabel(durationMs: number): string {
|
||||
const label = formatThreadBindingDurationLabel(durationMs);
|
||||
return label === "disabled" ? "off" : label;
|
||||
@@ -170,11 +181,15 @@ function isLegacyMissingSessionError(message: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function classifyAcpStatusProbeError(params: { error: unknown; isStaleRunning: boolean }): {
|
||||
function classifyAcpStatusProbeError(params: {
|
||||
error: unknown;
|
||||
isStaleRunning: boolean;
|
||||
isAcpRuntimeError: DiscordProviderSessionRuntimeModule["isAcpRuntimeError"];
|
||||
}): {
|
||||
status: "stale" | "uncertain";
|
||||
reason: string;
|
||||
} {
|
||||
if (isAcpRuntimeError(params.error) && params.error.code === "ACP_SESSION_INIT_FAILED") {
|
||||
if (params.isAcpRuntimeError(params.error) && params.error.code === "ACP_SESSION_INIT_FAILED") {
|
||||
return { status: "stale", reason: "session-init-failed" };
|
||||
}
|
||||
|
||||
@@ -194,6 +209,7 @@ async function probeDiscordAcpBindingHealth(params: {
|
||||
storedState?: "idle" | "running" | "error";
|
||||
lastActivityAt?: number;
|
||||
}): Promise<{ status: "healthy" | "stale" | "uncertain"; reason?: string }> {
|
||||
const { getAcpSessionManager, isAcpRuntimeError } = await loadDiscordProviderSessionRuntime();
|
||||
const manager = getAcpSessionManager();
|
||||
const statusProbeAbortController = new AbortController();
|
||||
const statusPromise = manager
|
||||
@@ -236,6 +252,7 @@ async function probeDiscordAcpBindingHealth(params: {
|
||||
return classifyAcpStatusProbeError({
|
||||
error: result.error,
|
||||
isStaleRunning,
|
||||
isAcpRuntimeError,
|
||||
});
|
||||
}
|
||||
if (result.status.state === "error") {
|
||||
@@ -477,17 +494,19 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
const replyToMode = opts.replyToMode ?? discordCfg.replyToMode ?? "off";
|
||||
const dmEnabled = dmConfig?.enabled ?? true;
|
||||
const dmPolicy = discordCfg.dmPolicy ?? dmConfig?.policy ?? "pairing";
|
||||
const threadBindingIdleTimeoutMs = resolveThreadBindingIdleTimeoutMs({
|
||||
channelIdleHoursRaw:
|
||||
discordAccountThreadBindings?.idleHours ?? discordRootThreadBindings?.idleHours,
|
||||
sessionIdleHoursRaw: cfg.session?.threadBindings?.idleHours,
|
||||
});
|
||||
const threadBindingMaxAgeMs = resolveThreadBindingMaxAgeMs({
|
||||
const discordProviderSessionRuntime = await loadDiscordProviderSessionRuntime();
|
||||
const threadBindingIdleTimeoutMs =
|
||||
discordProviderSessionRuntime.resolveThreadBindingIdleTimeoutMs({
|
||||
channelIdleHoursRaw:
|
||||
discordAccountThreadBindings?.idleHours ?? discordRootThreadBindings?.idleHours,
|
||||
sessionIdleHoursRaw: cfg.session?.threadBindings?.idleHours,
|
||||
});
|
||||
const threadBindingMaxAgeMs = discordProviderSessionRuntime.resolveThreadBindingMaxAgeMs({
|
||||
channelMaxAgeHoursRaw:
|
||||
discordAccountThreadBindings?.maxAgeHours ?? discordRootThreadBindings?.maxAgeHours,
|
||||
sessionMaxAgeHoursRaw: cfg.session?.threadBindings?.maxAgeHours,
|
||||
});
|
||||
const threadBindingsEnabled = resolveThreadBindingsEnabled({
|
||||
const threadBindingsEnabled = discordProviderSessionRuntime.resolveThreadBindingsEnabled({
|
||||
channelEnabledRaw: discordAccountThreadBindings?.enabled ?? discordRootThreadBindings?.enabled,
|
||||
sessionEnabledRaw: cfg.session?.threadBindings?.enabled,
|
||||
});
|
||||
@@ -591,17 +610,17 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
}
|
||||
const voiceManagerRef: { current: DiscordVoiceManager | null } = { current: null };
|
||||
const threadBindings = threadBindingsEnabled
|
||||
? createThreadBindingManager({
|
||||
? discordProviderSessionRuntime.createThreadBindingManager({
|
||||
accountId: account.accountId,
|
||||
token,
|
||||
cfg,
|
||||
idleTimeoutMs: threadBindingIdleTimeoutMs,
|
||||
maxAgeMs: threadBindingMaxAgeMs,
|
||||
})
|
||||
: createNoopThreadBindingManager(account.accountId);
|
||||
: discordProviderSessionRuntime.createNoopThreadBindingManager(account.accountId);
|
||||
if (threadBindingsEnabled) {
|
||||
const uncertainProbeKeys = new Set<string>();
|
||||
const reconciliation = await reconcileAcpThreadBindingsOnStartup({
|
||||
const reconciliation = await discordProviderSessionRuntime.reconcileAcpThreadBindingsOnStartup({
|
||||
cfg,
|
||||
accountId: account.accountId,
|
||||
sendFarewell: false,
|
||||
@@ -827,7 +846,10 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
});
|
||||
|
||||
const logger = createSubsystemLogger("discord/monitor");
|
||||
const guildHistories = new Map<string, HistoryEntry[]>();
|
||||
const guildHistories = new Map<
|
||||
string,
|
||||
import("openclaw/plugin-sdk/reply-history").HistoryEntry[]
|
||||
>();
|
||||
let botUserId: string | undefined;
|
||||
let botUserName: string | undefined;
|
||||
let voiceManager: DiscordVoiceManager | null = null;
|
||||
@@ -899,7 +921,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
registerDiscordListener(client.listeners, new DiscordVoiceReadyListener(voiceManager));
|
||||
}
|
||||
|
||||
const messageHandler = createDiscordMessageHandler({
|
||||
const messageHandler = discordProviderSessionRuntime.createDiscordMessageHandler({
|
||||
cfg,
|
||||
discordConfig: discordCfg,
|
||||
accountId: account.accountId,
|
||||
@@ -1034,5 +1056,5 @@ export const __testing = {
|
||||
resolveDiscordRuntimeGroupPolicy: resolveOpenProviderRuntimeGroupPolicy,
|
||||
resolveDefaultGroupPolicy,
|
||||
resolveDiscordRestFetch,
|
||||
resolveThreadBindingsEnabled,
|
||||
resolveThreadBindingsEnabled: resolveThreadBindingsEnabledForTesting,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
createAttachedChannelResultAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-send-result";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveOutboundSendDep, type OutboundIdentity } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import {
|
||||
resolveOutboundSendDep,
|
||||
type OutboundIdentity,
|
||||
} from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import {
|
||||
resolvePayloadMediaUrls,
|
||||
sendPayloadMediaSequenceOrFallback,
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
import { normalizeChatType } from "openclaw/plugin-sdk/account-resolution";
|
||||
import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
type DiscordSessionKeyContext = {
|
||||
ChatType?: string;
|
||||
From?: string;
|
||||
SenderId?: string;
|
||||
};
|
||||
|
||||
function normalizeDiscordChatType(raw?: string): "direct" | "group" | "channel" | undefined {
|
||||
const normalized = (raw ?? "").trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
if (normalized === "dm") {
|
||||
return "direct";
|
||||
}
|
||||
if (normalized === "group" || normalized === "channel" || normalized === "direct") {
|
||||
return normalized;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function normalizeExplicitDiscordSessionKey(
|
||||
sessionKey: string,
|
||||
ctx: Pick<MsgContext, "ChatType" | "From" | "SenderId">,
|
||||
ctx: DiscordSessionKeyContext,
|
||||
): string {
|
||||
let normalized = sessionKey.trim().toLowerCase();
|
||||
if (normalizeChatType(ctx.ChatType) !== "direct") {
|
||||
if (normalizeDiscordChatType(ctx.ChatType) !== "direct") {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
4
extensions/discord/timeouts.ts
Normal file
4
extensions/discord/timeouts.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
DISCORD_DEFAULT_INBOUND_WORKER_TIMEOUT_MS,
|
||||
DISCORD_DEFAULT_LISTENER_TIMEOUT_MS,
|
||||
} from "./src/monitor/timeouts.js";
|
||||
@@ -15,6 +15,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
createChannelDirectoryAdapter,
|
||||
createRuntimeDirectoryLiveAdapter,
|
||||
} from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "../runtime-api.js";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
|
||||
@@ -48,6 +48,26 @@ describe("feishu directory (config-backed)", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("normalizes spaced provider-prefixed peer entries", async () => {
|
||||
resolveFeishuAccountMock.mockReturnValueOnce({
|
||||
configured: false,
|
||||
config: {
|
||||
allowFrom: [" feishu:user:ou_alice "],
|
||||
dms: {
|
||||
" lark:dm:ou_carla ": {},
|
||||
},
|
||||
groups: {},
|
||||
groupAllowFrom: [],
|
||||
},
|
||||
});
|
||||
|
||||
const peers = await listFeishuDirectoryPeers({ cfg });
|
||||
expect(peers).toEqual([
|
||||
{ kind: "user", id: "ou_alice" },
|
||||
{ kind: "user", id: "ou_carla" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("merges groups map + groupAllowFrom into group entries", async () => {
|
||||
const groups = await listFeishuDirectoryGroups({ cfg });
|
||||
expect(groups).toEqual([
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -55,4 +55,32 @@ describe("googlechat directory", () => {
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes spaced provider-prefixed dm allowlist entries", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
googlechat: {
|
||||
serviceAccount: { client_email: "bot@example.com" },
|
||||
dm: { allowFrom: [" users/alice ", " googlechat:user:Bob@Example.com "] },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const directory = expectDirectorySurface(googlechatPlugin.directory);
|
||||
|
||||
await expect(
|
||||
directory.listPeers({
|
||||
cfg,
|
||||
accountId: undefined,
|
||||
query: undefined,
|
||||
limit: undefined,
|
||||
runtime: runtimeEnv,
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
{ kind: "user", id: "users/alice" },
|
||||
{ kind: "user", id: "users/bob@example.com" },
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { imessagePlugin } from "./channel.js";
|
||||
|
||||
function requireIMessageSendText() {
|
||||
const sendText = imessagePlugin.outbound?.sendText;
|
||||
if (!sendText) {
|
||||
throw new Error("imessage outbound.sendText unavailable");
|
||||
}
|
||||
return sendText;
|
||||
}
|
||||
|
||||
function requireIMessageSendMedia() {
|
||||
const sendMedia = imessagePlugin.outbound?.sendMedia;
|
||||
if (!sendMedia) {
|
||||
throw new Error("imessage outbound.sendMedia unavailable");
|
||||
}
|
||||
return sendMedia;
|
||||
}
|
||||
|
||||
describe("imessagePlugin outbound", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
@@ -12,10 +28,9 @@ describe("imessagePlugin outbound", () => {
|
||||
|
||||
it("forwards replyToId on direct sendText adapter path", async () => {
|
||||
const sendIMessage = vi.fn().mockResolvedValue({ messageId: "m-text" });
|
||||
const sendText = imessagePlugin.outbound?.sendText;
|
||||
expect(sendText).toBeDefined();
|
||||
const sendText = requireIMessageSendText();
|
||||
|
||||
const result = await sendText!({
|
||||
const result = await sendText({
|
||||
cfg,
|
||||
to: "chat_id:12",
|
||||
text: "hello",
|
||||
@@ -38,10 +53,9 @@ describe("imessagePlugin outbound", () => {
|
||||
|
||||
it("forwards replyToId on direct sendMedia adapter path", async () => {
|
||||
const sendIMessage = vi.fn().mockResolvedValue({ messageId: "m-media" });
|
||||
const sendMedia = imessagePlugin.outbound?.sendMedia;
|
||||
expect(sendMedia).toBeDefined();
|
||||
const sendMedia = requireIMessageSendMedia();
|
||||
|
||||
const result = await sendMedia!({
|
||||
const result = await sendMedia({
|
||||
cfg,
|
||||
to: "chat_id:77",
|
||||
text: "caption",
|
||||
@@ -66,11 +80,10 @@ describe("imessagePlugin outbound", () => {
|
||||
|
||||
it("forwards mediaLocalRoots on direct sendMedia adapter path", async () => {
|
||||
const sendIMessage = vi.fn().mockResolvedValue({ messageId: "m-media-local" });
|
||||
const sendMedia = imessagePlugin.outbound?.sendMedia;
|
||||
expect(sendMedia).toBeDefined();
|
||||
const sendMedia = requireIMessageSendMedia();
|
||||
const mediaLocalRoots = ["/tmp/workspace"];
|
||||
|
||||
const result = await sendMedia!({
|
||||
const result = await sendMedia({
|
||||
cfg,
|
||||
to: "chat_id:88",
|
||||
text: "caption",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { PAIRING_APPROVED_MESSAGE, resolveChannelMediaMaxBytes } from "../runtime-api.js";
|
||||
import type { ResolvedIMessageAccount } from "./accounts.js";
|
||||
import { monitorIMessageProvider } from "./monitor.js";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
|
||||
import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
|
||||
import { buildPassiveProbedChannelStatusSummary } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { buildOutboundBaseSessionKey, type RoutePeer } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
collectStatusIssuesFromLastError,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { resolveOutboundSendDep, type OutboundSendDeps } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import {
|
||||
createDirectTextMediaOutbound,
|
||||
createScopedChannelMediaMaxBytesResolver,
|
||||
} from "openclaw/plugin-sdk/media-runtime";
|
||||
import {
|
||||
resolveOutboundSendDep,
|
||||
type OutboundSendDeps,
|
||||
} from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { sendMessageIMessage } from "./send.js";
|
||||
|
||||
function resolveIMessageSender(deps: OutboundSendDeps | undefined) {
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
"dependencies": {
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -78,7 +78,9 @@ describe("irc setup wizard", () => {
|
||||
});
|
||||
|
||||
const promptAllowFrom = ircConfigureAdapter.dmPolicy?.promptAllowFrom;
|
||||
expect(promptAllowFrom).toBeTypeOf("function");
|
||||
if (!promptAllowFrom) {
|
||||
throw new Error("promptAllowFrom unavailable");
|
||||
}
|
||||
|
||||
const cfg: CoreConfig = {
|
||||
channels: {
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import { createStartAccountContext } from "../../../test/helpers/extensions/start-account-context.js";
|
||||
import type { OpenClawConfig, PluginRuntime, ResolvedLineAccount } from "../api.js";
|
||||
import type { PluginRuntime, ResolvedLineAccount } from "../api.js";
|
||||
import { linePlugin } from "./channel.js";
|
||||
import { setLineRuntime } from "./runtime.js";
|
||||
|
||||
function createRuntime() {
|
||||
const probeLineBot = vi.fn(async () => ({ ok: false }));
|
||||
const monitorLineProvider = vi.fn(async () => ({
|
||||
account: { accountId: "default" },
|
||||
handleWebhook: async () => {},
|
||||
@@ -16,7 +14,6 @@ function createRuntime() {
|
||||
const runtime = {
|
||||
channel: {
|
||||
line: {
|
||||
probeLineBot,
|
||||
monitorLineProvider,
|
||||
},
|
||||
},
|
||||
@@ -25,7 +22,7 @@ function createRuntime() {
|
||||
},
|
||||
} as unknown as PluginRuntime;
|
||||
|
||||
return { runtime, probeLineBot, monitorLineProvider };
|
||||
return { runtime, monitorLineProvider };
|
||||
}
|
||||
|
||||
function createAccount(params: { token: string; secret: string }): ResolvedLineAccount {
|
||||
@@ -39,53 +36,49 @@ function createAccount(params: { token: string; secret: string }): ResolvedLineA
|
||||
};
|
||||
}
|
||||
|
||||
function startLineAccount(params: { account: ResolvedLineAccount; abortSignal?: AbortSignal }) {
|
||||
const { runtime, monitorLineProvider } = createRuntime();
|
||||
setLineRuntime(runtime);
|
||||
return {
|
||||
monitorLineProvider,
|
||||
task: linePlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: params.account,
|
||||
abortSignal: params.abortSignal,
|
||||
}),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
describe("linePlugin gateway.startAccount", () => {
|
||||
it("fails startup when channel secret is missing", async () => {
|
||||
const { runtime, monitorLineProvider } = createRuntime();
|
||||
setLineRuntime(runtime);
|
||||
const { monitorLineProvider, task } = startLineAccount({
|
||||
account: createAccount({ token: "token", secret: " " }),
|
||||
});
|
||||
|
||||
await expect(
|
||||
linePlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: createAccount({ token: "token", secret: " " }),
|
||||
runtime: createRuntimeEnv(),
|
||||
}),
|
||||
),
|
||||
).rejects.toThrow(
|
||||
await expect(task).rejects.toThrow(
|
||||
'LINE webhook mode requires a non-empty channel secret for account "default".',
|
||||
);
|
||||
expect(monitorLineProvider).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fails startup when channel access token is missing", async () => {
|
||||
const { runtime, monitorLineProvider } = createRuntime();
|
||||
setLineRuntime(runtime);
|
||||
const { monitorLineProvider, task } = startLineAccount({
|
||||
account: createAccount({ token: " ", secret: "secret" }),
|
||||
});
|
||||
|
||||
await expect(
|
||||
linePlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: createAccount({ token: " ", secret: "secret" }),
|
||||
runtime: createRuntimeEnv(),
|
||||
}),
|
||||
),
|
||||
).rejects.toThrow(
|
||||
await expect(task).rejects.toThrow(
|
||||
'LINE webhook mode requires a non-empty channel access token for account "default".',
|
||||
);
|
||||
expect(monitorLineProvider).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("starts provider when token and secret are present", async () => {
|
||||
const { runtime, monitorLineProvider } = createRuntime();
|
||||
setLineRuntime(runtime);
|
||||
|
||||
const abort = new AbortController();
|
||||
const task = linePlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: createAccount({ token: "token", secret: "secret" }),
|
||||
runtime: createRuntimeEnv(),
|
||||
abortSignal: abort.signal,
|
||||
}),
|
||||
);
|
||||
const { monitorLineProvider, task } = startLineAccount({
|
||||
account: createAccount({ token: "token", secret: "secret" }),
|
||||
abortSignal: abort.signal,
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(monitorLineProvider).toHaveBeenCalledWith(
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"dependencies": {
|
||||
"@matrix-org/matrix-sdk-crypto-nodejs": "^0.4.0",
|
||||
"fake-indexeddb": "^6.2.5",
|
||||
"markdown-it": "14.1.0",
|
||||
"matrix-js-sdk": "^40.1.0",
|
||||
"markdown-it": "14.1.1",
|
||||
"matrix-js-sdk": "41.2.0-rc.0",
|
||||
"music-metadata": "^11.11.2",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
@@ -17,6 +17,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
listResolvedDirectoryEntriesFromSources,
|
||||
} from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { buildTrafficStatusSummary } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { matrixMessageActions } from "./actions.js";
|
||||
import { MatrixConfigSchema } from "./config-schema.js";
|
||||
import {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "OpenClaw Mattermost channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"ws": "^8.19.0",
|
||||
"ws": "^8.20.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -13,6 +13,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -21,6 +21,46 @@ function getDescribedActions(cfg: OpenClawConfig): string[] {
|
||||
return [...(mattermostPlugin.actions?.describeMessageTool?.({ cfg })?.actions ?? [])];
|
||||
}
|
||||
|
||||
function requireMattermostNormalizeTarget() {
|
||||
const normalize = mattermostPlugin.messaging?.normalizeTarget;
|
||||
if (!normalize) {
|
||||
throw new Error("mattermost messaging.normalizeTarget missing");
|
||||
}
|
||||
return normalize;
|
||||
}
|
||||
|
||||
function requireMattermostPairingNormalizer() {
|
||||
const normalize = mattermostPlugin.pairing?.normalizeAllowEntry;
|
||||
if (!normalize) {
|
||||
throw new Error("mattermost pairing.normalizeAllowEntry missing");
|
||||
}
|
||||
return normalize;
|
||||
}
|
||||
|
||||
function requireMattermostReplyToModeResolver() {
|
||||
const resolveReplyToMode = mattermostPlugin.threading?.resolveReplyToMode;
|
||||
if (!resolveReplyToMode) {
|
||||
throw new Error("mattermost threading.resolveReplyToMode missing");
|
||||
}
|
||||
return resolveReplyToMode;
|
||||
}
|
||||
|
||||
function requireMattermostSendText() {
|
||||
const sendText = mattermostPlugin.outbound?.sendText;
|
||||
if (!sendText) {
|
||||
throw new Error("mattermost outbound.sendText missing");
|
||||
}
|
||||
return sendText;
|
||||
}
|
||||
|
||||
function requireMattermostSendMedia() {
|
||||
const sendMedia = mattermostPlugin.outbound?.sendMedia;
|
||||
if (!sendMedia) {
|
||||
throw new Error("mattermost outbound.sendMedia missing");
|
||||
}
|
||||
return sendMedia;
|
||||
}
|
||||
|
||||
describe("mattermostPlugin", () => {
|
||||
beforeEach(() => {
|
||||
sendMessageMattermostMock.mockReset();
|
||||
@@ -32,49 +72,34 @@ describe("mattermostPlugin", () => {
|
||||
|
||||
describe("messaging", () => {
|
||||
it("keeps @username targets", () => {
|
||||
const normalize = mattermostPlugin.messaging?.normalizeTarget;
|
||||
if (!normalize) {
|
||||
return;
|
||||
}
|
||||
const normalize = requireMattermostNormalizeTarget();
|
||||
|
||||
expect(normalize("@Alice")).toBe("@Alice");
|
||||
expect(normalize("@alice")).toBe("@alice");
|
||||
});
|
||||
|
||||
it("normalizes mattermost: prefix to user:", () => {
|
||||
const normalize = mattermostPlugin.messaging?.normalizeTarget;
|
||||
if (!normalize) {
|
||||
return;
|
||||
}
|
||||
it("normalizes spaced mattermost prefixes to user targets", () => {
|
||||
const normalize = requireMattermostNormalizeTarget();
|
||||
|
||||
expect(normalize("mattermost:USER123")).toBe("user:USER123");
|
||||
expect(normalize(" mattermost:USER123 ")).toBe("user:USER123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("pairing", () => {
|
||||
it("normalizes allowlist entries", () => {
|
||||
const normalize = mattermostPlugin.pairing?.normalizeAllowEntry;
|
||||
if (!normalize) {
|
||||
return;
|
||||
}
|
||||
const normalize = requireMattermostPairingNormalizer();
|
||||
|
||||
expect(normalize("@Alice")).toBe("alice");
|
||||
expect(normalize("user:USER123")).toBe("user123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("capabilities", () => {
|
||||
it("declares reactions support", () => {
|
||||
expect(mattermostPlugin.capabilities?.reactions).toBe(true);
|
||||
expect(normalize(" @Alice ")).toBe("alice");
|
||||
expect(normalize(" mattermost:USER123 ")).toBe("user123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("threading", () => {
|
||||
it("uses replyToMode for channel messages and keeps direct messages off", () => {
|
||||
const resolveReplyToMode = mattermostPlugin.threading?.resolveReplyToMode;
|
||||
if (!resolveReplyToMode) {
|
||||
return;
|
||||
}
|
||||
const resolveReplyToMode = requireMattermostReplyToModeResolver();
|
||||
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
@@ -305,10 +330,7 @@ describe("mattermostPlugin", () => {
|
||||
|
||||
describe("outbound", () => {
|
||||
it("forwards mediaLocalRoots on sendMedia", async () => {
|
||||
const sendMedia = mattermostPlugin.outbound?.sendMedia;
|
||||
if (!sendMedia) {
|
||||
return;
|
||||
}
|
||||
const sendMedia = requireMattermostSendMedia();
|
||||
|
||||
await sendMedia({
|
||||
to: "channel:CHAN1",
|
||||
@@ -330,10 +352,7 @@ describe("mattermostPlugin", () => {
|
||||
});
|
||||
|
||||
it("threads resolved cfg on sendText", async () => {
|
||||
const sendText = mattermostPlugin.outbound?.sendText;
|
||||
if (!sendText) {
|
||||
return;
|
||||
}
|
||||
const sendText = requireMattermostSendText();
|
||||
const cfg = {
|
||||
channels: {
|
||||
mattermost: {
|
||||
@@ -361,10 +380,7 @@ describe("mattermostPlugin", () => {
|
||||
});
|
||||
|
||||
it("uses threadId as fallback when replyToId is absent (sendText)", async () => {
|
||||
const sendText = mattermostPlugin.outbound?.sendText;
|
||||
if (!sendText) {
|
||||
return;
|
||||
}
|
||||
const sendText = requireMattermostSendText();
|
||||
|
||||
await sendText({
|
||||
to: "channel:CHAN1",
|
||||
@@ -384,10 +400,7 @@ describe("mattermostPlugin", () => {
|
||||
});
|
||||
|
||||
it("uses threadId as fallback when replyToId is absent (sendMedia)", async () => {
|
||||
const sendMedia = mattermostPlugin.outbound?.sendMedia;
|
||||
if (!sendMedia) {
|
||||
return;
|
||||
}
|
||||
const sendMedia = requireMattermostSendMedia();
|
||||
|
||||
await sendMedia({
|
||||
to: "channel:CHAN1",
|
||||
@@ -414,7 +427,7 @@ describe("mattermostPlugin", () => {
|
||||
|
||||
const formatted = formatAllowFrom({
|
||||
cfg: {} as OpenClawConfig,
|
||||
allowFrom: ["@Alice", "user:USER123", "mattermost:BOT999"],
|
||||
allowFrom: [" @Alice ", " user:USER123 ", " mattermost:BOT999 "],
|
||||
});
|
||||
expect(formatted).toEqual(["@alice", "user123", "bot999"]);
|
||||
});
|
||||
|
||||
@@ -66,16 +66,12 @@ describe("memory plugin e2e", () => {
|
||||
}) as MemoryPluginTestConfig | undefined;
|
||||
}
|
||||
|
||||
test("memory plugin registers and initializes correctly", async () => {
|
||||
// Dynamic import to avoid loading LanceDB when not testing
|
||||
test("memory plugin exports stable metadata", async () => {
|
||||
const { default: memoryPlugin } = await import("./index.js");
|
||||
|
||||
expect(memoryPlugin.id).toBe("memory-lancedb");
|
||||
expect(memoryPlugin.name).toBe("Memory (LanceDB)");
|
||||
expect(memoryPlugin.kind).toBe("memory");
|
||||
expect(memoryPlugin.configSchema).toBeDefined();
|
||||
// oxlint-disable-next-line typescript/unbound-method
|
||||
expect(memoryPlugin.register).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
test("config schema parses valid config", async () => {
|
||||
@@ -84,7 +80,6 @@ describe("memory plugin e2e", () => {
|
||||
autoRecall: true,
|
||||
});
|
||||
|
||||
expect(config).toBeDefined();
|
||||
expect(config?.embedding?.apiKey).toBe(OPENAI_API_KEY);
|
||||
expect(config?.dbPath).toBe(getDbPath());
|
||||
expect(config?.captureMaxChars).toBe(500);
|
||||
@@ -214,7 +209,9 @@ describe("memory plugin e2e", () => {
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
memoryPlugin.register(mockApi as any);
|
||||
const recallTool = registeredTools.find((t) => t.opts?.name === "memory_recall")?.tool;
|
||||
expect(recallTool).toBeDefined();
|
||||
if (!recallTool) {
|
||||
throw new Error("memory_recall tool was not registered");
|
||||
}
|
||||
await recallTool.execute("test-call-dims", { query: "hello dimensions" });
|
||||
|
||||
expect(embeddingsCreate).toHaveBeenCalledWith({
|
||||
@@ -375,8 +372,8 @@ describeLive("memory plugin live tests", () => {
|
||||
});
|
||||
|
||||
expect(storeResult.details?.action).toBe("created");
|
||||
expect(storeResult.details?.id).toBeDefined();
|
||||
const storedId = storeResult.details?.id;
|
||||
expect(storedId).toMatch(/.+/);
|
||||
|
||||
// Test recall
|
||||
const recallResult = await recallTool.execute("test-call-2", {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@lancedb/lancedb": "^0.27.0",
|
||||
"@lancedb/lancedb": "^0.27.1",
|
||||
"@sinclair/typebox": "0.34.48",
|
||||
"openai": "^6.32.0"
|
||||
},
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
"description": "OpenClaw Microsoft Teams channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@microsoft/agents-hosting": "^1.3.1",
|
||||
"@microsoft/agents-hosting": "^1.4.1",
|
||||
"express": "^5.2.1",
|
||||
"uuid": "^11.1.0"
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"openclaw": "workspace:*"
|
||||
@@ -14,6 +14,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -82,4 +82,33 @@ describe("msteams directory", () => {
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes spaced allowlist and dm entries", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
msteams: {
|
||||
allowFrom: [" user:Bob ", " Alice "],
|
||||
dms: { " Carol ": {}, "user:Dave": {} },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const directory = expectDirectorySurface(msteamsPlugin.directory);
|
||||
|
||||
await expect(
|
||||
directory.listPeers({
|
||||
cfg,
|
||||
query: undefined,
|
||||
limit: undefined,
|
||||
runtime: runtimeEnv,
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
{ kind: "user", id: "user:Bob" },
|
||||
{ kind: "user", id: "user:Alice" },
|
||||
{ kind: "user", id: "user:Carol" },
|
||||
{ kind: "user", id: "user:Dave" },
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
createRuntimeDirectoryLiveAdapter,
|
||||
listDirectoryEntriesFromSources,
|
||||
} from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import type { ChannelMessageActionName, ChannelPlugin, OpenClawConfig } from "../runtime-api.js";
|
||||
import {
|
||||
buildProbeChannelStatusSummary,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import type { ChannelOutboundAdapter } from "../runtime-api.js";
|
||||
import { createMSTeamsPollStoreFs } from "./polls.js";
|
||||
import { getMSTeamsRuntime } from "./runtime.js";
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
38
extensions/nextcloud-talk/src/channel.security.test.ts
Normal file
38
extensions/nextcloud-talk/src/channel.security.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { nextcloudTalkPlugin } from "./channel.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
describe("nextcloudTalkPlugin security", () => {
|
||||
it("normalizes trimmed dm allowlist prefixes to lowercase ids", () => {
|
||||
const resolveDmPolicy = nextcloudTalkPlugin.security?.resolveDmPolicy;
|
||||
if (!resolveDmPolicy) {
|
||||
throw new Error("resolveDmPolicy unavailable");
|
||||
}
|
||||
|
||||
const cfg = {
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
baseUrl: "https://cloud.example.com",
|
||||
botSecret: "secret",
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: [" nc:User-Id "],
|
||||
},
|
||||
},
|
||||
} as CoreConfig;
|
||||
|
||||
const result = resolveDmPolicy({
|
||||
cfg,
|
||||
account: nextcloudTalkPlugin.config.resolveAccount(cfg, "default"),
|
||||
});
|
||||
if (!result) {
|
||||
throw new Error("nextcloud-talk resolveDmPolicy returned null");
|
||||
}
|
||||
|
||||
expect(result.policy).toBe("allowlist");
|
||||
expect(result.allowFrom).toEqual([" nc:User-Id "]);
|
||||
expect(result.normalizeEntry?.(" nc:User-Id ")).toBe("user-id");
|
||||
expect(nextcloudTalkPlugin.pairing?.normalizeAllowEntry?.(" nextcloud-talk:User-Id ")).toBe(
|
||||
"user-id",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -37,14 +37,28 @@ function buildAccount(): ResolvedNextcloudTalkAccount {
|
||||
};
|
||||
}
|
||||
|
||||
function mockStartedMonitor() {
|
||||
const stop = vi.fn();
|
||||
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
|
||||
return stop;
|
||||
}
|
||||
|
||||
function startNextcloudAccount(abortSignal?: AbortSignal) {
|
||||
return nextcloudTalkPlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: buildAccount(),
|
||||
abortSignal,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("nextcloudTalkPlugin gateway.startAccount", () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("keeps startAccount pending until abort, then stops the monitor", async () => {
|
||||
const stop = vi.fn();
|
||||
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
|
||||
const stop = mockStartedMonitor();
|
||||
const { abort, task, isSettled } = startAccountAndTrackLifecycle({
|
||||
startAccount: nextcloudTalkPlugin.gateway!.startAccount!,
|
||||
account: buildAccount(),
|
||||
@@ -59,17 +73,11 @@ describe("nextcloudTalkPlugin gateway.startAccount", () => {
|
||||
});
|
||||
|
||||
it("stops immediately when startAccount receives an already-aborted signal", async () => {
|
||||
const stop = vi.fn();
|
||||
hoisted.monitorNextcloudTalkProvider.mockResolvedValue({ stop });
|
||||
const stop = mockStartedMonitor();
|
||||
const abort = new AbortController();
|
||||
abort.abort();
|
||||
|
||||
await nextcloudTalkPlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: buildAccount(),
|
||||
abortSignal: abort.signal,
|
||||
}),
|
||||
);
|
||||
await startNextcloudAccount(abort.signal);
|
||||
|
||||
expect(hoisted.monitorNextcloudTalkProvider).toHaveBeenCalledOnce();
|
||||
expect(stop).toHaveBeenCalledOnce();
|
||||
|
||||
@@ -75,7 +75,12 @@ const resolveNextcloudTalkDmPolicy = createScopedDmSecurityResolver<ResolvedNext
|
||||
resolvePolicy: (account) => account.config.dmPolicy,
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
policyPathSuffix: "dmPolicy",
|
||||
normalizeEntry: (raw) => raw.replace(/^(nextcloud-talk|nc-talk|nc):/i, "").toLowerCase(),
|
||||
normalizeEntry: (raw) =>
|
||||
raw
|
||||
.trim()
|
||||
.replace(/^(nextcloud-talk|nc-talk|nc):/i, "")
|
||||
.trim()
|
||||
.toLowerCase(),
|
||||
});
|
||||
|
||||
const collectNextcloudTalkSecurityWarnings =
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -2,6 +2,38 @@ import { describe, expect, it } from "vitest";
|
||||
import { nostrPlugin } from "./channel.js";
|
||||
import { TEST_HEX_PRIVATE_KEY, createConfiguredNostrCfg } from "./test-fixtures.js";
|
||||
|
||||
function requireNostrLooksLikeId() {
|
||||
const looksLikeId = nostrPlugin.messaging?.targetResolver?.looksLikeId;
|
||||
if (!looksLikeId) {
|
||||
throw new Error("nostr messaging.targetResolver.looksLikeId missing");
|
||||
}
|
||||
return looksLikeId;
|
||||
}
|
||||
|
||||
function requireNostrNormalizeTarget() {
|
||||
const normalize = nostrPlugin.messaging?.normalizeTarget;
|
||||
if (!normalize) {
|
||||
throw new Error("nostr messaging.normalizeTarget missing");
|
||||
}
|
||||
return normalize;
|
||||
}
|
||||
|
||||
function requireNostrPairingNormalizer() {
|
||||
const normalize = nostrPlugin.pairing?.normalizeAllowEntry;
|
||||
if (!normalize) {
|
||||
throw new Error("nostr pairing.normalizeAllowEntry missing");
|
||||
}
|
||||
return normalize;
|
||||
}
|
||||
|
||||
function requireNostrResolveDmPolicy() {
|
||||
const resolveDmPolicy = nostrPlugin.security?.resolveDmPolicy;
|
||||
if (!resolveDmPolicy) {
|
||||
throw new Error("nostr security.resolveDmPolicy missing");
|
||||
}
|
||||
return resolveDmPolicy;
|
||||
}
|
||||
|
||||
describe("nostrPlugin", () => {
|
||||
describe("meta", () => {
|
||||
it("has correct id", () => {
|
||||
@@ -30,12 +62,6 @@ describe("nostrPlugin", () => {
|
||||
});
|
||||
|
||||
describe("config adapter", () => {
|
||||
it("has required config functions", () => {
|
||||
expect(nostrPlugin.config.listAccountIds).toBeTypeOf("function");
|
||||
expect(nostrPlugin.config.resolveAccount).toBeTypeOf("function");
|
||||
expect(nostrPlugin.config.isConfigured).toBeTypeOf("function");
|
||||
});
|
||||
|
||||
it("listAccountIds returns empty array for unconfigured", () => {
|
||||
const cfg = { channels: {} };
|
||||
const ids = nostrPlugin.config.listAccountIds(cfg);
|
||||
@@ -50,45 +76,30 @@ describe("nostrPlugin", () => {
|
||||
});
|
||||
|
||||
describe("messaging", () => {
|
||||
it("has target resolver", () => {
|
||||
expect(nostrPlugin.messaging?.targetResolver?.looksLikeId).toBeTypeOf("function");
|
||||
});
|
||||
|
||||
it("recognizes npub as valid target", () => {
|
||||
const looksLikeId = nostrPlugin.messaging?.targetResolver?.looksLikeId;
|
||||
if (!looksLikeId) {
|
||||
return;
|
||||
}
|
||||
const looksLikeId = requireNostrLooksLikeId();
|
||||
|
||||
expect(looksLikeId("npub1xyz123")).toBe(true);
|
||||
});
|
||||
|
||||
it("recognizes hex pubkey as valid target", () => {
|
||||
const looksLikeId = nostrPlugin.messaging?.targetResolver?.looksLikeId;
|
||||
if (!looksLikeId) {
|
||||
return;
|
||||
}
|
||||
const looksLikeId = requireNostrLooksLikeId();
|
||||
|
||||
expect(looksLikeId(TEST_HEX_PRIVATE_KEY)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects invalid input", () => {
|
||||
const looksLikeId = nostrPlugin.messaging?.targetResolver?.looksLikeId;
|
||||
if (!looksLikeId) {
|
||||
return;
|
||||
}
|
||||
const looksLikeId = requireNostrLooksLikeId();
|
||||
|
||||
expect(looksLikeId("not-a-pubkey")).toBe(false);
|
||||
expect(looksLikeId("")).toBe(false);
|
||||
});
|
||||
|
||||
it("normalizeTarget strips nostr: prefix", () => {
|
||||
const normalize = nostrPlugin.messaging?.normalizeTarget;
|
||||
if (!normalize) {
|
||||
return;
|
||||
}
|
||||
it("normalizeTarget strips spaced nostr prefixes", () => {
|
||||
const normalize = requireNostrNormalizeTarget();
|
||||
|
||||
expect(normalize(`nostr:${TEST_HEX_PRIVATE_KEY}`)).toBe(TEST_HEX_PRIVATE_KEY);
|
||||
expect(normalize(` nostr:${TEST_HEX_PRIVATE_KEY} `)).toBe(TEST_HEX_PRIVATE_KEY);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,37 +118,46 @@ describe("nostrPlugin", () => {
|
||||
expect(nostrPlugin.pairing?.idLabel).toBe("nostrPubkey");
|
||||
});
|
||||
|
||||
it("normalizes nostr: prefix in allow entries", () => {
|
||||
const normalize = nostrPlugin.pairing?.normalizeAllowEntry;
|
||||
if (!normalize) {
|
||||
return;
|
||||
}
|
||||
it("normalizes spaced nostr prefixes in allow entries", () => {
|
||||
const normalize = requireNostrPairingNormalizer();
|
||||
|
||||
expect(normalize(`nostr:${TEST_HEX_PRIVATE_KEY}`)).toBe(TEST_HEX_PRIVATE_KEY);
|
||||
expect(normalize(` nostr:${TEST_HEX_PRIVATE_KEY} `)).toBe(TEST_HEX_PRIVATE_KEY);
|
||||
});
|
||||
});
|
||||
|
||||
describe("security", () => {
|
||||
it("has resolveDmPolicy function", () => {
|
||||
expect(nostrPlugin.security?.resolveDmPolicy).toBeTypeOf("function");
|
||||
});
|
||||
});
|
||||
it("normalizes dm allowlist entries through the dm policy adapter", () => {
|
||||
const resolveDmPolicy = requireNostrResolveDmPolicy();
|
||||
|
||||
describe("gateway", () => {
|
||||
it("has startAccount function", () => {
|
||||
expect(nostrPlugin.gateway?.startAccount).toBeTypeOf("function");
|
||||
const cfg = createConfiguredNostrCfg({
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: [` nostr:${TEST_HEX_PRIVATE_KEY} `],
|
||||
});
|
||||
const account = nostrPlugin.config.resolveAccount(cfg, "default");
|
||||
|
||||
const result = resolveDmPolicy({ cfg, account });
|
||||
if (!result) {
|
||||
throw new Error("nostr resolveDmPolicy returned null");
|
||||
}
|
||||
|
||||
expect(result.policy).toBe("allowlist");
|
||||
expect(result.allowFrom).toEqual([` nostr:${TEST_HEX_PRIVATE_KEY} `]);
|
||||
expect(result.normalizeEntry?.(` nostr:${TEST_HEX_PRIVATE_KEY} `)).toBe(
|
||||
TEST_HEX_PRIVATE_KEY,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("status", () => {
|
||||
it("has default runtime", () => {
|
||||
expect(nostrPlugin.status?.defaultRuntime).toBeDefined();
|
||||
expect(nostrPlugin.status?.defaultRuntime?.accountId).toBe("default");
|
||||
expect(nostrPlugin.status?.defaultRuntime?.running).toBe(false);
|
||||
});
|
||||
|
||||
it("has buildAccountSnapshot function", () => {
|
||||
expect(nostrPlugin.status?.buildAccountSnapshot).toBeTypeOf("function");
|
||||
expect(nostrPlugin.status?.defaultRuntime).toEqual({
|
||||
accountId: "default",
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ const resolveNostrDmPolicy = createScopedDmSecurityResolver<ResolvedNostrAccount
|
||||
approveHint: formatPairingApproveHint("nostr"),
|
||||
normalizeEntry: (raw) => {
|
||||
try {
|
||||
return normalizePubkey(raw.replace(/^nostr:/i, "").trim());
|
||||
return normalizePubkey(raw.trim().replace(/^nostr:/i, ""));
|
||||
} catch {
|
||||
return raw.trim();
|
||||
}
|
||||
@@ -121,9 +121,9 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
||||
idLabel: "nostrPubkey",
|
||||
normalizeAllowEntry: (entry) => {
|
||||
try {
|
||||
return normalizePubkey(entry.replace(/^nostr:/i, ""));
|
||||
return normalizePubkey(entry.trim().replace(/^nostr:/i, ""));
|
||||
} catch {
|
||||
return entry;
|
||||
return entry.trim();
|
||||
}
|
||||
},
|
||||
notifyApproval: async ({ id }) => {
|
||||
@@ -142,7 +142,7 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
||||
messaging: {
|
||||
normalizeTarget: (target) => {
|
||||
// Strip nostr: prefix if present
|
||||
const cleaned = target.replace(/^nostr:/i, "").trim();
|
||||
const cleaned = target.trim().replace(/^nostr:/i, "");
|
||||
try {
|
||||
return normalizePubkey(cleaned);
|
||||
} catch {
|
||||
|
||||
@@ -65,8 +65,9 @@ describeLive("openrouter plugin live", () => {
|
||||
const provider =
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
providers.find((entry) => (entry as any).id === "openrouter");
|
||||
|
||||
expect(provider).toBeDefined();
|
||||
if (!provider) {
|
||||
throw new Error("openrouter provider was not registered");
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const resolved = (provider as any).resolveDynamicModel?.({
|
||||
|
||||
@@ -11,7 +11,7 @@ describe("signalPlugin outbound sendMedia", () => {
|
||||
throw new Error("signal outbound sendMedia is unavailable");
|
||||
}
|
||||
|
||||
await sendMedia({
|
||||
const result = await sendMedia({
|
||||
cfg: {} as never,
|
||||
to: "signal:+15551234567",
|
||||
text: "photo",
|
||||
@@ -30,6 +30,7 @@ describe("signalPlugin outbound sendMedia", () => {
|
||||
accountId: "default",
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual({ channel: "signal", messageId: "m1" });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
createAttachedChannelResultAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-send-result";
|
||||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { buildOutboundBaseSessionKey, type RoutePeer } from "openclaw/plugin-sdk/routing";
|
||||
import { resolveSignalAccount, type ResolvedSignalAccount } from "./accounts.js";
|
||||
|
||||
@@ -5,8 +5,11 @@ import {
|
||||
createAttachedChannelResultAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-send-result";
|
||||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveOutboundSendDep, type OutboundSendDeps } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { createScopedChannelMediaMaxBytesResolver } from "openclaw/plugin-sdk/media-runtime";
|
||||
import {
|
||||
resolveOutboundSendDep,
|
||||
type OutboundSendDeps,
|
||||
} from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { markdownToSignalTextChunks } from "./format.js";
|
||||
import { sendMessageSignal } from "./send.js";
|
||||
|
||||
@@ -29,6 +29,46 @@ async function getSlackConfiguredState(cfg: OpenClawConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
function requireSlackHandleAction() {
|
||||
const handleAction = slackPlugin.actions?.handleAction;
|
||||
if (!handleAction) {
|
||||
throw new Error("slack actions.handleAction unavailable");
|
||||
}
|
||||
return handleAction;
|
||||
}
|
||||
|
||||
function requireSlackSendText() {
|
||||
const sendText = slackPlugin.outbound?.sendText;
|
||||
if (!sendText) {
|
||||
throw new Error("slack outbound.sendText unavailable");
|
||||
}
|
||||
return sendText;
|
||||
}
|
||||
|
||||
function requireSlackSendMedia() {
|
||||
const sendMedia = slackPlugin.outbound?.sendMedia;
|
||||
if (!sendMedia) {
|
||||
throw new Error("slack outbound.sendMedia unavailable");
|
||||
}
|
||||
return sendMedia;
|
||||
}
|
||||
|
||||
function requireSlackSendPayload() {
|
||||
const sendPayload = slackOutbound.sendPayload;
|
||||
if (!sendPayload) {
|
||||
throw new Error("slack outbound.sendPayload unavailable");
|
||||
}
|
||||
return sendPayload;
|
||||
}
|
||||
|
||||
function requireSlackListPeers() {
|
||||
const listPeers = slackPlugin.directory?.listPeers;
|
||||
if (!listPeers) {
|
||||
throw new Error("slack directory.listPeers unavailable");
|
||||
}
|
||||
return listPeers;
|
||||
}
|
||||
|
||||
describe("slackPlugin actions", () => {
|
||||
it("prefers session lookup for announce target routing", () => {
|
||||
expect(slackPlugin.meta.preferSessionLookupForAnnounceTarget).toBe(true);
|
||||
@@ -58,10 +98,9 @@ describe("slackPlugin actions", () => {
|
||||
|
||||
it("forwards read threadId to Slack action handler", async () => {
|
||||
handleSlackActionMock.mockResolvedValueOnce({ messages: [], hasMore: false });
|
||||
const handleAction = slackPlugin.actions?.handleAction;
|
||||
expect(handleAction).toBeDefined();
|
||||
const handleAction = requireSlackHandleAction();
|
||||
|
||||
await handleAction!({
|
||||
await handleAction({
|
||||
action: "read",
|
||||
channel: "slack",
|
||||
accountId: "default",
|
||||
@@ -84,6 +123,45 @@ describe("slackPlugin actions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("slackPlugin security", () => {
|
||||
it("normalizes dm allowlist entries with trimmed prefixes", () => {
|
||||
const resolveDmPolicy = slackPlugin.security?.resolveDmPolicy;
|
||||
if (!resolveDmPolicy) {
|
||||
throw new Error("resolveDmPolicy unavailable");
|
||||
}
|
||||
|
||||
const result = resolveDmPolicy({
|
||||
cfg: {
|
||||
channels: {
|
||||
slack: {
|
||||
dm: { policy: "allowlist", allowFrom: [" slack:U123 "] },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
account: slackPlugin.config.resolveAccount(
|
||||
{
|
||||
channels: {
|
||||
slack: {
|
||||
botToken: "xoxb-test",
|
||||
appToken: "xapp-test",
|
||||
dm: { policy: "allowlist", allowFrom: [" slack:U123 "] },
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
"default",
|
||||
),
|
||||
});
|
||||
if (!result) {
|
||||
throw new Error("slack resolveDmPolicy returned null");
|
||||
}
|
||||
|
||||
expect(result.policy).toBe("allowlist");
|
||||
expect(result.allowFrom).toEqual([" slack:U123 "]);
|
||||
expect(result.normalizeEntry?.(" slack:U123 ")).toBe("U123");
|
||||
expect(result.normalizeEntry?.(" user:U999 ")).toBe("U999");
|
||||
});
|
||||
});
|
||||
|
||||
describe("slackPlugin outbound", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
@@ -96,10 +174,9 @@ describe("slackPlugin outbound", () => {
|
||||
|
||||
it("uses threadId as threadTs fallback for sendText", async () => {
|
||||
const sendSlack = vi.fn().mockResolvedValue({ messageId: "m-text" });
|
||||
const sendText = slackPlugin.outbound?.sendText;
|
||||
expect(sendText).toBeDefined();
|
||||
const sendText = requireSlackSendText();
|
||||
|
||||
const result = await sendText!({
|
||||
const result = await sendText({
|
||||
cfg,
|
||||
to: "C123",
|
||||
text: "hello",
|
||||
@@ -120,10 +197,9 @@ describe("slackPlugin outbound", () => {
|
||||
|
||||
it("prefers replyToId over threadId for sendMedia", async () => {
|
||||
const sendSlack = vi.fn().mockResolvedValue({ messageId: "m-media" });
|
||||
const sendMedia = slackPlugin.outbound?.sendMedia;
|
||||
expect(sendMedia).toBeDefined();
|
||||
const sendMedia = requireSlackSendMedia();
|
||||
|
||||
const result = await sendMedia!({
|
||||
const result = await sendMedia({
|
||||
cfg,
|
||||
to: "C999",
|
||||
text: "caption",
|
||||
@@ -147,11 +223,10 @@ describe("slackPlugin outbound", () => {
|
||||
|
||||
it("forwards mediaLocalRoots for sendMedia", async () => {
|
||||
const sendSlack = vi.fn().mockResolvedValue({ messageId: "m-media-local" });
|
||||
const sendMedia = slackPlugin.outbound?.sendMedia;
|
||||
expect(sendMedia).toBeDefined();
|
||||
const sendMedia = requireSlackSendMedia();
|
||||
const mediaLocalRoots = ["/tmp/workspace"];
|
||||
|
||||
const result = await sendMedia!({
|
||||
const result = await sendMedia({
|
||||
cfg,
|
||||
to: "C999",
|
||||
text: "caption",
|
||||
@@ -178,10 +253,9 @@ describe("slackPlugin outbound", () => {
|
||||
.mockResolvedValueOnce({ messageId: "m-media-1" })
|
||||
.mockResolvedValueOnce({ messageId: "m-media-2" })
|
||||
.mockResolvedValueOnce({ messageId: "m-final" });
|
||||
const sendPayload = slackOutbound.sendPayload;
|
||||
expect(sendPayload).toBeDefined();
|
||||
const sendPayload = requireSlackSendPayload();
|
||||
|
||||
const result = await sendPayload!({
|
||||
const result = await sendPayload({
|
||||
cfg,
|
||||
to: "C999",
|
||||
text: "",
|
||||
@@ -248,11 +322,10 @@ describe("slackPlugin outbound", () => {
|
||||
|
||||
describe("slackPlugin directory", () => {
|
||||
it("lists configured peers without throwing a ReferenceError", async () => {
|
||||
const listPeers = slackPlugin.directory?.listPeers;
|
||||
expect(listPeers).toBeDefined();
|
||||
const listPeers = requireSlackListPeers();
|
||||
|
||||
await expect(
|
||||
listPeers!({
|
||||
listPeers({
|
||||
cfg: {
|
||||
channels: {
|
||||
slack: {
|
||||
@@ -320,10 +393,9 @@ describe("slackPlugin outbound new targets", () => {
|
||||
|
||||
it("sends to a new user target via DM without erroring", async () => {
|
||||
const sendSlack = vi.fn().mockResolvedValue({ messageId: "m-new-user", channelId: "D999" });
|
||||
const sendText = slackPlugin.outbound?.sendText;
|
||||
expect(sendText).toBeDefined();
|
||||
const sendText = requireSlackSendText();
|
||||
|
||||
const result = await sendText!({
|
||||
const result = await sendText({
|
||||
cfg,
|
||||
to: "user:U99NEW",
|
||||
text: "hello new user",
|
||||
@@ -341,10 +413,9 @@ describe("slackPlugin outbound new targets", () => {
|
||||
|
||||
it("sends to a new channel target without erroring", async () => {
|
||||
const sendSlack = vi.fn().mockResolvedValue({ messageId: "m-new-chan", channelId: "C555" });
|
||||
const sendText = slackPlugin.outbound?.sendText;
|
||||
expect(sendText).toBeDefined();
|
||||
const sendText = requireSlackSendText();
|
||||
|
||||
const result = await sendText!({
|
||||
const result = await sendText({
|
||||
cfg,
|
||||
to: "channel:C555NEW",
|
||||
text: "hello channel",
|
||||
@@ -362,10 +433,9 @@ describe("slackPlugin outbound new targets", () => {
|
||||
|
||||
it("sends media to a new user target without erroring", async () => {
|
||||
const sendSlack = vi.fn().mockResolvedValue({ messageId: "m-new-media", channelId: "D888" });
|
||||
const sendMedia = slackPlugin.outbound?.sendMedia;
|
||||
expect(sendMedia).toBeDefined();
|
||||
const sendMedia = requireSlackSendMedia();
|
||||
|
||||
const result = await sendMedia!({
|
||||
const result = await sendMedia({
|
||||
cfg,
|
||||
to: "user:U88NEW",
|
||||
text: "here is a file",
|
||||
|
||||
@@ -20,7 +20,7 @@ import { buildPassiveProbedChannelStatusSummary } from "openclaw/plugin-sdk/exte
|
||||
import {
|
||||
createRuntimeOutboundDelegates,
|
||||
resolveOutboundSendDep,
|
||||
} from "openclaw/plugin-sdk/infra-runtime";
|
||||
} from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import {
|
||||
buildOutboundBaseSessionKey,
|
||||
normalizeOutboundThreadId,
|
||||
@@ -77,7 +77,11 @@ const resolveSlackDmPolicy = createScopedDmSecurityResolver<ResolvedSlackAccount
|
||||
resolvePolicy: (account) => account.dm?.policy,
|
||||
resolveAllowFrom: (account) => account.dm?.allowFrom,
|
||||
allowFromPathSuffix: "dm.",
|
||||
normalizeEntry: (raw) => raw.replace(/^(slack|user):/i, ""),
|
||||
normalizeEntry: (raw) =>
|
||||
raw
|
||||
.trim()
|
||||
.replace(/^(slack|user):/i, "")
|
||||
.trim(),
|
||||
});
|
||||
|
||||
// Select the appropriate Slack token for read/write operations.
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/channel-feedback";
|
||||
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
||||
import { resolveStorePath, updateLastRoute } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { resolveAgentOutboundIdentity } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { clearHistoryEntriesIfEnabled } from "openclaw/plugin-sdk/reply-history";
|
||||
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
||||
import { dispatchInboundMessage } from "openclaw/plugin-sdk/reply-runtime";
|
||||
|
||||
@@ -3,11 +3,14 @@ import {
|
||||
type ChannelOutboundAdapter,
|
||||
createAttachedChannelResultAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-send-result";
|
||||
import { resolveOutboundSendDep, type OutboundIdentity } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import {
|
||||
resolveInteractiveTextFallback,
|
||||
type InteractiveReply,
|
||||
} from "openclaw/plugin-sdk/interactive-runtime";
|
||||
import {
|
||||
resolveOutboundSendDep,
|
||||
type OutboundIdentity,
|
||||
} from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import {
|
||||
resolvePayloadMediaUrls,
|
||||
|
||||
@@ -54,7 +54,6 @@ describe("Synology channel wiring integration", () => {
|
||||
const registered = firstCall[0];
|
||||
expect(registered.path).toBe("/webhook/synology-alerts");
|
||||
expect(registered.accountId).toBe("alerts");
|
||||
expect(typeof registered.handler).toBe("function");
|
||||
|
||||
const req = makeReq(
|
||||
"POST",
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { makeSecurityAccount, registerPluginHttpRouteMock } from "./channel.test-mocks.js";
|
||||
import { sendMessage } from "./client.js";
|
||||
|
||||
vi.mock("./webhook-handler.js", () => ({
|
||||
createWebhookHandler: vi.fn(() => vi.fn()),
|
||||
}));
|
||||
|
||||
const { createSynologyChatPlugin } = await import("./channel.js");
|
||||
const mockSendMessage = vi.mocked(sendMessage);
|
||||
|
||||
describe("createSynologyChatPlugin", () => {
|
||||
it("returns a plugin object with all required sections", () => {
|
||||
const plugin = createSynologyChatPlugin();
|
||||
expect(plugin.id).toBe("synology-chat");
|
||||
expect(plugin.meta).toBeDefined();
|
||||
expect(plugin.capabilities).toBeDefined();
|
||||
expect(plugin.config).toBeDefined();
|
||||
expect(plugin.setup).toBeDefined();
|
||||
expect(plugin.setupWizard).toBeDefined();
|
||||
expect(plugin.security).toBeDefined();
|
||||
expect(plugin.outbound).toBeDefined();
|
||||
expect(plugin.gateway).toBeDefined();
|
||||
beforeEach(() => {
|
||||
mockSendMessage.mockClear();
|
||||
});
|
||||
|
||||
describe("meta", () => {
|
||||
@@ -40,17 +33,52 @@ describe("createSynologyChatPlugin", () => {
|
||||
});
|
||||
|
||||
describe("config", () => {
|
||||
it("listAccountIds delegates to accounts module", () => {
|
||||
it("listAccountIds includes default and named accounts when configured", () => {
|
||||
const plugin = createSynologyChatPlugin();
|
||||
const result = plugin.config.listAccountIds({});
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
const result = plugin.config.listAccountIds({
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
token: "base-token",
|
||||
accounts: {
|
||||
office: { token: "office-token" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result).toEqual(["default", "office"]);
|
||||
});
|
||||
|
||||
it("resolveAccount returns account config", () => {
|
||||
const cfg = { channels: { "synology-chat": { token: "t1" } } };
|
||||
it("resolveAccount merges account overrides with base config defaults", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
token: "base-token",
|
||||
incomingUrl: "https://nas/base",
|
||||
nasHost: "nas-base",
|
||||
allowedUserIds: ["base-user"],
|
||||
rateLimitPerMinute: 45,
|
||||
botName: "Base Bot",
|
||||
accounts: {
|
||||
office: {
|
||||
token: "office-token",
|
||||
allowInsecureSsl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const plugin = createSynologyChatPlugin();
|
||||
const account = plugin.config.resolveAccount(cfg, "default");
|
||||
expect(account.accountId).toBe("default");
|
||||
const account = plugin.config.resolveAccount(cfg, "office");
|
||||
expect(account).toMatchObject({
|
||||
accountId: "office",
|
||||
token: "office-token",
|
||||
incomingUrl: "https://nas/base",
|
||||
nasHost: "nas-base",
|
||||
allowedUserIds: ["base-user"],
|
||||
rateLimitPerMinute: 45,
|
||||
botName: "Base Bot",
|
||||
allowInsecureSsl: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("defaultAccountId returns 'default'", () => {
|
||||
@@ -86,23 +114,45 @@ describe("createSynologyChatPlugin", () => {
|
||||
allowInsecureSsl: true,
|
||||
};
|
||||
const result = plugin.security.resolveDmPolicy({ cfg: {}, account });
|
||||
if (!result) {
|
||||
throw new Error("resolveDmPolicy returned null");
|
||||
}
|
||||
expect(result.policy).toBe("allowlist");
|
||||
expect(result.allowFrom).toEqual(["user1"]);
|
||||
expect(typeof result.normalizeEntry).toBe("function");
|
||||
expect(result.normalizeEntry?.(" USER1 ")).toBe("user1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("pairing", () => {
|
||||
it("has notifyApproval and normalizeAllowEntry", () => {
|
||||
it("normalizes entries and notifies approved users", async () => {
|
||||
const plugin = createSynologyChatPlugin();
|
||||
expect(plugin.pairing.idLabel).toBe("synologyChatUserId");
|
||||
const normalize = plugin.pairing.normalizeAllowEntry;
|
||||
expect(typeof normalize).toBe("function");
|
||||
if (normalize) {
|
||||
expect(normalize(" USER1 ")).toBe("user1");
|
||||
const notifyApproval = plugin.pairing.notifyApproval;
|
||||
if (!normalize || !notifyApproval) {
|
||||
throw new Error("synology-chat pairing helpers unavailable");
|
||||
}
|
||||
expect(typeof plugin.pairing.notifyApproval).toBe("function");
|
||||
expect(normalize(" USER1 ")).toBe("user1");
|
||||
|
||||
await notifyApproval({
|
||||
cfg: {
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
token: "t",
|
||||
incomingUrl: "https://nas/incoming",
|
||||
allowInsecureSsl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
id: "USER1",
|
||||
});
|
||||
|
||||
expect(mockSendMessage).toHaveBeenCalledWith(
|
||||
"https://nas/incoming",
|
||||
"OpenClaw: your access has been approved.",
|
||||
"USER1",
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -174,9 +224,9 @@ describe("createSynologyChatPlugin", () => {
|
||||
it("returns formatting hints", () => {
|
||||
const plugin = createSynologyChatPlugin();
|
||||
const hints = plugin.agentPrompt.messageToolHints();
|
||||
expect(Array.isArray(hints)).toBe(true);
|
||||
expect(hints.length).toBeGreaterThan(5);
|
||||
expect(hints.some((h: string) => h.includes("<URL|display text>"))).toBe(true);
|
||||
expect(hints).toContain("### Synology Chat Formatting");
|
||||
expect(hints).toContain("**Links**: Use `<URL|display text>` to create clickable links.");
|
||||
expect(hints).toContain("- No buttons, cards, or interactive elements");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -212,9 +262,11 @@ describe("createSynologyChatPlugin", () => {
|
||||
text: "hello",
|
||||
to: "user1",
|
||||
});
|
||||
expect(result.channel).toBe("synology-chat");
|
||||
expect(result.messageId).toBeDefined();
|
||||
expect(result.chatId).toBe("user1");
|
||||
expect(result).toMatchObject({
|
||||
channel: "synology-chat",
|
||||
chatId: "user1",
|
||||
});
|
||||
expect(result.messageId).toMatch(/^sc-\d+$/);
|
||||
});
|
||||
|
||||
it("sendMedia throws when missing incomingUrl", async () => {
|
||||
|
||||
@@ -2,12 +2,6 @@ import { describe, expect, it } from "vitest";
|
||||
import plugin from "./index.js";
|
||||
|
||||
describe("tavily plugin", () => {
|
||||
it("exports a valid plugin entry with correct id and name", () => {
|
||||
expect(plugin.id).toBe("tavily");
|
||||
expect(plugin.name).toBe("Tavily Plugin");
|
||||
expect(typeof plugin.register).toBe("function");
|
||||
});
|
||||
|
||||
it("registers web search provider and two tools", () => {
|
||||
const registrations: {
|
||||
webSearchProviders: unknown[];
|
||||
@@ -26,6 +20,8 @@ describe("tavily plugin", () => {
|
||||
|
||||
plugin.register(mockApi as never);
|
||||
|
||||
expect(plugin.id).toBe("tavily");
|
||||
expect(plugin.name).toBe("Tavily Plugin");
|
||||
expect(registrations.webSearchProviders).toHaveLength(1);
|
||||
expect(registrations.tools).toHaveLength(2);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ const {
|
||||
botCtorSpy,
|
||||
commandSpy,
|
||||
dispatchReplyWithBufferedBlockDispatcher,
|
||||
getLoadWebMediaMock,
|
||||
getLoadConfigMock,
|
||||
getOnHandler,
|
||||
getReadChannelAllowFromStoreMock,
|
||||
@@ -1258,44 +1259,39 @@ describe("createTelegramBot", () => {
|
||||
});
|
||||
|
||||
it("sends GIF replies as animations", async () => {
|
||||
const loadWebMedia = getLoadWebMediaMock();
|
||||
replySpy.mockResolvedValueOnce({
|
||||
text: "caption",
|
||||
mediaUrl: "https://example.com/fun",
|
||||
});
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(Buffer.from("GIF89a"), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "image/gif",
|
||||
},
|
||||
}),
|
||||
);
|
||||
try {
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
loadWebMedia.mockResolvedValueOnce({
|
||||
buffer: Buffer.from("GIF89a"),
|
||||
contentType: "image/gif",
|
||||
fileName: "fun.gif",
|
||||
});
|
||||
|
||||
await handler({
|
||||
message: {
|
||||
chat: { id: 1234, type: "private" },
|
||||
text: "hello world",
|
||||
date: 1736380800,
|
||||
message_id: 5,
|
||||
from: { first_name: "Ada" },
|
||||
},
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
createTelegramBot({ token: "tok" });
|
||||
const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;
|
||||
|
||||
expect(sendAnimationSpy).toHaveBeenCalledTimes(1);
|
||||
expect(sendAnimationSpy).toHaveBeenCalledWith("1234", expect.anything(), {
|
||||
caption: "caption",
|
||||
parse_mode: "HTML",
|
||||
reply_to_message_id: undefined,
|
||||
});
|
||||
expect(sendPhotoSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
fetchSpy.mockRestore();
|
||||
}
|
||||
await handler({
|
||||
message: {
|
||||
chat: { id: 1234, type: "private" },
|
||||
text: "hello world",
|
||||
date: 1736380800,
|
||||
message_id: 5,
|
||||
from: { first_name: "Ada" },
|
||||
},
|
||||
me: { username: "openclaw_bot" },
|
||||
getFile: async () => ({ download: async () => new Uint8Array() }),
|
||||
});
|
||||
|
||||
expect(sendAnimationSpy).toHaveBeenCalledTimes(1);
|
||||
expect(sendAnimationSpy).toHaveBeenCalledWith("1234", expect.anything(), {
|
||||
caption: "caption",
|
||||
parse_mode: "HTML",
|
||||
reply_to_message_id: undefined,
|
||||
});
|
||||
expect(sendPhotoSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
function resetHarnessSpies() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { PluginRuntime } from "../../../src/plugins/runtime/types.js";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/extensions/runtime-env.js";
|
||||
import { createStartAccountContext } from "../../../test/helpers/extensions/start-account-context.js";
|
||||
import type { ResolvedTelegramAccount } from "./accounts.js";
|
||||
import * as auditModule from "./audit.js";
|
||||
@@ -59,6 +58,26 @@ function resolveAccount(cfg: OpenClawConfig, accountId: string): ResolvedTelegra
|
||||
return telegramPlugin.config.resolveAccount(cfg, accountId) as ResolvedTelegramAccount;
|
||||
}
|
||||
|
||||
function createStartTelegramContext(cfg: OpenClawConfig, accountId: string) {
|
||||
return createStartAccountContext({
|
||||
account: resolveAccount(cfg, accountId),
|
||||
cfg,
|
||||
});
|
||||
}
|
||||
|
||||
function startTelegramAccount(cfg: OpenClawConfig, accountId: string) {
|
||||
return telegramPlugin.gateway!.startAccount!(createStartTelegramContext(cfg, accountId));
|
||||
}
|
||||
|
||||
function installTelegramRuntime(telegram?: Record<string, unknown>) {
|
||||
setTelegramRuntime({
|
||||
channel: telegram ? { telegram } : undefined,
|
||||
logging: {
|
||||
shouldLogVerbose: () => false,
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
}
|
||||
|
||||
function installGatewayRuntime(params?: { probeOk?: boolean; botUsername?: string }) {
|
||||
const monitorTelegramProvider = vi
|
||||
.spyOn(monitorModule, "monitorTelegramProvider")
|
||||
@@ -87,11 +106,7 @@ function installGatewayRuntime(params?: { probeOk?: boolean; botUsername?: strin
|
||||
groups: [],
|
||||
elapsedMs: 0,
|
||||
}));
|
||||
setTelegramRuntime({
|
||||
logging: {
|
||||
shouldLogVerbose: () => false,
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
installTelegramRuntime();
|
||||
return {
|
||||
monitorTelegramProvider,
|
||||
probeTelegram,
|
||||
@@ -111,16 +126,21 @@ function configureOpsProxyNetwork(cfg: OpenClawConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
function createOpsProxyAccount() {
|
||||
const cfg = createCfg();
|
||||
configureOpsProxyNetwork(cfg);
|
||||
return {
|
||||
cfg,
|
||||
account: resolveAccount(cfg, "ops"),
|
||||
};
|
||||
}
|
||||
|
||||
function installSendMessageRuntime(
|
||||
sendMessageTelegram: ReturnType<typeof vi.fn>,
|
||||
): ReturnType<typeof vi.fn> {
|
||||
setTelegramRuntime({
|
||||
channel: {
|
||||
telegram: {
|
||||
sendMessageTelegram,
|
||||
},
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
installTelegramRuntime({
|
||||
sendMessageTelegram,
|
||||
});
|
||||
return sendMessageTelegram;
|
||||
}
|
||||
|
||||
@@ -167,9 +187,9 @@ describe("telegramPlugin groups", () => {
|
||||
describe("telegramPlugin duplicate token guard", () => {
|
||||
it("marks secondary account as not configured when token is shared", async () => {
|
||||
const cfg = createCfg();
|
||||
const alertsAccount = telegramPlugin.config.resolveAccount(cfg, "alerts");
|
||||
const workAccount = telegramPlugin.config.resolveAccount(cfg, "work");
|
||||
const opsAccount = telegramPlugin.config.resolveAccount(cfg, "ops");
|
||||
const alertsAccount = resolveAccount(cfg, "alerts");
|
||||
const workAccount = resolveAccount(cfg, "work");
|
||||
const opsAccount = resolveAccount(cfg, "ops");
|
||||
|
||||
expect(await telegramPlugin.config.isConfigured!(alertsAccount, cfg)).toBe(true);
|
||||
expect(await telegramPlugin.config.isConfigured!(workAccount, cfg)).toBe(false);
|
||||
@@ -182,7 +202,7 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
|
||||
it("surfaces duplicate-token reason in status snapshot", async () => {
|
||||
const cfg = createCfg();
|
||||
const workAccount = telegramPlugin.config.resolveAccount(cfg, "work");
|
||||
const workAccount = resolveAccount(cfg, "work");
|
||||
const snapshot = await telegramPlugin.status!.buildAccountSnapshot!({
|
||||
account: workAccount,
|
||||
cfg,
|
||||
@@ -201,15 +221,7 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
});
|
||||
const cfg = createCfg();
|
||||
|
||||
await expect(
|
||||
telegramPlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: resolveAccount(cfg, "work"),
|
||||
cfg,
|
||||
runtime: createRuntimeEnv(),
|
||||
}),
|
||||
),
|
||||
).rejects.toThrow("Duplicate Telegram bot token");
|
||||
await expect(startTelegramAccount(cfg, "work")).rejects.toThrow("Duplicate Telegram bot token");
|
||||
|
||||
expect(probeTelegramMock).not.toHaveBeenCalled();
|
||||
expect(monitorTelegramProviderMock).not.toHaveBeenCalled();
|
||||
@@ -237,13 +249,7 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
webhookPort: 9876,
|
||||
};
|
||||
|
||||
await telegramPlugin.gateway!.startAccount!(
|
||||
createStartAccountContext({
|
||||
account: resolveAccount(cfg, "ops"),
|
||||
cfg,
|
||||
runtime: createRuntimeEnv(),
|
||||
}),
|
||||
);
|
||||
await startTelegramAccount(cfg, "ops");
|
||||
|
||||
expect(probeTelegramMock).toHaveBeenCalledWith("token-ops", 2500, {
|
||||
accountId: "ops",
|
||||
@@ -264,25 +270,16 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
const runtimeProbeTelegram = vi.fn(async () => {
|
||||
throw new Error("runtime probe should not be used");
|
||||
});
|
||||
setTelegramRuntime({
|
||||
channel: {
|
||||
telegram: {
|
||||
probeTelegram: runtimeProbeTelegram,
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
shouldLogVerbose: () => false,
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
installTelegramRuntime({
|
||||
probeTelegram: runtimeProbeTelegram,
|
||||
});
|
||||
probeTelegramMock.mockResolvedValue({
|
||||
ok: true,
|
||||
bot: { username: "opsbot" },
|
||||
elapsedMs: 1,
|
||||
});
|
||||
|
||||
const cfg = createCfg();
|
||||
configureOpsProxyNetwork(cfg);
|
||||
const account = telegramPlugin.config.resolveAccount(cfg, "ops");
|
||||
const { cfg, account } = createOpsProxyAccount();
|
||||
|
||||
await telegramPlugin.status!.probeAccount!({
|
||||
account,
|
||||
@@ -308,17 +305,10 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
const runtimeAuditGroupMembership = vi.fn(async () => {
|
||||
throw new Error("runtime audit helper should not be used");
|
||||
});
|
||||
setTelegramRuntime({
|
||||
channel: {
|
||||
telegram: {
|
||||
collectUnmentionedGroupIds: runtimeCollectUnmentionedGroupIds,
|
||||
auditGroupMembership: runtimeAuditGroupMembership,
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
shouldLogVerbose: () => false,
|
||||
},
|
||||
} as unknown as PluginRuntime);
|
||||
installTelegramRuntime({
|
||||
collectUnmentionedGroupIds: runtimeCollectUnmentionedGroupIds,
|
||||
auditGroupMembership: runtimeAuditGroupMembership,
|
||||
});
|
||||
collectTelegramUnmentionedGroupIdsMock.mockReturnValue({
|
||||
groupIds: ["-100123"],
|
||||
unresolvedGroups: 0,
|
||||
@@ -333,15 +323,13 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
elapsedMs: 1,
|
||||
});
|
||||
|
||||
const cfg = createCfg();
|
||||
configureOpsProxyNetwork(cfg);
|
||||
const { cfg, account } = createOpsProxyAccount();
|
||||
cfg.channels!.telegram!.accounts!.ops = {
|
||||
...cfg.channels!.telegram!.accounts!.ops,
|
||||
groups: {
|
||||
"-100123": { requireMention: false },
|
||||
},
|
||||
};
|
||||
const account = telegramPlugin.config.resolveAccount(cfg, "ops");
|
||||
|
||||
await telegramPlugin.status!.auditAccount!({
|
||||
account,
|
||||
@@ -484,7 +472,7 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
const cfg = createCfg();
|
||||
cfg.channels!.telegram!.accounts!.ops = {} as never;
|
||||
|
||||
const alertsAccount = telegramPlugin.config.resolveAccount(cfg, "alerts");
|
||||
const alertsAccount = resolveAccount(cfg, "alerts");
|
||||
expect(await telegramPlugin.config.isConfigured!(alertsAccount, cfg)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -496,11 +484,7 @@ describe("telegramPlugin duplicate token guard", () => {
|
||||
monitorTelegramProviderMock.mockResolvedValue(undefined);
|
||||
|
||||
const cfg = createCfg();
|
||||
const ctx = createStartAccountContext({
|
||||
account: resolveAccount(cfg, "ops"),
|
||||
cfg,
|
||||
runtime: createRuntimeEnv(),
|
||||
});
|
||||
const ctx = createStartTelegramContext(cfg, "ops");
|
||||
ctx.account = {
|
||||
...ctx.account,
|
||||
token: undefined as unknown as string,
|
||||
|
||||
@@ -9,7 +9,10 @@ import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import { createChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { resolveExecApprovalCommandDisplay } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { buildExecApprovalPendingReplyPayload } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { resolveOutboundSendDep, type OutboundSendDeps } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import {
|
||||
resolveOutboundSendDep,
|
||||
type OutboundSendDeps,
|
||||
} from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import {
|
||||
buildOutboundBaseSessionKey,
|
||||
normalizeMessageChannel,
|
||||
|
||||
@@ -3,8 +3,11 @@ import {
|
||||
attachChannelToResult,
|
||||
createAttachedChannelResultAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-send-result";
|
||||
import { resolveOutboundSendDep, type OutboundSendDeps } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { resolveInteractiveTextFallback } from "openclaw/plugin-sdk/interactive-runtime";
|
||||
import {
|
||||
resolveOutboundSendDep,
|
||||
type OutboundSendDeps,
|
||||
} from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import {
|
||||
resolvePayloadMediaUrls,
|
||||
sendPayloadMediaSequenceOrFallback,
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
"description": "OpenClaw Tlon/Urbit channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.1000.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.1000.0",
|
||||
"@tloncorp/tlon-skill": "0.2.2",
|
||||
"@aws-sdk/client-s3": "3.1014.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.1014.0",
|
||||
"@tloncorp/tlon-skill": "0.3.0",
|
||||
"@urbit/aura": "^3.0.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
@@ -16,6 +16,11 @@
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -2,8 +2,8 @@ import { createHybridChannelConfigAdapter } from "openclaw/plugin-sdk/channel-co
|
||||
import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-runtime";
|
||||
import { tlonChannelConfigSchema } from "./config-schema.js";
|
||||
import { resolveTlonOutboundSessionRoute } from "./session-route.js";
|
||||
import {
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
"@twurple/chat": "^8.0.3",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.14"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
|
||||
@@ -79,9 +79,13 @@ describe("outbound", () => {
|
||||
expect(twitchOutbound.textChunkLimit).toBe(500);
|
||||
});
|
||||
|
||||
it("should have chunker function", () => {
|
||||
expect(twitchOutbound.chunker).toBeDefined();
|
||||
expect(typeof twitchOutbound.chunker).toBe("function");
|
||||
it("should chunk long messages at 500 characters", () => {
|
||||
const chunker = twitchOutbound.chunker;
|
||||
if (!chunker) {
|
||||
throw new Error("twitch outbound.chunker unavailable");
|
||||
}
|
||||
|
||||
expect(chunker("a".repeat(600), 500)).toEqual(["a".repeat(500), "a".repeat(100)]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,13 @@ import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../api.js";
|
||||
import { twitchPlugin } from "./plugin.js";
|
||||
|
||||
describe("twitchPlugin pairing", () => {
|
||||
it("normalizes trimmed twitch user prefixes in allow entries", () => {
|
||||
expect(twitchPlugin.pairing?.normalizeAllowEntry?.(" twitch:user:123456 ")).toBe("123456");
|
||||
expect(twitchPlugin.pairing?.normalizeAllowEntry?.(" user789012 ")).toBe("789012");
|
||||
});
|
||||
});
|
||||
|
||||
describe("twitchPlugin.status.buildAccountSnapshot", () => {
|
||||
it("uses the resolved account ID for multi-account configs", async () => {
|
||||
const secondary = {
|
||||
|
||||
@@ -58,7 +58,7 @@ export const twitchPlugin: ChannelPlugin<TwitchAccountConfig> = {
|
||||
/** Pairing configuration */
|
||||
pairing: {
|
||||
idLabel: "twitchUserId",
|
||||
normalizeAllowEntry: (entry) => entry.replace(/^(twitch:)?user:?/i, ""),
|
||||
normalizeAllowEntry: (entry) => entry.trim().replace(/^(twitch:)?user:?/i, ""),
|
||||
notifyApproval: async ({ id }) => {
|
||||
// Note: Twitch doesn't support DMs from bots, so pairing approval is limited
|
||||
// We'll log the approval instead
|
||||
|
||||
@@ -113,9 +113,12 @@ describe("setup surface helpers", () => {
|
||||
expect(result).toBe("oauth:test123");
|
||||
|
||||
// Test the validate function
|
||||
expect(capturedValidate).toBeDefined();
|
||||
expect(capturedValidate!("")).toBe("Required");
|
||||
expect(capturedValidate!("notoauth")).toBe("Token should start with 'oauth:'");
|
||||
if (!capturedValidate) {
|
||||
throw new Error("promptToken validate callback was not captured");
|
||||
}
|
||||
expect(capturedValidate("")).toBe("Required");
|
||||
expect(capturedValidate("notoauth")).toBe("Token should start with 'oauth:'");
|
||||
expect(capturedValidate("oauth:goodtoken")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return early when no existing token and no env token", async () => {
|
||||
|
||||
@@ -50,8 +50,12 @@ describe("status", () => {
|
||||
const issues = collectTwitchStatusIssues(snapshots);
|
||||
|
||||
expect(issues.length).toBeGreaterThan(0);
|
||||
const disabledIssue = issues.find((i) => i.message.includes("disabled"));
|
||||
expect(disabledIssue).toBeDefined();
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "config",
|
||||
message: "Twitch account is disabled",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should detect missing clientId when account configured (simplified config)", () => {
|
||||
@@ -64,8 +68,12 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots, () => mockCfg as never);
|
||||
|
||||
const clientIdIssue = issues.find((i) => i.message.includes("client ID"));
|
||||
expect(clientIdIssue).toBeDefined();
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "config",
|
||||
message: "Twitch client ID is required",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should warn about oauth: prefix in token (simplified config)", () => {
|
||||
@@ -78,9 +86,12 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots, () => mockCfg as never);
|
||||
|
||||
const prefixIssue = issues.find((i) => i.message.includes("oauth:"));
|
||||
expect(prefixIssue).toBeDefined();
|
||||
expect(prefixIssue?.kind).toBe("config");
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "config",
|
||||
message: "Token contains 'oauth:' prefix (will be stripped)",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should detect clientSecret without refreshToken (simplified config)", () => {
|
||||
@@ -95,8 +106,12 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots, () => mockCfg as never);
|
||||
|
||||
const secretIssue = issues.find((i) => i.message.includes("clientSecret"));
|
||||
expect(secretIssue).toBeDefined();
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "config",
|
||||
message: "clientSecret provided without refreshToken",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should detect empty allowFrom array (simplified config)", () => {
|
||||
@@ -110,8 +125,12 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots, () => mockCfg as never);
|
||||
|
||||
const allowFromIssue = issues.find((i) => i.message.includes("allowFrom"));
|
||||
expect(allowFromIssue).toBeDefined();
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "config",
|
||||
message: "allowFrom is configured but empty",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should detect allowedRoles 'all' with allowFrom conflict (simplified config)", () => {
|
||||
@@ -126,9 +145,12 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots, () => mockCfg as never);
|
||||
|
||||
const conflictIssue = issues.find((i) => i.kind === "intent");
|
||||
expect(conflictIssue).toBeDefined();
|
||||
expect(conflictIssue?.message).toContain("allowedRoles is set to 'all'");
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "intent",
|
||||
message: "allowedRoles is set to 'all' but allowFrom is also configured",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should detect runtime errors", () => {
|
||||
@@ -138,9 +160,12 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots);
|
||||
|
||||
const runtimeIssue = issues.find((i) => i.kind === "runtime");
|
||||
expect(runtimeIssue).toBeDefined();
|
||||
expect(runtimeIssue?.message).toContain("Connection timeout");
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "runtime",
|
||||
message: "Last error: Connection timeout",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should detect accounts that never connected", () => {
|
||||
@@ -154,10 +179,12 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots);
|
||||
|
||||
const neverConnectedIssue = issues.find((i) =>
|
||||
i.message.includes("never connected successfully"),
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "runtime",
|
||||
message: "Account has never connected successfully",
|
||||
}),
|
||||
);
|
||||
expect(neverConnectedIssue).toBeDefined();
|
||||
});
|
||||
|
||||
it("should detect long-running connections", () => {
|
||||
@@ -172,8 +199,12 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots);
|
||||
|
||||
const uptimeIssue = issues.find((i) => i.message.includes("running for"));
|
||||
expect(uptimeIssue).toBeDefined();
|
||||
expect(issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
kind: "runtime",
|
||||
message: "Connection has been running for 8 days",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle empty snapshots array", () => {
|
||||
@@ -194,8 +225,13 @@ describe("status", () => {
|
||||
|
||||
const issues = collectTwitchStatusIssues(snapshots);
|
||||
|
||||
// Should not crash, may return empty or minimal issues
|
||||
expect(Array.isArray(issues)).toBe(true);
|
||||
expect(issues).toEqual([
|
||||
expect.objectContaining({
|
||||
accountId: "unknown",
|
||||
kind: "config",
|
||||
message: "Twitch account is not properly configured",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -348,8 +348,10 @@ describe("TwitchClientManager", () => {
|
||||
it("should send message successfully", async () => {
|
||||
const result = await manager.sendMessage(testAccount, "testchannel", "Hello, world!");
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.messageId).toBeDefined();
|
||||
expect(result).toMatchObject({ ok: true });
|
||||
expect(result.messageId).toMatch(
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
|
||||
);
|
||||
expect(mockSay).toHaveBeenCalledWith("testchannel", "Hello, world!");
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user