patchcord 0.6.47 → 0.6.48
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/.claude-plugin/plugin.json +1 -1
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/subscribe/SKILL.md +9 -3
- package/package.json +1 -1
- package/per-project-skills/jcode/subscribe/SKILL.md +15 -6
- package/scripts/lib/stall-signal.mjs +15 -2
- package/scripts/subscribe.mjs +13 -4
- package/skills/subscribe/SKILL.md +9 -3
package/agent-plugin/plugin.json
CHANGED
|
@@ -48,7 +48,7 @@ So, if you have no `Monitor`, **use `--stall-signal`. It was built for exactly
|
|
|
48
48
|
this and it is not optional here:**
|
|
49
49
|
|
|
50
50
|
```
|
|
51
|
-
patchcord subscribe --stall-signal
|
|
51
|
+
patchcord subscribe --stall-signal
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
run as a background command with your harness's stall wake set to **15
|
|
@@ -61,8 +61,14 @@ seconds** (jcode: `stall_wake_seconds: 15`).
|
|
|
61
61
|
because a message came in. It turns wake-on-silence into wake-on-message
|
|
62
62
|
using only the primitive your harness has.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
**Run it raw. Do not pipe it through `grep`.** Step 2's command filters
|
|
65
|
+
stdout down to `^PATCHCORD:` lines, and that filter removes the keepalives
|
|
66
|
+
this mode depends on. Your harness measures the stall on the OUTPUT of the
|
|
67
|
+
pipeline, so a filtered stream is silent all the time: the wake fires on
|
|
68
|
+
nothing every 15 s, and the deliberate quiet after a real message looks
|
|
69
|
+
exactly like idle. Filtering here does not merely add noise, it deletes the
|
|
70
|
+
signal. Ignore the `HEARTBEAT:` lines when you read the output; they exist
|
|
71
|
+
for the stall detector, not for you.
|
|
66
72
|
|
|
67
73
|
2. **The two numbers must agree.** Your stall wake MUST equal `--stall-signal`'s
|
|
68
74
|
quiet window. Both default to 15 s, so setting `15` and passing no inline
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchcord",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.48",
|
|
4
4
|
"description": "Cross-machine agent messaging for Claude Code and Codex",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json agent-plugin/plugin.json"
|
|
@@ -31,7 +31,7 @@ path.
|
|
|
31
31
|
|
|
32
32
|
```
|
|
33
33
|
Bash(
|
|
34
|
-
command: "patchcord subscribe --stall-signal
|
|
34
|
+
command: "patchcord subscribe --stall-signal",
|
|
35
35
|
run_in_background: true,
|
|
36
36
|
stall_wake_seconds: 15
|
|
37
37
|
)
|
|
@@ -46,10 +46,19 @@ path.
|
|
|
46
46
|
number predates `--stall-signal` and is shorter than nothing: it is
|
|
47
47
|
simply the wrong number for this mechanism, not a safer one.
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
your
|
|
52
|
-
the
|
|
49
|
+
**DO NOT PIPE THIS THROUGH `grep`.** Every other subscribe command in this
|
|
50
|
+
plugin filters stdout down to `^PATCHCORD:` lines, and doing that here
|
|
51
|
+
breaks the entire mechanism: your harness measures the stall on what comes
|
|
52
|
+
OUT of the pipeline, and `grep` removes exactly the `HEARTBEAT:` keepalives
|
|
53
|
+
that are supposed to keep the pipe looking alive. Filtered, the stream is
|
|
54
|
+
silent all the time, so the wake fires on nothing every 15 s and the quiet
|
|
55
|
+
window after a real message is indistinguishable from idle. That is the
|
|
56
|
+
noisy, useless wake loop `--stall-signal` exists to replace.
|
|
57
|
+
|
|
58
|
+
So the raw stream is what the background task must see. It carries
|
|
59
|
+
`HEARTBEAT: <timestamp>` every 5 s and `PATCHCORD: ...` on a real message.
|
|
60
|
+
Ignore the heartbeats when you read the output; they are for the stall
|
|
61
|
+
detector, not for you.
|
|
53
62
|
|
|
54
63
|
3. **Tell the user one line:** "Patchcord listener active — I'll check
|
|
55
64
|
regularly for new messages." Do not say "as messages arrive" — the
|
|
@@ -83,7 +92,7 @@ cannot tell which one happened from the wake alone:
|
|
|
83
92
|
|
|
84
93
|
```
|
|
85
94
|
Bash(
|
|
86
|
-
command: "patchcord subscribe --replace --stall-signal
|
|
95
|
+
command: "patchcord subscribe --replace --stall-signal",
|
|
87
96
|
run_in_background: true,
|
|
88
97
|
stall_wake_seconds: 15
|
|
89
98
|
)
|
|
@@ -21,8 +21,21 @@
|
|
|
21
21
|
// pidfile — all things subscribe.mjs does as soon as it runs.
|
|
22
22
|
//
|
|
23
23
|
// THE KEEPALIVE LINE ITSELF (written by subscribe.mjs, not here) starts with
|
|
24
|
-
// "HEARTBEAT:", which does not match `^PATCHCORD
|
|
25
|
-
//
|
|
24
|
+
// "HEARTBEAT:", which does not match `^PATCHCORD:`.
|
|
25
|
+
//
|
|
26
|
+
// THAT USED TO BE DESCRIBED HERE AS A CONVENIENCE — the grep every other
|
|
27
|
+
// subscribe skill uses "drops it for free". It is the opposite, and the
|
|
28
|
+
// mistake cost a working listener in production: the harness measures the
|
|
29
|
+
// stall on what comes OUT of the pipeline, so a `| grep '^PATCHCORD:'` in
|
|
30
|
+
// front of a --stall-signal listener deletes every keepalive before anything
|
|
31
|
+
// can see it. The stream is then silent all the time, the wake fires on
|
|
32
|
+
// nothing every stall window, and the deliberate quiet after a real message
|
|
33
|
+
// is indistinguishable from idle. Filtering does not add noise here, it
|
|
34
|
+
// removes the signal.
|
|
35
|
+
//
|
|
36
|
+
// So the keepalive prefix exists to be IGNORED BY A READER, not removed by a
|
|
37
|
+
// filter. --stall-signal must be run raw. Every skill that documents it is
|
|
38
|
+
// pinned to that by tests in tests/test-stall-signal.mjs.
|
|
26
39
|
|
|
27
40
|
export const STALL_SIGNAL_DEFAULTS = Object.freeze({
|
|
28
41
|
// Written to stdout on this interval while idle. Must stay well under
|
package/scripts/subscribe.mjs
CHANGED
|
@@ -53,10 +53,12 @@ const STALL_SIGNAL_MODE = STALL_SIGNAL_OPTS !== null;
|
|
|
53
53
|
if (STALL_SIGNAL_MODE && HERMES_MODE) {
|
|
54
54
|
die("--stall-signal and --hermes are mutually exclusive — each is its own wake mechanism, pick one");
|
|
55
55
|
}
|
|
56
|
-
// The keepalive line's own prefix, chosen so it can never collide with a
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
56
|
+
// The keepalive line's own prefix, chosen so it can never collide with a real
|
|
57
|
+
// notification. It must be IGNORED by whoever reads the output, and never
|
|
58
|
+
// FILTERED OUT of it: a `| grep '^PATCHCORD:'` in front of a --stall-signal
|
|
59
|
+
// listener removes exactly the lines that keep the pipe from looking idle,
|
|
60
|
+
// which turns wake-on-message back into wake-on-silence. See the header of
|
|
61
|
+
// scripts/lib/stall-signal.mjs.
|
|
60
62
|
const STALL_SIGNAL_KEEPALIVE_PREFIX = "HEARTBEAT:";
|
|
61
63
|
// Suppress keepalives until this timestamp (ms since epoch). 0 = never
|
|
62
64
|
// suppressed. Set by notify() on every real message.
|
|
@@ -502,6 +504,13 @@ async function run() {
|
|
|
502
504
|
`subscribe: stall-signal keepalive=${STALL_SIGNAL_OPTS.keepaliveMs}ms ` +
|
|
503
505
|
`quiet=${STALL_SIGNAL_OPTS.quietMs}ms stall=${STALL_SIGNAL_OPTS.stallMs}ms`
|
|
504
506
|
);
|
|
507
|
+
// Said out loud on every start, because the mistake is invisible once
|
|
508
|
+
// made: a filtered stream looks like a working listener that simply never
|
|
509
|
+
// wakes anyone, which is exactly what was reported from production.
|
|
510
|
+
logErr(
|
|
511
|
+
"subscribe: stall-signal writes HEARTBEAT: lines to stdout — do NOT pipe " +
|
|
512
|
+
"this through `grep '^PATCHCORD:'`, it deletes them and the wake stops working"
|
|
513
|
+
);
|
|
505
514
|
stallSignalTimer = setInterval(() => {
|
|
506
515
|
if (Date.now() >= stallSignalQuietUntil) {
|
|
507
516
|
process.stdout.write(`${STALL_SIGNAL_KEEPALIVE_PREFIX} ${new Date().toISOString()}\n`);
|
|
@@ -48,7 +48,7 @@ So, if you have no `Monitor`, **use `--stall-signal`. It was built for exactly
|
|
|
48
48
|
this and it is not optional here:**
|
|
49
49
|
|
|
50
50
|
```
|
|
51
|
-
patchcord subscribe --stall-signal
|
|
51
|
+
patchcord subscribe --stall-signal
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
run as a background command with your harness's stall wake set to **15
|
|
@@ -61,8 +61,14 @@ seconds** (jcode: `stall_wake_seconds: 15`).
|
|
|
61
61
|
because a message came in. It turns wake-on-silence into wake-on-message
|
|
62
62
|
using only the primitive your harness has.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
**Run it raw. Do not pipe it through `grep`.** Step 2's command filters
|
|
65
|
+
stdout down to `^PATCHCORD:` lines, and that filter removes the keepalives
|
|
66
|
+
this mode depends on. Your harness measures the stall on the OUTPUT of the
|
|
67
|
+
pipeline, so a filtered stream is silent all the time: the wake fires on
|
|
68
|
+
nothing every 15 s, and the deliberate quiet after a real message looks
|
|
69
|
+
exactly like idle. Filtering here does not merely add noise, it deletes the
|
|
70
|
+
signal. Ignore the `HEARTBEAT:` lines when you read the output; they exist
|
|
71
|
+
for the stall detector, not for you.
|
|
66
72
|
|
|
67
73
|
2. **The two numbers must agree.** Your stall wake MUST equal `--stall-signal`'s
|
|
68
74
|
quiet window. Both default to 15 s, so setting `15` and passing no inline
|