Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1a598da
fix(sandbox): pair WhatsApp login over the loopback gateway
hunglp6d Jul 9, 2026
afe5727
merge: main into fix/whatsapp-login-loopback (nemoclaw-start.sh taken…
hunglp6d Jul 10, 2026
078a1fe
fix(whatsapp): pair login over the loopback gateway instead of post-p…
hunglp6d Jul 10, 2026
f65abc3
fix(messaging): install OpenClaw plugins via npm-pack spec to preserv…
hunglp6d Jul 10, 2026
0af1b3c
Merge branch 'main' of github.com:NVIDIA/NemoClaw into fix/whatsapp-l…
hunglp6d Jul 10, 2026
7c6b53e
Merge branch 'main' into fix/whatsapp-login-loopback
hunglp6d Jul 10, 2026
afdebd2
fix(messaging): align contract tests with npm-pack install and restor…
hunglp6d Jul 10, 2026
55f77c9
Merge branch 'main' into fix/whatsapp-login-loopback
hunglp6d Jul 10, 2026
ac08021
fix(whatsapp): restrict explicit login gateway override to loopback h…
hunglp6d Jul 10, 2026
341f8eb
fix(whatsapp): state root cause ownership and removal condition for t…
hunglp6d Jul 10, 2026
ee121b7
Merge branch 'main' into fix/whatsapp-login-loopback
hunglp6d Jul 11, 2026
16776c4
fix(whatsapp): reject userinfo in the loopback gateway override allow…
hunglp6d Jul 11, 2026
aaa2620
Merge branch 'fix/whatsapp-login-loopback' of github.com:NVIDIA/NemoC…
hunglp6d Jul 11, 2026
801a949
test(whatsapp): keep gateway token fixture linear
cv Jul 11, 2026
3d0ec5a
fix(whatsapp): harden loopback token dispatch
cv Jul 11, 2026
bfb6293
test(whatsapp): assert rejected token stays redacted
cv Jul 11, 2026
78110fe
fix(whatsapp): isolate gateway tokens from caller URLs
cv Jul 11, 2026
a47c388
test(runtime): exercise absolute OpenClaw dispatch
cv Jul 11, 2026
eefbb8a
docs(messaging): fix WhatsApp variant guidance
cv Jul 11, 2026
1b22b22
merge: sync main into WhatsApp fix
cv Jul 11, 2026
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
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,10 @@ RUN set -eu; \
# Install non-messaging OpenClaw plugins that need to match the runtime.
# Reviewed-archive invariants (#5896): registry SRI, packed-byte SRI, contained
# basename in a fresh directory, local-archive-only install, and cleanup.
# The verified tarball installs through the `npm-pack:` spec so OpenClaw
# records npm provenance; bare archive-path installs record archive
# provenance, which fails the trusted-official-install check gating
# openKeyedStore on OpenClaw >= 2026.6.10.
# hadolint ignore=DL3059,DL4006
RUN set -eu; \
verify_openclaw_plugin_integrity() { \
Expand Down Expand Up @@ -1070,7 +1074,7 @@ RUN set -eu; \
plugin_spec="${1}@${OPENCLAW_VERSION}"; \
plugin_archive="$(verify_openclaw_plugin_integrity "$plugin_spec")"; \
NPM_CONFIG_IGNORE_SCRIPTS=true npm_config_ignore_scripts=true \
openclaw plugins install "$plugin_archive" --pin; \
openclaw plugins install "npm-pack:${plugin_archive}"; \
}; \
NEMOCLAW_OPENCLAW_PLUGIN_PACK_DIR="$(mktemp -d)"; \
if [ "$NEMOCLAW_OPENCLAW_OTEL" = "1" ] || [ "$NEMOCLAW_WEB_SEARCH_ENABLED" = "1" ]; then \
Expand Down
7 changes: 1 addition & 6 deletions docs/manage-sandboxes/messaging-channels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,7 @@ hermes whatsapp # Hermes sandboxes
```

<AgentOnly variant="openclaw">
NemoClaw validates the gateway URL before pairing and renders the WhatsApp QR code in a compact terminal form so it fits in smaller terminal windows.
After a successful QR login, NemoClaw restarts the WhatsApp channel through a bounded call to the sandbox's generated private gateway URL so the new session takes effect without granting `operator.admin` to the pairing device.
NemoClaw sends the gateway token only to that generated private URL.
If you set a different `OPENCLAW_GATEWAY_URL`, the login can save credentials, but NemoClaw does not automatically restart the channel.
Exit the sandbox and run `$$nemoclaw <sandbox> channels status --channel whatsapp` to confirm that it reconnects.
If the bounded restart fails for another reason, the pairing remains saved and the same status command reports the channel state.
For OpenClaw sandboxes, NemoClaw validates the gateway URL before pairing and renders the WhatsApp QR code in a compact terminal form so it fits in smaller terminal windows.
If pairing exits with a gateway close such as `1008`, rerun the login command one time and then check `$$nemoclaw <sandbox> channels status --channel whatsapp` so you can diagnose the gateway/session path separately from QR rendering.
</AgentOnly>

Expand Down
557 changes: 239 additions & 318 deletions scripts/nemoclaw-start.sh

Large diffs are not rendered by default.

20 changes: 12 additions & 8 deletions src/lib/messaging/applier/build/messaging-build-applier.mts
Original file line number Diff line number Diff line change
Expand Up @@ -661,14 +661,18 @@ export function installOpenClawMessagingPlugins(plan: MessagingBuildPlan | null,
for (const install of collectOpenClawMessagingPluginInstalls(plan, env)) {
const packed = packVerifiedOpenClawPluginArchive(install, env);
try {
runCommand(
["openclaw", "plugins", "install", packed.archivePath, ...(install.pin ? ["--pin"] : [])],
{
...env,
NPM_CONFIG_IGNORE_SCRIPTS: "true",
npm_config_ignore_scripts: "true",
},
);
// Install through the `npm-pack:` spec so OpenClaw records npm
// provenance (source, resolved name/version, integrity) for the
// verified tarball. A bare archive path records archive provenance,
// which fails the trusted-official-install check gating openKeyedStore
// on OpenClaw >= 2026.6.10 and crash-loops channel plugins that use
// keyed state (e.g. WhatsApp). npm-pack installs always record the
// exact resolved version, so `--pin` is not needed.
runCommand(["openclaw", "plugins", "install", `npm-pack:${packed.archivePath}`], {
...env,
NPM_CONFIG_IGNORE_SCRIPTS: "true",
npm_config_ignore_scripts: "true",
});
} finally {
rmSync(packed.rootDir, { recursive: true, force: true });
}
Expand Down
4 changes: 2 additions & 2 deletions test/messaging-build-applier-integrity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ describe("messaging-build-applier.mts: plugin archive integrity", () => {
expect(trace).toContain("npm|view|@openclaw/slack@2026.6.10|dist.integrity");
expect(trace).toContain("npm|view|@openclaw/slack@2026.6.10|dist.tarball");
expect(trace).toContain("npm|pack|@openclaw/slack@2026.6.10|--pack-destination");
expect(trace).toContain("openclaw|plugins|install");
expect(trace).toContain("slack-2026.6.10.tgz|--pin");
expect(trace).toContain("openclaw|plugins|install|npm-pack:");
expect(trace).toContain("slack-2026.6.10.tgz|");
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
Expand Down
24 changes: 12 additions & 12 deletions test/messaging-build-applier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,8 @@ describe("messaging-build-applier.mts: agent-install", () => {
const trace = fs.readFileSync(tracePath, "utf-8");
expect(trace).toContain("npm|view|@openclaw/discord@2026.6.10|dist.integrity");
expect(trace).toContain("npm|pack|@openclaw/discord@2026.6.10|--pack-destination");
expect(trace).toContain("plugins|install|");
expect(trace).toContain("discord-2026.6.10.tgz|--pin");
expect(trace).toContain("ignore-scripts=true/true");
expect(trace).toContain("plugins|install|npm-pack:");
expect(trace).toContain("discord-2026.6.10.tgz|ignore-scripts=true/true");
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
Expand Down Expand Up @@ -675,8 +674,8 @@ describe("messaging-build-applier.mts: agent-install", () => {
expect(trace).toContain("npm|view|@openclaw/msteams@2026.6.10|dist.integrity");
expect(trace).toContain("npm|view|@openclaw/msteams@2026.6.10|dist.tarball");
expect(trace).toContain("npm|pack|@openclaw/msteams@2026.6.10|--pack-destination");
expect(trace).toContain("openclaw|plugins|install|");
expect(trace).toContain("msteams-2026.6.10.tgz|--pin");
expect(trace).toContain("openclaw|plugins|install|npm-pack:");
expect(trace).toContain("msteams-2026.6.10.tgz|");
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
Expand Down Expand Up @@ -866,7 +865,8 @@ describe("messaging-build-applier.mts: agent-install", () => {
expect(trace).toContain(`npm|view|${packageSpec}|dist.integrity`);
expect(trace).toContain(`npm|view|${packageSpec}|dist.tarball`);
expect(trace).toContain(`npm|pack|${packageSpec}|--pack-destination`);
expect(trace).toContain(`${archiveName}|--pin|||`);
expect(trace).toContain("plugins|install|npm-pack:");
expect(trace).toContain(`${archiveName}||||`);
}
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
Expand Down Expand Up @@ -919,8 +919,8 @@ describe("messaging-build-applier.mts: agent-install", () => {
expect(trace).toContain("npm|view|@openclaw/slack@2026.6.10|dist.integrity");
expect(trace).toContain("npm|view|@openclaw/slack@2026.6.10|dist.tarball");
expect(trace).toContain("npm|pack|@openclaw/slack@2026.6.10|--pack-destination");
expect(trace).toContain("openclaw|plugins|install|");
expect(trace).toContain("slack-2026.6.10.tgz|--pin");
expect(trace).toContain("openclaw|plugins|install|npm-pack:");
expect(trace).toContain("slack-2026.6.10.tgz|");
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
Expand Down Expand Up @@ -1065,8 +1065,8 @@ describe("messaging-build-applier.mts: agent-install", () => {
"const args = process.argv.slice(2);",
'fs.appendFileSync(process.env.OPENCLAW_TRACE, `${args.join("|")}|${process.env.DISCORD_BOT_TOKEN || ""}|${process.env.BRAVE_API_KEY || ""}\\n`);',
'if (args[0] === "plugins" && args[1] === "install") {',
' if (!args[2].endsWith("discord-2026.6.10.tgz")) process.exit(41);',
' if (args[3] !== "--pin") process.exit(47);',
' if (!args[2].startsWith("npm-pack:") || !args[2].endsWith("discord-2026.6.10.tgz")) process.exit(41);',
" if (args.length !== 3) process.exit(47);",
" process.exit(0);",
"}",
'if (args[0] === "doctor" && args[1] === "--fix" && args[2] === "--non-interactive") {',
Expand Down Expand Up @@ -1133,8 +1133,8 @@ describe("messaging-build-applier.mts: agent-install", () => {
const trace = fs.readFileSync(tracePath, "utf-8");
expect(trace).toContain("npm|view|@openclaw/discord@2026.6.10|dist.integrity||");
expect(trace).toContain("npm|pack|@openclaw/discord@2026.6.10|--pack-destination||");
expect(trace).toContain("plugins|install|");
expect(trace).toContain("discord-2026.6.10.tgz|--pin||");
expect(trace).toContain("plugins|install|npm-pack:");
expect(trace).toContain("discord-2026.6.10.tgz||");
expect(trace).toContain(
"doctor|--fix|--non-interactive|openshell:resolve:env:DISCORD_BOT_TOKEN|openshell:resolve:env:BRAVE_API_KEY",
);
Expand Down
Loading
Loading