@floomhq/signaldash 0.36.0 → 0.38.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 +57 -17
- package/bin/sd.mjs +37 -3
- package/package.json +2 -2
- package/skills/signaldash/SKILL.md +37 -13
- package/skills/signaldash-safe-usage/SKILL.md +11 -6
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ setup installs both from the same pinned npm package
|
|
|
102
102
|
the human chose to execute:
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
npx -y @floomhq/signaldash@0.
|
|
105
|
+
npx -y @floomhq/signaldash@0.38.0 <invite-code>
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
Run that command in a terminal, not in an agent chat. Do not ask an agent to
|
|
@@ -132,6 +132,7 @@ SignalDash exposes:
|
|
|
132
132
|
- `li_list_chats`
|
|
133
133
|
- `li_read_messages(chat_id)`
|
|
134
134
|
- `li_mark_read(chat_id)`
|
|
135
|
+
- `li_start_chat(member_ids, text, confirm?, approval_hash?, dry_run?)`
|
|
135
136
|
- `li_send_message(chat_id?, text?, expected_watermark?, mark_read?, secretary_receipt_id?)`
|
|
136
137
|
- `li_send_invitation(provider_id, note?, confirm?)`
|
|
137
138
|
- `li_invitations_received(limit?, cursor?)`
|
|
@@ -163,6 +164,7 @@ SignalDash exposes:
|
|
|
163
164
|
- `wa_mark_read(chat_id)`
|
|
164
165
|
- `wa_get_attachment(chat_id, message_id, attachment_id)`
|
|
165
166
|
- `wa_transcribe_voice(chat_id, message_id, attachment_id)`
|
|
167
|
+
- `wa_start_chat(member_ids, text, confirm?, approval_hash?, dry_run?)`
|
|
166
168
|
- `wa_send_message(chat_id, text?, attachments?, expected_watermark?, mark_read?)`
|
|
167
169
|
- `wa_delete_message(chat_id, message_id)`
|
|
168
170
|
- `wa_delete_messages(messages)`
|
|
@@ -187,6 +189,24 @@ SignalDash exposes:
|
|
|
187
189
|
Every operation runs through the hosted SignalDash backend. Agents never
|
|
188
190
|
receive the Unipile access key.
|
|
189
191
|
|
|
192
|
+
`li_start_chat` and `wa_start_chat` are separate tools because LinkedIn and
|
|
193
|
+
WhatsApp expose different stable member-ID formats and spend different budget
|
|
194
|
+
lanes. Both use the same safety contract. A first call previews 1 to 10 exact
|
|
195
|
+
member IDs and the exact first message. A confirmed call requires the
|
|
196
|
+
payload-bound approval hash, resolves every member live, rejects protected or
|
|
197
|
+
suppressed contacts, and checks attendee-scoped provider evidence for an exact
|
|
198
|
+
existing member set. An existing match is returned without creating a chat or
|
|
199
|
+
sending the text. A new chat costs one action, independent of member count,
|
|
200
|
+
because the provider performs one irreversible conversation-and-message write.
|
|
201
|
+
Success requires readback of exactly one matching chat and exactly one own first
|
|
202
|
+
message. `dry_run:true` repeats the live preflight and stops before the provider
|
|
203
|
+
write, without consuming approval or budget. Database capability flags
|
|
204
|
+
`linkedin_chat_start` and `whatsapp_chat_start` disable the paths immediately.
|
|
205
|
+
When WhatsApp resolves an `@lid` alias to a canonical member ID, the first
|
|
206
|
+
preview returns `member_alias_requires_exact_id` and `resolved_member_id`.
|
|
207
|
+
Preview again with that exact ID before approval. SignalDash never substitutes
|
|
208
|
+
an unapproved member identity inside a confirmed write.
|
|
209
|
+
|
|
190
210
|
The inspiration library also extracts LinkedIn post references already stored
|
|
191
211
|
inside conversation `signals`. This path scans SQLite only and makes zero
|
|
192
212
|
LinkedIn or Unipile calls. Feed URLs and post slugs become canonical activity
|
|
@@ -305,6 +325,16 @@ uses the same sender binding, action ledger, duplicate guard, daily budget, and
|
|
|
305
325
|
provider-warning lock as immediate publishing. An interrupted or ambiguous
|
|
306
326
|
execution fails closed and is never retried automatically.
|
|
307
327
|
|
|
328
|
+
`li_scheduled_posts` is the shared planning read across posts stored by
|
|
329
|
+
SignalDash and the configured Buffer LinkedIn channel. Every item names its
|
|
330
|
+
source, and the response reports independent completeness for SignalDash,
|
|
331
|
+
Buffer, and native LinkedIn. Unipile has no documented endpoint for posts or
|
|
332
|
+
drafts scheduled natively in LinkedIn, so that source is always explicit as
|
|
333
|
+
`unavailable`. SignalDash does not call undocumented Voyager routes or automate
|
|
334
|
+
linkedin.com to fill the gap. An empty item list therefore means only that the
|
|
335
|
+
visible SignalDash and Buffer sources are empty, never that the native LinkedIn
|
|
336
|
+
calendar is empty.
|
|
337
|
+
|
|
308
338
|
`sd_schedule_message` puts one exact message into one exact chat at one exact
|
|
309
339
|
time, on WhatsApp or LinkedIn, text only. It enqueues; it never sends. The
|
|
310
340
|
attachment bytes a WhatsApp send accepts are refused here rather than held on
|
|
@@ -361,9 +391,10 @@ explicitly enables it with `confirm:true`. The optional filters are an exact
|
|
|
361
391
|
public-identifier allowlist and description-keyword matches; when both groups
|
|
362
392
|
are configured, both must match. The single backend worker reads one bounded
|
|
363
393
|
received-invitation page, accepts at most five invitations per run and ten per
|
|
364
|
-
UTC day by default, jitters every action, and also consumes the
|
|
365
|
-
LinkedIn
|
|
366
|
-
setting. `sd_auto_accept_status` exposes accepted counts
|
|
394
|
+
UTC day by default, jitters every action, and also consumes the invitation
|
|
395
|
+
daily lane and aggregate LinkedIn brake. Three consecutive parse or provider
|
|
396
|
+
errors disable the setting. `sd_auto_accept_status` exposes accepted counts
|
|
397
|
+
today and this week,
|
|
367
398
|
failed attempts today, remaining capacity, the stop reason, and sanitized
|
|
368
399
|
records for invitations whose provider shape could not be parsed. Both the current nested
|
|
369
400
|
`specifics.shared_secret` and legacy top-level shape are supported; secrets are
|
|
@@ -415,22 +446,30 @@ npx -y @floomhq/signaldash connections linkedin-connections.csv
|
|
|
415
446
|
|
|
416
447
|
## Safety limits
|
|
417
448
|
|
|
418
|
-
SignalDash enforces
|
|
449
|
+
SignalDash enforces daily write caps. The operating skill adds the human
|
|
419
450
|
workflow around that runtime control:
|
|
420
451
|
|
|
421
|
-
- Every successful
|
|
422
|
-
`
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
452
|
+
- Every successful LinkedIn write includes aggregate `rate_limit` data and its
|
|
453
|
+
applicable `lane_rate_limit` when classified. `rate_limit.limit`, `used`,
|
|
454
|
+
`remaining`, and `resets_at` retain their stable response shape. A lane
|
|
455
|
+
refusal returns HTTP
|
|
456
|
+
429 with an exact invitation, message, or engagement code and
|
|
457
|
+
`rate_limit_lane`; the aggregate brake retains
|
|
458
|
+
`code: "rate_limit_exceeded"`. Every 429 includes `Retry-After`.
|
|
459
|
+
Counters are persisted server-side and reset at midnight UTC.
|
|
460
|
+
- LinkedIn uses three per-sender lanes: 20 invitation actions, 30 messages in
|
|
461
|
+
existing threads, and 50 replies or reactions on own posts per UTC day by
|
|
462
|
+
default. Publishing and first comments count with own-post engagement. A
|
|
463
|
+
separate aggregate cap of 75 is the emergency brake across the lanes. Sent
|
|
464
|
+
invitations retain the unchanged 100-attempt UTC-week policy.
|
|
465
|
+
- WhatsApp and email sends together, and message deletes, retain their own
|
|
466
|
+
independent budgets. A `429` names
|
|
427
467
|
the cap it hit; it is never a statement about another lane. Two caveats worth
|
|
428
468
|
knowing: on the day a deployment first upgrades past the lane split, that
|
|
429
469
|
day's existing count is still read on the LinkedIn side (it is ambiguous, and
|
|
430
470
|
the conservative reading is the safe one) and clears at the next UTC midnight;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
budget but a WhatsApp budget each.
|
|
471
|
+
LinkedIn, WhatsApp/email, and delete ledgers are keyed to the stable logical
|
|
472
|
+
user or sender rather than multiplied by live bearer sessions.
|
|
434
473
|
- Read the exact thread before every send.
|
|
435
474
|
- Preview an exact invitation target and note before sending it.
|
|
436
475
|
- List the exact received or sent invitation before accepting or withdrawing it.
|
|
@@ -439,7 +478,8 @@ workflow around that runtime control:
|
|
|
439
478
|
- Never infer a recipient from a partial name.
|
|
440
479
|
- Never send a duplicate or retry an ambiguous timeout without re-reading.
|
|
441
480
|
- Invitation auto-accept remains off until explicitly enabled, consumes both
|
|
442
|
-
its dedicated daily cap
|
|
481
|
+
its dedicated daily cap, the invitation lane, and the aggregate LinkedIn
|
|
482
|
+
brake, and disables
|
|
443
483
|
itself after three consecutive parse or provider errors.
|
|
444
484
|
- Do not parallelize sends or work around a rate limit.
|
|
445
485
|
- Do not bypass paid-discovery previews, pacing, request caps, or cost caps.
|
|
@@ -449,8 +489,8 @@ workflow around that runtime control:
|
|
|
449
489
|
|
|
450
490
|
LinkedIn invitation writes are single-object actions. SignalDash serializes
|
|
451
491
|
them with other LinkedIn writes, requires an exact preview or list read,
|
|
452
|
-
applies the
|
|
453
|
-
policy by default. For a new invitation, jitter completes before the final
|
|
492
|
+
applies the invitation daily lane and aggregate brake, and enforces a separate
|
|
493
|
+
100-attempt weekly policy by default. For a new invitation, jitter completes before the final
|
|
454
494
|
preflight and action reservation; that preflight verifies relationship state,
|
|
455
495
|
pending invitations in both directions, and absence of an existing one-to-one
|
|
456
496
|
chat. Provider timeouts become non-retryable `outcome_unknown` locks.
|
package/bin/sd.mjs
CHANGED
|
@@ -491,6 +491,23 @@ const TOOLS = [
|
|
|
491
491
|
additionalProperties: false,
|
|
492
492
|
},
|
|
493
493
|
},
|
|
494
|
+
{
|
|
495
|
+
name: "li_start_chat",
|
|
496
|
+
path: "/li/start_chat",
|
|
497
|
+
description: "Preview or start one LinkedIn direct or group chat with 1 to 10 exact provider member IDs and one exact first message. First call without confirm. After human approval of the complete member set and text, repeat the exact payload with confirm:true and approval_hash. dry_run:true runs the full live preflight twice and stops immediately before the provider write. One created chat spends one thread-message and aggregate action regardless of member count. Existing exact member sets are returned without creating or sending anything.",
|
|
498
|
+
inputSchema: {
|
|
499
|
+
type: "object",
|
|
500
|
+
properties: {
|
|
501
|
+
member_ids: { type: "array", minItems: 1, maxItems: 10, uniqueItems: true, items: { type: "string", minLength: 3, maxLength: 500, pattern: "^(?:ACo|ACw|AE)[A-Za-z0-9_-]+$" } },
|
|
502
|
+
text: { type: "string", minLength: 1, maxLength: 5000 },
|
|
503
|
+
confirm: { type: "boolean", default: false },
|
|
504
|
+
approval_hash: { type: "string", minLength: 64, maxLength: 64, pattern: "^[0-9a-f]{64}$" },
|
|
505
|
+
dry_run: { type: "boolean", const: true },
|
|
506
|
+
},
|
|
507
|
+
required: ["member_ids", "text"],
|
|
508
|
+
additionalProperties: false,
|
|
509
|
+
},
|
|
510
|
+
},
|
|
494
511
|
{
|
|
495
512
|
name: "li_send_message",
|
|
496
513
|
ch: "li",
|
|
@@ -637,7 +654,7 @@ const TOOLS = [
|
|
|
637
654
|
{
|
|
638
655
|
name: "sd_budget_status",
|
|
639
656
|
path: "/sd/budget_status",
|
|
640
|
-
description: "Read authoritative SignalDash LinkedIn attempt capacity for
|
|
657
|
+
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.",
|
|
641
658
|
inputSchema: {
|
|
642
659
|
type: "object",
|
|
643
660
|
properties: {},
|
|
@@ -948,7 +965,7 @@ const TOOLS = [
|
|
|
948
965
|
{
|
|
949
966
|
name: "sd_campaign_preview",
|
|
950
967
|
path: "/sd/campaign/preview",
|
|
951
|
-
description: "Inspect one campaign before approval: every exact recipient, every exclusion and its reason, the exact message text for each step, the timing, the
|
|
968
|
+
description: "Inspect one campaign before approval: every exact recipient, every exclusion and its reason, the exact message text for each step, the timing, the invitation and existing-thread message lanes, the aggregate brake, and the approval_url to send the human. Show the human this content. This read also authorizes a later exact cancel. An agent cannot approve a campaign; only the authenticated human page can, and it can hand you a one-time code for sd_campaign_approve.",
|
|
952
969
|
inputSchema: {
|
|
953
970
|
type: "object",
|
|
954
971
|
properties: {
|
|
@@ -1205,6 +1222,23 @@ const TOOLS = [
|
|
|
1205
1222
|
additionalProperties: false,
|
|
1206
1223
|
},
|
|
1207
1224
|
},
|
|
1225
|
+
{
|
|
1226
|
+
name: "wa_start_chat",
|
|
1227
|
+
path: "/wa/start_chat",
|
|
1228
|
+
description: "Preview or start one WhatsApp direct or group chat with 1 to 10 exact @s.whatsapp.net or @lid member IDs and one exact first message. First call without confirm. If the provider maps an @lid alias to a canonical ID, repeat the preview with the returned resolved_member_id so the human approves the exact provider-write identity. After human approval of the complete member set and text, repeat the exact payload with confirm:true and approval_hash. dry_run:true runs the full live preflight twice and stops immediately before the provider write. One created chat spends one WhatsApp/email send action regardless of member count. Existing exact member sets are returned without creating or sending anything.",
|
|
1229
|
+
inputSchema: {
|
|
1230
|
+
type: "object",
|
|
1231
|
+
properties: {
|
|
1232
|
+
member_ids: { type: "array", minItems: 1, maxItems: 10, uniqueItems: true, items: { type: "string", minLength: 5, maxLength: 500, pattern: "^[A-Za-z0-9._-]+@(?:s\\.whatsapp\\.net|lid)$" } },
|
|
1233
|
+
text: { type: "string", minLength: 1, maxLength: 5000 },
|
|
1234
|
+
confirm: { type: "boolean", default: false },
|
|
1235
|
+
approval_hash: { type: "string", minLength: 64, maxLength: 64, pattern: "^[0-9a-f]{64}$" },
|
|
1236
|
+
dry_run: { type: "boolean", const: true },
|
|
1237
|
+
},
|
|
1238
|
+
required: ["member_ids", "text"],
|
|
1239
|
+
additionalProperties: false,
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1208
1242
|
{
|
|
1209
1243
|
name: "wa_send_message",
|
|
1210
1244
|
ch: "wa",
|
|
@@ -1529,7 +1563,7 @@ const TOOLS = [
|
|
|
1529
1563
|
{
|
|
1530
1564
|
name: "li_scheduled_posts",
|
|
1531
1565
|
path: "/li/scheduled_posts",
|
|
1532
|
-
description: "
|
|
1566
|
+
description: "Read the shared LinkedIn content calendar from SignalDash and the configured Buffer channel. Always inspect completeness and per-source state. Native LinkedIn scheduled posts and drafts are NOT visible because Unipile has no documented read route for them, and SignalDash does not use raw Voyager routes or linkedin.com browser access. An empty items array is never proof that the native LinkedIn calendar is empty.",
|
|
1533
1567
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1534
1568
|
},
|
|
1535
1569
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@floomhq/signaldash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "Secure LinkedIn, WhatsApp, and email access for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node --test",
|
|
21
|
-
"check": "node --check bin/sd.mjs && node --check server/server.cjs && node --check server/composio-email.cjs && node --check server/attachments.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/message-scheduler.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 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"
|
|
21
|
+
"check": "node --check bin/sd.mjs && node --check server/server.cjs && node --check server/buffer-calendar.cjs && node --check server/composio-email.cjs && node --check server/attachments.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/message-scheduler.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 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"
|
|
22
22
|
},
|
|
23
23
|
"keywords": [
|
|
24
24
|
"mcp",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: signaldash
|
|
3
|
-
description: Operate the user's SignalDash connection to LinkedIn, WhatsApp, and email. Use this skill whenever the user says or implies "check my LinkedIn", "who messaged me", "triage my inbox", "read the thread with X", "reply to X", "send this message", "send a connection request", "create an invitation batch", "run a campaign", "connect and then message them", "message them when they accept", "check invitations", "accept this invitation", "auto-accept my invitations", "check auto-accept status", "withdraw this invitation", "search my LinkedIn connections", "find people in my network", "find people beyond my network", "suppress this contact", "check contact state", "check my action budget", "check WhatsApp", "check my email", "draft a LinkedIn post", "who engaged with my post", "who liked or commented", or "export my LinkedIn connections". Covers durable installation, invite-based setup, local-first connection search, capped paid discovery, immutable human-approved invitation batches, the human-approved campaign loop that messages a person only after a proven acceptance and stops on any reply, opt-in rate-capped invitation auto-accept, exact contact state and suppression, authoritative LinkedIn action-budget status, safe reading, drafting, approved sends, exact invitation actions, post engagement analysis, and paced connection export.
|
|
3
|
+
description: Operate the user's SignalDash connection to LinkedIn, WhatsApp, and email. Use this skill whenever the user says or implies "check my LinkedIn", "who messaged me", "triage my inbox", "read the thread with X", "reply to X", "send this message", "start a chat", "create a group chat", "introduce these people", "send a connection request", "create an invitation batch", "run a campaign", "connect and then message them", "message them when they accept", "check invitations", "accept this invitation", "auto-accept my invitations", "check auto-accept status", "withdraw this invitation", "search my LinkedIn connections", "find people in my network", "find people beyond my network", "suppress this contact", "check contact state", "check my action budget", "check WhatsApp", "check my email", "draft a LinkedIn post", "who engaged with my post", "who liked or commented", or "export my LinkedIn connections". Covers durable installation, invite-based setup, exact human-approved direct and group chat creation, local-first connection search, capped paid discovery, immutable human-approved invitation batches, the human-approved campaign loop that messages a person only after a proven acceptance and stops on any reply, opt-in rate-capped invitation auto-accept, exact contact state and suppression, authoritative LinkedIn action-budget status, safe reading, drafting, approved sends, exact invitation actions, post engagement analysis, and paced connection export.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Operate SignalDash
|
|
@@ -375,6 +375,7 @@ Use the exact tool names and argument keys below. Limits are optional.
|
|
|
375
375
|
| `li_list_chats` | `limit` integer 1-100, default 20; `cursor` optional; `search` optional string, max 200 characters; `max_scan` optional integer 1-500; `unread` optional boolean | Find recent LinkedIn chats, unread counts, and exact `chat_id` values. `unread:true` is forwarded to the provider as `unread=true`; SignalDash does not fetch a full chat page and filter it locally. Search accepts a unique stored display name, exact `public_id`, member id, or ordinary chat field. A stored name/public id is resolved to its verified member id before matching, so a provider row with `name:null` remains findable. Without an explicit `max_scan`, a zero-match search extends from 200 up to a hard 500-chat/five-page bound; explicit bounds remain exact. Read `scanned_chats`, `pages_fetched`, `scan_limit`, and `exhaustive` before concluding absence. The response also reports any resolved public/member ids. Any other key, including `text` and `member_id`, is refused with `unsupported_parameter` rather than accepted and ignored. |
|
|
376
376
|
| `li_read_messages` | `chat_id` required; `limit` 1-100, default 30 | Read one resolved LinkedIn conversation before summarizing, drafting, or sending. The response includes `current_watermark`, the resolved `chat`, and `sender_name` on each message where the exact sender identity can be resolved. |
|
|
377
377
|
| `li_mark_read` | `chat_id` required | Explicitly clear one LinkedIn chat's unread state. Reading never clears unread automatically. |
|
|
378
|
+
| `li_start_chat` | `member_ids` required array of 1-10 exact `ACo`, `ACw`, or `AE` provider member IDs; `text` required exact first message; `confirm`, `approval_hash`, and `dry_run:true` apply only after preview | Start one direct or group chat. Preview first, show the complete member-ID set and exact text, then repeat that exact payload with the returned approval hash only after explicit approval. SignalDash resolves every member live, blocks protected or suppressed contacts, returns an existing exact member-set chat without sending, and charges one thread-message plus aggregate action for a new chat regardless of member count. `dry_run:true` proves the full path up to the provider write without consuming approval or budget. |
|
|
378
379
|
| `li_send_message` | Manual path: `chat_id` and `text` required; `expected_watermark` optional exact 64-character watermark; `mark_read` optional boolean. Secretary path: `secretary_receipt_id` alone. | Send one approved LinkedIn reply after an immediate read of that exact chat. A Secretary receipt derives the exact stored chat and text and refuses every caller-supplied override. |
|
|
379
380
|
| `li_send_invitation` | `provider_id` required; `note` optional, max 300 exact characters; `confirm` optional, default false | First preview one exact target and note. The server verifies relationship, both invitation directions, and absence of an existing one-to-one chat. After exact approval, repeat the identical call with `confirm:true`; jitter completes before the final preflight and action reservation. |
|
|
380
381
|
| `li_invitations_received` | `limit` integer 1-100, default 50; `cursor` optional | List one bounded page of received invitations. This read authorizes only the exact returned invitation IDs for a later accept. |
|
|
@@ -382,7 +383,7 @@ Use the exact tool names and argument keys below. Limits are optional.
|
|
|
382
383
|
| `li_invitations_sent` | `limit` integer 1-100, default 50; `cursor` optional | List one bounded page of sent invitations. This read authorizes only the exact returned invitation IDs for a later withdrawal. |
|
|
383
384
|
| `li_withdraw_invitation` | `invitation_id` required; `confirm:true` required | Withdraw one exact currently pending sent invitation after a fresh `li_invitations_sent` read and approval. |
|
|
384
385
|
| `sd_contact_state` | `channel` required (`linkedin`, `whatsapp`, or `email`); `identifiers` required array of 1-8 exact `{kind,value}` objects; `action` optional (`get` default or `suppress`); suppression also requires an allowed `reason` and `confirm:true` | Inspect exact tenant/channel-scoped contact history or add a protective suppression. It never infers that identifiers on different channels belong to one person. |
|
|
385
|
-
| `sd_budget_status` | no arguments | Read the current sender binding,
|
|
386
|
+
| `sd_budget_status` | no arguments | Read the current sender binding, separate invitation, existing-thread message, and own-post engagement daily lanes, the aggregate emergency brake, weekly invitation usage, lock state, and UTC resets from the authoritative server ledger. |
|
|
386
387
|
| `sd_settings_get` | no arguments | Read the authenticated user's persistent SignalDash settings. Auto-accept is the first supported setting and is false for every existing user until explicitly changed. |
|
|
387
388
|
| `sd_settings_set` | `auto_accept_linkedin` required boolean; `auto_accept_linkedin_filters` optional object with `public_identifiers` and `description_keywords`; `confirm:true` required | Update the general per-user settings surface after exact human approval. This changes no future or unknown setting implicitly. |
|
|
388
389
|
| `sd_auto_accept_status` | no arguments | Inspect whether auto-accept is enabled, its exact filters, accepted today and this week, failed attempts today, dedicated and shared capacity, repeated-error stop state, and sanitized unparseable invitation records. |
|
|
@@ -393,7 +394,7 @@ Use the exact tool names and argument keys below. Limits are optional.
|
|
|
393
394
|
| `li_get_invitation_batch` | `batch_id` required | Inspect every stored target, exact note, exclusion, hash, timing, capacity, and result. Use the returned browser review path for human approval. This read also authorizes a later exact cancel. |
|
|
394
395
|
| `li_cancel_invitation_batch` | `batch_id` required; `approval_view_hash` required string or null exactly as inspected; `confirm:true` required | Permanently cancel unstarted targets in one freshly inspected batch. It cannot recall an executing invitation, and restarting requires a new preview and approval. |
|
|
395
396
|
| `sd_campaign_create` | `source_label` required, max 120 code points; `time_zone` required IANA timezone; `messages` required array of 1-5 exact `{text,after_days?}` objects, text max 1200 code points; `target_source` optional (`explicit` default or `post_engagers`); `targets` array of up to 150 `{profile_url?,provider_id?,inclusion_reason,note?,display_name?,headline?,adopt_existing_invitation?}` for `explicit` (at most 10 adopted targets per campaign, and an adopted target may not carry a `note`); `engagers` object with `post_limit` 1-10, `max_targets` 1-150, `inclusion_reason`, `note` for `post_engagers`; `invite_ttl_days` optional 1-60, default 21 | Create one campaign: a connection request to each exact person, then the exact approved message(s) once that person is proven to have accepted, then an optional follow-up that stops on any reply. The server never generates a target or a word of text. Nothing is sent before human approval. |
|
|
396
|
-
| `sd_campaign_preview` | `campaign_id` required | Inspect every exact recipient, every exclusion and reason, the exact text of every message step, the timing, the
|
|
397
|
+
| `sd_campaign_preview` | `campaign_id` required | Inspect every exact recipient, every exclusion and reason, the exact text of every message step, the timing, the invitation and existing-thread message lanes, the aggregate brake, and the `approval_url` to hand the human. This read also authorizes a later exact cancel. |
|
|
397
398
|
| `sd_campaign_approve` | `campaign_id` required; `confirm_token` required in the exact `sd-xxxx-xxxx-xxxx` form the human read off the approval page | Record the human's approval using the single-use code that authenticated page minted for them. An agent cannot mint, guess, or bypass that code. |
|
|
398
399
|
| `sd_campaign_status` | `campaign_id` required | Monitor per person: invited, acceptance proven, messaged, replied and halted, expired, or excluded, plus every message step and any provider response SignalDash could not parse. |
|
|
399
400
|
| `sd_campaign_cancel` | `campaign_id` required; `approval_view_hash` required string or null exactly as inspected; `confirm:true` required | Revoke the approval and stop every unsent invitation and unsent message. It cannot recall an executing write, and restarting requires a new preview and approval. |
|
|
@@ -406,6 +407,7 @@ Use the exact tool names and argument keys below. Limits are optional.
|
|
|
406
407
|
| `wa_mark_read` | `chat_id` required | Explicitly clear one WhatsApp chat's unread state. Reading never clears unread automatically. |
|
|
407
408
|
| `wa_get_attachment` | `chat_id`, `message_id`, `attachment_id` all required, max 500 characters each | Download one attachment of one message in a chat this account owns. Read the chat first: the exact `message_id` and `attachment_id` come from `wa_read_messages`. Returns the stored path on the SignalDash host, mimetype, byte size and sha256. |
|
|
408
409
|
| `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. |
|
|
410
|
+
| `wa_start_chat` | `member_ids` required array of 1-10 exact `@s.whatsapp.net` or `@lid` provider member IDs; `text` required exact first message; `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. |
|
|
409
411
|
| `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 false | 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:true` only when the approved workflow also calls for clearing unread 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. |
|
|
410
412
|
| `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. WhatsApp applies its own time and role limits to deleting for everyone and can answer successfully without removing anything, so read the chat again to confirm the message is gone. |
|
|
411
413
|
| `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. |
|
|
@@ -421,7 +423,7 @@ Use the exact tool names and argument keys below. Limits are optional.
|
|
|
421
423
|
| `li_delete_comment` | `post_id`, `comment_id`, and `confirm:true` required | Currently unavailable: the fixture-tested Unipile v2 wrapper is held at database capability state `untested` until live compatibility is proved against Federico's own removable comment. When enabled it proves own comment identity and readback. Deletion is remediation, not rollback. |
|
|
422
424
|
| `li_draft_post` | `text` required, max 3000; `publish` optional; `scheduled_at` optional offset-qualified ISO date-time; `mentions` optional array of up to 20 exact `{name,profile_id}` objects; `attachments` optional array of up to 4 exact `{filename,content_type,content_base64}` images; `first_comment` optional, max 1250 | Create a server-confirmed draft, publish now, or persist an exact future LinkedIn post and its approved first comment. A schedule is stored only with `publish:true` after approval of the exact text, time, mentions, images, and comment. |
|
|
423
425
|
| `li_set_scheduled_post_first_comment` | `id` required UUID; `first_comment` required, max 1250; `confirm:true` required | Attach one exact approved first comment to a scheduled post. SignalDash publishes it through the same connected account after the post and never republishes the post if the comment fails. |
|
|
424
|
-
| `li_scheduled_posts` | no arguments |
|
|
426
|
+
| `li_scheduled_posts` | no arguments | Read one shared content-calendar view across SignalDash and the configured Buffer LinkedIn channel. Inspect `completeness` and every `sources.*.state` before treating absence as an empty calendar. Native LinkedIn scheduled posts and drafts are invisible because Unipile has no documented read route for them; SignalDash does not use raw Voyager routes or linkedin.com browser access. An empty `items` array proves only that the visible sources returned no entries. |
|
|
425
427
|
| `li_cancel_scheduled_post` | `id` required UUID; `confirm:true` required | Cancel one exact post while it is still scheduled. It cannot recall an executing or published post. |
|
|
426
428
|
| `sd_schedule_message` | `channel` required, `whatsapp` or `linkedin`; `chat_id` required, max 500; `text` required, max 5000; `scheduled_at` required offset-qualified ISO date-time from 60 seconds to 365 days ahead; `confirm:true` required | Schedule one exact message into one chat you have already read. Read that exact chat first, at `limit` 10 or more on LinkedIn: SignalDash records what the thread looked like and refuses at send time if the conversation moved. Text only; attachments are refused rather than dropped. One message at one time, never a sequence. |
|
|
427
429
|
| `sd_scheduled_messages` | `state` optional, one of `scheduled`, `executing`, `sent`, `cancelled`, `failed`, `needs_review`; `channel` optional | List only this authenticated user's scheduled messages and their durable states. Returns every matching row, unpaginated, and always reports `needs_review_count` outside your filter. |
|
|
@@ -436,6 +438,7 @@ sd_secretary_push_set({"enabled":false,"confirm":true})
|
|
|
436
438
|
sd_inspiration_list({})
|
|
437
439
|
li_list_chats({"limit":20})
|
|
438
440
|
li_read_messages({"chat_id":"chat_li_7f3a","limit":20})
|
|
441
|
+
li_start_chat({"member_ids":["ACoAAExactMemberOne","ACoAAExactMemberTwo"],"text":"Amina, meet Jo. I think you two have a lot to compare."})
|
|
439
442
|
li_send_message({"chat_id":"chat_li_7f3a","text":"Yes. I’ll send it this afternoon."})
|
|
440
443
|
li_send_invitation({"provider_id":"ACoAAExactMember","note":"Hi Amina, I enjoyed your post on agent safety."})
|
|
441
444
|
li_send_invitation({"provider_id":"ACoAAExactMember","note":"Hi Amina, I enjoyed your post on agent safety.","confirm":true})
|
|
@@ -467,6 +470,7 @@ wa_list_chats({"limit":20})
|
|
|
467
470
|
wa_read_messages({"chat_id":"chat_wa_91b2","limit":20})
|
|
468
471
|
wa_get_attachment({"chat_id":"chat_wa_91b2","message_id":"msg_wa_5c71","attachment_id":"att_wa_0a33"})
|
|
469
472
|
wa_transcribe_voice({"chat_id":"chat_wa_91b2","message_id":"msg_wa_5c71","attachment_id":"att_wa_0a33"})
|
|
473
|
+
wa_start_chat({"member_ids":["4915111111111@s.whatsapp.net","202700000000000@lid"],"text":"Amina, meet Jo. I think you two have a lot to compare."})
|
|
470
474
|
wa_send_message({"chat_id":"chat_wa_91b2","text":"16:30 works. See you then."})
|
|
471
475
|
wa_send_message({"chat_id":"chat_wa_91b2","text":"Q3 numbers attached.","attachments":[{"filename":"q3-arr.csv","content_type":"text/csv","content_base64":"<base64>"}]})
|
|
472
476
|
wa_delete_message({"chat_id":"chat_wa_91b2","message_id":"msg_wa_5c71"})
|
|
@@ -510,7 +514,11 @@ permission to publish or schedule. For a future post, first call
|
|
|
510
514
|
offset-qualified UTC instant, mentions, attachment count, and first comment.
|
|
511
515
|
After explicit
|
|
512
516
|
approval, repeat the identical payload with `publish:true`. Verify the stored
|
|
513
|
-
record with `li_scheduled_posts`.
|
|
517
|
+
record with `li_scheduled_posts`. Before scheduling, read that shared view and
|
|
518
|
+
inspect its `completeness` plus the SignalDash, Buffer, and native LinkedIn
|
|
519
|
+
source states. A date conflict in either visible source blocks planning until
|
|
520
|
+
the human resolves it. Native LinkedIn remains explicitly blind, so never call
|
|
521
|
+
an empty result an empty calendar. For an existing scheduled post, use
|
|
514
522
|
`li_set_scheduled_post_first_comment` only after approval of the exact comment.
|
|
515
523
|
SignalDash persists the published post ID before sending the comment, so a
|
|
516
524
|
comment failure never republishes the post. It fails interrupted or ambiguous
|
|
@@ -553,14 +561,24 @@ with `confirm:true`. Allowed reasons are `opt_out`, `not_interested`,
|
|
|
553
561
|
manual LinkedIn work or any campaign action. It reports attempts that
|
|
554
562
|
SignalDash has committed in its authoritative SQLite ledger:
|
|
555
563
|
|
|
556
|
-
-
|
|
564
|
+
- invitations, including accept and single-object withdrawal maintenance;
|
|
565
|
+
- messages into existing threads;
|
|
566
|
+
- replies, reactions, publishing, and first comments on the sender's own posts;
|
|
567
|
+
- the aggregate daily emergency brake (`H`);
|
|
557
568
|
- the campaign-excluded portion (`R = min(4, H)`);
|
|
558
569
|
- the combined ceiling across all campaigns (`C = H - R`);
|
|
559
570
|
- total, manual, campaign, and unknown attempts for the UTC policy day;
|
|
560
|
-
- raw
|
|
571
|
+
- raw per-lane, aggregate, and combined-campaign capacity remaining;
|
|
561
572
|
- actions available now after connection and sender-lock state;
|
|
562
573
|
- weekly LinkedIn invitation usage and its Monday 00:00 UTC reset.
|
|
563
574
|
|
|
575
|
+
The code defaults are 20 invitation actions, 30 existing-thread messages, and
|
|
576
|
+
50 own-post engagement actions per UTC day. A separate aggregate cap of 75 is
|
|
577
|
+
the emergency brake across them. The three lane caps do not add into a promise
|
|
578
|
+
of 100 actions. Every lane still uses the same serialized provider queue and
|
|
579
|
+
its existing action-specific randomized pacing. The default 100-attempt UTC
|
|
580
|
+
week for sent invitations remains separate and unchanged.
|
|
581
|
+
|
|
564
582
|
`R` is capacity campaigns cannot consume. It is not a reserve or guaranteed
|
|
565
583
|
manual allowance: manual activity can consume the total cap first, and a later
|
|
566
584
|
manual action can still be rejected at `H`. Campaign capacity is shared across
|
|
@@ -1270,10 +1288,14 @@ public LinkedIn posts.
|
|
|
1270
1288
|
9. Confirm delivery by reading the thread after the action.
|
|
1271
1289
|
10. Account health outranks throughput and task completion.
|
|
1272
1290
|
|
|
1273
|
-
The hosted backend defaults to
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
100-attempt
|
|
1291
|
+
The hosted backend defaults to three LinkedIn lanes per stable logical sender
|
|
1292
|
+
and UTC day: 20 invitation actions, 30 messages in existing threads, and 50
|
|
1293
|
+
own-post engagement actions. An aggregate 75-attempt emergency brake bounds the
|
|
1294
|
+
combined lanes. Invitation sends also use the unchanged default 100-attempt
|
|
1295
|
+
weekly policy. Separate counters do not alter randomized action spacing, the
|
|
1296
|
+
single serialized provider queue, duplicate protection, or provider-warning
|
|
1297
|
+
locks. An exact comment reply text already submitted by that sender on the same
|
|
1298
|
+
UTC day is refused as `duplicate_comment_text`.
|
|
1277
1299
|
|
|
1278
1300
|
WhatsApp and email share ONE separate daily bucket, and it is independent of
|
|
1279
1301
|
the LinkedIn one: a WhatsApp message never spends LinkedIn capacity and a
|
|
@@ -1750,8 +1772,10 @@ Before reporting completion, verify:
|
|
|
1750
1772
|
- No sender lock or `outcome_unknown` result was retried.
|
|
1751
1773
|
- A send was confirmed by a post-send read.
|
|
1752
1774
|
- A post draft remained unpublished and unscheduled unless `publish:true` was
|
|
1753
|
-
explicitly approved.
|
|
1754
|
-
|
|
1775
|
+
explicitly approved. The shared calendar was read first, its source-specific
|
|
1776
|
+
completeness was reported, and every visible scheduled post was read back
|
|
1777
|
+
with its exact UTC instant, source, and durable state. Native LinkedIn
|
|
1778
|
+
blindness was never represented as an empty calendar.
|
|
1755
1779
|
- An export ended with `+ saved` and a non-empty CSV.
|
|
1756
1780
|
|
|
1757
1781
|
If any item is unverified, state exactly what remains incomplete.
|
|
@@ -77,15 +77,20 @@ On no, call `sd_secretary_reject` for that exact disposition and send nothing.
|
|
|
77
77
|
Inspect `already_in_contact`, `contact_state`, and `blocking_reasons`.
|
|
78
78
|
Resolve, review, and approve every later action through its one-object flow.
|
|
79
79
|
- Read `sd_budget_status` before planning multiple LinkedIn actions. Treat its
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
attempts, not all
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
invitation, existing-thread message, and own-post engagement lanes
|
|
81
|
+
separately. Treat the aggregate cap and campaign-excluded portion as brakes,
|
|
82
|
+
never as a promised allowance. Its counts cover SignalDash attempts, not all
|
|
83
|
+
native LinkedIn activity.
|
|
84
|
+
- Keep normal messaging at human volume. Code defaults are 20 invitation
|
|
85
|
+
actions, 30 existing-thread messages, 50 own-post engagement actions, and a
|
|
86
|
+
75-attempt aggregate brake per UTC day. The unchanged sent-invitation policy
|
|
87
|
+
also caps attempts at 100 per UTC week.
|
|
86
88
|
- Let SignalDash serialize LinkedIn writes and apply action-specific jitter. Do
|
|
87
89
|
not parallelize write calls or bypass the guard. Invitation jitter completes
|
|
88
90
|
before the server's final provider preflight and action reservation.
|
|
91
|
+
- Never reuse exact comment reply text during the same UTC day. SignalDash
|
|
92
|
+
refuses it as `duplicate_comment_text`; changing punctuation only to evade
|
|
93
|
+
that guard is prohibited.
|
|
89
94
|
- Campaign invitations use a sender-local Monday-Friday 09:00-17:00 work
|
|
90
95
|
window and a durable 90-180 second per-sender pacing interval. Timing is
|
|
91
96
|
checked before final preflight and atomically during action reservation.
|