mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-24 00:11:31 +00:00
fix: harden plugin docker e2e
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -124,7 +124,9 @@ Looking for third-party plugins? See [Community Plugins](/plugins/community).
|
||||
| `slots` | Exclusive slot selectors (e.g. `memory`, `contextEngine`) |
|
||||
| `entries.\<id\>` | 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.
|
||||
|
||||
<Accordion title="Plugin states: disabled vs missing vs invalid">
|
||||
- **Disabled**: plugin exists but enablement rules turned it off. Config is preserved.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -8,6 +8,7 @@ export type {
|
||||
TelegramActionConfig,
|
||||
TelegramNetworkConfig,
|
||||
} from "openclaw/plugin-sdk/telegram";
|
||||
export type { TelegramApiOverride } from "./src/send.js";
|
||||
export type {
|
||||
OpenClawPluginService,
|
||||
OpenClawPluginServiceContext,
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
import { resolveTelegramVoiceSend } from "./voice.js";
|
||||
|
||||
type TelegramApi = Bot["api"];
|
||||
type TelegramApiOverride = Partial<TelegramApi>;
|
||||
export type TelegramApiOverride = Partial<TelegramApi>;
|
||||
const InputFileCtor: typeof grammy.InputFile =
|
||||
typeof grammy.InputFile === "function"
|
||||
? grammy.InputFile
|
||||
|
||||
@@ -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:*"
|
||||
|
||||
91
pnpm-lock.yaml
generated
91
pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -182,7 +182,7 @@ export type PluginRuntimeChannel = {
|
||||
token?: string;
|
||||
accountId?: string;
|
||||
verbose?: boolean;
|
||||
api?: Partial<import("grammy").Bot["api"]>;
|
||||
api?: import("../../plugin-sdk/telegram.js").TelegramApiOverride;
|
||||
retry?: import("../../infra/retry.js").RetryConfig;
|
||||
cfg?: ReturnType<typeof import("../../config/config.js").loadConfig>;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user