@floomhq/signaldash 0.37.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 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.37.0 <invite-code>
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
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",
@@ -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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/signaldash",
3
- "version": "0.37.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": {
@@ -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. |
@@ -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. |
@@ -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"})