fix: refine whatsapp personal phone onboarding

This commit is contained in:
Peter Steinberger
2026-01-07 20:49:44 +01:00
parent ef644b8369
commit 54960d1380
3 changed files with 87 additions and 9 deletions

View File

@@ -22,7 +22,7 @@
- macOS: prevent gateway launchd startup race where the app could kill a just-started gateway; avoid unnecessary `bootout` and ensure the job is enabled at login. Fixes #306. Thanks @gupsammy for PR #387.
- Pairing: generate DM pairing codes with CSPRNG, expire pending codes after 1 hour, and avoid re-sending codes for already pending requests.
- Pairing: lock + atomically write pairing stores with 0600 perms and stop logging pairing codes in provider logs.
- WhatsApp: add self-phone mode to suppress pairing replies for outbound DMs and prompt during onboarding.
- WhatsApp: add self-phone mode (no pairing replies for outbound DMs) and onboarding prompt for personal vs separate numbers (auto allowlist + response prefix for personal).
- Discord: include all inbound attachments in `MediaPaths`/`MediaUrls` (back-compat `MediaPath`/`MediaUrl` still first).
- Sandbox: add `agent.sandbox.workspaceAccess` (`none`/`ro`/`rw`) to control agent workspace visibility inside the container; `ro` hard-disables `write`/`edit`.
- Routing: allow per-agent sandbox overrides (including `workspaceAccess` and `sandbox.tools`) plus per-agent tool policies in multi-agent configs. Thanks @pasogott for PR #380.

View File

@@ -61,7 +61,25 @@ WhatsApp requires a real mobile number for verification. VoIP and virtual number
- Pairing: unknown senders get a pairing code (approve via `clawdbot pairing approve --provider whatsapp <code>`; codes expire after 1 hour).
- Open: requires `whatsapp.allowFrom` to include `"*"`.
- Self messages are always allowed; “self-chat mode” still requires `whatsapp.allowFrom` to include your own number.
- **Same-phone mode**: set `whatsapp.selfChatMode=true` when Clawdbot runs on your personal WhatsApp number. This suppresses pairing replies for outbound DMs.
### Same-phone mode (personal number)
If you run Clawdbot on your **personal WhatsApp number**, set:
```json
{
"whatsapp": {
"selfChatMode": true
}
}
```
Behavior:
- Suppresses pairing replies for **outbound DMs** (prevents spamming contacts).
- Inbound unknown senders still follow `whatsapp.dmPolicy`.
Recommended for personal numbers:
- Set `whatsapp.dmPolicy="allowlist"` and add your number to `whatsapp.allowFrom`.
- Set `messages.responsePrefix` (for example, `[clawdbot]`) so replies are clearly labeled.
- **Group policy**: `whatsapp.groupPolicy` controls group handling (`open|disabled|allowlist`).
- `allowlist` uses `whatsapp.groupAllowFrom` (fallback: explicit `whatsapp.allowFrom`).
- **Self-chat mode**: avoids auto read receipts and ignores mention JIDs.

View File

@@ -202,6 +202,19 @@ function setWhatsAppAllowFrom(cfg: ClawdbotConfig, allowFrom?: string[]) {
};
}
function setMessagesResponsePrefix(
cfg: ClawdbotConfig,
responsePrefix?: string,
) {
return {
...cfg,
messages: {
...cfg.messages,
responsePrefix,
},
};
}
function setWhatsAppSelfChatMode(
cfg: ClawdbotConfig,
selfChatMode?: boolean,
@@ -403,6 +416,7 @@ async function promptWhatsAppAllowFrom(
const existingAllowFrom = cfg.whatsapp?.allowFrom ?? [];
const existingLabel =
existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
const existingResponsePrefix = cfg.messages?.responsePrefix;
await prompter.note(
[
@@ -418,6 +432,56 @@ async function promptWhatsAppAllowFrom(
"WhatsApp DM access",
);
const phoneMode = (await prompter.select({
message: "WhatsApp phone setup",
options: [
{ value: "personal", label: "This is my personal phone number" },
{ value: "separate", label: "Separate phone just for Clawdbot" },
],
})) as "personal" | "separate";
if (phoneMode === "personal") {
const entry = await prompter.text({
message: "Your WhatsApp number (E.164)",
placeholder: "+15555550123",
initialValue: existingAllowFrom[0],
validate: (value) => {
const raw = String(value ?? "").trim();
if (!raw) return "Required";
const normalized = normalizeE164(raw);
if (!normalized) return `Invalid number: ${raw}`;
return undefined;
},
});
const normalized = normalizeE164(String(entry).trim());
const merged = [
...existingAllowFrom
.filter((item) => item !== "*")
.map((item) => normalizeE164(item))
.filter(Boolean),
normalized,
];
const unique = [...new Set(merged.filter(Boolean))];
let next = setWhatsAppSelfChatMode(cfg, true);
next = setWhatsAppDmPolicy(next, "allowlist");
next = setWhatsAppAllowFrom(next, unique);
if (existingResponsePrefix === undefined) {
next = setMessagesResponsePrefix(next, "[clawdbot]");
}
await prompter.note(
[
"Personal phone mode enabled.",
"- dmPolicy set to allowlist (pairing skipped)",
`- allowFrom includes ${normalized}`,
existingResponsePrefix === undefined
? "- responsePrefix set to [clawdbot]"
: "- responsePrefix left unchanged",
].join("\n"),
"WhatsApp personal phone",
);
return next;
}
const policy = (await prompter.select({
message: "WhatsApp DM policy",
options: [
@@ -428,7 +492,8 @@ async function promptWhatsAppAllowFrom(
],
})) as DmPolicy;
let next = setWhatsAppDmPolicy(cfg, policy);
let next = setWhatsAppSelfChatMode(cfg, false);
next = setWhatsAppDmPolicy(next, policy);
if (policy === "open") {
next = setWhatsAppAllowFrom(next, ["*"]);
}
@@ -490,12 +555,7 @@ async function promptWhatsAppAllowFrom(
next = setWhatsAppAllowFrom(next, unique);
}
const selfChatMode = await prompter.confirm({
message:
"Same-phone setup? (using your personal WhatsApp number for Clawdbot)",
initialValue: next.whatsapp?.selfChatMode ?? false,
});
return setWhatsAppSelfChatMode(next, selfChatMode);
return next;
}
type SetupProvidersOptions = {