patchcord 0.6.37 → 0.6.39
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/package.json
CHANGED
package/scripts/check-inbox.sh
CHANGED
|
@@ -11,7 +11,7 @@ if [ -f "$HOOK_DIR/lib/inbox-guard.sh" ]; then
|
|
|
11
11
|
else
|
|
12
12
|
# Degrade to the pre-guard behaviour rather than dying: a partial install
|
|
13
13
|
# must still nudge, just without the identity hint or the loop breaker.
|
|
14
|
-
pc_inbox_reason() { printf '%s patchcord message(s) waiting
|
|
14
|
+
pc_inbox_reason() { printf '%s patchcord message(s) waiting — call inbox() and reply.' "$1"; }
|
|
15
15
|
pc_streak_ok() { return 0; }
|
|
16
16
|
pc_state_path() { printf ''; }
|
|
17
17
|
pc_streak_n() { printf '1'; }
|
|
@@ -75,7 +75,22 @@ fi
|
|
|
75
75
|
# Check inbox — one lightweight HTTP call
|
|
76
76
|
MACHINE_NAME=$(hostname -s 2>/dev/null || echo "unknown")
|
|
77
77
|
INSTALL_PATH=$(dirname "$MCP_JSON")
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
# PER-INVOCATION response file. This was a FIXED path (/tmp/patchcord_inbox.json)
|
|
80
|
+
# shared by every agent on the machine, and a host running several seats stops
|
|
81
|
+
# them at overlapping times: one hook's curl truncates and rewrites the file
|
|
82
|
+
# another hook is reading, and either one's `rm -f` deletes it underneath the
|
|
83
|
+
# other. The reader then hands a half-written body to jq, which is exactly the
|
|
84
|
+
# "Invalid numeric literal at line 1, column N" this produced on the Mac mini.
|
|
85
|
+
# A shared mutable temp path between concurrent agents is not a race that
|
|
86
|
+
# happens rarely; it is one that happens whenever the machine is busy.
|
|
87
|
+
RESP_FILE=$(mktemp "${TMPDIR:-/tmp}/patchcord_inbox.XXXXXX" 2>/dev/null || echo "")
|
|
88
|
+
[ -z "$RESP_FILE" ] && exit 0
|
|
89
|
+
# Remove it on ANY exit path, including the early returns below, so a crash
|
|
90
|
+
# cannot leave per-invocation files accumulating in /tmp.
|
|
91
|
+
trap 'rm -f "$RESP_FILE"' EXIT
|
|
92
|
+
|
|
93
|
+
HTTP_CODE=$(curl -s -o "$RESP_FILE" -w "%{http_code}" --max-time 5 \
|
|
79
94
|
-H "Authorization: Bearer ${TOKEN}" \
|
|
80
95
|
-H "x-patchcord-machine: ${MACHINE_NAME}" \
|
|
81
96
|
-H "x-patchcord-install-path: ${INSTALL_PATH}" \
|
|
@@ -86,18 +101,25 @@ if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
|
|
|
86
101
|
"decision": "block",
|
|
87
102
|
"reason": "PATCHCORD AUTH FAILED: token rejected by server (HTTP '"$HTTP_CODE"'). Check your token in .mcp.json — it may be wrong, expired, or not yet registered on the server."
|
|
88
103
|
}'
|
|
89
|
-
rm -f /tmp/patchcord_inbox.json
|
|
90
104
|
exit 0
|
|
91
105
|
fi
|
|
92
106
|
|
|
93
107
|
if [ "$HTTP_CODE" = "000" ]; then
|
|
94
108
|
# Server unreachable — skip silently
|
|
95
|
-
rm -f /tmp/patchcord_inbox.json
|
|
96
109
|
exit 0
|
|
97
110
|
fi
|
|
98
111
|
|
|
99
|
-
RESPONSE=$(cat
|
|
100
|
-
|
|
112
|
+
RESPONSE=$(cat "$RESP_FILE" 2>/dev/null || echo "")
|
|
113
|
+
|
|
114
|
+
# "COULD NOT TELL" IS NOT "NOTHING PENDING". Below, an unparseable body used to
|
|
115
|
+
# fall through jq's `// 0` default to COUNT=0, which takes the same exit path as
|
|
116
|
+
# a genuinely empty inbox AND clears the streak state. So a corrupted response
|
|
117
|
+
# told the agent it had no messages, silently, with no way to tell the two apart
|
|
118
|
+
# — the same absence-reads-as-a-value shape that cost this project a night.
|
|
119
|
+
# Refuse to answer instead: leave the streak state alone and say nothing.
|
|
120
|
+
if ! printf '%s' "$RESPONSE" | jq -e 'type == "object"' >/dev/null 2>&1; then
|
|
121
|
+
exit 0
|
|
122
|
+
fi
|
|
101
123
|
|
|
102
124
|
# ── Auto-apply custom skill from web console ──────────────────
|
|
103
125
|
# Writes to .claude/skills/patchcord-custom/SKILL.md — Claude Code
|
|
@@ -142,7 +164,14 @@ if [ "$COUNT" -eq 0 ]; then
|
|
|
142
164
|
exit 0
|
|
143
165
|
fi
|
|
144
166
|
|
|
145
|
-
|
|
167
|
+
# PER-IDENTITY lock. This was a single global path, so on a host running several
|
|
168
|
+
# seats one agent's notification suppressed EVERY other agent's for 5s — an
|
|
169
|
+
# agent with real pending messages could be silenced by an unrelated seat that
|
|
170
|
+
# happened to stop first. The dedupe is meant to stop ONE identity being told
|
|
171
|
+
# twice by the Stop and Notification hooks, never to stop a second identity
|
|
172
|
+
# being told at all.
|
|
173
|
+
NOTIFY_LOCK=$(pc_state_path "patchcord_notify_lock" "$NAMESPACE" "$AGENT_ID" 2>/dev/null || echo "")
|
|
174
|
+
[ -z "$NOTIFY_LOCK" ] && NOTIFY_LOCK="${TMPDIR:-/tmp}/patchcord_notify_lock_$(printf '%s' "${NAMESPACE}/${AGENT_ID}" | tr -c 'a-zA-Z0-9' '_')"
|
|
146
175
|
LOCK_AGE=5
|
|
147
176
|
if [ -f "$NOTIFY_LOCK" ]; then
|
|
148
177
|
LOCK_MTIME=$(stat -c %Y "$NOTIFY_LOCK" 2>/dev/null || stat -f %m "$NOTIFY_LOCK" 2>/dev/null || echo "0")
|
|
@@ -17,7 +17,7 @@ elif [ -f "$HOOK_DIR/patchcord-inbox-guard.sh" ]; then
|
|
|
17
17
|
# shellcheck source=lib/inbox-guard.sh
|
|
18
18
|
. "$HOOK_DIR/patchcord-inbox-guard.sh"
|
|
19
19
|
else
|
|
20
|
-
pc_inbox_reason() { printf '%s patchcord message(s) waiting
|
|
20
|
+
pc_inbox_reason() { printf '%s patchcord message(s) waiting — call inbox() and reply.' "$1"; }
|
|
21
21
|
pc_streak_ok() { return 0; }
|
|
22
22
|
pc_state_path() { printf ''; }
|
|
23
23
|
pc_streak_n() { printf '1'; }
|
|
@@ -147,10 +147,15 @@ echo "$NOW $(( STREAK + 1 ))" > "$STATE_FILE"
|
|
|
147
147
|
NAMESPACE=$(echo "$RESPONSE" | jq -r '.namespace_id // empty' 2>/dev/null || true)
|
|
148
148
|
AGENT_ID=$(echo "$RESPONSE" | jq -r '.agent_id // empty' 2>/dev/null || true)
|
|
149
149
|
|
|
150
|
+
# The identity belongs in the FIRST sentence, not appended after it. Tacked on
|
|
151
|
+
# the end it reads as an afterthought and the reader has already moved on.
|
|
152
|
+
WHO=""
|
|
153
|
+
[ -n "$NAMESPACE" ] && [ -n "$AGENT_ID" ] && WHO=" for ${AGENT_ID}@${NAMESPACE}"
|
|
154
|
+
|
|
150
155
|
if [ "$COUNT" -eq 1 ]; then
|
|
151
|
-
MSG="Patchcord: 1 pending message
|
|
156
|
+
MSG="Patchcord: 1 pending message${WHO} — call the patchcord inbox tool, do what it asks, then reply with what you did."
|
|
152
157
|
else
|
|
153
|
-
MSG="Patchcord: ${COUNT} pending messages
|
|
158
|
+
MSG="Patchcord: ${COUNT} pending messages${WHO} — call the patchcord inbox tool, do what each asks, then reply to each with what you did."
|
|
154
159
|
fi
|
|
155
160
|
|
|
156
161
|
# Name the identity the count belongs to. This hook reads the token from
|
|
@@ -160,7 +165,8 @@ fi
|
|
|
160
165
|
# the old one — a contradiction neither side can see alone. See
|
|
161
166
|
# lib/inbox-guard.sh.
|
|
162
167
|
if [ -n "$NAMESPACE" ] && [ -n "$AGENT_ID" ]; then
|
|
163
|
-
MSG="${MSG}
|
|
168
|
+
MSG="${MSG}
|
|
169
|
+
If the inbox tool shows a different agent or 0 pending: the token on disk changed after this session started, so this session is signed in as someone else. Do not retry — tell the user and ask them to restart the session."
|
|
164
170
|
fi
|
|
165
171
|
|
|
166
172
|
jq -n --arg msg "$MSG" '{followup_message: $msg}'
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
# The nudge text. Names the identity so a session authenticated as a different
|
|
26
26
|
# agent can recognise the mismatch.
|
|
27
27
|
#
|
|
28
|
+
# BOTH BRANCHES ARE READ BY A HUMAN. They are printed in the transcript, so
|
|
29
|
+
# every word is read by a person as well as by an agent: lead with the action,
|
|
30
|
+
# keep the diagnosis to one sentence, and never emit "message(s)" — singular and
|
|
31
|
+
# plural are already computed one line below.
|
|
32
|
+
#
|
|
28
33
|
# THE STALE-IDENTITY EXPLANATION IS ONLY EMITTED ONCE THE SYMPTOM HAS ACTUALLY
|
|
29
34
|
# OCCURRED. It used to be appended to EVERY nudge, fleet-wide, which was wrong
|
|
30
35
|
# for three reasons:
|
|
@@ -53,28 +58,31 @@
|
|
|
53
58
|
pc_inbox_reason() {
|
|
54
59
|
local count="$1" ns="$2" agent="$3" call="${4:-inbox()}" hint="${5:-restart this session}"
|
|
55
60
|
local streak="${6:-1}"
|
|
56
|
-
local noun="
|
|
61
|
+
local noun="messages"
|
|
57
62
|
[ "$count" = "1" ] && noun="message"
|
|
58
63
|
case "$streak" in ''|*[!0-9]*) streak=1 ;; esac
|
|
59
64
|
|
|
60
65
|
if [ -z "$ns" ] || [ -z "$agent" ]; then
|
|
61
|
-
printf '%s patchcord %s waiting
|
|
66
|
+
printf '%s patchcord %s waiting — call %s and reply.' \
|
|
62
67
|
"$count" "$noun" "$call"
|
|
63
68
|
return
|
|
64
69
|
fi
|
|
65
70
|
|
|
66
71
|
if [ "$streak" -lt 2 ]; then
|
|
67
|
-
printf '%s patchcord %s
|
|
72
|
+
printf '%s patchcord %s for %s@%s — call %s and reply.' \
|
|
68
73
|
"$count" "$noun" "$agent" "$ns" "$call"
|
|
69
74
|
return
|
|
70
75
|
fi
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
# Second consecutive identical block: the nudge did NOT clear, which is the
|
|
78
|
+
# actual evidence of the stale-token condition. Even here the action comes
|
|
79
|
+
# first and the diagnosis is one sentence on its own line — a repeat is a
|
|
80
|
+
# worse moment to make someone parse a paragraph, not a better one.
|
|
81
|
+
printf 'Still %s patchcord %s for %s@%s — %s did not clear it. Call %s once more.
|
|
82
|
+
If it shows a different agent or 0 pending: the token on disk changed after this session started, so this session is signed in as someone else. Do not retry — tell the user and ask them to %s.' \
|
|
73
83
|
"$count" "$noun" "$agent" "$ns" \
|
|
74
|
-
"$call" \
|
|
75
|
-
"$
|
|
76
|
-
"$agent" "$ns" \
|
|
77
|
-
"$call" "$hint"
|
|
84
|
+
"$call" "$call" \
|
|
85
|
+
"$hint"
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
# pc_streak_n <state_file>
|