social-autoposter 1.6.151 → 1.6.153

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.
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.6.151",
3
- "installedAt": "2026-06-29T22:20:34.015Z"
2
+ "version": "1.6.153",
3
+ "installedAt": "2026-06-29T22:59:53.681Z"
4
4
  }
package/mcp/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "dxt_version": "0.1",
3
3
  "name": "social-autoposter",
4
4
  "display_name": "S4L",
5
- "version": "1.6.151",
5
+ "version": "1.6.153",
6
6
  "description": "Draft, review, approve, and autopilot X/Twitter posts.",
7
7
  "long_description": "The disclaimer above is generic Claude boilerplate. S4L is an open source product developed by Mediar.ai Incorporated, a VC-backed San Francisco-based startup.\n\nTo get started:\n\n1\\. Copy this prompt: **Set me up on S4L end to end**\n\n2\\. Quit fully with CMD+Q, restart Claude, and paste the prompt into a new chat.",
8
8
  "author": {
@@ -143,6 +143,31 @@ AUTOPILOT_RUNNING_STALL_SECONDS = 900
143
143
  # truth, scripts/schedule_state.py (FIRING_WINDOW there). _schedule_state delegates
144
144
  # to it, so it is intentionally NOT redefined here.
145
145
 
146
+ # How long the producer can sit narrating "drafting replies (Nm)" before we treat
147
+ # the draft as STUCK rather than healthy. The producer writes that label the whole
148
+ # time it blocks waiting for a worker to return a result (up to its 30-min queue
149
+ # timeout). A healthy drain clears in ~1-2 min; if the label has been "drafting"
150
+ # this long, the worker keeps dying mid-run (host inactivity-kill) or never claims,
151
+ # and nothing is draining — so we flip the menu bar from a reassuring spinner to
152
+ # ⚠ instead of letting the stale "drafting (8m)" lie persist. Well above any
153
+ # healthy single drain (the worker itself dies at ~2 min today).
154
+ DRAFT_STUCK_SECONDS = 300
155
+
156
+
157
+ def _label_elapsed_secs(label):
158
+ """Parse the trailing duration the producer encodes in a drafting activity
159
+ label — 'drafting replies (8m)', '... (queued 18m)', '... (45s)' — into
160
+ seconds. Returns 0 when there's no parseable duration. _fmt_dur (claude_job.py)
161
+ only ever emits '<n>s' (<60s) or '<n>m', so this mirror stays trivial."""
162
+ if not label:
163
+ return 0
164
+ import re
165
+ matches = re.findall(r"(\d+)\s*([sm])\b", str(label))
166
+ if not matches:
167
+ return 0
168
+ n, unit = matches[-1]
169
+ return int(n) * (60 if unit == "m" else 1)
170
+
146
171
 
147
172
  def _glyph(status):
148
173
  return GLYPH.get(status, "·")
@@ -1013,6 +1038,22 @@ class S4LMenuBar(rumps.App):
1013
1038
  attention = True
1014
1039
  else:
1015
1040
  self._stall_reason_info = ("", "")
1041
+ # Draft worker stuck/killed: the producer narrates "drafting replies (Nm)"
1042
+ # the whole time it blocks waiting for a worker to return a result, with NO
1043
+ # idea the worker died. A healthy drain clears in ~1-2 min; once that label
1044
+ # has been "drafting" past DRAFT_STUCK_SECONDS the worker keeps getting
1045
+ # killed mid-run (or never claims) and nothing is draining — flip to ⚠
1046
+ # instead of leaving the reassuring "drafting (8m)" spinner up. Skip when a
1047
+ # more specific cause (rate limit) already owns the reason.
1048
+ if setup_complete and self._stall_reason_info[0] != "rate_limited":
1049
+ _act = st.read_activity()
1050
+ if (
1051
+ _act
1052
+ and _act.get("state") == "drafting"
1053
+ and _label_elapsed_secs(_act.get("label")) >= DRAFT_STUCK_SECONDS
1054
+ ):
1055
+ attention = True
1056
+ self._stall_reason_info = ("draft_stuck", _act.get("label") or "")
1016
1057
  # Drop the stale "drafting" spinner while we need attention so the ⚠ shows.
1017
1058
  self._stalled = attention
1018
1059
 
@@ -1350,6 +1391,14 @@ class S4LMenuBar(rumps.App):
1350
1391
  items.append(self._label(
1351
1392
  " " + (self._stall_reason_info[1] or "wait for reset or switch account")
1352
1393
  ))
1394
+ elif self._stall_reason_info[0] == "draft_stuck":
1395
+ # Routines fire and the producer keeps narrating "drafting" but the
1396
+ # worker keeps getting killed mid-run / never returns a result. Don't
1397
+ # offer Re-arm (routines are fine); state the real problem.
1398
+ items.append(self._label("⚠ Draft not completing — worker keeps getting killed"))
1399
+ items.append(self._label(
1400
+ " " + (self._stall_reason_info[1] or "drafting") + " — no result yet"
1401
+ ))
1353
1402
  elif schedule_state == "disabled":
1354
1403
  items.append(self._label("⚠ Draft tasks are scheduled but disabled"))
1355
1404
  items.append(rumps.MenuItem("Set up draft schedule for this account", callback=self._rearm))
package/mcp/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/social-autoposter-mcp",
3
- "version": "1.6.151",
3
+ "version": "1.6.153",
4
4
  "private": true,
5
5
  "description": "Desktop MCP client for social-autoposter (X/Twitter rail): manual draft/review/approve loop, autopilot control, and stats. Thin wrapper over the existing pipeline scripts.",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "social-autoposter",
3
- "version": "1.6.151",
3
+ "version": "1.6.153",
4
4
  "description": "Automated social posting pipeline for Reddit, X/Twitter, LinkedIn, and Moltbook. Install as a Claude Code agent skill.",
5
5
  "bin": {
6
6
  "social-autoposter": "bin/cli.js"
@@ -1772,21 +1772,23 @@ log "Engagement style assigned: mode=$PICKED_MODE style=${PICKED_STYLE:-(invent)
1772
1772
  # The arm is stamped onto every post this cycle via SAPS_DRAFT_PROMPT_VARIANT
1773
1773
  # (read by twitter_post_plan.py -> log_post.py -> posts.draft_prompt_variant),
1774
1774
  # mirroring the tail_link_variant plumbing. Split tunable via
1775
- # TWITTER_DRAFT_PROMPT_AB_RATE (fraction assigned to 'treatment'). DEFAULT 0 =
1776
- # 100% control = experiment OFF (no behavior change anywhere until explicitly
1777
- # enabled, mirroring how the "written with ai" tag is off by default). Set it to
1778
- # e.g. 0.5 in .env to activate a 50/50 split. The dashboard reads the SAME var
1775
+ # TWITTER_DRAFT_PROMPT_AB_RATE = fraction of cycles assigned to 'treatment' (the
1776
+ # decoupled directive). CODE DEFAULT 1 = 100% treatment: a fresh plugin install /
1777
+ # new user with no override drafts in the DECOUPLED style by default (the old
1778
+ # concede->pivot 'control' is opt-in, not the default for customers). OUR own
1779
+ # install pins this to 0.5 in .env for a 50/50 holdback so we can measure
1780
+ # decoupled vs the old behavior on our account. The dashboard reads the SAME var
1779
1781
  # with the SAME default (bin/server.js), so display and routing never diverge.
1780
- DRAFT_PROMPT_AB_RATE="${TWITTER_DRAFT_PROMPT_AB_RATE:-0}"
1782
+ DRAFT_PROMPT_AB_RATE="${TWITTER_DRAFT_PROMPT_AB_RATE:-1}"
1781
1783
  SAPS_DRAFT_PROMPT_VARIANT=$(python3 -c "
1782
1784
  import random
1783
1785
  try:
1784
1786
  rate = float('$DRAFT_PROMPT_AB_RATE')
1785
1787
  except Exception:
1786
- rate = 0.0
1788
+ rate = 1.0
1787
1789
  rate = min(1.0, max(0.0, rate))
1788
1790
  print('treatment' if random.random() < rate else 'control')
1789
- " 2>/dev/null || echo control)
1791
+ " 2>/dev/null || echo treatment)
1790
1792
  export SAPS_DRAFT_PROMPT_VARIANT
1791
1793
  log "Draft-prompt A/B arm: $SAPS_DRAFT_PROMPT_VARIANT (rate=$DRAFT_PROMPT_AB_RATE)"
1792
1794
  if [ "$SAPS_DRAFT_PROMPT_VARIANT" = "treatment" ]; then