@floomhq/signaldash 0.36.0 → 0.37.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.36.0 <invite-code>
105
+ npx -y @floomhq/signaldash@0.37.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
@@ -305,6 +305,16 @@ uses the same sender binding, action ledger, duplicate guard, daily budget, and
305
305
  provider-warning lock as immediate publishing. An interrupted or ambiguous
306
306
  execution fails closed and is never retried automatically.
307
307
 
308
+ `li_scheduled_posts` is the shared planning read across posts stored by
309
+ SignalDash and the configured Buffer LinkedIn channel. Every item names its
310
+ source, and the response reports independent completeness for SignalDash,
311
+ Buffer, and native LinkedIn. Unipile has no documented endpoint for posts or
312
+ drafts scheduled natively in LinkedIn, so that source is always explicit as
313
+ `unavailable`. SignalDash does not call undocumented Voyager routes or automate
314
+ linkedin.com to fill the gap. An empty item list therefore means only that the
315
+ visible SignalDash and Buffer sources are empty, never that the native LinkedIn
316
+ calendar is empty.
317
+
308
318
  `sd_schedule_message` puts one exact message into one exact chat at one exact
309
319
  time, on WhatsApp or LinkedIn, text only. It enqueues; it never sends. The
310
320
  attachment bytes a WhatsApp send accepts are refused here rather than held on
@@ -361,9 +371,10 @@ explicitly enables it with `confirm:true`. The optional filters are an exact
361
371
  public-identifier allowlist and description-keyword matches; when both groups
362
372
  are configured, both must match. The single backend worker reads one bounded
363
373
  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 shared daily
365
- LinkedIn action budget. Three consecutive parse or provider errors disable the
366
- setting. `sd_auto_accept_status` exposes accepted counts today and this week,
374
+ UTC day by default, jitters every action, and also consumes the invitation
375
+ daily lane and aggregate LinkedIn brake. Three consecutive parse or provider
376
+ errors disable the setting. `sd_auto_accept_status` exposes accepted counts
377
+ today and this week,
367
378
  failed attempts today, remaining capacity, the stop reason, and sanitized
368
379
  records for invitations whose provider shape could not be parsed. Both the current nested
369
380
  `specifics.shared_secret` and legacy top-level shape are supported; secrets are
@@ -415,22 +426,30 @@ npx -y @floomhq/signaldash connections linkedin-connections.csv
415
426
 
416
427
  ## Safety limits
417
428
 
418
- SignalDash enforces a daily send cap. The operating skill adds the human
429
+ SignalDash enforces daily write caps. The operating skill adds the human
419
430
  workflow around that runtime control:
420
431
 
421
- - Every successful send includes `rate_limit.limit`, `used`, `remaining`, and
422
- `resets_at`, plus matching `X-RateLimit-*` headers. When the per-user cap is
423
- exhausted, SignalDash returns HTTP 429 with `code: "rate_limit_exceeded"` and
424
- `Retry-After`. Usage is persisted server-side and resets at midnight UTC.
425
- - The caps are per channel lane: LinkedIn actions, WhatsApp and email sends
426
- together, and message deletes each spend their own daily budget. A `429` names
432
+ - Every successful LinkedIn write includes aggregate `rate_limit` data and its
433
+ applicable `lane_rate_limit` when classified. `rate_limit.limit`, `used`,
434
+ `remaining`, and `resets_at` retain their stable response shape. A lane
435
+ refusal returns HTTP
436
+ 429 with an exact invitation, message, or engagement code and
437
+ `rate_limit_lane`; the aggregate brake retains
438
+ `code: "rate_limit_exceeded"`. Every 429 includes `Retry-After`.
439
+ Counters are persisted server-side and reset at midnight UTC.
440
+ - LinkedIn uses three per-sender lanes: 20 invitation actions, 30 messages in
441
+ existing threads, and 50 replies or reactions on own posts per UTC day by
442
+ default. Publishing and first comments count with own-post engagement. A
443
+ separate aggregate cap of 75 is the emergency brake across the lanes. Sent
444
+ invitations retain the unchanged 100-attempt UTC-week policy.
445
+ - WhatsApp and email sends together, and message deletes, retain their own
446
+ independent budgets. A `429` names
427
447
  the cap it hit; it is never a statement about another lane. Two caveats worth
428
448
  knowing: on the day a deployment first upgrades past the lane split, that
429
449
  day's existing count is still read on the LinkedIn side (it is ambiguous, and
430
450
  the conservative reading is the safe one) and clears at the next UTC midnight;
431
- and the WhatsApp/email lane is keyed per access token while the LinkedIn
432
- ledger is keyed per user, so several live tokens for one user get one LinkedIn
433
- budget but a WhatsApp budget each.
451
+ LinkedIn, WhatsApp/email, and delete ledgers are keyed to the stable logical
452
+ user or sender rather than multiplied by live bearer sessions.
434
453
  - Read the exact thread before every send.
435
454
  - Preview an exact invitation target and note before sending it.
436
455
  - List the exact received or sent invitation before accepting or withdrawing it.
@@ -439,7 +458,8 @@ workflow around that runtime control:
439
458
  - Never infer a recipient from a partial name.
440
459
  - Never send a duplicate or retry an ambiguous timeout without re-reading.
441
460
  - Invitation auto-accept remains off until explicitly enabled, consumes both
442
- its dedicated daily cap and the shared LinkedIn action cap, and disables
461
+ its dedicated daily cap, the invitation lane, and the aggregate LinkedIn
462
+ brake, and disables
443
463
  itself after three consecutive parse or provider errors.
444
464
  - Do not parallelize sends or work around a rate limit.
445
465
  - Do not bypass paid-discovery previews, pacing, request caps, or cost caps.
@@ -449,8 +469,8 @@ workflow around that runtime control:
449
469
 
450
470
  LinkedIn invitation writes are single-object actions. SignalDash serializes
451
471
  them with other LinkedIn writes, requires an exact preview or list read,
452
- applies the shared daily action cap, and enforces a separate 100-attempt weekly
453
- policy by default. For a new invitation, jitter completes before the final
472
+ applies the invitation daily lane and aggregate brake, and enforces a separate
473
+ 100-attempt weekly policy by default. For a new invitation, jitter completes before the final
454
474
  preflight and action reservation; that preflight verifies relationship state,
455
475
  pending invitations in both directions, and absence of an existing one-to-one
456
476
  chat. Provider timeouts become non-retryable `outcome_unknown` locks.
package/bin/sd.mjs CHANGED
@@ -637,7 +637,7 @@ const TOOLS = [
637
637
  {
638
638
  name: "sd_budget_status",
639
639
  path: "/sd/budget_status",
640
- description: "Read authoritative SignalDash LinkedIn attempt capacity for this UTC day and week across manual work and all campaigns. Reports the campaign-excluded portion without promising a reserved manual slot.",
640
+ 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
641
  inputSchema: {
642
642
  type: "object",
643
643
  properties: {},
@@ -948,7 +948,7 @@ const TOOLS = [
948
948
  {
949
949
  name: "sd_campaign_preview",
950
950
  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 shared daily budget, 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.",
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 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
952
  inputSchema: {
953
953
  type: "object",
954
954
  properties: {
@@ -1529,7 +1529,7 @@ const TOOLS = [
1529
1529
  {
1530
1530
  name: "li_scheduled_posts",
1531
1531
  path: "/li/scheduled_posts",
1532
- description: "List this authenticated user's scheduled LinkedIn posts and their current states.",
1532
+ 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
1533
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
1534
1534
  },
1535
1535
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/signaldash",
3
- "version": "0.36.0",
3
+ "version": "0.37.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",
@@ -382,7 +382,7 @@ Use the exact tool names and argument keys below. Limits are optional.
382
382
  | `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
383
  | `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
384
  | `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, daily total/manual/campaign/unknown attempts, total and combined-campaign capacity, weekly invitation usage, lock state, and UTC resets from the authoritative server ledger. |
385
+ | `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
386
  | `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
387
  | `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
388
  | `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 +393,7 @@ Use the exact tool names and argument keys below. Limits are optional.
393
393
  | `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
394
  | `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
395
  | `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 shared daily budget, and the `approval_url` to hand the human. This read also authorizes a later exact cancel. |
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 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
397
  | `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
398
  | `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
399
  | `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. |
@@ -421,7 +421,7 @@ Use the exact tool names and argument keys below. Limits are optional.
421
421
  | `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
422
  | `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
423
  | `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 | List only this authenticated user's scheduled LinkedIn posts and their durable states. |
424
+ | `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
425
  | `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
426
  | `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
427
  | `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. |
@@ -510,7 +510,11 @@ permission to publish or schedule. For a future post, first call
510
510
  offset-qualified UTC instant, mentions, attachment count, and first comment.
511
511
  After explicit
512
512
  approval, repeat the identical payload with `publish:true`. Verify the stored
513
- record with `li_scheduled_posts`. For an existing scheduled post, use
513
+ record with `li_scheduled_posts`. Before scheduling, read that shared view and
514
+ inspect its `completeness` plus the SignalDash, Buffer, and native LinkedIn
515
+ source states. A date conflict in either visible source blocks planning until
516
+ the human resolves it. Native LinkedIn remains explicitly blind, so never call
517
+ an empty result an empty calendar. For an existing scheduled post, use
514
518
  `li_set_scheduled_post_first_comment` only after approval of the exact comment.
515
519
  SignalDash persists the published post ID before sending the comment, so a
516
520
  comment failure never republishes the post. It fails interrupted or ambiguous
@@ -553,14 +557,24 @@ with `confirm:true`. Allowed reasons are `opt_out`, `not_interested`,
553
557
  manual LinkedIn work or any campaign action. It reports attempts that
554
558
  SignalDash has committed in its authoritative SQLite ledger:
555
559
 
556
- - the total daily cap (`H`);
560
+ - invitations, including accept and single-object withdrawal maintenance;
561
+ - messages into existing threads;
562
+ - replies, reactions, publishing, and first comments on the sender's own posts;
563
+ - the aggregate daily emergency brake (`H`);
557
564
  - the campaign-excluded portion (`R = min(4, H)`);
558
565
  - the combined ceiling across all campaigns (`C = H - R`);
559
566
  - total, manual, campaign, and unknown attempts for the UTC policy day;
560
- - raw total and combined-campaign capacity remaining;
567
+ - raw per-lane, aggregate, and combined-campaign capacity remaining;
561
568
  - actions available now after connection and sender-lock state;
562
569
  - weekly LinkedIn invitation usage and its Monday 00:00 UTC reset.
563
570
 
571
+ The code defaults are 20 invitation actions, 30 existing-thread messages, and
572
+ 50 own-post engagement actions per UTC day. A separate aggregate cap of 75 is
573
+ the emergency brake across them. The three lane caps do not add into a promise
574
+ of 100 actions. Every lane still uses the same serialized provider queue and
575
+ its existing action-specific randomized pacing. The default 100-attempt UTC
576
+ week for sent invitations remains separate and unchanged.
577
+
564
578
  `R` is capacity campaigns cannot consume. It is not a reserve or guaranteed
565
579
  manual allowance: manual activity can consume the total cap first, and a later
566
580
  manual action can still be rejected at `H`. Campaign capacity is shared across
@@ -1270,10 +1284,14 @@ public LinkedIn posts.
1270
1284
  9. Confirm delivery by reading the thread after the action.
1271
1285
  10. Account health outranks throughput and task completion.
1272
1286
 
1273
- The hosted backend defaults to 20 LinkedIn action attempts per stable logical
1274
- sender per UTC day, shared across messages, invitations, invitation
1275
- maintenance, and post publishing. Invitation sends also use the default
1276
- 100-attempt weekly policy.
1287
+ The hosted backend defaults to three LinkedIn lanes per stable logical sender
1288
+ and UTC day: 20 invitation actions, 30 messages in existing threads, and 50
1289
+ own-post engagement actions. An aggregate 75-attempt emergency brake bounds the
1290
+ combined lanes. Invitation sends also use the unchanged default 100-attempt
1291
+ weekly policy. Separate counters do not alter randomized action spacing, the
1292
+ single serialized provider queue, duplicate protection, or provider-warning
1293
+ locks. An exact comment reply text already submitted by that sender on the same
1294
+ UTC day is refused as `duplicate_comment_text`.
1277
1295
 
1278
1296
  WhatsApp and email share ONE separate daily bucket, and it is independent of
1279
1297
  the LinkedIn one: a WhatsApp message never spends LinkedIn capacity and a
@@ -1750,8 +1768,10 @@ Before reporting completion, verify:
1750
1768
  - No sender lock or `outcome_unknown` result was retried.
1751
1769
  - A send was confirmed by a post-send read.
1752
1770
  - A post draft remained unpublished and unscheduled unless `publish:true` was
1753
- explicitly approved. Every scheduled post was read back with its exact UTC
1754
- instant, mention count, attachment count, first comment, and durable states.
1771
+ explicitly approved. The shared calendar was read first, its source-specific
1772
+ completeness was reported, and every visible scheduled post was read back
1773
+ with its exact UTC instant, source, and durable state. Native LinkedIn
1774
+ blindness was never represented as an empty calendar.
1755
1775
  - An export ended with `+ saved` and a non-empty CSV.
1756
1776
 
1757
1777
  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
- campaign-excluded portion as capacity campaigns cannot consume, never as a
81
- reserved or guaranteed manual allowance. Its counts cover SignalDash
82
- attempts, not all native LinkedIn activity.
83
- - Keep normal messaging at human volume. Use the cap reported in each hosted
84
- response; LinkedIn messages, invitations, invitation maintenance, and post
85
- publishing share the stable sender's daily action budget.
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.