docs(skills): merge add-signal-v2 lessons into add-signal, drop v2
Absorbs battle-tested knowledge from the v2 skill into the upstream
add-signal: registration paths (new number + linked device), CAPTCHA
flow, VoIP SMS-first timing, Java prereq, config-lock warning, wiring
SQL for groups, not_member silent-drop fix, GroupV2 groupId extraction
note, and UUID-based platform ID format.
Corrects a factual error in the upstream: DM platform IDs are
signal:{UUID} (ACI), not phone numbers.
Removes the now-redundant add-signal-v2 skill.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,311 +0,0 @@
|
|||||||
# Add Signal Channel (v2)
|
|
||||||
|
|
||||||
Adds Signal messaging support to NanoClaw v2 using `signal-sdk` (a TypeScript
|
|
||||||
wrapper around `signal-cli`). Unlike Telegram/Discord, Signal has no bot API —
|
|
||||||
NanoClaw registers as a full Signal account on a dedicated phone number.
|
|
||||||
|
|
||||||
**Two registration paths:**
|
|
||||||
- **New number (recommended):** Register a dedicated SIM or VoIP number as a
|
|
||||||
standalone Signal account. NanoClaw owns the number entirely.
|
|
||||||
- **Linked device:** Join an existing Signal account as a secondary device via
|
|
||||||
QR code. Simpler, but NanoClaw shares your personal number.
|
|
||||||
|
|
||||||
Both paths are documented below. The new-number path is battle-tested.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pre-flight
|
|
||||||
|
|
||||||
Check if `src/channels/signal.ts` exists and the import is uncommented in
|
|
||||||
`src/channels/index.ts`. If both are in place, skip to Registration.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
### 1. Check Java
|
|
||||||
|
|
||||||
Java 17+ is required. Check:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
java -version
|
|
||||||
```
|
|
||||||
|
|
||||||
If missing:
|
|
||||||
- **RHEL/CentOS/Fedora:** `sudo dnf install -y java-17-openjdk`
|
|
||||||
- **Debian/Ubuntu:** `sudo apt-get install -y default-jre`
|
|
||||||
- **macOS:** `brew install --cask temurin@17`
|
|
||||||
|
|
||||||
Java 17–25 all work. Java 25 (RHEL9 default) is confirmed working.
|
|
||||||
|
|
||||||
### 2. Install signal-cli
|
|
||||||
|
|
||||||
The `signal-sdk` npm package bundles signal-cli, but the bundled version is
|
|
||||||
often outdated. Install the latest standalone binary:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SIGNAL_CLI_VERSION=$(curl -fsSL https://api.github.com/repos/AsamK/signal-cli/releases/latest | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'][1:])")
|
|
||||||
curl -fsSL "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux-native.tar.gz" \
|
|
||||||
| tar -xz -C ~/.local
|
|
||||||
ln -sf ~/.local/signal-cli ~/.local/bin/signal-cli
|
|
||||||
signal-cli --version
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note:** The Linux native tarball extracts a single binary directly to
|
|
||||||
> `~/.local/signal-cli` (not into a subdirectory). The symlink above handles this.
|
|
||||||
|
|
||||||
### 3. Install signal-sdk
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install signal-sdk
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Enable the adapter
|
|
||||||
|
|
||||||
Uncomment the Signal import in `src/channels/index.ts`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import './signal.js';
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Build
|
|
||||||
|
|
||||||
Always build with Node 22 (nvm):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source ~/.nvm/nvm.sh && nvm use 22 && npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Path A: Register a new Signal number
|
|
||||||
|
|
||||||
Use this if you have a dedicated SIM or VoIP number for NanoClaw.
|
|
||||||
|
|
||||||
> **VoIP numbers:** Signal requires SMS verification before voice. Some VoIP
|
|
||||||
> providers are blocked even for voice calls. If registration fails with an auth
|
|
||||||
> error, try a different provider or a physical SIM.
|
|
||||||
|
|
||||||
### Step 1: Request SMS verification
|
|
||||||
|
|
||||||
Signal requires a CAPTCHA on first registration:
|
|
||||||
|
|
||||||
1. Open `https://signalcaptchas.org/registration/generate.html` in a browser
|
|
||||||
2. Solve the captcha
|
|
||||||
3. Right-click the **"Open Signal"** button → **Copy Link**
|
|
||||||
4. The link starts with `signalcaptcha://...`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SIGNAL_CLI_CONFIG_PATH=/path/to/nanoclaw/data/signal \
|
|
||||||
signal-cli -u +YOURNUMBER register \
|
|
||||||
--captcha "PASTE_CAPTCHA_TOKEN_HERE"
|
|
||||||
```
|
|
||||||
|
|
||||||
The captcha token is everything after `signalcaptcha://` in the copied link.
|
|
||||||
|
|
||||||
### Step 2: Voice call fallback (VoIP numbers without SMS)
|
|
||||||
|
|
||||||
If your number cannot receive SMS, wait ~60 seconds after the SMS request then
|
|
||||||
request a voice call:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SIGNAL_CLI_CONFIG_PATH=/path/to/nanoclaw/data/signal \
|
|
||||||
signal-cli -u +YOURNUMBER register --voice \
|
|
||||||
--captcha "SAME_CAPTCHA_TOKEN"
|
|
||||||
```
|
|
||||||
|
|
||||||
Signal will call your number and read a 6-digit code.
|
|
||||||
|
|
||||||
> The captcha token from Step 1 is reusable for the voice retry — no need to
|
|
||||||
> solve a new one.
|
|
||||||
|
|
||||||
### Step 3: Verify
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SIGNAL_CLI_CONFIG_PATH=/path/to/nanoclaw/data/signal \
|
|
||||||
signal-cli -u +YOURNUMBER verify CODE
|
|
||||||
```
|
|
||||||
|
|
||||||
No output = success.
|
|
||||||
|
|
||||||
### Step 4: Set profile name (optional)
|
|
||||||
|
|
||||||
> ⚠ signal-sdk holds an exclusive lock on `data/signal/` while nanoclaw is
|
|
||||||
> running. Stop the service before running signal-cli commands, then restart.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl --user stop nanoclaw
|
|
||||||
SIGNAL_CLI_CONFIG_PATH=/path/to/nanoclaw/data/signal \
|
|
||||||
signal-cli -u +YOURNUMBER updateProfile --name "YourBotName"
|
|
||||||
systemctl --user start nanoclaw
|
|
||||||
```
|
|
||||||
|
|
||||||
To set an avatar too:
|
|
||||||
```bash
|
|
||||||
signal-cli -u +YOURNUMBER updateProfile --name "YourBotName" --avatar /path/to/avatar.jpg
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Path B: Link as secondary device
|
|
||||||
|
|
||||||
Use this to join an existing Signal account as a secondary device.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p data/signal
|
|
||||||
export SIGNAL_CLI_CONFIG_PATH=$(pwd)/data/signal
|
|
||||||
npx signal-sdk link -n "NanoClaw" -a +YOURNUMBER
|
|
||||||
```
|
|
||||||
|
|
||||||
This prints a QR code. On your phone: **Settings → Linked Devices → Link New Device**.
|
|
||||||
Scan the code within ~30 seconds.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configure environment
|
|
||||||
|
|
||||||
Add to `.env`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SIGNAL_PHONE_NUMBER=+YOURNUMBER
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Wire to an agent
|
|
||||||
|
|
||||||
### DMs
|
|
||||||
|
|
||||||
After the service starts, **send any message** to the Signal number from your
|
|
||||||
personal Signal app. The router auto-creates a `messaging_groups` row. Then:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sqlite3 data/v2.db \
|
|
||||||
"SELECT id, platform_id FROM messaging_groups WHERE channel_type='signal' ORDER BY created_at DESC LIMIT 5"
|
|
||||||
```
|
|
||||||
|
|
||||||
Pass the `id` and `platform_id` to `/init-first-agent` or wire manually.
|
|
||||||
|
|
||||||
**Important:** DM `platform_id` is UUID-based, not phone-based:
|
|
||||||
- DM: `signal:3de71d7f-ffa3-437e-b4db-097534bccd46` (UUID of sender)
|
|
||||||
- Group: `signal:UwaIz6bc09Olg1pBr/XeBuQf6z3fyCZoNj/Y3Tpe3hI=` (base64 group ID)
|
|
||||||
|
|
||||||
### Groups
|
|
||||||
|
|
||||||
Add the Signal number to a group from your phone. Send any message — the router
|
|
||||||
auto-creates the group's `messaging_groups` row. Wire it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sqlite3 data/v2.db \
|
|
||||||
"SELECT id, platform_id FROM messaging_groups WHERE channel_type='signal' ORDER BY created_at DESC LIMIT 5"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then insert the wiring (replace IDs):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
|
||||||
sqlite3 data/v2.db "
|
|
||||||
INSERT OR IGNORE INTO messaging_group_agents
|
|
||||||
(id, messaging_group_id, agent_group_id, session_mode, priority, created_at)
|
|
||||||
VALUES
|
|
||||||
('mga-'||hex(randomblob(8)), 'mg-GROUPID', 'ag-AGENTID', 'isolated', 0, '$NOW');
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `session_mode='isolated'` for groups so each group has its own session.
|
|
||||||
|
|
||||||
### Grant user access
|
|
||||||
|
|
||||||
Users who message via Signal need to be granted membership. Without this,
|
|
||||||
messages are silently dropped with `not_member`. After first contact:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
|
||||||
sqlite3 data/v2.db "
|
|
||||||
INSERT OR REPLACE INTO user_roles (user_id, role, agent_group_id, granted_by, granted_at)
|
|
||||||
VALUES ('signal:UUID', 'owner', NULL, 'system', '$NOW');
|
|
||||||
INSERT OR IGNORE INTO agent_group_members (user_id, agent_group_id, added_by, added_at)
|
|
||||||
VALUES ('signal:UUID', 'ag-AGENTID', 'system', '$NOW');
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
Find the UUID from `messaging_groups.platform_id` or the `users` table.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Voice Transcription (optional)
|
|
||||||
|
|
||||||
Inbound voice messages are automatically transcribed using a local whisper
|
|
||||||
binary. The feature degrades gracefully — if whisper or ffmpeg is missing,
|
|
||||||
voice messages are still delivered as attachments with no transcript.
|
|
||||||
|
|
||||||
See `/add-voice-transcription-free-whisper` for full setup instructions
|
|
||||||
(ffmpeg, whisper backends, model download, environment variables, troubleshooting).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Channel Info
|
|
||||||
|
|
||||||
- **type**: `signal`
|
|
||||||
- **terminology**: "Chats" (1:1) and "groups" (multi-member). No threads.
|
|
||||||
- **supports-threads**: no
|
|
||||||
- **inbound**: text, reactions (forwarded to agent with emoji + targetTimestamp), images, files, voice (transcribed via whisper-cli if installed)
|
|
||||||
- **outbound**: text, reactions (sendReaction), file attachments
|
|
||||||
- **platform-id-format**:
|
|
||||||
- DM: `signal:{UUID}` — sender's Signal UUID, **not** their phone number
|
|
||||||
- Group: `signal:{base64GroupId}`
|
|
||||||
- **how-to-find-id**: Send a message to the bot, then query `messaging_groups`
|
|
||||||
as shown above.
|
|
||||||
- **config-lock**: signal-sdk holds an exclusive lock on `data/signal/` while
|
|
||||||
nanoclaw is running. Stop the service before running any `signal-cli` commands.
|
|
||||||
- **attachment storage**: signal-sdk launches signal-cli **without** a `--config`
|
|
||||||
flag, so signal-cli stores attachments at the XDG default
|
|
||||||
(`~/.local/share/signal-cli/attachments/`), not under `data/signal/`. The
|
|
||||||
adapter checks both locations. Verify with:
|
|
||||||
`ps aux | grep signal-cli` — if there is no `-c` argument, XDG default is in use.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**`Config file is in use by another instance`** — nanoclaw is running and
|
|
||||||
signal-sdk has the lock. Stop the service, run the command, restart.
|
|
||||||
|
|
||||||
**Messages dropped with `not_member`** — the Signal user hasn't been granted
|
|
||||||
membership. See "Grant user access" above. This affects every new Signal user,
|
|
||||||
including the owner's Signal identity (which is separate from their Telegram
|
|
||||||
identity even if it's the same person).
|
|
||||||
|
|
||||||
**Captcha required** — Signal requires captcha for new registrations. Go to
|
|
||||||
`https://signalcaptchas.org/registration/generate.html`, solve it, right-click
|
|
||||||
"Open Signal", copy the link, extract the token after `signalcaptcha://`.
|
|
||||||
|
|
||||||
**`Invalid verification method: Before requesting voice verification…`** —
|
|
||||||
You must request SMS first, wait ~60 seconds, then request voice. Both can use
|
|
||||||
the same captcha token.
|
|
||||||
|
|
||||||
**`The provided model identifier is invalid` (Bedrock)** — unrelated to Signal;
|
|
||||||
this is a LiteLLM model ID issue.
|
|
||||||
|
|
||||||
**Group replies going to DM instead of group** — modern Signal groups use
|
|
||||||
GroupV2. The adapter must extract the group ID from
|
|
||||||
`envelope?.dataMessage?.groupV2?.id` (not just `groupInfo?.groupId`, which is
|
|
||||||
GroupV1/legacy). Check `src/channels/signal.ts` and confirm the groupId
|
|
||||||
extraction falls through to `groupV2.id`.
|
|
||||||
|
|
||||||
**Voice messages / attachments silently skipped, no transcript** — signal-sdk
|
|
||||||
launches signal-cli without a `--config` flag, so attachments land at the XDG
|
|
||||||
default (`~/.local/share/signal-cli/attachments/`) rather than under
|
|
||||||
`data/signal/`. Confirm with `ps aux | grep signal-cli` — if there is no `-c`
|
|
||||||
argument in the process line, the XDG default is in use. The adapter falls back
|
|
||||||
to that location automatically. If you still see no "Signal attachment saved"
|
|
||||||
log lines, add a debug log around the `if (!storedPath) continue` guard in
|
|
||||||
`src/channels/signal.ts` to inspect `att.storedFilename` and `att.id`.
|
|
||||||
|
|
||||||
**Java not found** — install Java 17+ (see Install step 1).
|
|
||||||
|
|
||||||
**QR code expired (Path B)** — QR codes expire in ~30 seconds. Re-run the
|
|
||||||
link command to generate a new one.
|
|
||||||
|
|
||||||
**signal-cli binary location** — The native Linux tarball extracts directly to
|
|
||||||
`~/.local/signal-cli` (a single file, not a directory). The system `aws` or
|
|
||||||
other tools named `signal-cli` won't be in PATH by default; check
|
|
||||||
`~/.local/bin/signal-cli`.
|
|
||||||
@@ -5,20 +5,116 @@ description: Add Signal channel integration via signal-cli TCP daemon. Native ad
|
|||||||
|
|
||||||
# Add Signal Channel
|
# Add Signal Channel
|
||||||
|
|
||||||
Adds Signal messaging support via a native adapter that speaks JSON-RPC to a [signal-cli](https://github.com/AsamK/signal-cli) TCP daemon. No Chat SDK bridge, no npm deps — only Node.js builtins.
|
Adds Signal messaging support via a native adapter that speaks JSON-RPC to a [signal-cli](https://github.com/AsamK/signal-cli) TCP daemon. No Chat SDK bridge — only Node.js builtins (`node:net`, `node:child_process`, `node:fs`).
|
||||||
|
|
||||||
|
Unlike Telegram or Discord, Signal has no bot API. NanoClaw registers as a full Signal account on a dedicated phone number (recommended) or links as a secondary device on your existing number.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
`signal-cli` installed and a Signal account linked:
|
### Java
|
||||||
|
|
||||||
- macOS: `brew install signal-cli`
|
signal-cli requires Java 17+:
|
||||||
- Linux: download from [GitHub releases](https://github.com/AsamK/signal-cli/releases)
|
|
||||||
- Link your account: `signal-cli -a +1YOURNUMBER link` (follow the QR instructions)
|
```bash
|
||||||
|
java -version
|
||||||
|
```
|
||||||
|
|
||||||
|
If missing:
|
||||||
|
- **macOS:** `brew install --cask temurin@17`
|
||||||
|
- **Debian/Ubuntu:** `sudo apt-get install -y default-jre`
|
||||||
|
- **RHEL/Fedora:** `sudo dnf install -y java-17-openjdk`
|
||||||
|
|
||||||
|
Java 17–25 all work.
|
||||||
|
|
||||||
|
### signal-cli
|
||||||
|
|
||||||
|
- **macOS:** `brew install signal-cli`
|
||||||
|
- **Linux:** download the native binary from [GitHub releases](https://github.com/AsamK/signal-cli/releases):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SIGNAL_CLI_VERSION=$(curl -fsSL https://api.github.com/repos/AsamK/signal-cli/releases/latest | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'][1:])")
|
||||||
|
curl -fsSL "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-Linux-native.tar.gz" \
|
||||||
|
| tar -xz -C ~/.local
|
||||||
|
ln -sf ~/.local/signal-cli ~/.local/bin/signal-cli
|
||||||
|
signal-cli --version
|
||||||
|
```
|
||||||
|
|
||||||
|
> The Linux native tarball extracts a single binary directly to `~/.local/signal-cli` (not into a subdirectory). The symlink above puts it on PATH.
|
||||||
|
|
||||||
|
## Registration
|
||||||
|
|
||||||
|
Two paths. The new-number path is recommended and battle-tested.
|
||||||
|
|
||||||
|
### Path A: Register a new number (recommended)
|
||||||
|
|
||||||
|
Use a dedicated SIM or VoIP number. NanoClaw owns it entirely.
|
||||||
|
|
||||||
|
> **VoIP numbers:** Signal requires SMS verification before voice. Some VoIP providers are blocked even for voice calls. If registration fails with an auth error, try a different provider or a physical SIM.
|
||||||
|
|
||||||
|
**Step 1: Solve the CAPTCHA**
|
||||||
|
|
||||||
|
Signal requires a CAPTCHA on first registration:
|
||||||
|
|
||||||
|
1. Open `https://signalcaptchas.org/registration/generate.html` in a browser
|
||||||
|
2. Solve the captcha
|
||||||
|
3. Right-click the **"Open Signal"** button → **Copy Link**
|
||||||
|
4. The link starts with `signalcaptcha://` — the token is everything after that prefix
|
||||||
|
|
||||||
|
**Step 2: Request SMS verification**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
signal-cli -a +1YOURNUMBER register --captcha "PASTE_TOKEN_HERE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Voice call fallback (if your number can't receive SMS)**
|
||||||
|
|
||||||
|
Wait ~60 seconds after the SMS request, then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
signal-cli -a +1YOURNUMBER register --voice --captcha "SAME_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
Signal calls your number and reads a 6-digit code. The same captcha token is reusable — no need to solve a new one.
|
||||||
|
|
||||||
|
> You must request SMS first. Requesting voice immediately fails with `Invalid verification method: Before requesting voice verification…`
|
||||||
|
|
||||||
|
**Step 4: Verify**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
signal-cli -a +1YOURNUMBER verify CODE
|
||||||
|
```
|
||||||
|
|
||||||
|
No output = success.
|
||||||
|
|
||||||
|
**Step 5: Set profile name (optional)**
|
||||||
|
|
||||||
|
> ⚠ Stop NanoClaw before running signal-cli commands — the daemon holds an exclusive lock on its data directory while running.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
|
||||||
|
signal-cli -a +1YOURNUMBER updateProfile --name "YourBotName"
|
||||||
|
# optionally: --avatar /path/to/avatar.jpg
|
||||||
|
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
systemctl --user stop nanoclaw
|
||||||
|
signal-cli -a +1YOURNUMBER updateProfile --name "YourBotName"
|
||||||
|
systemctl --user start nanoclaw
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path B: Link as secondary device
|
||||||
|
|
||||||
|
Joins an existing Signal account as a secondary device. Simpler, but NanoClaw shares your personal number.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
signal-cli -a +1YOURNUMBER link --name "NanoClaw"
|
||||||
|
```
|
||||||
|
|
||||||
|
This prints a `tsdevice:` URI. Scan it as a QR code on your phone: **Settings → Linked Devices → Link New Device**. QR codes expire in ~30 seconds — re-run if it expires.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
NanoClaw doesn't ship channels in trunk. This skill copies the Signal adapter and its tests in from the `channels` branch.
|
|
||||||
|
|
||||||
### Pre-flight (idempotent)
|
### Pre-flight (idempotent)
|
||||||
|
|
||||||
Skip to **Credentials** if all of these are already in place:
|
Skip to **Credentials** if all of these are already in place:
|
||||||
@@ -55,7 +151,7 @@ import './signal.js';
|
|||||||
pnpm run build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
No npm packages to install — the adapter uses only Node.js builtins (`node:net`, `node:child_process`, `node:fs`).
|
No npm packages to install — the adapter uses only Node.js builtins.
|
||||||
|
|
||||||
## Credentials
|
## Credentials
|
||||||
|
|
||||||
@@ -97,27 +193,73 @@ launchctl kickstart -k gui/$(id -u)/com.nanoclaw
|
|||||||
systemctl --user restart nanoclaw
|
systemctl --user restart nanoclaw
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Wiring
|
||||||
|
|
||||||
|
### DMs
|
||||||
|
|
||||||
|
After the service starts, send any message to the Signal number from your personal Signal app. The router auto-creates a `messaging_groups` row. Then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sqlite3 data/v2.db \
|
||||||
|
"SELECT id, platform_id FROM messaging_groups WHERE channel_type='signal' ORDER BY created_at DESC LIMIT 5"
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass the `id` to `/init-first-agent` or `/manage-channels` to wire it to an agent group.
|
||||||
|
|
||||||
|
### Groups
|
||||||
|
|
||||||
|
Add the Signal number to a group from your phone, send any message, then wire the resulting row the same way. For isolated per-group sessions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
||||||
|
sqlite3 data/v2.db "
|
||||||
|
INSERT OR IGNORE INTO messaging_group_agents
|
||||||
|
(id, messaging_group_id, agent_group_id, session_mode, priority, created_at)
|
||||||
|
VALUES
|
||||||
|
('mga-'||hex(randomblob(8)), 'mg-GROUPID', 'ag-AGENTID', 'isolated', 0, '$NOW');
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grant user access
|
||||||
|
|
||||||
|
New Signal users (including the owner's Signal identity) are silently dropped with `not_member` until granted access. After the user's first message appears in `messaging_groups`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
||||||
|
sqlite3 data/v2.db "
|
||||||
|
INSERT OR REPLACE INTO user_roles (user_id, role, agent_group_id, granted_by, granted_at)
|
||||||
|
VALUES ('signal:UUID', 'owner', NULL, 'system', '$NOW');
|
||||||
|
INSERT OR IGNORE INTO agent_group_members (user_id, agent_group_id, added_by, added_at)
|
||||||
|
VALUES ('signal:UUID', 'ag-AGENTID', 'system', '$NOW');
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
Find the UUID from `messaging_groups.platform_id` or the `users` table.
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
If you're in the middle of `/setup`, return to the setup flow now.
|
If you're in the middle of `/setup`, return to the setup flow now.
|
||||||
|
|
||||||
Otherwise, run `/init-first-agent` to create an agent and wire it to your Signal DM, or `/manage-channels` to wire this channel to an existing agent group. Signal is direct-addressable — your phone number is the platform ID.
|
Otherwise, run `/init-first-agent` to create an agent and wire it to your Signal DM, or `/manage-channels` to wire this channel to an existing agent group.
|
||||||
|
|
||||||
## Channel Info
|
## Channel Info
|
||||||
|
|
||||||
- **type**: `signal`
|
- **type**: `signal`
|
||||||
- **terminology**: Signal has "chats" (1:1 DMs) and "groups."
|
- **terminology**: Signal has "chats" (1:1 DMs) and "groups"
|
||||||
- **how-to-find-id**: DMs use your phone number (e.g. `+15555550123`). Groups use `group:<groupId>` — find group IDs via `signal-cli -a +1YOURNUMBER listGroups`.
|
|
||||||
- **supports-threads**: no
|
- **supports-threads**: no
|
||||||
|
- **platform-id-format**:
|
||||||
|
- DM: `signal:{UUID}` — sender's Signal UUID (ACI), **not** their phone number
|
||||||
|
- Group: `signal:{base64GroupId}` — base64-encoded GroupV2 ID
|
||||||
|
- **how-to-find-id**: Send a message to the bot, then query `messaging_groups` as shown above
|
||||||
- **typical-use**: Personal assistant via Signal DMs or small group chats
|
- **typical-use**: Personal assistant via Signal DMs or small group chats
|
||||||
- **default-isolation**: One agent per Signal account. Multiple chats with the same operator can share an agent group; groups with other people should typically be separate.
|
- **default-isolation**: One agent per Signal account. Multiple chats with the same operator can share an agent group; groups with other people should typically use `isolated` session mode
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- Markdown formatting — `**bold**`, `*italic*` / `_italic_`, `` `code` ``, ` ```code fence``` `, `~~strike~~`, `||spoiler||` (converted to Signal's offset-based text styles)
|
- Markdown formatting — `**bold**`, `*italic*` / `_italic_`, `` `code` ``, ` ```code fence``` `, `~~strike~~`, `||spoiler||` (converted to Signal's offset-based text styles)
|
||||||
- Quoted replies — `replyTo*` fields populated from Signal quotes
|
- Quoted replies — `replyTo*` fields populated from Signal quotes
|
||||||
- Typing indicators — DMs only (Signal doesn't support group typing)
|
- Typing indicators — DMs only (Signal doesn't support group typing)
|
||||||
- Echo suppression — outbound messages are matched on `(platformId, text)` within a 10 s TTL to avoid syncMessage loops
|
- Echo suppression — outbound messages matched on `(platformId, text)` within a 10 s TTL to avoid syncMessage loops
|
||||||
- Note to Self — messages you send to your own account from another device route to the agent as inbound with `isFromMe: true`
|
- Note to Self — messages you send to your own account from another device route to the agent as inbound with `isFromMe: true`
|
||||||
- Voice attachments — detected but not transcribed by default; the agent receives `[Voice Message]` placeholder text. Run `/add-voice-transcription` for local transcription via parakeet-mlx
|
- Voice attachments — detected but not transcribed by default; the agent receives `[Voice Message]` placeholder text. Run `/add-voice-transcription` for local transcription via parakeet-mlx
|
||||||
|
|
||||||
@@ -145,4 +287,32 @@ If you see `Signal daemon not reachable at 127.0.0.1:7583` and `SIGNAL_MANAGE_DA
|
|||||||
|
|
||||||
### Lost connection mid-session
|
### Lost connection mid-session
|
||||||
|
|
||||||
If you see `Signal channel lost TCP connection to signal-cli daemon` in the logs, the daemon dropped us. There's no auto-reconnect yet — restart the service to re-establish.
|
If you see `Signal channel lost TCP connection to signal-cli daemon` in the logs, the daemon dropped the connection. Restart the service to re-establish.
|
||||||
|
|
||||||
|
### Messages dropped with `not_member`
|
||||||
|
|
||||||
|
The Signal user hasn't been granted membership. See "Grant user access" above. This affects every new Signal user, including the owner's Signal identity — which is a separate user record from their identity on other channels even if it's the same person.
|
||||||
|
|
||||||
|
### Captcha required
|
||||||
|
|
||||||
|
Signal requires a captcha for new registrations. Go to `https://signalcaptchas.org/registration/generate.html`, solve it, right-click "Open Signal", copy the link, extract the token after `signalcaptcha://`.
|
||||||
|
|
||||||
|
### `Invalid verification method: Before requesting voice verification…`
|
||||||
|
|
||||||
|
You must request SMS first, wait ~60 seconds, then request voice. Both steps can use the same captcha token.
|
||||||
|
|
||||||
|
### Config file in use / daemon lock
|
||||||
|
|
||||||
|
signal-cli holds an exclusive lock on its data directory while the daemon is running. Stop NanoClaw before running any `signal-cli` commands directly, then restart afterward.
|
||||||
|
|
||||||
|
### Group replies going to DM instead of group
|
||||||
|
|
||||||
|
Modern Signal groups use GroupV2. The adapter must extract the group ID from `envelope?.dataMessage?.groupV2?.id` — not `groupInfo?.groupId`, which is GroupV1/legacy. If group messages are routing as DMs, check `src/channels/signal.ts` and confirm the groupId extraction falls through to `groupV2.id`.
|
||||||
|
|
||||||
|
### Java not found
|
||||||
|
|
||||||
|
Install Java 17+ — see the Prerequisites section above.
|
||||||
|
|
||||||
|
### QR code expired (Path B)
|
||||||
|
|
||||||
|
QR codes expire in ~30 seconds. Re-run the link command to generate a new one.
|
||||||
|
|||||||
Reference in New Issue
Block a user