@floomhq/signaldash 0.43.1 → 0.44.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
@@ -121,7 +121,7 @@ setup installs both from the same pinned npm package
121
121
  the human chose to execute:
122
122
 
123
123
  ```bash
124
- npx -y @floomhq/signaldash@0.43.1 <invite-code>
124
+ npx -y @floomhq/signaldash@0.44.0 <invite-code>
125
125
  ```
126
126
 
127
127
  Run that command in a terminal, not in an agent chat. Do not ask an agent to
package/bin/sd.mjs CHANGED
@@ -1584,12 +1584,25 @@ const TOOLS = [
1584
1584
  name: "wa_send_message",
1585
1585
  ch: "wa",
1586
1586
  action: "send",
1587
- description: "Send a WhatsApp message (rate-safe). args: chat_id, text, attachments, expected_watermark, mark_read. Read the chat first: the read returns current_watermark and a send into a thread this account has not read recently is refused. Own outbound messages added after that read do not invalidate the draft, but a counterparty message or mutation returns 409 thread_changed with new_messages, changed_kind, and current_watermark. Optional expected_watermark binds the send to that exact reviewed state. A re-read that fails is 502 thread_preflight_unavailable and nothing was sent. attachments optionally carries up to 4 base64 files as exact {filename, content_type, content_base64} objects, at most 16 MiB per file and 16 MiB per message, and accepts images, PDF, CSV, plain text, JSON, xlsx, zip and mp4/mov video. text is the caption and may be omitted when a file is attached, but a call carrying neither text nor an attachment is refused. Defaults to mark_read:true after confirmed success; mark_read:false preserves unread state. An attachment send is rate-limited, deduplicated and recorded exactly like a text send, and spends the same daily budget. If a send times out or the provider never confirms it, the message may still have been delivered: SignalDash records it and refuses an identical retry with 409 send_outcome_unknown. Read the chat, and only if the message is genuinely absent, resend the identical payload with confirm_resend:true.",
1587
+ description: "Send a WhatsApp message (rate-safe). args: chat_id, text, attachments, mentions, expected_watermark, mark_read. Read the chat first: the read returns current_watermark and a send into a thread this account has not read recently is refused. Own outbound messages added after that read do not invalidate the draft, but a counterparty message or mutation returns 409 thread_changed with new_messages, changed_kind, and current_watermark. Optional expected_watermark binds the send to that exact reviewed state. A re-read that fails is 502 thread_preflight_unavailable and nothing was sent. attachments optionally carries up to 4 base64 files as exact {filename, content_type, content_base64} objects, at most 16 MiB per file and 16 MiB per message, and accepts images, PDF, CSV, plain text, JSON, xlsx, zip and mp4/mov video. text is the caption and may be omitted when a file is attached, but a call carrying neither text nor an attachment is refused. Defaults to mark_read:true after confirmed success; mark_read:false preserves unread state. An attachment send is rate-limited, deduplicated and recorded exactly like a text send, and spends the same daily budget. If a send times out or the provider never confirms it, the message may still have been delivered: SignalDash records it and refuses an identical retry with 409 send_outcome_unknown. Read the chat, and only if the message is genuinely absent, resend the identical payload with confirm_resend:true. GROUP @MENTIONS (WhatsApp groups only): put {{0}}, {{1}} ... in text where each person is tagged and pass mentions:[{profile_id}] in the same order, profile_id being the participant id from wa_read_messages (sender_id, e.g. 81983023665190@lid, or <phone>@s.whatsapp.net). WhatsApp renders each as @<digits> of that id and notifies the person; typing \"@Name\" as plain text notifies no one. Every {{i}} needs mentions[i] and every mention needs its {{i}}; a mention into a non-group chat is refused with mentions_require_group_chat. Mentions are part of the duplicate-send fingerprint.",
1588
1588
  inputSchema: {
1589
1589
  type: "object",
1590
1590
  properties: {
1591
1591
  chat_id: { type: "string", minLength: 1, maxLength: 500 },
1592
1592
  text: { type: "string", minLength: 1, maxLength: 5000 },
1593
+ mentions: {
1594
+ type: "array", minItems: 1, maxItems: 20,
1595
+ description: "WhatsApp groups only. mentions[i] is tagged where text has {{i}}.",
1596
+ items: {
1597
+ type: "object",
1598
+ properties: {
1599
+ profile_id: { type: "string", pattern: "^\\d+@(s\\.whatsapp\\.net|lid)$", description: "Participant JID, e.g. 81983023665190@lid." },
1600
+ name: { type: "string", maxLength: 200, description: "Optional label for your own readability; WhatsApp ignores it." },
1601
+ },
1602
+ required: ["profile_id"],
1603
+ additionalProperties: false,
1604
+ },
1605
+ },
1593
1606
  attachments: {
1594
1607
  type: "array", maxItems: 4,
1595
1608
  items: {
@@ -1869,7 +1882,7 @@ const TOOLS = [
1869
1882
  {
1870
1883
  name: "li_reply_to_comment",
1871
1884
  path: "/li/reply_to_comment",
1872
- description: "Reply once to one exact inbound comment on the authenticated sender's own LinkedIn post. First read li_post_comments and preserve the exact post_id, parent_comment_id, trigger_comment_id, text, and returned signaldash_watermark. Optional mentions tag people in the reply: spell each person's name into text exactly once as a whole word and pass a matching mentions entry, exactly as li_draft_post does. LinkedIn already notifies the comment's author, so a mention is for tagging a THIRD person. SignalDash re-proves post ownership, trigger identity and content, absence of an own duplicate, sender generation, budget, and provider safety immediately before writing. A 2xx is not success until bounded readback finds exactly one own reply whose landed text equals text, so a tag whose display name does not render exactly as written is reported unverified rather than assumed sent. A Secretary approval uses secretary_receipt_id alone.",
1885
+ description: "Reply once to one exact inbound comment on the authenticated sender's own LinkedIn post. First read li_post_comments and preserve the exact post_id, parent_comment_id, trigger_comment_id, text, and returned signaldash_watermark. Optional mentions tag people in the reply: spell each person's name into text exactly once as a whole word (or pass occurrence when the name repeats) and pass a matching mentions entry, exactly as li_draft_post does. LinkedIn already notifies the comment's author, so a mention is for tagging a THIRD person. SignalDash re-proves post ownership, trigger identity and content, absence of an own duplicate, sender generation, budget, and provider safety immediately before writing. A 2xx is not success until bounded readback finds exactly one own reply whose landed text equals text, so a tag whose display name does not render exactly as written is reported unverified rather than assumed sent. A Secretary approval uses secretary_receipt_id alone.",
1873
1886
  inputSchema: {
1874
1887
  type: "object",
1875
1888
  properties: {
@@ -1879,12 +1892,16 @@ const TOOLS = [
1879
1892
  text: { type: "string", minLength: 1, maxLength: 1250 },
1880
1893
  mentions: {
1881
1894
  type: "array", maxItems: 20,
1882
- description: "People to tag. Each name must appear exactly once in text as a whole word; SignalDash replaces that one occurrence with the provider's mention token. Use the person's exact LinkedIn display name, because the landed comment is verified against text character for character.",
1895
+ description: "People to tag. Each name must appear exactly once in text as a whole word, or, when it repeats, carry occurrence (1-based) naming which whole-word occurrence to tag; SignalDash replaces that one occurrence with the provider's mention token. Use the person's exact LinkedIn display name, because the landed comment is verified against text character for character.",
1883
1896
  items: {
1884
1897
  type: "object",
1885
1898
  properties: {
1886
1899
  name: { type: "string", minLength: 1, maxLength: 120 },
1887
1900
  profile_id: { type: "string", minLength: 1, maxLength: 250 },
1901
+ occurrence: {
1902
+ type: "integer", minimum: 1,
1903
+ description: "Only when name appears more than once in text as a whole word: the 1-based occurrence to tag (1 = first). Without it a repeated name is refused as mention_name_ambiguous; a value above the real count is refused as mention_occurrence_out_of_range.",
1904
+ },
1888
1905
  },
1889
1906
  required: ["name", "profile_id"],
1890
1907
  },
@@ -1959,7 +1976,7 @@ const TOOLS = [
1959
1976
  {
1960
1977
  name: "li_draft_post",
1961
1978
  path: "/li/create_post",
1962
- description: "Draft, publish, or schedule a LinkedIn post. A schedule with attachments requires an offset-qualified scheduled_at, a preview, then publish:true with confirm:true and the preview's single-use approval_hash bound to the exact text, time, mentions, attachments, first comment, and content-pipeline item. When the content pipeline is enabled, use an approved content_pipeline_id or preview one exact reasoned content_pipeline_override and repeat it with confirm:true plus its nested single-use approval_hash. Optional mentions, up to 20 attachments, and an account-owner-authored first_comment are preserved for the scheduled publish. An attachment is EITHER one of the four image types carrying content_base64, OR video/mp4 carrying content_url and no bytes: one absolute https URL streamed at publish time, at most 64 MiB, refused if it is not https, resolves to a private address, redirects to one, exceeds the ceiling, answers a different content type, or times out. A video attachment must be the only attachment on the post. Optional repost_post_id republishes an existing LinkedIn post with text as the commentary on top: it needs scheduled_at, is resolved read-only and shown in the preview (post_id, author, opening text), and always needs the preview's approval_hash with confirm:true; mentions and first_comment work as on any post.",
1979
+ description: "Draft, publish, or schedule a LinkedIn post. A schedule with attachments requires an offset-qualified scheduled_at, a preview, then publish:true with confirm:true and the preview's single-use approval_hash bound to the exact text, time, mentions, attachments, first comment, and content-pipeline item. When the content pipeline is enabled, use an approved content_pipeline_id or preview one exact reasoned content_pipeline_override and repeat it with confirm:true plus its nested single-use approval_hash. Optional mentions, up to 20 attachments, and an account-owner-authored first_comment are preserved for the scheduled publish. Each mention name must appear in text as a whole word exactly once; when it repeats (\"Apple ships the Apple II.\"), add occurrence (1-based) to tag that one occurrence, and the occurrence is part of what the approval_hash binds. An attachment is EITHER one of the four image types carrying content_base64, OR video/mp4 carrying content_url and no bytes: one absolute https URL streamed at publish time, at most 64 MiB, refused if it is not https, resolves to a private address, redirects to one, exceeds the ceiling, answers a different content type, or times out. A video attachment must be the only attachment on the post. Optional repost_post_id republishes an existing LinkedIn post with text as the commentary on top: it needs scheduled_at, is resolved read-only and shown in the preview (post_id, author, opening text), and always needs the preview's approval_hash with confirm:true; mentions and first_comment work as on any post.",
1963
1980
  inputSchema: {
1964
1981
  type: "object",
1965
1982
  properties: {
@@ -1988,6 +2005,10 @@ const TOOLS = [
1988
2005
  properties: {
1989
2006
  name: { type: "string", minLength: 1, maxLength: 120 },
1990
2007
  profile_id: { type: "string", minLength: 1, maxLength: 250 },
2008
+ occurrence: {
2009
+ type: "integer", minimum: 1,
2010
+ description: "Only when name appears more than once in text as a whole word: the 1-based occurrence to tag (1 = first). Without it a repeated name is refused as mention_name_ambiguous; a value above the real count is refused as mention_occurrence_out_of_range.",
2011
+ },
1991
2012
  },
1992
2013
  required: ["name", "profile_id"],
1993
2014
  },
@@ -2041,6 +2062,10 @@ const TOOLS = [
2041
2062
  properties: {
2042
2063
  name: { type: "string", minLength: 1, maxLength: 120 },
2043
2064
  profile_id: { type: "string", minLength: 1, maxLength: 250 },
2065
+ occurrence: {
2066
+ type: "integer", minimum: 1,
2067
+ description: "Only when name appears more than once in text as a whole word: the 1-based occurrence to tag (1 = first). Without it a repeated name is refused as mention_name_ambiguous; a value above the real count is refused as mention_occurrence_out_of_range.",
2068
+ },
2044
2069
  },
2045
2070
  required: ["name", "profile_id"],
2046
2071
  additionalProperties: false,
@@ -2141,6 +2166,10 @@ const TOOLS = [
2141
2166
  properties: {
2142
2167
  name: { type: "string", minLength: 1, maxLength: 120 },
2143
2168
  profile_id: { type: "string", minLength: 1, maxLength: 250 },
2169
+ occurrence: {
2170
+ type: "integer", minimum: 1,
2171
+ description: "Only when name appears more than once in text as a whole word: the 1-based occurrence to tag (1 = first). Without it a repeated name is refused as mention_name_ambiguous; a value above the real count is refused as mention_occurrence_out_of_range.",
2172
+ },
2144
2173
  },
2145
2174
  required: ["name", "profile_id"],
2146
2175
  additionalProperties: false,
@@ -2319,6 +2348,10 @@ const TOOLS = [
2319
2348
  properties: {
2320
2349
  name: { type: "string", minLength: 1, maxLength: 120 },
2321
2350
  profile_id: { type: "string", minLength: 1, maxLength: 250 },
2351
+ occurrence: {
2352
+ type: "integer", minimum: 1,
2353
+ description: "Only when name appears more than once in text as a whole word: the 1-based occurrence to tag (1 = first). Without it a repeated name is refused as mention_name_ambiguous; a value above the real count is refused as mention_occurrence_out_of_range.",
2354
+ },
2322
2355
  },
2323
2356
  required: ["name", "profile_id"],
2324
2357
  additionalProperties: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/signaldash",
3
- "version": "0.43.1",
3
+ "version": "0.44.0",
4
4
  "description": "Secure LinkedIn, WhatsApp, and email access for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -451,7 +451,7 @@ Use the exact tool names and argument keys below. Limits are optional.
451
451
  | `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. |
452
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. |
453
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. |
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. |
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; `mentions` optional array of 1 to 20 `{profile_id, name?}` (WhatsApp groups only; `profile_id` matches `^\d+@(s\.whatsapp\.net|lid)$`, `name` is ignored); `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. Group @mentions: typing `@Name` notifies nobody. Put `{{0}}`, `{{1}}` ... in `text` where each person is tagged and pass `mentions:[{profile_id}]` in the same order, `profile_id` being the participant id from `wa_read_messages` (`sender_id`, e.g. `81983023665190@lid`, or `<phone>@s.whatsapp.net`); WhatsApp renders each as `@<digits>` and notifies that person. Every `{{i}}` needs `mentions[i]` and every mention needs its `{{i}}` (`400 invalid_mentions`); a mention into a chat that is not a group is refused with `400 mentions_require_group_chat`. Mentions are part of the duplicate fingerprint: the same words tagging someone else are a different message. |
455
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
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
457
  | `ig_mark_read` | `chat_id` required | Explicitly clear one Instagram chat's unread state. |
@@ -465,11 +465,11 @@ Use the exact tool names and argument keys below. Limits are optional.
465
465
  | `li_my_posts` | `limit` default 10, max 50; `member_id` optional | Find the user's latest posts and exact `social_id` values. Omit `member_id` to use the connected user's own ID. Pass `social_id`, not a different numeric `id`, to the engagement tools. |
466
466
  | `li_post_reactions` | `post_id` required; use the exact `social_id` from `li_my_posts`; `limit` default 50, max 100; `cursor` optional | Read one bounded page of reactors. Always inspect `completeness.state`, `total`, and `next_cursor`; continue with the cursor while state is `incomplete`. Numeric ids are resolved against the user's own recent posts when possible, including ugcPost-backed multi-image posts. The provider total is reported when available and is never guessed from `li_my_posts`. A reaction does not authorize outreach. |
467
467
  | `li_post_comments` | `post_id` required; `comment_id` optional exact parent id; `resolve_reply_state` optional boolean, not combinable with `comment_id`; use the exact `social_id` from `li_my_posts`; `limit` default 50, max 100; `cursor` optional | Read one bounded page of comments, or pass `comment_id` to read replies to that exact parent. Pass `resolve_reply_state: true` before replying to anything: each comment then carries `reply_state.replied_by_me`. Skip every comment whose `replied_by_me` is `true`, and skip every comment whose `replied_by_me` is `null`, which means unknown and never means nobody replied. Only `no_replies` and `answered_by_others` are proven unanswered. Always inspect `completeness.state`, `total`, and `next_cursor`; continue with the cursor while state is `incomplete`. Also inspect `reply_resolution.state`; `partial` or `aborted` means some comments were never resolved. Numeric ids are resolved against the user's own recent posts when possible. The provider total is reported when available and is never guessed from `li_my_posts`. |
468
- | `li_reply_to_comment` | Manual path: `post_id`, `parent_comment_id`, `trigger_comment_id`, and `text` required; `mentions` optional array of `{name, profile_id}`; `expected_watermark` optional exact 64-character watermark. Secretary path: `secretary_receipt_id` alone. | Reply once to one exact inbound comment on this sender's own post after `li_post_comments`. SignalDash re-proves post ownership, the unchanged trigger, its author and parent, no own duplicate, sender generation, budget, and provider health immediately before writing. A 2xx is not success until readback finds exactly one matching own reply. Each mention name must appear exactly once in `text` as a whole word; SignalDash swaps that occurrence for the provider's token, and every tagged person is checked against suppression and protection exactly like the person being replied to. Because the landed comment is compared against `text` character for character, use the person's exact LinkedIn display name or the write is reported unverified. |
468
+ | `li_reply_to_comment` | Manual path: `post_id`, `parent_comment_id`, `trigger_comment_id`, and `text` required; `mentions` optional array of `{name, profile_id, occurrence?}`; `expected_watermark` optional exact 64-character watermark. Secretary path: `secretary_receipt_id` alone. | Reply once to one exact inbound comment on this sender's own post after `li_post_comments`. SignalDash re-proves post ownership, the unchanged trigger, its author and parent, no own duplicate, sender generation, budget, and provider health immediately before writing. A 2xx is not success until readback finds exactly one matching own reply. Each mention name must appear exactly once in `text` as a whole word, or carry a 1-based `occurrence` when it repeats; SignalDash swaps that occurrence for the provider's token, and every tagged person is checked against suppression and protection exactly like the person being replied to. Because the landed comment is compared against `text` character for character, use the person's exact LinkedIn display name or the write is reported unverified. |
469
469
  | `li_like_comment` | `post_id`, `parent_comment_id`, and `comment_id` required; `expected_watermark` optional exact 64-character watermark | Like once one exact inbound comment on this sender's own post after `li_post_comments`. SignalDash re-proves post ownership, the unchanged comment, its author and parent, no own like, sender generation, budget, and provider health immediately before writing. A 2xx is not success until bounded readback finds exactly one own like. |
470
470
  | `li_delete_message` | `chat_id`, `message_id`, and `confirm:true` required | Remediate one exact own LinkedIn message within 60 minutes of sending. SignalDash proves account, exact chat, own authorship, timestamp eligibility, a separate remediation budget, and the provider's post-delete state. Success requires `deleted:1` or a genuine 404. A present row or failed readback returns `502 deleted_unconfirmed`, locks the sender, and is never retried automatically. This cannot undo delivery, reading, or notifications and never relaxes a send gate. |
471
471
  | `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. |
472
- | `li_draft_post` | `text` required, max 3000; `publish` optional; `scheduled_at` optional offset-qualified ISO date-time; `content_pipeline_id` normally required for scheduling while that user's pipeline is enabled; `content_pipeline_override` optional exact `{reason,confirm?,approval_hash?}`; `mentions` optional array of up to 25 exact `{name,profile_id}` objects; `attachments` optional array of up to 20 exact objects, each either an image as `{filename,content_type,content_base64}` (max 5 MiB each and 12 MiB total) or one video as `{filename,content_type:"video/mp4",content_url,content_sha256?}`; `first_comment` optional, max 1250; `repost_post_id` optional, scheduled only, the numeric activity id or `urn:li:activity|ugcPost|share:<digits>` of a post to repost with `text` as commentary; scheduled attachments and every repost require top-level `confirm:true` plus the preview's `approval_hash` | Create a server-confirmed draft, publish now, or persist an exact future LinkedIn post and its approved first comment. A scheduled attachment preview returns a single-use approval bound to the exact text, time, mentions, attachment filenames/types plus image-byte digests or video URL/digest, first comment, and pipeline item. A repost preview resolves the target read-only and returns `repost` (post_id, social_id, author, share_url, text_excerpt) so the human approves which post is republished; the approval is bound to that post id. An enabled pipeline normally runs its fact, exact-text-and-attachment, shared-calendar, frequency, and breathing preflight. For one deliberate exception, preview the exact payload with `{reason}`, show it to the human, then repeat it with nested `confirm:true` and the returned single-use payload-bound `approval_hash`. The audit record preserves the reason, preview ID, and approval time; every non-pipeline provider safety guard remains active. |
472
+ | `li_draft_post` | `text` required, max 3000; `publish` optional; `scheduled_at` optional offset-qualified ISO date-time; `content_pipeline_id` normally required for scheduling while that user's pipeline is enabled; `content_pipeline_override` optional exact `{reason,confirm?,approval_hash?}`; `mentions` optional array of up to 25 exact `{name,profile_id,occurrence?}` objects, where `occurrence` (1-based) picks one whole-word repeat of a name that appears more than once and is bound into the approval; `attachments` optional array of up to 20 exact objects, each either an image as `{filename,content_type,content_base64}` (max 5 MiB each and 12 MiB total) or one video as `{filename,content_type:"video/mp4",content_url,content_sha256?}`; `first_comment` optional, max 1250; `repost_post_id` optional, scheduled only, the numeric activity id or `urn:li:activity|ugcPost|share:<digits>` of a post to repost with `text` as commentary; scheduled attachments and every repost require top-level `confirm:true` plus the preview's `approval_hash` | Create a server-confirmed draft, publish now, or persist an exact future LinkedIn post and its approved first comment. A scheduled attachment preview returns a single-use approval bound to the exact text, time, mentions, attachment filenames/types plus image-byte digests or video URL/digest, first comment, and pipeline item. A repost preview resolves the target read-only and returns `repost` (post_id, social_id, author, share_url, text_excerpt) so the human approves which post is republished; the approval is bound to that post id. An enabled pipeline normally runs its fact, exact-text-and-attachment, shared-calendar, frequency, and breathing preflight. For one deliberate exception, preview the exact payload with `{reason}`, show it to the human, then repeat it with nested `confirm:true` and the returned single-use payload-bound `approval_hash`. The audit record preserves the reason, preview ID, and approval time; every non-pipeline provider safety guard remains active. |
473
473
  | `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. |
474
474
  | `li_save_post_draft` | `text` required, max 3000; `mentions` optional up to 25; `attachments` optional up to 20, images by `content_base64` (max 5 MiB each and 12 MiB total) or one video/mp4 by `content_url`; `first_comment` optional, max 1250; `source_key` optional tenant-scoped idempotency key, max 256; `metadata` optional bounded structured source/editorial context | Persist one arbitrary LinkedIn draft without scheduling or publishing. Exact ordered image bytes and ordinary duplicate drafts are retained. An identical `source_key` replay returns the existing draft; changed content under that key is refused. Tenant limits are 1,000 drafts and 100 MiB decoded attachment bytes. No provider call or action budget is used. |
475
475
  | `li_post_drafts` | optional `review_ttl_hours` (`1` or `24`) | List durable SignalDash drafts with attachment metadata, never base64, and return a fresh one-use calendar link. The default handoff and review session last 60 minutes; pass `24` for a full day. The calendar uses the scheduled-post card design for unscheduled SignalDash and Buffer drafts, with a visible `DRAFT` tag. Its review session saves copy, notes, and up to four private feedback images inline without reloading the calendar. Reviewers can explicitly replace the post's current images with those uploads; unchecked uploads remain private references. Each SignalDash draft card also displays its exact version and next verified free 08:00 UTC slot. `Approve & schedule` rechecks the complete SignalDash and Buffer calendar, then atomically moves the exact draft into that slot without a provider call. Stored drafts remain separate from scheduled posts until this explicit approval succeeds. |
@@ -539,6 +539,7 @@ wa_transcribe_voice({"chat_id":"chat_wa_91b2","message_id":"msg_wa_5c71","attach
539
539
  wa_start_chat({"member_ids":["4915111111111@s.whatsapp.net","202700000000000@lid"],"subject":"Amina <> Jo","text":"Amina, meet Jo. I think you two have a lot to compare."})
540
540
  wa_send_message({"chat_id":"chat_wa_91b2","text":"16:30 works. See you then."})
541
541
  wa_send_message({"chat_id":"chat_wa_91b2","text":"Q3 numbers attached.","attachments":[{"filename":"q3-arr.csv","content_type":"text/csv","content_base64":"<base64>"}]})
542
+ wa_send_message({"chat_id":"chat_wa_group","text":"{{0}} can you confirm Thursday?","mentions":[{"profile_id":"81983023665190@lid"}]})
542
543
  wa_delete_message({"chat_id":"chat_wa_91b2","message_id":"msg_wa_5c71"})
543
544
  wa_delete_messages({"messages":[{"chat_id":"chat_wa_91b2","message_id":"msg_wa_5c71"},{"chat_id":"chat_wa_91b2","message_id":"msg_wa_5c72"}]})
544
545
  email_list({"limit":20})