social-autoposter 1.6.150 → 1.6.152

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/mcp/dist/index.js CHANGED
@@ -2077,7 +2077,7 @@ async function autopilotLoaded() {
2077
2077
  // fires every minute, claims ONE job, runs the pipeline's own prompt as its
2078
2078
  // Claude turn, writes the result back, and stops.
2079
2079
  // ===========================================================================
2080
- const QUEUE_WORKER_PROMPT_VERSION = 5; // v5: drop the per-run draft cap (v3/v4). The ~90s kills were NOT a drafting-time limit; they were unattended permission-prompt stalls on un-auto-approved Bash calls. Real fix = blanket Bash allow in queueWorkerAllowedTools so no prompt is ever emitted. Worker drafts the FULL set again.
2080
+ const QUEUE_WORKER_PROMPT_VERSION = 6; // v6: the ~90s kill is a host INACTIVITY watchdog (resets on each tool call), not a fixed cap. The worker was drafting all candidates in ONE silent turn (its HARD RULES even forbade log_draft.py), starving the clock -> killed mid-draft. v6 requires incremental draft-then-log per candidate and allows the log_draft.py persist commands, so per-candidate tool calls keep the session alive and the full set drains.
2081
2081
  const QUEUE_WORKER_PROMPT_MARKER = "saps_queue_worker_prompt_version";
2082
2082
  // One spec per worker task. queueType MUST match scripts/claude_job.py TAG_TO_TYPE.
2083
2083
  const QUEUE_WORKERS = [
@@ -2180,6 +2180,13 @@ function queueWorkerBody(spec) {
2180
2180
  const job = path.join(repoDir(), "scripts", "claude_job.py");
2181
2181
  const sd = sapsStateDir();
2182
2182
  const outDir = queueDir();
2183
+ // The Phase-2b "twitter-prep" worker drafts replies for several candidates. The
2184
+ // host kills an unattended scheduled session ~90s after its LAST tool call (an
2185
+ // inactivity watchdog). Drafting everything in one silent turn starves that clock
2186
+ // and the session is killed mid-draft having submitted nothing. So it MUST work
2187
+ // incrementally — draft + run log_draft.py per candidate — which persists drafts
2188
+ // (the pipeline already wants this) AND keeps the activity clock alive.
2189
+ const isDraft = spec.queueType === "twitter-prep";
2183
2190
  return [
2184
2191
  `You are the S4L "${spec.human}" queue worker. Run ONE iteration, then STOP.`,
2185
2192
  ``,
@@ -2189,6 +2196,23 @@ function queueWorkerBody(spec) {
2189
2196
  `You do this with Bash and Write, and NOTHING else. This run is unattended — ` +
2190
2197
  `reaching for any other tool, or trying to "investigate", STALLS it forever.`,
2191
2198
  ``,
2199
+ ...(isDraft
2200
+ ? [
2201
+ `PACING — CRITICAL: this unattended session is terminated ~90 seconds after ` +
2202
+ `your LAST tool call (a host inactivity timeout). The prompt below asks ` +
2203
+ `you to draft replies for SEVERAL candidates. Do NOT draft them all ` +
2204
+ `silently in one turn — that emits no tool calls, the clock expires, and ` +
2205
+ `you are KILLED mid-draft having submitted NOTHING. Work ONE candidate at ` +
2206
+ `a time: draft its reply, then IMMEDIATELY run that candidate's ` +
2207
+ `log_draft.py command exactly as the prompt's persist step (3a) specifies ` +
2208
+ `(a quick Bash call), THEN move to the next. Those per-candidate Bash ` +
2209
+ `calls are what keep the session alive. Begin the first candidate ` +
2210
+ `promptly — do NOT spend a long silent stretch analysing before your ` +
2211
+ `first tool call. Only after EVERY candidate is drafted and logged do you ` +
2212
+ `do step 3.`,
2213
+ ``,
2214
+ ]
2215
+ : []),
2192
2216
  `Steps:`,
2193
2217
  `1. Claim the next job. Run this EXACT Bash command:`,
2194
2218
  ` ${py} ${job} next --type ${spec.queueType} --prompt-file --state-dir ${sd}`,
@@ -2211,11 +2235,18 @@ function queueWorkerBody(spec) {
2211
2235
  `4. Report in ONE short line what you did, then STOP. Do NOT claim another job, ` +
2212
2236
  `do NOT loop, do NOT read other files, do NOT call any other tool.`,
2213
2237
  ``,
2214
- `HARD RULES: ONLY the Bash tool (to run claude_job.py), the Read tool (to read ` +
2215
- `the prompt/schema sidecar files), and the Write tool (to write the result ` +
2216
- `file). NEVER run any other shell command. NEVER edit, post, or touch anything ` +
2217
- `else. An empty queue is the NORMAL, expected case most minutes it is success, ` +
2218
- `not a problem to debug.`,
2238
+ isDraft
2239
+ ? `HARD RULES: use ONLY the Bash tool (to run claude_job.py AND the per-candidate ` +
2240
+ `log_draft.py persist commands the prompt tells you to run), the Read tool (the ` +
2241
+ `prompt/schema sidecar + the SKILL/config files the prompt names), and the Write ` +
2242
+ `tool (the result file). NEVER post, reply, edit a draft, open a browser, or run ` +
2243
+ `any command the prompt does not explicitly give you. An empty queue is the ` +
2244
+ `NORMAL, expected case most minutes — it is success, not a problem to debug.`
2245
+ : `HARD RULES: ONLY the Bash tool (to run claude_job.py), the Read tool (to read ` +
2246
+ `the prompt/schema sidecar files), and the Write tool (to write the result ` +
2247
+ `file). NEVER run any other shell command. NEVER edit, post, or touch anything ` +
2248
+ `else. An empty queue is the NORMAL, expected case most minutes — it is success, ` +
2249
+ `not a problem to debug.`,
2219
2250
  ].join("\n");
2220
2251
  }
2221
2252
  // Full canonical SKILL.md (frontmatter + body + version marker) the MCP writes
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.6.150",
3
- "installedAt": "2026-06-29T22:17:40.330Z"
2
+ "version": "1.6.152",
3
+ "installedAt": "2026-06-29T22:42:31.815Z"
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.150",
5
+ "version": "1.6.152",
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": {
package/mcp/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/social-autoposter-mcp",
3
- "version": "1.6.150",
3
+ "version": "1.6.152",
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.150",
3
+ "version": "1.6.152",
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
@@ -1,33 +0,0 @@
1
- import json, sys
2
- sys.path.insert(0, "scripts")
3
- import http_api
4
-
5
- resp = http_api.api_get("/api/v1/dms/pending")
6
- rows = (resp.get("data") or {}).get("pending") or []
7
- ids = [r["id"] for r in rows]
8
- out = []
9
- for dm_id in ids:
10
- r = http_api.api_get(f"/api/v1/dms/{dm_id}", ok_on_404=True)
11
- dm = (r.get("data") or {}).get("dm") or {}
12
- mr = http_api.api_get(f"/api/v1/dms/{dm_id}/messages")
13
- msgs = (mr.get("data") or {}).get("messages") or []
14
- hist = [{"d": m.get("direction"), "a": m.get("author"), "c": (m.get("content") or "")} for m in msgs]
15
- out.append({
16
- "id": dm_id,
17
- "platform": dm.get("platform"),
18
- "author": dm.get("their_author"),
19
- "surface": dm.get("surface"),
20
- "chat_url": dm.get("chat_url"),
21
- "tier": dm.get("tier"),
22
- "qual": dm.get("qualification_status"),
23
- "mode": dm.get("mode"),
24
- "target_project": dm.get("target_project"),
25
- "project_name": dm.get("project_name"),
26
- "interest": dm.get("interest_level"),
27
- "origin_public_comment_url": dm.get("origin_public_comment_url"),
28
- "our_prior_public_reply_url": dm.get("our_prior_public_reply_url"),
29
- "booking_sent": dm.get("booking_link_sent_at"),
30
- "msgcount": dm.get("message_count"),
31
- "hist": hist,
32
- })
33
- print(json.dumps(out, indent=1, ensure_ascii=False))