patchcord 0.6.38 → 0.6.40
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/bin/patchcord.mjs
CHANGED
|
@@ -789,8 +789,23 @@ if (cmd === "whoami") {
|
|
|
789
789
|
// provisioned". Say explicitly that identity is fine.
|
|
790
790
|
console.log(`self-description: (not set — optional; your identity ${json.agent}@${json.namespace} is fully provisioned. Set one with: patchcord whoami --propose "<text>")`);
|
|
791
791
|
}
|
|
792
|
-
|
|
793
|
-
|
|
792
|
+
// TTY-ONLY, AND THAT IS THE WHOLE POINT.
|
|
793
|
+
//
|
|
794
|
+
// This tip is addressed to a human reading a terminal. It was printed
|
|
795
|
+
// unconditionally, which meant it also landed in the stdout an AGENT captures
|
|
796
|
+
// from a tool call — and to a model, a suggestion in a tool result is
|
|
797
|
+
// indistinguishable from an instruction. An agy session's own record shows the
|
|
798
|
+
// consequence exactly: 6 `patchcord whoami` calls, 6 `patchcord agents` calls,
|
|
799
|
+
// paired one-to-one, each `agents` immediately following the tip in the
|
|
800
|
+
// previous command's output. Nothing in any skill or config asked for it. We
|
|
801
|
+
// did, in our own stdout, every single time.
|
|
802
|
+
//
|
|
803
|
+
// A tip is help. An unrequested command is work. The difference is entirely in
|
|
804
|
+
// who is reading, and `isTTY` is the one place that difference is knowable.
|
|
805
|
+
if (process.stdout.isTTY) {
|
|
806
|
+
console.log();
|
|
807
|
+
console.log(`tip: \`patchcord agents\` returns whoami for all peers.`);
|
|
808
|
+
}
|
|
794
809
|
for (const w of warnings) {
|
|
795
810
|
// stderr, matching the global-scope warning above. The line is addressed
|
|
796
811
|
// to the AGENT and says to relay it, because the human has no reason to
|
|
@@ -1432,10 +1447,38 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1432
1447
|
// Antigravity CLI: PROJECT-scoped config at .agents/mcp_config.json (one
|
|
1433
1448
|
// project = one namespace = one agent). Remote HTTP uses `serverUrl`.
|
|
1434
1449
|
const adir = join(dir, ".agents"); mkdirSync(adir, { recursive: true });
|
|
1435
|
-
|
|
1450
|
+
const agWritten = writeJson(join(adir, "mcp_config.json"), (o) => {
|
|
1436
1451
|
o.mcpServers = o.mcpServers || {};
|
|
1437
1452
|
o.mcpServers.patchcord = { serverUrl: `${baseUrl}/mcp`, headers: hdr };
|
|
1438
1453
|
});
|
|
1454
|
+
// THE SKILL IS A GUARD, NOT DOCUMENTATION, AND IT WAS MISSING HERE.
|
|
1455
|
+
//
|
|
1456
|
+
// The interactive installer copies these; this path did not, so every seat
|
|
1457
|
+
// provisioned by `patchcord provision --tool agy` — which is how a
|
|
1458
|
+
// mux-deployed Team is built — got the credential and none of the rules.
|
|
1459
|
+
// Three live agy seats were checked: one had only patchcord-wait, two had
|
|
1460
|
+
// no skills at all.
|
|
1461
|
+
//
|
|
1462
|
+
// What goes missing is specifically the line "Do not run the `patchcord`
|
|
1463
|
+
// CLI (whoami, inbox, recall, HTTP calls)". Without it, a seat whose MCP
|
|
1464
|
+
// has not loaded reaches for the CLI instead — and one that did wrote its
|
|
1465
|
+
// own send_patchcord.js and ran it eight times. The skill exists, is
|
|
1466
|
+
// correctly worded, and was installed by one provisioning path out of two,
|
|
1467
|
+
// which is the same as not having it for anyone who arrives by the other.
|
|
1468
|
+
//
|
|
1469
|
+
// Copied the same way hermes is above, which is why the pattern was
|
|
1470
|
+
// already here to notice.
|
|
1471
|
+
try {
|
|
1472
|
+
const agSkills = join(pluginRoot, "per-project-skills", "antigravity");
|
|
1473
|
+
for (const [src, dest] of [["inbox", "patchcord"], ["wait", "patchcord-wait"]]) {
|
|
1474
|
+
const from = join(agSkills, src, "SKILL.md");
|
|
1475
|
+
if (!existsSync(from)) continue;
|
|
1476
|
+
const to = join(adir, "skills", dest);
|
|
1477
|
+
mkdirSync(to, { recursive: true });
|
|
1478
|
+
cpSync(from, join(to, "SKILL.md"));
|
|
1479
|
+
}
|
|
1480
|
+
} catch {}
|
|
1481
|
+
return agWritten;
|
|
1439
1482
|
}
|
|
1440
1483
|
if (tool === "hermes") {
|
|
1441
1484
|
// Hermes reads MCP servers ONLY from its GLOBAL ~/.hermes/config.yaml
|
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'; }
|
|
@@ -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>
|