diff --git a/docs/help/testing.md b/docs/help/testing.md index 81b0943a334..0e8d0993230 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -430,7 +430,7 @@ These run `pnpm test:live` inside the repo Docker image, mounting your local con - Gateway + dev agent: `pnpm test:docker:live-gateway` (script: `scripts/test-live-gateway-models-docker.sh`) - Onboarding wizard (TTY, full scaffolding): `pnpm test:docker:onboard` (script: `scripts/e2e/onboard-docker.sh`) - Gateway networking (two containers, WS auth + health): `pnpm test:docker:gateway-network` (script: `scripts/e2e/gateway-network-docker.sh`) -- Plugins (custom extension load + registry smoke): `pnpm test:docker:plugins` (script: `scripts/e2e/plugins-docker.sh`) +- Plugins (install smoke + `/plugin` alias + Claude-bundle restart semantics): `pnpm test:docker:plugins` (script: `scripts/e2e/plugins-docker.sh`) The live-model Docker runners also bind-mount the current checkout read-only and stage it into a temporary workdir inside the container. This keeps the runtime diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index 0e8aaa4ee58..120fdc64e12 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -124,7 +124,9 @@ Looking for third-party plugins? See [Community Plugins](/plugins/community). | `slots` | Exclusive slot selectors (e.g. `memory`, `contextEngine`) | | `entries.\` | Per-plugin toggles + config | -Config changes **require a gateway restart**. +Config changes **require a gateway restart**. If the Gateway is running with config +watch + in-process restart enabled (the default `openclaw gateway` path), that +restart is usually performed automatically a moment after the config write lands. - **Disabled**: plugin exists but enablement rules turned it off. Config is preserved. diff --git a/docs/tools/slash-commands.md b/docs/tools/slash-commands.md index 3881006829d..4dfb6fd74f7 100644 --- a/docs/tools/slash-commands.md +++ b/docs/tools/slash-commands.md @@ -96,6 +96,8 @@ Text + native (when enabled): - `/config show|get|set|unset` (persist config to disk, owner-only; requires `commands.config: true`) - `/mcp show|get|set|unset` (manage OpenClaw MCP server config, owner-only; requires `commands.mcp: true`) - `/plugins list|show|get|enable|disable` (inspect discovered plugins and toggle enablement, owner-only for writes; requires `commands.plugins: true`) + - `/plugin` is an alias for `/plugins`. + - Enable/disable writes still reply with a restart hint. On a watched foreground gateway, OpenClaw may perform that restart automatically right after the write. - `/debug show|set|unset|reset` (runtime overrides, owner-only; requires `commands.debug: true`) - `/usage off|tokens|full|cost` (per-response usage footer or local cost summary) - `/tts off|always|inbound|tagged|status|provider|limit|summary|audio` (control TTS; see [/tts](/tools/tts)) diff --git a/extensions/telegram/runtime-api.ts b/extensions/telegram/runtime-api.ts index c069a35e40e..11af2533713 100644 --- a/extensions/telegram/runtime-api.ts +++ b/extensions/telegram/runtime-api.ts @@ -8,6 +8,7 @@ export type { TelegramActionConfig, TelegramNetworkConfig, } from "openclaw/plugin-sdk/telegram"; +export type { TelegramApiOverride } from "./src/send.js"; export type { OpenClawPluginService, OpenClawPluginServiceContext, diff --git a/extensions/telegram/src/send.ts b/extensions/telegram/src/send.ts index 28c97639362..8cd429eb4cc 100644 --- a/extensions/telegram/src/send.ts +++ b/extensions/telegram/src/send.ts @@ -44,7 +44,7 @@ import { import { resolveTelegramVoiceSend } from "./voice.js"; type TelegramApi = Bot["api"]; -type TelegramApiOverride = Partial; +export type TelegramApiOverride = Partial; const InputFileCtor: typeof grammy.InputFile = typeof grammy.InputFile === "function" ? grammy.InputFile diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index ffb489c1402..016c83793f5 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -4,7 +4,8 @@ "description": "OpenClaw WhatsApp channel plugin", "type": "module", "dependencies": { - "@whiskeysockets/baileys": "7.0.0-rc.9" + "@whiskeysockets/baileys": "7.0.0-rc.9", + "jimp": "^1.6.0" }, "devDependencies": { "openclaw": "workspace:*" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5b4de07798..a31ed2f6a54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -656,6 +656,9 @@ importers: '@whiskeysockets/baileys': specifier: 7.0.0-rc.9 version: 7.0.0-rc.9(audio-decode@2.2.3)(jimp@1.6.0)(sharp@0.34.5) + jimp: + specifier: ^1.6.0 + version: 1.6.0 devDependencies: openclaw: specifier: workspace:* @@ -7858,7 +7861,6 @@ snapshots: mime: 3.0.0 transitivePeerDependencies: - supports-color - optional: true '@jimp/diff@1.6.0': dependencies: @@ -7868,10 +7870,8 @@ snapshots: pixelmatch: 5.3.0 transitivePeerDependencies: - supports-color - optional: true - '@jimp/file-ops@1.6.0': - optional: true + '@jimp/file-ops@1.6.0': {} '@jimp/js-bmp@1.6.0': dependencies: @@ -7881,7 +7881,6 @@ snapshots: bmp-ts: 1.0.9 transitivePeerDependencies: - supports-color - optional: true '@jimp/js-gif@1.6.0': dependencies: @@ -7891,7 +7890,6 @@ snapshots: omggif: 1.0.10 transitivePeerDependencies: - supports-color - optional: true '@jimp/js-jpeg@1.6.0': dependencies: @@ -7900,7 +7898,6 @@ snapshots: jpeg-js: 0.4.4 transitivePeerDependencies: - supports-color - optional: true '@jimp/js-png@1.6.0': dependencies: @@ -7909,7 +7906,6 @@ snapshots: pngjs: 7.0.0 transitivePeerDependencies: - supports-color - optional: true '@jimp/js-tiff@1.6.0': dependencies: @@ -7918,14 +7914,12 @@ snapshots: utif2: 4.1.0 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-blit@1.6.0': dependencies: '@jimp/types': 1.6.0 '@jimp/utils': 1.6.0 zod: 3.25.76 - optional: true '@jimp/plugin-blur@1.6.0': dependencies: @@ -7933,13 +7927,11 @@ snapshots: '@jimp/utils': 1.6.0 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-circle@1.6.0': dependencies: '@jimp/types': 1.6.0 zod: 3.25.76 - optional: true '@jimp/plugin-color@1.6.0': dependencies: @@ -7950,7 +7942,6 @@ snapshots: zod: 3.25.76 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-contain@1.6.0': dependencies: @@ -7962,7 +7953,6 @@ snapshots: zod: 3.25.76 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-cover@1.6.0': dependencies: @@ -7973,7 +7963,6 @@ snapshots: zod: 3.25.76 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-crop@1.6.0': dependencies: @@ -7983,32 +7972,27 @@ snapshots: zod: 3.25.76 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-displace@1.6.0': dependencies: '@jimp/types': 1.6.0 '@jimp/utils': 1.6.0 zod: 3.25.76 - optional: true '@jimp/plugin-dither@1.6.0': dependencies: '@jimp/types': 1.6.0 - optional: true '@jimp/plugin-fisheye@1.6.0': dependencies: '@jimp/types': 1.6.0 '@jimp/utils': 1.6.0 zod: 3.25.76 - optional: true '@jimp/plugin-flip@1.6.0': dependencies: '@jimp/types': 1.6.0 zod: 3.25.76 - optional: true '@jimp/plugin-hash@1.6.0': dependencies: @@ -8024,13 +8008,11 @@ snapshots: any-base: 1.1.0 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-mask@1.6.0': dependencies: '@jimp/types': 1.6.0 zod: 3.25.76 - optional: true '@jimp/plugin-print@1.6.0': dependencies: @@ -8046,13 +8028,11 @@ snapshots: zod: 3.25.76 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-quantize@1.6.0': dependencies: image-q: 4.0.0 zod: 3.25.76 - optional: true '@jimp/plugin-resize@1.6.0': dependencies: @@ -8061,7 +8041,6 @@ snapshots: zod: 3.25.76 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-rotate@1.6.0': dependencies: @@ -8073,7 +8052,6 @@ snapshots: zod: 3.25.76 transitivePeerDependencies: - supports-color - optional: true '@jimp/plugin-threshold@1.6.0': dependencies: @@ -8085,18 +8063,15 @@ snapshots: zod: 3.25.76 transitivePeerDependencies: - supports-color - optional: true '@jimp/types@1.6.0': dependencies: zod: 3.25.76 - optional: true '@jimp/utils@1.6.0': dependencies: '@jimp/types': 1.6.0 tinycolor2: 1.6.0 - optional: true '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -9973,8 +9948,7 @@ snapshots: '@types/node@10.17.60': {} - '@types/node@16.9.1': - optional: true + '@types/node@16.9.1': {} '@types/node@20.19.37': dependencies: @@ -10268,8 +10242,7 @@ snapshots: ansis@4.2.0: {} - any-base@1.1.0: - optional: true + any-base@1.1.0: {} any-promise@1.3.0: {} @@ -10352,8 +10325,7 @@ snapshots: audio-type@2.4.0: optional: true - await-to-js@3.0.0: - optional: true + await-to-js@3.0.0: {} axios@1.13.6: dependencies: @@ -10427,8 +10399,7 @@ snapshots: execa: 4.1.0 which: 2.0.2 - bmp-ts@1.0.9: - optional: true + bmp-ts@1.0.9: {} body-parser@2.2.2: dependencies: @@ -10898,8 +10869,7 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - exif-parser@0.1.12: - optional: true + exif-parser@0.1.12: {} expect-type@1.3.0: {} @@ -11157,7 +11127,6 @@ snapshots: dependencies: image-q: 4.0.0 omggif: 1.0.10 - optional: true gitignore-to-glob@0.3.0: {} @@ -11352,7 +11321,6 @@ snapshots: image-q@4.0.0: dependencies: '@types/node': 16.9.1 - optional: true immediate@3.0.6: {} @@ -11514,7 +11482,6 @@ snapshots: '@jimp/utils': 1.6.0 transitivePeerDependencies: - supports-color - optional: true jiti@2.6.1: {} @@ -11522,8 +11489,7 @@ snapshots: jose@6.2.2: {} - jpeg-js@0.4.4: - optional: true + jpeg-js@0.4.4: {} js-stringify@1.0.2: {} @@ -11917,8 +11883,7 @@ snapshots: dependencies: mime-db: 1.54.0 - mime@3.0.0: - optional: true + mime@3.0.0: {} mimic-fn@2.1.0: {} @@ -12134,8 +12099,7 @@ snapshots: dependencies: jwt-decode: 4.0.0 - omggif@1.0.10: - optional: true + omggif@1.0.10: {} on-exit-leak-free@2.1.2: {} @@ -12310,17 +12274,14 @@ snapshots: pako@2.1.0: {} - parse-bmfont-ascii@1.0.6: - optional: true + parse-bmfont-ascii@1.0.6: {} - parse-bmfont-binary@1.0.6: - optional: true + parse-bmfont-binary@1.0.6: {} parse-bmfont-xml@1.1.6: dependencies: xml-parse-from-string: 1.0.1 xml2js: 0.5.0 - optional: true parse-ms@3.0.0: {} @@ -12396,7 +12357,6 @@ snapshots: pixelmatch@5.3.0: dependencies: pngjs: 6.0.0 - optional: true pkce-challenge@5.0.1: {} @@ -12408,8 +12368,7 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - pngjs@6.0.0: - optional: true + pngjs@6.0.0: {} pngjs@7.0.0: {} @@ -12791,8 +12750,7 @@ snapshots: sandwich-stream@2.0.2: optional: true - sax@1.6.0: - optional: true + sax@1.6.0: {} saxes@6.0.0: dependencies: @@ -12935,8 +12893,7 @@ snapshots: transitivePeerDependencies: - supports-color - simple-xml-to-json@1.2.4: - optional: true + simple-xml-to-json@1.2.4: {} simple-yenc@1.0.4: optional: true @@ -13182,8 +13139,7 @@ snapshots: tinybench@2.9.0: {} - tinycolor2@1.6.0: - optional: true + tinycolor2@1.6.0: {} tinyexec@1.0.4: {} @@ -13353,7 +13309,6 @@ snapshots: utif2@4.1.0: dependencies: pako: 1.0.11 - optional: true util-deprecate@1.0.2: {} @@ -13494,17 +13449,14 @@ snapshots: xml-name-validator@5.0.0: {} - xml-parse-from-string@1.0.1: - optional: true + xml-parse-from-string@1.0.1: {} xml2js@0.5.0: dependencies: sax: 1.6.0 xmlbuilder: 11.0.1 - optional: true - xmlbuilder@11.0.1: - optional: true + xmlbuilder@11.0.1: {} xmlchars@2.2.0: {} @@ -13565,8 +13517,7 @@ snapshots: zod@3.25.75: {} - zod@3.25.76: - optional: true + zod@3.25.76: {} zod@4.3.6: {} diff --git a/scripts/e2e/Dockerfile b/scripts/e2e/Dockerfile index 841044361af..ba8f0d46545 100644 --- a/scripts/e2e/Dockerfile +++ b/scripts/e2e/Dockerfile @@ -26,7 +26,7 @@ COPY --chown=appuser:appuser patches ./patches RUN --mount=type=cache,id=openclaw-pnpm-store,target=/home/appuser/.local/share/pnpm/store,sharing=locked \ pnpm install --frozen-lockfile -COPY --chown=appuser:appuser tsconfig.json tsconfig.plugin-sdk.dts.json tsdown.config.ts vitest.config.ts vitest.e2e.config.ts openclaw.mjs ./ +COPY --chown=appuser:appuser tsconfig.json tsconfig.plugin-sdk.dts.json tsdown.config.ts vitest.config.ts vitest.e2e.config.ts vitest.performance-config.ts openclaw.mjs ./ COPY --chown=appuser:appuser src ./src COPY --chown=appuser:appuser test ./test COPY --chown=appuser:appuser scripts ./scripts diff --git a/scripts/e2e/plugins-docker.sh b/scripts/e2e/plugins-docker.sh index 632d6924099..19222e59520 100755 --- a/scripts/e2e/plugins-docker.sh +++ b/scripts/e2e/plugins-docker.sh @@ -8,7 +8,7 @@ echo "Building Docker image..." docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" echo "Running plugins Docker E2E..." -docker run --rm -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 -i "$IMAGE_NAME" bash -s <<'EOF' +docker run --rm -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 -e OPENAI_API_KEY -i "$IMAGE_NAME" bash -s <<'EOF' set -euo pipefail if [ -f dist/index.mjs ]; then @@ -25,6 +25,190 @@ export OPENCLAW_ENTRY home_dir=$(mktemp -d "/tmp/openclaw-plugins-e2e.XXXXXX") export HOME="$home_dir" +gateway_pid="" + +stop_gateway() { + if [ -n "${gateway_pid:-}" ] && kill -0 "$gateway_pid" 2>/dev/null; then + kill "$gateway_pid" 2>/dev/null || true + wait "$gateway_pid" 2>/dev/null || true + fi + gateway_pid="" +} + +start_gateway() { + local log_file="$1" + : > "$log_file" + node "$OPENCLAW_ENTRY" gateway --port 18789 --bind loopback --allow-unconfigured \ + >"$log_file" 2>&1 & + gateway_pid=$! + + for _ in $(seq 1 120); do + if grep -q "listening on ws://" "$log_file"; then + return 0 + fi + if ! kill -0 "$gateway_pid" 2>/dev/null; then + echo "Gateway exited unexpectedly" + cat "$log_file" + exit 1 + fi + sleep 0.25 + done + + echo "Timed out waiting for gateway to start" + cat "$log_file" + exit 1 +} + +wait_for_gateway_health() { + for _ in $(seq 1 120); do + if node "$OPENCLAW_ENTRY" gateway health \ + --url ws://127.0.0.1:18789 \ + --token plugin-e2e-token \ + --json >/dev/null 2>&1; then + return 0 + fi + sleep 0.25 + done + + echo "Timed out waiting for gateway health" + return 1 +} + +run_gateway_chat_json() { + local session_key="$1" + local message="$2" + local output_file="$3" + local timeout_ms="${4:-15000}" + node - <<'NODE' "$OPENCLAW_ENTRY" "$session_key" "$message" "$output_file" "$timeout_ms" +const { execFileSync } = require("node:child_process"); +const fs = require("node:fs"); +const { randomUUID } = require("node:crypto"); + +const [, , entry, sessionKey, message, outputFile, timeoutRaw] = process.argv; +const timeoutMs = Number(timeoutRaw) > 0 ? Number(timeoutRaw) : 15000; +const gatewayArgs = [ + entry, + "gateway", + "call", + "--url", + "ws://127.0.0.1:18789", + "--token", + "plugin-e2e-token", + "--timeout", + "10000", + "--json", +]; + +const callGateway = (method, params) => { + try { + return { + ok: true, + value: JSON.parse( + execFileSync("node", [...gatewayArgs, method, "--params", JSON.stringify(params)], { + encoding: "utf8", + }), + ), + }; + } catch (error) { + const stderr = typeof error?.stderr === "string" ? error.stderr : ""; + const stdout = typeof error?.stdout === "string" ? error.stdout : ""; + const message = [String(error), stderr.trim(), stdout.trim()].filter(Boolean).join("\n"); + return { ok: false, error: new Error(message) }; + } +}; + +const extractText = (messageLike) => { + if (!messageLike || typeof messageLike !== "object") { + return ""; + } + if (typeof messageLike.text === "string" && messageLike.text.trim()) { + return messageLike.text.trim(); + } + const content = Array.isArray(messageLike.content) ? messageLike.content : []; + return content + .map((part) => + part && + typeof part === "object" && + part.type === "text" && + typeof part.text === "string" + ? part.text.trim() + : "", + ) + .filter(Boolean) + .join("\n\n") + .trim(); +}; + +const findLatestAssistantText = (history) => { + const messages = Array.isArray(history?.messages) ? history.messages : []; + for (let index = messages.length - 1; index >= 0; index -= 1) { + const candidate = messages[index]; + if (!candidate || typeof candidate !== "object" || candidate.role !== "assistant") { + continue; + } + const text = extractText(candidate); + if (text) { + return { text, message: candidate }; + } + } + return null; +}; + +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +async function main() { + const runId = `plugin-e2e-${randomUUID()}`; + const sendResult = callGateway("chat.send", { + sessionKey, + message, + idempotencyKey: runId, + }); + if (!sendResult.ok) { + throw sendResult.error; + } + + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + const historyResult = callGateway("chat.history", { sessionKey }); + if (!historyResult.ok) { + await sleep(150); + continue; + } + const history = historyResult.value; + const latestAssistant = findLatestAssistantText(history); + if (latestAssistant) { + fs.writeFileSync( + outputFile, + `${JSON.stringify( + { + sessionKey, + runId, + text: latestAssistant.text, + message: latestAssistant.message, + history, + }, + null, + 2, + )}\n`, + "utf8", + ); + return; + } + await sleep(100); + } + + throw new Error(`timed out waiting for assistant reply for ${sessionKey}`); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); +}); +NODE +} + +trap 'stop_gateway' EXIT + write_fixture_plugin() { local dir="$1" local id="$2" @@ -265,6 +449,123 @@ if (!Array.isArray(plugin.gatewayMethods) || !plugin.gatewayMethods.includes("de console.log("ok"); NODE +echo "Testing /plugin alias with Claude bundle restart semantics..." +bundle_root="$HOME/.openclaw/extensions/claude-bundle-e2e" +mkdir -p "$bundle_root/.claude-plugin" "$bundle_root/commands" +cat > "$bundle_root/.claude-plugin/plugin.json" <<'JSON' +{ + "name": "claude-bundle-e2e" +} +JSON +cat > "$bundle_root/commands/office-hours.md" <<'MD' +--- +description: Help with architecture and rollout planning +--- +Act as an engineering advisor. + +Focus on: +$ARGUMENTS +MD + +node - <<'NODE' +const fs = require("node:fs"); +const path = require("node:path"); + +const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json"); +const config = fs.existsSync(configPath) + ? JSON.parse(fs.readFileSync(configPath, "utf8")) + : {}; +config.gateway = { + ...(config.gateway || {}), + port: 18789, + auth: { mode: "token", token: "plugin-e2e-token" }, + controlUi: { enabled: false }, +}; +if (process.env.OPENAI_API_KEY) { + config.agents = { + ...(config.agents || {}), + defaults: { + ...(config.agents?.defaults || {}), + model: { primary: "openai/gpt-5.4" }, + }, + }; +} +config.commands = { + ...(config.commands || {}), + text: true, + plugins: true, +}; +fs.mkdirSync(path.dirname(configPath), { recursive: true }); +fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8"); +NODE + +gateway_log="/tmp/openclaw-plugin-command-e2e.log" +start_gateway "$gateway_log" +wait_for_gateway_health + +run_gateway_chat_json "plugin-e2e-list" "/plugin list" /tmp/plugin-command-list.json +node - <<'NODE' +const fs = require("node:fs"); +const payload = JSON.parse(fs.readFileSync("/tmp/plugin-command-list.json", "utf8")); +const text = payload.text || ""; +if (!text.includes("claude-bundle-e2e")) { + throw new Error(`expected plugin in /plugin list output, got:\n${text}`); +} +if (!text.includes("[disabled]")) { + throw new Error(`expected disabled status before enable, got:\n${text}`); +} +console.log("ok"); +NODE + +run_gateway_chat_json "plugin-e2e-enable" "/plugin enable claude-bundle-e2e" /tmp/plugin-command-enable.json +node - <<'NODE' +const fs = require("node:fs"); +const payload = JSON.parse(fs.readFileSync("/tmp/plugin-command-enable.json", "utf8")); +const text = payload.text || ""; +if (!text.includes('Plugin "claude-bundle-e2e" enabled')) { + throw new Error(`expected enable confirmation, got:\n${text}`); +} +if (!text.includes("Restart the gateway to apply.")) { + throw new Error(`expected restart hint, got:\n${text}`); +} +console.log("ok"); +NODE + +wait_for_gateway_health +run_gateway_chat_json "plugin-e2e-show" "/plugin show claude-bundle-e2e" /tmp/plugin-command-show.json +node - <<'NODE' +const fs = require("node:fs"); +const payload = JSON.parse(fs.readFileSync("/tmp/plugin-command-show.json", "utf8")); +const text = payload.text || ""; +if (!text.includes('"bundleFormat": "claude"')) { + throw new Error(`expected Claude bundle inspect payload, got:\n${text}`); +} +if (!text.includes('"enabled": true')) { + throw new Error(`expected enabled inspect payload, got:\n${text}`); +} +console.log("ok"); +NODE + +if [ -n "${OPENAI_API_KEY:-}" ]; then + echo "Testing Claude bundle command invocation..." + run_gateway_chat_json \ + "plugin-e2e-live" \ + "/office_hours Reply with exactly BUNDLE_OK and nothing else." \ + /tmp/plugin-command-live.json \ + 60000 + node - <<'NODE' +const fs = require("node:fs"); +const payload = JSON.parse(fs.readFileSync("/tmp/plugin-command-live.json", "utf8")); +const text = payload.text || ""; +if (!text.includes("BUNDLE_OK")) { + throw new Error(`expected Claude bundle command reply, got:\n${text}`); +} +console.log("ok"); +NODE +else + echo "Skipping live Claude bundle command invocation (OPENAI_API_KEY not set)." +fi + echo "Testing marketplace install and update flows..." marketplace_root="$HOME/.claude/plugins/marketplaces/fixture-marketplace" mkdir -p "$HOME/.claude/plugins" "$marketplace_root/.claude-plugin" diff --git a/src/plugins/bundled-runtime-deps.test.ts b/src/plugins/bundled-runtime-deps.test.ts index aed26eb6e01..83ee008bc7c 100644 --- a/src/plugins/bundled-runtime-deps.test.ts +++ b/src/plugins/bundled-runtime-deps.test.ts @@ -46,6 +46,10 @@ describe("bundled plugin runtime dependencies", () => { expectPluginOwnsRuntimeDep("extensions/whatsapp/package.json", "@whiskeysockets/baileys"); }); + it("keeps WhatsApp image helper deps plugin-local so bundled builds resolve Baileys peers", () => { + expectPluginOwnsRuntimeDep("extensions/whatsapp/package.json", "jimp"); + }); + it("keeps bundled proxy-agent deps plugin-local instead of mirroring them into the root package", () => { expectPluginOwnsRuntimeDep("extensions/discord/package.json", "https-proxy-agent"); }); diff --git a/src/plugins/runtime/runtime-telegram-contract.ts b/src/plugins/runtime/runtime-telegram-contract.ts index 09e7f1ff139..42d1e95ced2 100644 --- a/src/plugins/runtime/runtime-telegram-contract.ts +++ b/src/plugins/runtime/runtime-telegram-contract.ts @@ -25,6 +25,7 @@ export type { TelegramInlineButtons, } from "../../../extensions/telegram/api.js"; export type { StickerMetadata } from "../../../extensions/telegram/api.js"; +export type { TelegramApiOverride } from "../../../extensions/telegram/runtime-api.js"; export { emptyPluginConfigSchema } from "../config-schema.js"; export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js"; diff --git a/src/plugins/runtime/types-channel.ts b/src/plugins/runtime/types-channel.ts index 26d4b01da47..2049d0df26a 100644 --- a/src/plugins/runtime/types-channel.ts +++ b/src/plugins/runtime/types-channel.ts @@ -182,7 +182,7 @@ export type PluginRuntimeChannel = { token?: string; accountId?: string; verbose?: boolean; - api?: Partial; + api?: import("../../plugin-sdk/telegram.js").TelegramApiOverride; retry?: import("../../infra/retry.js").RetryConfig; cfg?: ReturnType; },