social-autoposter 1.6.93 → 1.6.94

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
@@ -655,6 +655,7 @@ async function postApproved(batchId, plan) {
655
655
  // the post for the browser. Reset is guaranteed by scheduleShellLockRelease()
656
656
  // in the finally below, so an early/failed post can't wedge scanning.
657
657
  postingActive = true;
658
+ startPostingFlagHeartbeat(); // cross-instance: a sibling MCP's scan defers too
658
659
  // Posting is a priority over scanning: abort any in-flight plugin scan so the
659
660
  // approved post takes the browser immediately instead of waiting on the lock.
660
661
  // Plugin pipeline only — never affects the plist autopilot.
@@ -2020,6 +2021,64 @@ function sapsStateDir() {
2020
2021
  return (process.env.SAPS_STATE_DIR ||
2021
2022
  path.join(process.env.HOME || os.homedir(), ".social-autoposter-mcp"));
2022
2023
  }
2024
+ // ---- Cross-instance "posting active" flag ----------------------------------
2025
+ // posting-active.json in the shared state dir is the CROSS-MCP-INSTANCE version
2026
+ // of the in-process `postingActive` flag. The autopilot scan and the post
2027
+ // sometimes run in the SAME MCP (the in-process flag covers that) and sometimes
2028
+ // in TWO SEPARATE MCP instances (different agent sessions each spawn their own).
2029
+ // A file every instance's scan_candidates reads makes the mutual exclusion hold
2030
+ // regardless of which topology Claude Desktop happens to use. Heartbeat'd with a
2031
+ // short TTL so a crashed poster's flag self-clears and never wedges scanning.
2032
+ const POSTING_FLAG_TTL_MS = 45_000;
2033
+ let postingFlagHeartbeat = null;
2034
+ function postingFlagPath() {
2035
+ return path.join(sapsStateDir(), "posting-active.json");
2036
+ }
2037
+ function writePostingFlag() {
2038
+ try {
2039
+ fs.mkdirSync(sapsStateDir(), { recursive: true });
2040
+ fs.writeFileSync(postingFlagPath(), JSON.stringify({ pid: process.pid, expires_at: Date.now() + POSTING_FLAG_TTL_MS }) + "\n", "utf-8");
2041
+ }
2042
+ catch {
2043
+ /* best effort */
2044
+ }
2045
+ }
2046
+ function startPostingFlagHeartbeat() {
2047
+ writePostingFlag();
2048
+ if (postingFlagHeartbeat)
2049
+ return;
2050
+ // Refresh well within the TTL so a long batch stays flagged, but a dead poster
2051
+ // expires within POSTING_FLAG_TTL_MS.
2052
+ postingFlagHeartbeat = setInterval(() => {
2053
+ if (postingActive)
2054
+ writePostingFlag();
2055
+ }, Math.floor(POSTING_FLAG_TTL_MS / 2));
2056
+ if (typeof postingFlagHeartbeat.unref === "function")
2057
+ postingFlagHeartbeat.unref();
2058
+ }
2059
+ function stopPostingFlagHeartbeat() {
2060
+ if (postingFlagHeartbeat) {
2061
+ clearInterval(postingFlagHeartbeat);
2062
+ postingFlagHeartbeat = null;
2063
+ }
2064
+ try {
2065
+ fs.rmSync(postingFlagPath(), { force: true });
2066
+ }
2067
+ catch {
2068
+ /* best effort */
2069
+ }
2070
+ }
2071
+ // True when ANY MCP instance has a FRESH posting flag on disk. Absent or expired
2072
+ // == not posting. This is what makes a sibling instance's scan_candidates defer.
2073
+ function isPostingFlagFresh() {
2074
+ try {
2075
+ const j = JSON.parse(fs.readFileSync(postingFlagPath(), "utf-8"));
2076
+ return typeof j?.expires_at === "number" && j.expires_at > Date.now();
2077
+ }
2078
+ catch {
2079
+ return false;
2080
+ }
2081
+ }
2023
2082
  // activity.json: a tiny "what's running right now" signal the menu bar reads to
2024
2083
  // show a loading spinner + label (scanning / drafting / posting / …). Written by
2025
2084
  // long-running tools, cleared when they finish. Best-effort; absence == idle.
@@ -2327,6 +2386,7 @@ function scheduleShellLockRelease() {
2327
2386
  shellLockReleaseTimer = setTimeout(() => {
2328
2387
  shellLockReleaseTimer = null;
2329
2388
  postingActive = false; // posting drained -> the autopilot may scan again
2389
+ stopPostingFlagHeartbeat(); // clear the cross-instance flag too
2330
2390
  releaseShellBrowserLock();
2331
2391
  }, SHELL_LOCK_GRACE_MS);
2332
2392
  }
@@ -2573,7 +2633,7 @@ tool("scan_candidates", {
2573
2633
  // post are children of THIS MCP, so this in-process gate is exact — the
2574
2634
  // autopilot never even starts a scan to interrupt a post. Posting always wins;
2575
2635
  // the autopilot just re-calls and runs once the batch drains.
2576
- if (postingActive)
2636
+ if (postingActive || isPostingFlagFresh())
2577
2637
  return scanDeferredForPost();
2578
2638
  // Long-poll the single in-flight scan job (see the ScanJob registry above).
2579
2639
  // A finished-but-unconsumed job: hand back its result and clear the slot so a
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.6.93",
3
- "installedAt": "2026-06-23T20:32:39.303Z"
2
+ "version": "1.6.94",
3
+ "installedAt": "2026-06-24T00:11:08.740Z"
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.93",
5
+ "version": "1.6.94",
6
6
  "description": "Draft, review, approve, and autopilot X/Twitter posts. Thin desktop client over the S4L pipeline.",
7
7
  "long_description": "A guided assistant that drafts, reviews, and autopilots X/Twitter posts.\nTo get started:\n1. Click **Configure** and set every tool permission to **Always Allow**.\n2. Copy this prompt: **Set me up on S4L end to end**.\n3. 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.93",
3
+ "version": "1.6.94",
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.93",
3
+ "version": "1.6.94",
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"