@floomhq/signaldash 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -1
- package/bin/sd.mjs +85 -6
- package/package.json +2 -2
- package/skills/signaldash/SKILL.md +49 -0
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ the human to complete. The equivalent manual sequence is:
|
|
|
23
23
|
npx -y @floomhq/signaldash login <invite-code>
|
|
24
24
|
npx -y @floomhq/signaldash connect linkedin
|
|
25
25
|
npx -y @floomhq/signaldash connect whatsapp
|
|
26
|
+
npx -y @floomhq/signaldash connect instagram # optional
|
|
26
27
|
npx -y @floomhq/signaldash connect email
|
|
27
28
|
```
|
|
28
29
|
|
|
@@ -120,7 +121,7 @@ setup installs both from the same pinned npm package
|
|
|
120
121
|
the human chose to execute:
|
|
121
122
|
|
|
122
123
|
```bash
|
|
123
|
-
npx -y @floomhq/signaldash@0.
|
|
124
|
+
npx -y @floomhq/signaldash@0.42.0 <invite-code>
|
|
124
125
|
```
|
|
125
126
|
|
|
126
127
|
Run that command in a terminal, not in an agent chat. Do not ask an agent to
|
|
@@ -208,6 +209,11 @@ timer, scheduler entry, Buffer item, or provider write.
|
|
|
208
209
|
- `wa_send_message(chat_id, text?, attachments?, expected_watermark?, mark_read?)` — confirmed sends clear unread by default; `mark_read:false` preserves unread.
|
|
209
210
|
- `wa_delete_message(chat_id, message_id)`
|
|
210
211
|
- `wa_delete_messages(messages)`
|
|
212
|
+
- `ig_list_chats(limit?, cursor?, search?, max_scan?, unread?)`
|
|
213
|
+
- `ig_read_messages(chat_id, limit?)`
|
|
214
|
+
- `ig_mark_read(chat_id)`
|
|
215
|
+
- `ig_get_attachment(chat_id, message_id, attachment_id)`
|
|
216
|
+
- `ig_send_message(chat_id, text, expected_watermark?, mark_read?)`: text only, same read-before-send guards as WhatsApp, Instagram budget below.
|
|
211
217
|
- `email_list(limit)`
|
|
212
218
|
- `email_read(thread_id, limit)`
|
|
213
219
|
- `email_send(to, subject, body)`
|
|
@@ -728,6 +734,33 @@ workflow around that runtime control:
|
|
|
728
734
|
the conservative reading is the safe one) and clears at the next UTC midnight;
|
|
729
735
|
LinkedIn, WhatsApp/email, and delete ledgers are keyed to the stable logical
|
|
730
736
|
user or sender rather than multiplied by live bearer sessions.
|
|
737
|
+
- Instagram DMs have their own budget per connected Instagram account, never
|
|
738
|
+
shared with LinkedIn or with the WhatsApp/email lane. Defaults: 50 sends per
|
|
739
|
+
UTC day, of which at most 15 may open a new conversation; 250 sends and 60
|
|
740
|
+
new conversations per UTC week (Monday 00:00 UTC); 10 sends per rolling hour;
|
|
741
|
+
at least 45 seconds between sends plus 0 to 30 seconds of random jitter. A
|
|
742
|
+
send counts as a new conversation when the page of the thread that was read
|
|
743
|
+
holds no message from the account itself (a cold DM, or a first answer to a
|
|
744
|
+
message request); every other send is a reply. A freshly connected account
|
|
745
|
+
warms up over 14 days: the first 24 hours allow 10 replies and no new
|
|
746
|
+
conversation, days 1 to 6 allow 20 sends and 5 new conversations, days 7 to
|
|
747
|
+
13 allow 35 sends and 10 new conversations. A provider 403 or 429 on a send
|
|
748
|
+
pauses Instagram sends for that account for 24 hours. `sd_budget_status`
|
|
749
|
+
reports all of it under `instagram`. Per-account overrides go in
|
|
750
|
+
`SIGNALDASH_INSTAGRAM_BUDGET_OVERRIDES`, a JSON object keyed by the Unipile
|
|
751
|
+
account id with any of `daily_total`, `new_chats_daily`, `weekly_total`,
|
|
752
|
+
`new_chats_weekly`, `hourly_total`, `min_gap_ms`, `gap_jitter_ms` and
|
|
753
|
+
`warmup` (boolean). Overrides can raise or lower a limit but never past the
|
|
754
|
+
hard ceilings of 100 per day, 10 per hour, 40 new conversations per day, 500
|
|
755
|
+
per week, 200 new conversations per week, and a 20 second minimum gap.
|
|
756
|
+
Sources for the numbers: Unipile provider limits (at most 100 actions per day
|
|
757
|
+
and 10 per hour per Instagram account, start new accounts lower,
|
|
758
|
+
https://developer.unipile.com/docs/provider-limits-and-restrictions), Wave
|
|
759
|
+
(50 DMs per day including follow-ups as the common ceiling, 10 per day for a
|
|
760
|
+
new account, https://www.usewave.co/blog/instagram-dm-limits) and Celestia
|
|
761
|
+
Leads (20 to 35 cold DMs per day once warmed, under 160 per week, pause 24 to
|
|
762
|
+
72 hours after an action block,
|
|
763
|
+
https://celestialeads.com/blog/instagram-dm-automation-guide).
|
|
731
764
|
- Read the exact thread before every send.
|
|
732
765
|
- Preview an exact invitation target and note before sending it.
|
|
733
766
|
- List the exact received or sent invitation before accepting or withdrawing it.
|
package/bin/sd.mjs
CHANGED
|
@@ -278,8 +278,8 @@ export async function cmdConnect(provider, dependencies = {}) {
|
|
|
278
278
|
const now = dependencies.now || Date.now;
|
|
279
279
|
const log = dependencies.log || console.log;
|
|
280
280
|
const error = dependencies.error || console.error;
|
|
281
|
-
if (!["linkedin", "whatsapp", "email"].includes(provider)) {
|
|
282
|
-
error("usage: signaldash connect linkedin|whatsapp|email");
|
|
281
|
+
if (!["linkedin", "whatsapp", "instagram", "email"].includes(provider)) {
|
|
282
|
+
error("usage: signaldash connect linkedin|whatsapp|instagram|email");
|
|
283
283
|
process.exitCode = 1;
|
|
284
284
|
return;
|
|
285
285
|
}
|
|
@@ -941,7 +941,7 @@ const TOOLS = [
|
|
|
941
941
|
properties: {
|
|
942
942
|
channel: {
|
|
943
943
|
type: "string",
|
|
944
|
-
enum: ["linkedin", "whatsapp", "email"],
|
|
944
|
+
enum: ["linkedin", "whatsapp", "instagram", "email"],
|
|
945
945
|
},
|
|
946
946
|
identifiers: {
|
|
947
947
|
type: "array",
|
|
@@ -992,7 +992,7 @@ const TOOLS = [
|
|
|
992
992
|
{
|
|
993
993
|
name: "sd_budget_status",
|
|
994
994
|
path: "/sd/budget_status",
|
|
995
|
-
description: "Read authoritative SignalDash LinkedIn attempt capacity for the invitation, existing-thread message, and own-post engagement daily lanes, the aggregate emergency brake, and the unchanged weekly invitation cap.",
|
|
995
|
+
description: "Read authoritative SignalDash LinkedIn attempt capacity for the invitation, existing-thread message, and own-post engagement daily lanes, the aggregate emergency brake, and the unchanged weekly invitation cap. When Instagram is connected, `instagram` reports that account's own DM budget: daily, weekly and hourly totals, the separate new-conversation caps, the warm-up stage, the next allowed send time and any provider pause.",
|
|
996
996
|
inputSchema: {
|
|
997
997
|
type: "object",
|
|
998
998
|
properties: {},
|
|
@@ -1679,6 +1679,85 @@ const TOOLS = [
|
|
|
1679
1679
|
additionalProperties: false,
|
|
1680
1680
|
},
|
|
1681
1681
|
},
|
|
1682
|
+
{
|
|
1683
|
+
name: "ig_list_chats",
|
|
1684
|
+
ch: "ig",
|
|
1685
|
+
action: "list_chats",
|
|
1686
|
+
description: "List or search the Instagram DM chats of the Instagram account bound to you. `unread:true` asks the provider for unread chats only. A search filters over a bounded scan of chat pages and reports `scanned_chats` and `exhaustive`, so a miss is never a proven absence.",
|
|
1687
|
+
inputSchema: {
|
|
1688
|
+
type: "object",
|
|
1689
|
+
properties: {
|
|
1690
|
+
limit: { type: "integer", minimum: 1, maximum: 100, description: "How many chats to return. With `search`, how many MATCHES to return." },
|
|
1691
|
+
cursor: { type: "string", maxLength: 4000, description: "Continue from a previous page or a previous search." },
|
|
1692
|
+
search: { type: "string", minLength: 1, maxLength: 200, description: "Filter chats case-insensitively on names, usernames and counterpart identifiers." },
|
|
1693
|
+
max_scan: { type: "integer", minimum: 1, maximum: 500, description: "How many chats the search may scan before it stops and reports exhaustive:false. Default 200." },
|
|
1694
|
+
unread: { type: "boolean", description: "Only unread chats." },
|
|
1695
|
+
},
|
|
1696
|
+
additionalProperties: false,
|
|
1697
|
+
},
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
name: "ig_read_messages",
|
|
1701
|
+
ch: "ig",
|
|
1702
|
+
action: "read",
|
|
1703
|
+
description: "Read messages and the resolved chat in one Instagram DM thread of your bound Instagram account. Returns current_watermark for a later ig_send_message. Reading never marks the chat read. args: chat_id",
|
|
1704
|
+
inputSchema: {
|
|
1705
|
+
type: "object",
|
|
1706
|
+
properties: {
|
|
1707
|
+
chat_id: { type: "string", minLength: 1, maxLength: 500 },
|
|
1708
|
+
limit: { type: "integer", minimum: 1, maximum: 100 },
|
|
1709
|
+
},
|
|
1710
|
+
required: ["chat_id"],
|
|
1711
|
+
additionalProperties: false,
|
|
1712
|
+
},
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
name: "ig_mark_read",
|
|
1716
|
+
ch: "ig",
|
|
1717
|
+
action: "mark_read",
|
|
1718
|
+
description: "Explicitly clear the unread state of one Instagram DM chat of your bound account. Reading a thread never clears it automatically.",
|
|
1719
|
+
inputSchema: {
|
|
1720
|
+
type: "object",
|
|
1721
|
+
properties: {
|
|
1722
|
+
chat_id: { type: "string", minLength: 1, maxLength: 500 },
|
|
1723
|
+
},
|
|
1724
|
+
required: ["chat_id"],
|
|
1725
|
+
additionalProperties: false,
|
|
1726
|
+
},
|
|
1727
|
+
},
|
|
1728
|
+
{
|
|
1729
|
+
name: "ig_get_attachment",
|
|
1730
|
+
path: "/ig/get_attachment",
|
|
1731
|
+
description: "Download one attachment of one Instagram DM message in a chat your bound Instagram account owns and store it on the SignalDash host. Read the chat first to obtain the exact message_id and attachment_id. Returns the stored path, mimetype, byte size and sha256.",
|
|
1732
|
+
inputSchema: {
|
|
1733
|
+
type: "object",
|
|
1734
|
+
properties: {
|
|
1735
|
+
chat_id: { type: "string", minLength: 1, maxLength: 500 },
|
|
1736
|
+
message_id: { type: "string", minLength: 1, maxLength: 500 },
|
|
1737
|
+
attachment_id: { type: "string", minLength: 1, maxLength: 500 },
|
|
1738
|
+
},
|
|
1739
|
+
required: ["chat_id", "message_id", "attachment_id"],
|
|
1740
|
+
additionalProperties: false,
|
|
1741
|
+
},
|
|
1742
|
+
},
|
|
1743
|
+
{
|
|
1744
|
+
name: "ig_send_message",
|
|
1745
|
+
ch: "ig",
|
|
1746
|
+
action: "send",
|
|
1747
|
+
description: "Send one Instagram DM (text only) in an existing chat of your bound Instagram account. Same guards as wa_send_message: read the chat first with ig_read_messages, a changed thread returns 409 thread_changed, an identical message is refused, and an unconfirmed send blocks its retry until you re-read and pass confirm_resend:true. Instagram has its own conservative per-account budget: a send into a thread with no message of yours on the page you read counts as a NEW conversation (much smaller cap, none at all in the first 24 hours after connecting), every other send is a reply; daily, weekly and hourly totals apply to both, sends are spaced at least 45 seconds apart plus random jitter, a freshly connected account warms up over 14 days, and a provider 403/429 pauses Instagram sends for 24 hours. A refusal names the exact cap and when it frees up; check sd_budget_status first. args: chat_id, text, expected_watermark, mark_read",
|
|
1748
|
+
inputSchema: {
|
|
1749
|
+
type: "object",
|
|
1750
|
+
properties: {
|
|
1751
|
+
chat_id: { type: "string", minLength: 1, maxLength: 500 },
|
|
1752
|
+
text: { type: "string", minLength: 1, maxLength: 5000 },
|
|
1753
|
+
confirm_resend: { type: "boolean", const: true },
|
|
1754
|
+
expected_watermark: { type: "string", minLength: 64, maxLength: 64, pattern: "^[0-9a-f]{64}$", description: "Bind the send to the exact current_watermark returned by the read or a thread_changed response." },
|
|
1755
|
+
mark_read: { type: "boolean", default: true, description: "Clear unread after confirmed success; false preserves unread state." },
|
|
1756
|
+
},
|
|
1757
|
+
required: ["chat_id", "text"],
|
|
1758
|
+
additionalProperties: false,
|
|
1759
|
+
},
|
|
1760
|
+
},
|
|
1682
1761
|
{
|
|
1683
1762
|
name: "email_list",
|
|
1684
1763
|
path: "/email/list",
|
|
@@ -2646,7 +2725,7 @@ export async function cmdStatus(dependencies = {}) {
|
|
|
2646
2725
|
const request = dependencies.request || api;
|
|
2647
2726
|
log("");
|
|
2648
2727
|
log(" " + chalk.bold("SignalDash") + chalk.dim(" " + (cfg.backend || DEFAULT_BACKEND)));
|
|
2649
|
-
for (const provider of ["linkedin", "whatsapp", "email"]) {
|
|
2728
|
+
for (const provider of ["linkedin", "whatsapp", "instagram", "email"]) {
|
|
2650
2729
|
const r = await request(`/connect/${provider}/status`, undefined, { method: "GET" });
|
|
2651
2730
|
const ok = r.status === 200 && r.json.connected;
|
|
2652
2731
|
const unavailable = r.json?.code === "upstream_authentication_failed"
|
|
@@ -2785,7 +2864,7 @@ function printHelp(log = console.log) {
|
|
|
2785
2864
|
signaldash <invite-code> set up everything in one go
|
|
2786
2865
|
signaldash login-token <token> store a session token you were given
|
|
2787
2866
|
signaldash status show what is connected
|
|
2788
|
-
signaldash connect linkedin|whatsapp|email
|
|
2867
|
+
signaldash connect linkedin|whatsapp|instagram|email
|
|
2789
2868
|
signaldash connections [file.csv] export your LinkedIn connections
|
|
2790
2869
|
signaldash skill install the agent skill
|
|
2791
2870
|
signaldash mcp run the MCP server (used by your agent)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floomhq/signaldash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Secure LinkedIn, WhatsApp, and email access for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"test": "node --test",
|
|
22
|
-
"check": "node --check bin/sd.mjs && node --check server/server.cjs && node --check server/outbound-text-policy.cjs && node --check server/intro-request-notifier.cjs && node --check server/content-pipeline.cjs && node --check server/x-signal-engine.cjs && node --check server/buffer-calendar.cjs && node --check server/composio-email.cjs && node --check server/attachments.cjs && node --check server/media-fetch.cjs && node --check server/write-control.cjs && node --check server/secretary-store.cjs && node --check server/secretary-collector.cjs && node --check server/secretary-latest.cjs && node --check server/secretary-approvals.cjs && node --check server/secretary-push.cjs && node --check server/secretary-rules.cjs && node --check server/secretary-shadow-gate.cjs && node --check server/secretary-vault-importer.cjs && node --check server/secretary-inspiration.cjs && node --check server/linkedin-archive-importer.cjs && node --check server/voice-profile.cjs && node --check server/campaign-store.cjs && node --check server/campaign-runner.cjs && node --check server/withdrawal-store.cjs && node --check server/post-scheduler.cjs && node --check server/draft-rewrite-worker.cjs && node --check server/message-scheduler.cjs && node --check server/invitation-followups.cjs && node --check server/wa-chat-index.cjs && node --check scripts/import-linkedin-archive.mjs && node --check scripts/import-secretary-vault-snapshot.mjs && node --check scripts/rebuild-secretary-actors.mjs && node --check scripts/extract-conversation-inspiration.mjs && node --check scripts/render-content-board.mjs && node --check scripts/collect-x-signals.mjs && node --check bin/signaldash.js && node --check lib/cli.js && node --check lib/config-file.js && node --check lib/mcp.js && node --check lib/rate-guard.js && node --check lib/secrets.js && node --check lib/unipile.js && node --check lib/x-signal-collector.cjs"
|
|
22
|
+
"check": "node --check bin/sd.mjs && node --check server/server.cjs && node --check server/outbound-text-policy.cjs && node --check server/intro-request-notifier.cjs && node --check server/content-pipeline.cjs && node --check server/x-signal-engine.cjs && node --check server/buffer-calendar.cjs && node --check server/composio-email.cjs && node --check server/attachments.cjs && node --check server/media-fetch.cjs && node --check server/write-control.cjs && node --check server/secretary-store.cjs && node --check server/secretary-collector.cjs && node --check server/secretary-latest.cjs && node --check server/secretary-approvals.cjs && node --check server/secretary-push.cjs && node --check server/secretary-rules.cjs && node --check server/secretary-shadow-gate.cjs && node --check server/secretary-vault-importer.cjs && node --check server/secretary-inspiration.cjs && node --check server/linkedin-archive-importer.cjs && node --check server/voice-profile.cjs && node --check server/campaign-store.cjs && node --check server/campaign-runner.cjs && node --check server/withdrawal-store.cjs && node --check server/post-scheduler.cjs && node --check server/draft-rewrite-worker.cjs && node --check server/message-scheduler.cjs && node --check server/invitation-followups.cjs && node --check server/wa-chat-index.cjs && node --check server/instagram-budget.cjs && node --check scripts/import-linkedin-archive.mjs && node --check scripts/import-secretary-vault-snapshot.mjs && node --check scripts/rebuild-secretary-actors.mjs && node --check scripts/extract-conversation-inspiration.mjs && node --check scripts/render-content-board.mjs && node --check scripts/collect-x-signals.mjs && node --check bin/signaldash.js && node --check lib/cli.js && node --check lib/config-file.js && node --check lib/mcp.js && node --check lib/rate-guard.js && node --check lib/secrets.js && node --check lib/unipile.js && node --check lib/x-signal-collector.cjs"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"mcp",
|
|
@@ -261,9 +261,12 @@ Run a missing provider explicitly:
|
|
|
261
261
|
```bash
|
|
262
262
|
npx -y @floomhq/signaldash connect linkedin
|
|
263
263
|
npx -y @floomhq/signaldash connect whatsapp
|
|
264
|
+
npx -y @floomhq/signaldash connect instagram
|
|
264
265
|
npx -y @floomhq/signaldash connect email
|
|
265
266
|
```
|
|
266
267
|
|
|
268
|
+
Instagram is opt-in like email: the one-command setup does not connect it.
|
|
269
|
+
|
|
267
270
|
Interpret output as follows:
|
|
268
271
|
|
|
269
272
|
- Success: `Connected linkedin: <name>` or `status` shows `+ linkedin`.
|
|
@@ -449,6 +452,11 @@ Use the exact tool names and argument keys below. Limits are optional.
|
|
|
449
452
|
| `wa_transcribe_voice` | `chat_id`, `message_id`, `attachment_id` all required, max 500 characters each; `backend` optional, exactly `gemini` or `whisper` | Turn one WhatsApp voice note into text through SignalDash instead of fetching provider bytes yourself. Always read the returned `backend`: `gemini` is the accurate default, `whisper-small` is the weak local fallback and mangles German with English terms mixed in, and a fallback also carries `fallback_reason`. Non-audio attachments are refused with `415 not_audio`; an unknown backend with `400 unknown_backend`; a transcription that exceeds its time limit returns `504 transcription_timeout` with the stored audio path. |
|
|
450
453
|
| `wa_start_chat` | `member_ids` required array of 1-10 exact `@s.whatsapp.net` or `@lid` provider member IDs; `text` required exact first message; optional `subject` is the group name of a new group chat (2+ members, at most 100 characters, bound into the approval; there is no rename afterwards, so always pass it for a group); `confirm`, `approval_hash`, and `dry_run:true` apply only after preview | Start one direct or group chat under the same exact approval, live member resolution, protected-contact, duplicate-set, and readback contract as LinkedIn. If an `@lid` resolves to another canonical ID, repeat the preview with the returned `resolved_member_id`; SignalDash never substitutes an unapproved identity. A new chat spends one WhatsApp/email send action regardless of member count. An existing exact member-set chat is returned without sending. |
|
|
451
454
|
| `wa_send_message` | `chat_id` required; `text` optional only when a file is attached, max 5000 characters; `attachments` optional array of up to 4 exact `{filename, content_type, content_base64}` files, at most 16 MiB per file and 16 MiB per message, types `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `application/pdf`, `text/csv`, `text/plain`, `application/json`, `application/zip`, xlsx; `expected_watermark` optional exact 64-character watermark; `mark_read` optional boolean, default true | Send one approved reply, one approved file, or both, in an existing WhatsApp conversation after an immediate re-read. Own outbound additions do not invalidate the read; inbound additions or mutations return `new_messages`, `changed_kind`, and `current_watermark`. Pass `expected_watermark` to bind the send to the exact reviewed state and `mark_read:false` to preserve unread instead of clearing it after the confirmed send. A file spends the same daily send budget and is recorded the same way as a text message; there is no separate attachment budget. A call carrying neither text nor an attachment is refused with `400 text_or_attachment_required`. Attachments are checked before anything is reserved, so a refusal costs no send: `400 unsupported_attachment_type`, `400 attachment_too_large`, `400 attachments_too_large`, `400 too_many_attachments`, `400 malformed_attachment_base64`, `400 invalid_attachment_filename`, and `413 request_too_large` when the whole body is too big to read. Nothing is ever truncated or dropped silently. The same caption with the same file is refused as `409 duplicate_send`; the same caption with a different file is a different message and goes through. If a send times out or the provider never confirms it, the message may still have been delivered: an identical retry is refused with `409 send_outcome_unknown`. Read the chat AGAIN, and ONLY if the message is genuinely absent, resend the identical payload with `confirm_resend:true`. The re-read is enforced, not advisory: a `confirm_resend` whose most recent read of that chat predates the failed attempt is refused with `428 reread_after_failed_send_required`, because a read taken before the attempt cannot show whether the message arrived. A re-read failure remains `502 thread_preflight_unavailable`, and a legacy proof with no watermark remains `428 read_before_send_required`; neither costs send budget. LinkedIn messages carry text only. |
|
|
455
|
+
| `ig_list_chats` | `limit` integer 1-100, default 20; `cursor` optional; `search` optional string, max 200 characters; `max_scan` optional integer 1-500, default 200; `unread` optional boolean | Find an existing Instagram DM conversation and its exact `chat_id` on the Instagram account bound to this user. A search filters over a bounded scan: read `search.exhaustive` before concluding anything, and continue from `cursor`. |
|
|
456
|
+
| `ig_read_messages` | `chat_id` required; `limit` 1-100, default 30 | Read one Instagram DM thread before summarizing, drafting, or sending. Returns `current_watermark`. Reading never clears unread. |
|
|
457
|
+
| `ig_mark_read` | `chat_id` required | Explicitly clear one Instagram chat's unread state. |
|
|
458
|
+
| `ig_get_attachment` | `chat_id`, `message_id`, `attachment_id` all required, max 500 characters each | Download one attachment of one Instagram DM in a chat this account owns, stored on the SignalDash host. |
|
|
459
|
+
| `ig_send_message` | `chat_id` and `text` required, text max 5000 characters; `expected_watermark` optional; `mark_read` optional boolean, default true; `confirm_resend:true` only after a re-read following `409 send_outcome_unknown` | Send one approved Instagram DM, text only, in an existing chat after an immediate re-read. Same guards as `wa_send_message`: `428 read_before_send_required`, `409 thread_changed`, `409 duplicate_send`, `409 send_outcome_unknown`, `409 contact_suppressed`. Before writing it confirms the account bound to this user is a ready Instagram account (`409 instagram_account_unverified` otherwise). A thread whose read page holds no message of this account counts as a NEW conversation, which has a much smaller cap. Budget refusals are `429` with an exact code: `instagram_pacing_cooldown`, `instagram_hourly_limit`, `instagram_daily_limit`, `instagram_weekly_limit`, `instagram_new_chat_daily_limit`, `instagram_new_chat_weekly_limit`, `instagram_account_paused`; each carries `retry_after_seconds` and the full `instagram_budget`. Never retry early or work around one. |
|
|
452
460
|
| `wa_delete_message` | `chat_id`, `message_id` both required, max 500 characters each | Retract one message THIS account sent, in a chat this account owns. The exact `message_id` comes from `wa_read_messages`. Irreversible and never retried: someone else's message is refused with `403 message_not_own`, a message outside this chat with `403 message_forbidden`, and a delete already recorded for this exact chat and message with `409 duplicate_delete`. Deletes spend their own daily budget, so `429 rate_limit_exceeded` here never means you are out of sends. SignalDash re-reads after the delete and returns success only when `deleted:1` or a genuine 404 proves the message gone. A present row or failed readback returns `502 deleted_unconfirmed`, records an unknown outcome, and must not be retried. |
|
|
453
461
|
| `wa_delete_messages` | `messages` required array of 1-200 exact `{chat_id, message_id}` objects | Retract several messages this account sent. Same ownership, budget and audit path as `wa_delete_message`, executed strictly one at a time with a pause between them, never in parallel. Always read the per-entry `ok`, `code` and `error`: a partial result is normal. Entries the batch never reached before its time limit come back with `skipped:true` and `code:batch_deadline`, and were not attempted; resend exactly those to resume. |
|
|
454
462
|
| `email_list` | `limit` integer 1-100, default 20; `cursor` optional, max 4096 | List the newest message in each recent email thread and obtain `thread_id`. The response carries a `cursor`; pass it back to read the next page, and omit it for the first. |
|
|
@@ -708,6 +716,29 @@ time and work window after a campaign action has established them. The
|
|
|
708
716
|
scheduler, persisted tenant-breaker, active-batch, and target-count fields
|
|
709
717
|
reflect the current invitation-batch runtime.
|
|
710
718
|
|
|
719
|
+
### Instagram DM budget
|
|
720
|
+
|
|
721
|
+
Instagram has its own budget per connected Instagram account. It never spends
|
|
722
|
+
LinkedIn lanes or the WhatsApp/email send lane, and `sd_budget_status` reports
|
|
723
|
+
it under `instagram`, so "my action budget" always includes Instagram once it
|
|
724
|
+
is connected. Defaults:
|
|
725
|
+
|
|
726
|
+
- 50 sends per UTC day, of which at most 15 may open a new conversation;
|
|
727
|
+
- 250 sends and 60 new conversations per UTC week, reset Monday 00:00 UTC;
|
|
728
|
+
- 10 sends per rolling hour;
|
|
729
|
+
- at least 45 seconds between sends, plus 0 to 30 seconds of random jitter;
|
|
730
|
+
- warm-up after connecting: first 24 hours 10 replies and no new conversation,
|
|
731
|
+
days 1 to 6 20 sends and 5 new conversations, days 7 to 13 35 sends and 10
|
|
732
|
+
new conversations, full limits from day 14;
|
|
733
|
+
- a provider 403 or 429 pauses Instagram sends for 24 hours.
|
|
734
|
+
|
|
735
|
+
A new conversation is a send into a thread whose read page holds no message
|
|
736
|
+
from this account (a cold DM or a first answer to a message request). Replies
|
|
737
|
+
are everything else. Prefer replies; never start cold Instagram outreach
|
|
738
|
+
without the human approving each exact message. An operator can set a
|
|
739
|
+
per-account override, but the hard ceilings stay at 100 per day, 10 per hour
|
|
740
|
+
and 40 new conversations per day.
|
|
741
|
+
|
|
711
742
|
### Opt-in invitation auto-accept
|
|
712
743
|
|
|
713
744
|
`sd_settings_get` is the single read surface for persistent SignalDash
|
|
@@ -1605,6 +1636,24 @@ looping `wa_delete_message` yourself: it paces them and never runs two at once.
|
|
|
1605
1636
|
Read the per-entry results, and resend only the entries marked
|
|
1606
1637
|
`code:batch_deadline`, which were never attempted.
|
|
1607
1638
|
|
|
1639
|
+
### Flow 3b: find and reply to an Instagram DM
|
|
1640
|
+
|
|
1641
|
+
Instagram is connected with `signaldash connect instagram`, which prints a
|
|
1642
|
+
link the human opens in their own browser to sign in to Instagram. The account
|
|
1643
|
+
is bound to that human's own SignalDash user, never to the workspace.
|
|
1644
|
+
|
|
1645
|
+
1. `sd_budget_status` and read `instagram`: remaining sends, remaining new
|
|
1646
|
+
conversations, warm-up stage, and `next_send_allowed_at`.
|
|
1647
|
+
2. `ig_list_chats` with `search` or `unread:true` to find the exact `chat_id`.
|
|
1648
|
+
3. `ig_read_messages` for that chat. Draft the reply and show the human the
|
|
1649
|
+
exact text.
|
|
1650
|
+
4. After approval, `ig_send_message` with `chat_id`, `text` and the
|
|
1651
|
+
`expected_watermark` from the read. On `409 thread_changed`, show the new
|
|
1652
|
+
messages and redraft. On any `429 instagram_*` code, stop and report the
|
|
1653
|
+
cap and `retry_after_seconds`; never retry early.
|
|
1654
|
+
5. `ig_mark_read` only when the human wants the chat cleared and the send did
|
|
1655
|
+
not already clear it.
|
|
1656
|
+
|
|
1608
1657
|
### Flow 4: send, accept, or withdraw one LinkedIn invitation
|
|
1609
1658
|
|
|
1610
1659
|
For a new invitation:
|