@m13v/s4l 1.7.6-rc.7 → 1.7.6-rc.9
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/version.json +2 -2
- package/mcp/manifest.json +1 -1
- package/mcp/menubar/s4l_menubar.py +27 -11
- package/mcp/package.json +1 -1
- package/package.json +1 -1
- package/scripts/cdp_ready_check.py +81 -50
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.6-rc.
|
|
5
|
+
"version": "1.7.6-rc.9",
|
|
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": {
|
|
@@ -2938,11 +2938,14 @@ class S4LMenuBar(rumps.App):
|
|
|
2938
2938
|
flight, and not snoozed (the snooze label and gate take precedence)."""
|
|
2939
2939
|
if pending_count <= 0 or self._review_active:
|
|
2940
2940
|
return 0.0
|
|
2941
|
-
#
|
|
2942
|
-
#
|
|
2943
|
-
#
|
|
2941
|
+
# Canvas never auto-pops (2026-07-17, user direction): the explicit
|
|
2942
|
+
# "Review N pending drafts" click is the ONLY way to open it. Report
|
|
2943
|
+
# the same indefinite hold as the cards-mode "Never" preset so the
|
|
2944
|
+
# menu shows the courtesy line. The reveal cadence itself remains a
|
|
2945
|
+
# cards-mode concept (its menu control is hidden in canvas mode,
|
|
2946
|
+
# 2026-07-16), so a stale cadence value is never consulted here.
|
|
2944
2947
|
if st.read_review_layout() != "cards":
|
|
2945
|
-
return
|
|
2948
|
+
return -1.0
|
|
2946
2949
|
now = time.time()
|
|
2947
2950
|
if now < self._review_snooze_until:
|
|
2948
2951
|
return 0.0
|
|
@@ -3063,12 +3066,20 @@ class S4LMenuBar(rumps.App):
|
|
|
3063
3066
|
# card keeps growing live) and skipped for focus=True (the menu's
|
|
3064
3067
|
# "Review N pending drafts" is an explicit ask). _last_review_sig is NOT
|
|
3065
3068
|
# stamped on a hold, so the same set still presents fresh when due.
|
|
3066
|
-
#
|
|
3067
|
-
#
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3069
|
+
# No panel is open past the _review_active branch above, so the
|
|
3070
|
+
# preference IS the layout that is about to present.
|
|
3071
|
+
if not focus:
|
|
3072
|
+
if st.read_review_layout() != "cards":
|
|
3073
|
+
# Canvas never auto-pops (2026-07-17, user direction): the
|
|
3074
|
+
# explicit "Review N pending drafts" click (focus=True) is the
|
|
3075
|
+
# ONLY way to open the canvas. The extend-open branch above
|
|
3076
|
+
# still grows an already-open canvas live; _last_review_sig is
|
|
3077
|
+
# NOT stamped here, so the whole backlog presents fresh on the
|
|
3078
|
+
# click.
|
|
3079
|
+
return
|
|
3080
|
+
# Cards mode: pace fresh pop-ups by the reveal cadence. The
|
|
3081
|
+
# cadence control is a cards-only concept (hidden while View Mode
|
|
3082
|
+
# is Canvas, 2026-07-16), so it is never consulted for canvas.
|
|
3072
3083
|
cadence = st.read_reveal_cadence()
|
|
3073
3084
|
if cadence < 0:
|
|
3074
3085
|
# "Never" preset: cards never auto-pop, only an explicit
|
|
@@ -4069,7 +4080,12 @@ class S4LMenuBar(rumps.App):
|
|
|
4069
4080
|
# now regardless.
|
|
4070
4081
|
hold_until = self._reveal_hold_until(pending_count)
|
|
4071
4082
|
if hold_until < 0:
|
|
4072
|
-
|
|
4083
|
+
if st.read_review_layout() == "cards":
|
|
4084
|
+
items.append(self._label("Cards hidden — see count in the menu bar"))
|
|
4085
|
+
else:
|
|
4086
|
+
# Canvas mode: it never auto-opens; the review item above
|
|
4087
|
+
# is the one way in.
|
|
4088
|
+
items.append(self._label("Canvas opens from the review item above"))
|
|
4073
4089
|
elif hold_until:
|
|
4074
4090
|
items.append(
|
|
4075
4091
|
self._label(
|
package/mcp/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m13v/s4l-mcp",
|
|
3
|
-
"version": "1.7.6-rc.
|
|
3
|
+
"version": "1.7.6-rc.9",
|
|
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,44 +1,61 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""Raw-websocket CDP readiness probe for the harness Chrome.
|
|
3
3
|
|
|
4
|
-
Exit 0 when a
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
downstream attach then eats Playwright's 180s default timeout while holding
|
|
9
|
-
the browser lock (S4L-4H, Karol 2026-07-11; identical wedge locally 2026-07-09,
|
|
10
|
-
twice, same Chrome instance both times).
|
|
4
|
+
Exit 0 when a real DevTools websocket handshake completes against the given
|
|
5
|
+
CDP URL (browser-level connect + Browser.getVersion round-trip), 1 when it
|
|
6
|
+
does not. /json/version alone is a LIVENESS check: a Chrome whose ws accept
|
|
7
|
+
path died still passes it; this probe exercises the actual session dispatch.
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
HISTORY — why raw websocket and NOT Playwright (2026-07-17): the original
|
|
10
|
+
probe used sync_playwright().connect_over_cdp. Wedge-diag captures
|
|
11
|
+
(skill/logs/wedge-diag/, v3 discriminator) proved that during every observed
|
|
12
|
+
"wedge" the raw ws handshake completed in <5s while the Playwright connect
|
|
13
|
+
timed out at 20s — with Chrome's DevToolsHandlerThread idle in kevent64 the
|
|
14
|
+
whole time. The stall lives in Playwright's node driver under machine load,
|
|
15
|
+
NOT in Chrome; the Playwright-based verdict was executing a healthy browser
|
|
16
|
+
roughly hourly for days (every cdp_wedge relaunch 07-14..07-17). A probe must
|
|
17
|
+
not depend on machinery heavier than the thing it probes. The S4L-4H /
|
|
18
|
+
Karol-box incidents that motivated the deep probe are equally covered by the
|
|
19
|
+
ws round-trip below (and in hindsight may have been this same client-side
|
|
20
|
+
stall).
|
|
21
|
+
|
|
22
|
+
Also preserves the renderer-liveness sweep (2026-07-14): a tab whose RENDERER
|
|
23
|
+
crashed ("Aw, Snap") keeps its title/url in every listing while sitting dead;
|
|
24
|
+
probe each page with a trivial Runtime.evaluate and reload it IN PLACE on
|
|
25
|
+
failure — fresh renderer, no kill, no new window, no focus change.
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
(twitter-backend.sh writes it to skill/logs/cdp-health.json, which
|
|
16
|
-
memory_snapshot.py carries onto the per-minute heartbeat sample).
|
|
27
|
+
Usage: cdp_ready_check.py [CDP_URL] [TIMEOUT_MS]
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
liveness
|
|
29
|
+
Prints a one-line JSON verdict to stdout (same shape/keys as before; mode is
|
|
30
|
+
now "raw_ws"). Falls back to an HTTP-only probe when websocket-client is not
|
|
31
|
+
importable, so a bare python3 caller degrades to liveness instead of failing.
|
|
21
32
|
"""
|
|
22
33
|
import json
|
|
23
34
|
import sys
|
|
24
35
|
import time
|
|
36
|
+
import urllib.request
|
|
37
|
+
|
|
38
|
+
# ProxyHandler({}): loopback CDP must never route through a proxy. macOS
|
|
39
|
+
# system proxy settings leak into urllib's default opener, and a box-wide
|
|
40
|
+
# forwarder 403s 127.0.0.1 probes (2026-07-13 root cause of a "wedged
|
|
41
|
+
# Chrome" misdiagnosis).
|
|
42
|
+
_OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_json(url: str, timeout: float):
|
|
46
|
+
with _OPENER.open(url, timeout=timeout) as r:
|
|
47
|
+
return json.loads(r.read())
|
|
25
48
|
|
|
26
49
|
|
|
27
50
|
def main() -> int:
|
|
28
51
|
url = (sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:9555").rstrip("/")
|
|
29
|
-
|
|
52
|
+
timeout_s = (int(sys.argv[2]) if len(sys.argv) > 2 else 20000) / 1000.0
|
|
30
53
|
t0 = time.time()
|
|
31
54
|
try:
|
|
32
|
-
|
|
55
|
+
import websocket # websocket-client, present in the owned runtime venv
|
|
33
56
|
except Exception:
|
|
34
|
-
import urllib.request
|
|
35
57
|
try:
|
|
36
|
-
|
|
37
|
-
# macOS system proxy settings leak into urllib's default opener, and
|
|
38
|
-
# a box-wide forwarder 403s 127.0.0.1 probes (2026-07-13 root cause
|
|
39
|
-
# of the "wedged Chrome" misdiagnosis).
|
|
40
|
-
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
|
41
|
-
opener.open(f"{url}/json/version", timeout=3)
|
|
58
|
+
_OPENER.open(f"{url}/json/version", timeout=3)
|
|
42
59
|
print(json.dumps({"ready": True, "mode": "http-only"}))
|
|
43
60
|
return 0
|
|
44
61
|
except Exception as e:
|
|
@@ -46,35 +63,49 @@ def main() -> int:
|
|
|
46
63
|
"ready": False, "mode": "http-only", "error": str(e)[:120],
|
|
47
64
|
}))
|
|
48
65
|
return 1
|
|
66
|
+
|
|
67
|
+
def _ws_call(ws_url: str, method: str, timeout: float, **params):
|
|
68
|
+
# suppress_origin: Chrome 111+ rejects ws clients whose Origin header
|
|
69
|
+
# is not in --remote-allow-origins (same pattern as the tab parker).
|
|
70
|
+
ws = websocket.create_connection(ws_url, timeout=timeout, suppress_origin=True)
|
|
71
|
+
try:
|
|
72
|
+
ws.send(json.dumps({"id": 1, "method": method, "params": params}))
|
|
73
|
+
deadline = time.time() + timeout
|
|
74
|
+
while time.time() < deadline:
|
|
75
|
+
msg = json.loads(ws.recv())
|
|
76
|
+
if msg.get("id") == 1:
|
|
77
|
+
return msg
|
|
78
|
+
raise TimeoutError(f"no reply to {method}")
|
|
79
|
+
finally:
|
|
80
|
+
ws.close()
|
|
81
|
+
|
|
49
82
|
try:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
# Browser-level handshake: the readiness verdict.
|
|
84
|
+
info = _get_json(f"{url}/json/version", timeout=3)
|
|
85
|
+
_ws_call(info["webSocketDebuggerUrl"], "Browser.getVersion", timeout_s)
|
|
86
|
+
|
|
87
|
+
# Renderer-liveness sweep: revive crashed tabs in place, best-effort;
|
|
88
|
+
# revival must never fail the readiness verdict the wedge gate uses.
|
|
89
|
+
revived = 0
|
|
90
|
+
pages = []
|
|
91
|
+
try:
|
|
92
|
+
pages = [t for t in _get_json(f"{url}/json/list", timeout=3)
|
|
93
|
+
if t.get("type") == "page" and t.get("webSocketDebuggerUrl")]
|
|
94
|
+
for t in pages:
|
|
95
|
+
try:
|
|
96
|
+
_ws_call(t["webSocketDebuggerUrl"], "Runtime.evaluate",
|
|
97
|
+
4.0, expression="1")
|
|
98
|
+
except Exception:
|
|
66
99
|
try:
|
|
67
|
-
|
|
68
|
-
|
|
100
|
+
_ws_call(t["webSocketDebuggerUrl"], "Page.reload", 8.0)
|
|
101
|
+
revived += 1
|
|
69
102
|
except Exception:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
pass
|
|
75
|
-
browser.close()
|
|
103
|
+
pass
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
|
|
76
107
|
out = {
|
|
77
|
-
"ready": True, "mode": "
|
|
108
|
+
"ready": True, "mode": "raw_ws", "contexts": len(pages),
|
|
78
109
|
"elapsed_s": round(time.time() - t0, 2),
|
|
79
110
|
}
|
|
80
111
|
if revived:
|
|
@@ -83,7 +114,7 @@ def main() -> int:
|
|
|
83
114
|
return 0
|
|
84
115
|
except Exception as e:
|
|
85
116
|
print(json.dumps({
|
|
86
|
-
"ready": False, "mode": "
|
|
117
|
+
"ready": False, "mode": "raw_ws",
|
|
87
118
|
"elapsed_s": round(time.time() - t0, 2),
|
|
88
119
|
"error": str(e)[:200].replace("\n", " "),
|
|
89
120
|
}))
|