Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -406,13 +406,16 @@ ENV NPM_CONFIG_OFFLINE=true \
# This must fail the image build if registration fails; otherwise the sandbox
# can boot with a discoverable plugin manifest but without the /nemoclaw runtime
# command registered in the active Gateway.
# Re-apply WeChat account seeding after OpenClaw doctor/plugin-install touches
# openclaw.json; the seed script no-ops unless WeChat is actively configured.
# Prune non-runtime metadata from staged bundled plugin dependencies before
# this layer is committed; deleting it in a later layer would not reduce the
# OCI image imported by k3s.
# hadolint ignore=DL3059,DL4006
RUN openclaw plugins install /opt/nemoclaw \
&& openclaw plugins enable nemoclaw \
&& openclaw plugins inspect nemoclaw --json > /dev/null \
&& python3 /usr/local/lib/nemoclaw/seed-wechat-accounts.py \
&& if [ -d /sandbox/.openclaw/plugin-runtime-deps ]; then \
find /sandbox/.openclaw/plugin-runtime-deps -type f \( \
-name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o \
Expand Down
33 changes: 31 additions & 2 deletions scripts/seed-wechat-accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
# is registered. Without channels.openclaw-weixin.accounts.<id>.enabled=true
# in openclaw.json, the plugin's auth/accounts.ts considers the account
# disabled and the bridge won't start, even if the per-account state files
# above exist. generate-openclaw-config.py invokes this only after
# plugins.installs.openclaw-weixin has been preserved from the base image.
# above exist. The patch also restores the openclaw-weixin plugin registry
# entry because later OpenClaw config rewrites can drop it while leaving the
# pre-installed extension files in place.
#
# State dir resolution mirrors the upstream's resolveStateDir():
# $OPENCLAW_STATE_DIR || $CLAWDBOT_STATE_DIR || ~/.openclaw
Expand Down Expand Up @@ -53,6 +54,8 @@
import sys


WECHAT_PLUGIN_ID = "openclaw-weixin"
WECHAT_PLUGIN_SPEC = "@tencent-weixin/openclaw-weixin@2.4.2"
WECHAT_TOKEN_PLACEHOLDER = "openshell:resolve:env:WECHAT_BOT_TOKEN"


Expand Down Expand Up @@ -141,6 +144,32 @@ def _patch_openclaw_config(account_id: str) -> None:
)
return

plugins = cfg.setdefault("plugins", {})
if not isinstance(plugins, dict):
plugins = {}
cfg["plugins"] = plugins
installs = plugins.setdefault("installs", {})
if not isinstance(installs, dict):
installs = {}
plugins["installs"] = installs
installs.setdefault(
WECHAT_PLUGIN_ID,
{
"type": "npm",
"spec": WECHAT_PLUGIN_SPEC,
"resolved": WECHAT_PLUGIN_SPEC,
},
)
entries = plugins.setdefault("entries", {})
if not isinstance(entries, dict):
entries = {}
plugins["entries"] = entries
wechat_entry = entries.setdefault(WECHAT_PLUGIN_ID, {})
if not isinstance(wechat_entry, dict):
wechat_entry = {}
entries[WECHAT_PLUGIN_ID] = wechat_entry
wechat_entry["enabled"] = True

channels = cfg.setdefault("channels", {})
weixin = channels.setdefault("openclaw-weixin", {})
weixin["channelConfigUpdatedAt"] = _js_iso_utc()
Expand Down
35 changes: 35 additions & 0 deletions test/seed-wechat-accounts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,41 @@ describe("seed-wechat-accounts.py: openclaw.json patching (channels.openclaw-wei
expect(cfg.channels["openclaw-weixin"].accounts.primary.enabled).toBe(true);
});

it("restores plugin registration and channel block after a later OpenClaw config rewrite drops them", () => {
// The Dockerfile invokes this seed script again after OpenClaw doctor and
// plugin installation because those commands can rewrite openclaw.json
// after generate-openclaw-config.py first runs. Re-running the seed must
// be enough to put the upstream WeChat plugin and channel registration
// back; otherwise the gateway rejects channels.openclaw-weixin as an
// unknown channel id at startup.
writeOpenclawConfig({
channels: {
telegram: { accounts: { default: { enabled: true } } },
slack: { accounts: { default: { enabled: true } } },
},
plugins: {},
});

const result = runSeed({
NEMOCLAW_WECHAT_CONFIG_B64: configB64({
accountId: "primary",
baseUrl: "https://ilinkai.wechat.com",
userId: "wxid-42",
}),
});
expect(result.status).toBe(0);

const cfg = readJson(path.join(tmpDir, ".openclaw", "openclaw.json"));
expect(cfg.plugins.installs["openclaw-weixin"]).toEqual({
type: "npm",
spec: "@tencent-weixin/openclaw-weixin@2.4.2",
resolved: "@tencent-weixin/openclaw-weixin@2.4.2",
});
expect(cfg.plugins.entries["openclaw-weixin"].enabled).toBe(true);
expect(Object.keys(cfg.channels)).toEqual(["telegram", "slack", "openclaw-weixin"]);
expect(cfg.channels["openclaw-weixin"].accounts.primary.enabled).toBe(true);
});

it("bails (and warns) when openclaw.json is missing — does not invent a config", () => {
// generate-openclaw-config.py runs first and is responsible for producing
// openclaw.json. If it failed silently, we'd rather print a warning than
Expand Down
Loading