@m13v/s4l 1.7.5-rc.44 → 1.7.5-rc.45
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 +51 -3
- package/mcp/dist/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/package.json +1 -1
- package/package.json +1 -1
package/mcp/dist/index.js
CHANGED
|
@@ -1011,6 +1011,27 @@ async function postApproved(batchId, plan) {
|
|
|
1011
1011
|
plan = fresh;
|
|
1012
1012
|
}
|
|
1013
1013
|
}
|
|
1014
|
+
// Arm the cross-instance posting flag HERE, before the Reddit drain, not just
|
|
1015
|
+
// before the Twitter phase below. Root cause (found 2026-07-16): this flag
|
|
1016
|
+
// used to get set only right before ensureTwitterBrowserForPost(), AFTER the
|
|
1017
|
+
// whole Reddit drain loop above (one runPython("post_reddit.py") call per
|
|
1018
|
+
// approved Reddit card, each with real CDP/browser overhead — a batch of a
|
|
1019
|
+
// handful of cards routinely runs minutes). For that entire window this
|
|
1020
|
+
// instance's isPeerDrainActive() reported false, so a SECOND MCP instance's
|
|
1021
|
+
// postApproved() (e.g. a fresh one-shot worker boot running its own startup
|
|
1022
|
+
// backlog drain) could pass the SAME gate check above and start its own
|
|
1023
|
+
// drain concurrently. Both eventually reached their own Twitter phase and
|
|
1024
|
+
// both spawned scripts/twitter_post_plan.py with S4L_LOCK_ROLE=post — the
|
|
1025
|
+
// ONLY code path that ever sets that role. Two live "post" role holders
|
|
1026
|
+
// can't preempt each other (browser_mutex.py case 6: correct, preempting a
|
|
1027
|
+
// peer mid-post risks a double-post), so one always waited 45s and gave up
|
|
1028
|
+
// — the "Twitter browser locked by session python:<pid> ... waited 45s,
|
|
1029
|
+
// giving up" failures that silently killed 11 real approved drafts over
|
|
1030
|
+
// 2026-07-15/16. Arming the flag for the FULL function closes that window:
|
|
1031
|
+
// a peer's gate check now blocks for the entire drain, Reddit included, so
|
|
1032
|
+
// only one instance ever reaches the Twitter phase at a time.
|
|
1033
|
+
postingActive = true;
|
|
1034
|
+
startPostingFlagHeartbeat();
|
|
1014
1035
|
// Post every card the user APPROVED that hasn't already landed or been ruled out.
|
|
1015
1036
|
// `approved` is now a DURABLE decision (sticky, never cleared by a later call), so
|
|
1016
1037
|
// filtering out posted/terminal here makes this idempotent: re-running it only
|
|
@@ -1029,8 +1050,11 @@ async function postApproved(batchId, plan) {
|
|
|
1029
1050
|
delete c.discard_reason;
|
|
1030
1051
|
}
|
|
1031
1052
|
}
|
|
1032
|
-
if (approved.length === 0)
|
|
1053
|
+
if (approved.length === 0) {
|
|
1054
|
+
postingActive = false;
|
|
1055
|
+
stopPostingFlagHeartbeat();
|
|
1033
1056
|
return { attempted: 0, exit_code: 0, summary: "nothing approved" };
|
|
1057
|
+
}
|
|
1034
1058
|
// ---- Reddit cards (2026-07-14): drain them FIRST, independently ----------
|
|
1035
1059
|
// A reddit card carries the verbatim draft decision (reddit_decision) plus
|
|
1036
1060
|
// plan metadata (reddit_plan_meta), so approval reconstructs a one-decision
|
|
@@ -1204,7 +1228,10 @@ async function postApproved(batchId, plan) {
|
|
|
1204
1228
|
}
|
|
1205
1229
|
if (approvedTwitter.length === 0) {
|
|
1206
1230
|
// All-reddit batch: persist the stamps and return without touching the
|
|
1207
|
-
// twitter preflight/lock path.
|
|
1231
|
+
// twitter preflight/lock path. Must clear the flag armed above ourselves —
|
|
1232
|
+
// this branch never reaches the Twitter phase's scheduleShellLockRelease().
|
|
1233
|
+
postingActive = false;
|
|
1234
|
+
stopPostingFlagHeartbeat();
|
|
1208
1235
|
if (redditSkippedPeerDrain) {
|
|
1209
1236
|
return {
|
|
1210
1237
|
attempted: 0,
|
|
@@ -1230,6 +1257,8 @@ async function postApproved(batchId, plan) {
|
|
|
1230
1257
|
if (!readConfiguredTwitterHandle())
|
|
1231
1258
|
await ensurePostingHandle();
|
|
1232
1259
|
if (!readConfiguredTwitterHandle()) {
|
|
1260
|
+
postingActive = false;
|
|
1261
|
+
stopPostingFlagHeartbeat();
|
|
1233
1262
|
return {
|
|
1234
1263
|
attempted: 0,
|
|
1235
1264
|
exit_code: 0,
|
|
@@ -2863,7 +2892,22 @@ tool("post_drafts", {
|
|
|
2863
2892
|
warnings,
|
|
2864
2893
|
});
|
|
2865
2894
|
}
|
|
2866
|
-
|
|
2895
|
+
// postApproved now arms the cross-instance posting flag before the Reddit
|
|
2896
|
+
// drain (see its own comment), not just before the Twitter phase. Every
|
|
2897
|
+
// op inside that drain is already individually best-effort try/caught, so
|
|
2898
|
+
// this is belt-and-suspenders: if something still throws past all of
|
|
2899
|
+
// that, clear the flag before it escapes rather than leave this MCP
|
|
2900
|
+
// instance's posting permanently gated behind a flag nothing will ever
|
|
2901
|
+
// reset.
|
|
2902
|
+
let result;
|
|
2903
|
+
try {
|
|
2904
|
+
result = await postApproved(batch_id, plan);
|
|
2905
|
+
}
|
|
2906
|
+
catch (e) {
|
|
2907
|
+
postingActive = false;
|
|
2908
|
+
stopPostingFlagHeartbeat();
|
|
2909
|
+
throw e;
|
|
2910
|
+
}
|
|
2867
2911
|
// Report the REAL posted count from the pipeline, not the approved count.
|
|
2868
2912
|
// A run can approve N yet land 0 (browser/session failure); reporting
|
|
2869
2913
|
// approve.size here told the agent "posted: N" on a total failure.
|
|
@@ -5658,6 +5702,10 @@ async function drainApprovedBacklog() {
|
|
|
5658
5702
|
}
|
|
5659
5703
|
catch (e) {
|
|
5660
5704
|
console.error("[post] drainApprovedBacklog error:", e?.message || e);
|
|
5705
|
+
// Same reasoning as the other postApproved call site: don't let an
|
|
5706
|
+
// escaped exception leave the cross-instance posting flag stuck true.
|
|
5707
|
+
postingActive = false;
|
|
5708
|
+
stopPostingFlagHeartbeat();
|
|
5661
5709
|
}
|
|
5662
5710
|
}
|
|
5663
5711
|
async function main() {
|
package/mcp/dist/version.json
CHANGED
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.7.5-rc.
|
|
5
|
+
"version": "1.7.5-rc.45",
|
|
6
6
|
"description": "Draft, review, approve, and autopilot X/Twitter posts.",
|
|
7
7
|
"long_description": "## **⚠️ The disclaimer above is generic Claude boilerplate.** Anthropic shows the same warning on every plugin regardless of what it does; any plugin has the same level of access as any app you download from the internet.\n\nS4L 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 plugin end to end**\n\n2\\. Quit with CMD+Q, reopen Claude, paste into a new chat.\n\nWhat happens next:\n\n* About every 5 minutes S4L scans X for posts that match your topics and drafts replies in your voice.\n* Drafts show up as review cards, usually the first within a few minutes. Nothing is posted automatically; you approve each one.\n* Posting autopilot stays off until you explicitly turn it on.",
|
|
8
8
|
"author": {
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/s4l-mcp",
|
|
3
|
-
"version": "1.7.5-rc.
|
|
3
|
+
"version": "1.7.5-rc.45",
|
|
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