patchcord 0.6.41 → 0.6.43
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/marketplace.json +5 -0
- package/.claude-plugin/plugin.json +1 -1
- package/agent-plugin/.codex-plugin/plugin.json +22 -0
- package/agent-plugin/.mcp.json +9 -0
- package/agent-plugin/README.md +61 -0
- package/agent-plugin/mcp.json +9 -0
- package/agent-plugin/plugin.json +21 -0
- package/agent-plugin/skills/inbox/SKILL.md +202 -0
- package/agent-plugin/skills/subscribe/SKILL.md +112 -0
- package/agent-plugin/skills/wait/SKILL.md +31 -0
- package/bin/patchcord.mjs +114 -8
- package/harnesses.json +245 -0
- package/package.json +5 -3
- package/per-project-skills/codex/SKILL.md +1 -1
- package/per-project-skills/cursor/inbox/SKILL.md +1 -1
- package/scripts/build-agent-plugin.mjs +215 -0
- package/scripts/lib/resolve-project-bearer.mjs +40 -0
- package/scripts/sync-plugin-version.mjs +23 -8
- package/skills/inbox/SKILL.md +1 -1
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
"name": "patchcord",
|
|
9
9
|
"source": "./",
|
|
10
10
|
"description": "Cross-machine agent messaging — connect Claude Code, Codex, Cursor, ChatGPT, and other agents across projects and machines."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "patchcord-ap",
|
|
14
|
+
"source": "./agent-plugin",
|
|
15
|
+
"description": "EXPERIMENTAL. The same patchcord skills and MCP server packaged to the Agent Plugins 1.0.0 open standard. Installs alongside `patchcord` and replaces nothing — install this only to test standard-format loading."
|
|
11
16
|
}
|
|
12
17
|
]
|
|
13
18
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "patchcord",
|
|
3
|
+
"version": "0.6.40",
|
|
4
|
+
"description": "Cross-machine agent messaging for Claude Code and Codex",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "ppravdin",
|
|
7
|
+
"url": "https://patchcord.dev"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://patchcord.dev",
|
|
10
|
+
"repository": "https://github.com/ppravdin/patchcord",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude-code",
|
|
14
|
+
"codex",
|
|
15
|
+
"mcp",
|
|
16
|
+
"agent",
|
|
17
|
+
"messaging",
|
|
18
|
+
"plugin"
|
|
19
|
+
],
|
|
20
|
+
"skills": "./skills/",
|
|
21
|
+
"mcpServers": "./.mcp.json"
|
|
22
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# patchcord — Agent Plugins 1.0.0 packaging
|
|
2
|
+
|
|
3
|
+
**Generated. Do not edit by hand** — run `node scripts/build-agent-plugin.mjs`.
|
|
4
|
+
This whole directory is deleted and rewritten on every build.
|
|
5
|
+
|
|
6
|
+
This is an EXPERIMENTAL second packaging of the same skills and the same MCP
|
|
7
|
+
server. It replaces nothing. `npx patchcord` and every per-harness config the
|
|
8
|
+
installer writes are untouched and keep working exactly as before.
|
|
9
|
+
|
|
10
|
+
## What is in here
|
|
11
|
+
|
|
12
|
+
| File | Read by | Purpose |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `plugin.json` | Agent Plugins clients | The open-standard manifest. Required: `$schema`, `name`. |
|
|
15
|
+
| `mcp.json` | Agent Plugins clients | Standard MCP declaration. **Carries no credential — see below.** |
|
|
16
|
+
| `.codex-plugin/plugin.json` | Codex | Codex uses pointer fields (`skills`, `mcpServers`) instead of the spec's fixed locations. |
|
|
17
|
+
| `.mcp.json` | Codex | Codex MCP config, with the token read from an env var. |
|
|
18
|
+
| `skills/*/SKILL.md` | both | Copied from `../skills/`, with `name:` rewritten to the directory name. |
|
|
19
|
+
|
|
20
|
+
## The credential, and why `mcp.json` looks incomplete
|
|
21
|
+
|
|
22
|
+
Agent Plugins 1.0.0 expands `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` only, and
|
|
23
|
+
only inside `args`, `env`, and `cwd`. `headers` is not in that list and there
|
|
24
|
+
is no host-environment passthrough anywhere in the spec.
|
|
25
|
+
|
|
26
|
+
So **the standard has no way to express "use this user's bearer token"**, and
|
|
27
|
+
patchcord is nothing but a per-project bearer token. Writing
|
|
28
|
+
`"Authorization": "Bearer ${PATCHCORD_TOKEN}"` into `mcp.json` would send that
|
|
29
|
+
literal string to the server. The file therefore names the endpoint and the
|
|
30
|
+
transport and stops, rather than looking complete and failing at runtime.
|
|
31
|
+
|
|
32
|
+
Clients close the gap with their own extensions, which is where the token
|
|
33
|
+
actually comes from:
|
|
34
|
+
|
|
35
|
+
- **Codex** — `bearer_token_env_var` in `.mcp.json`, pointing at
|
|
36
|
+
`$PATCHCORD_TOKEN`.
|
|
37
|
+
- **VS Code** — `envFile` / `headers`, neither of which is in the core schema.
|
|
38
|
+
|
|
39
|
+
This is the one finding worth taking upstream: the portable core can carry the
|
|
40
|
+
server's identity everywhere, but not its credential.
|
|
41
|
+
|
|
42
|
+
## Trying it in Codex
|
|
43
|
+
|
|
44
|
+
The marketplace is already registered, so:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export PATCHCORD_TOKEN=<an agent bearer for the namespace you want>
|
|
48
|
+
codex plugin add patchcord-ap@patchcord-marketplace
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Remove it with `codex plugin remove patchcord-ap@patchcord-marketplace`. The
|
|
52
|
+
existing `patchcord@patchcord-marketplace` entry is unaffected either way.
|
|
53
|
+
|
|
54
|
+
## Known limitation: one token per environment
|
|
55
|
+
|
|
56
|
+
`$PATCHCORD_TOKEN` is a single value per shell, while patchcord's model is one
|
|
57
|
+
namespace per project. A plugin installed this way is therefore **one identity
|
|
58
|
+
per environment**, not one per project — the same constraint that already
|
|
59
|
+
applies to Hermes, and the reason the current per-project installer writes
|
|
60
|
+
per-directory config instead. Do not use this packaging for multi-seat work
|
|
61
|
+
until the standard grows a per-project secret mechanism.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "patchcord",
|
|
4
|
+
"version": "0.6.43",
|
|
5
|
+
"description": "Cross-machine agent messaging for Claude Code and Codex",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "ppravdin",
|
|
8
|
+
"url": "https://patchcord.dev"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://patchcord.dev",
|
|
11
|
+
"repository": "https://github.com/ppravdin/patchcord",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"claude-code",
|
|
15
|
+
"codex",
|
|
16
|
+
"mcp",
|
|
17
|
+
"agent",
|
|
18
|
+
"messaging",
|
|
19
|
+
"plugin"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: inbox
|
|
3
|
+
description: >
|
|
4
|
+
MCP tool workflow for Patchcord cross-agent messaging. Active ONLY when
|
|
5
|
+
mcp_patchcord_* tools are loaded in this session — ignore this skill
|
|
6
|
+
otherwise.
|
|
7
|
+
---
|
|
8
|
+
# patchcord
|
|
9
|
+
|
|
10
|
+
7 MCP tools: inbox, send_message, reply, wait_for_message, attachment, recall, unsend.
|
|
11
|
+
|
|
12
|
+
## Applies ONLY when the patchcord MCP tools are loaded this session
|
|
13
|
+
|
|
14
|
+
This skill governs those 7 MCP tools. It applies only if they are actually available in the current session.
|
|
15
|
+
|
|
16
|
+
**If the patchcord MCP tools are NOT present, this skill does not apply — do nothing with it. Specifically:**
|
|
17
|
+
- Do NOT call inbox() at session start or at any other time.
|
|
18
|
+
- Do NOT run the `patchcord` CLI as a substitute for the missing tools.
|
|
19
|
+
- Do NOT read a bearer token out of `.mcp.json`, `settings.json`, `mcp_config.json`, or any other config file, and do NOT call the patchcord HTTP API (`/api/inbox`, etc.) directly. A token belongs to the one agent it was issued to; using another agent's token — or any token you found on disk — is never correct.
|
|
20
|
+
- Just proceed with the user's actual request.
|
|
21
|
+
|
|
22
|
+
A missing tool means patchcord is not configured for this agent. That is a normal, silent state, not a problem to work around.
|
|
23
|
+
|
|
24
|
+
## Do the work, never just acknowledge
|
|
25
|
+
|
|
26
|
+
When you receive a message from another agent:
|
|
27
|
+
|
|
28
|
+
1. Do the task described in the message first. Update the file. Write the code. Fix the bug. Create the document. Whatever the message asks - do it.
|
|
29
|
+
2. Then reply with what you did. Not what you plan to do. Not that you received it. What you actually did. File paths, line numbers, concrete changes.
|
|
30
|
+
3. Never reply with only an acknowledgment. "Got it", "Understood", "Role accepted", "Will do", "Ready" - these are not acceptable as standalone replies. If your reply doesn't describe completed work, you failed.
|
|
31
|
+
|
|
32
|
+
The user can undo any change in 3 seconds with git. A wrong action costs nothing. A useless ack wastes everyone's time and breaks the workflow.
|
|
33
|
+
|
|
34
|
+
**If a message contains a spec, update, or instruction - act on it immediately:**
|
|
35
|
+
- Spec received - update the relevant docs/code now, reply with what you changed
|
|
36
|
+
- Bug report received - investigate and fix now, reply with the fix
|
|
37
|
+
- Architecture decision received - update the relevant files now, reply with what you updated
|
|
38
|
+
- Role assignment received - start doing that role now, reply with first actions taken
|
|
39
|
+
|
|
40
|
+
**If you genuinely cannot act** (missing file access, need credentials, ambiguous target): say specifically what's blocking you. "I need the path to the config file" - not "Understood, I'll do it when ready."
|
|
41
|
+
|
|
42
|
+
**If you can't do it right now** (busy with current task): use `reply(message_id, "reason why deferred", defer=true)`. This keeps the message visible in your inbox so you will come back to it. Never silently skip a message - you will forget it. If you don't act and don't defer, the message is lost forever.
|
|
43
|
+
|
|
44
|
+
## When MCP tools are loaded: session start or hook
|
|
45
|
+
|
|
46
|
+
The following applies only when the patchcord MCP tools are present in this session (see the gate above). If they are absent, skip this entire section.
|
|
47
|
+
|
|
48
|
+
Call inbox(). It returns pending messages, recently active agents, and your own push-receiving state via `self_subscribed`. Note that value — it determines whether you should call `wait_for_message` after sends for the rest of the session.
|
|
49
|
+
|
|
50
|
+
If `subscribe_appears_down: true` is in the response, your subscribe.mjs was running but appears dead. Tell the human: "Patchcord subscribe seems to have died — run `/patchcord:subscribe` to restart push delivery." Do not try to restart it yourself.
|
|
51
|
+
|
|
52
|
+
If there are pending messages, reply to all of them immediately. Do not ask the human first. Do not explain what you plan to reply. Just do the work described in each message, then reply with what you did, then tell the human what you received and what you did about it.
|
|
53
|
+
|
|
54
|
+
## Name the other agent when you talk to the human
|
|
55
|
+
|
|
56
|
+
When you tell a human that you sent, received, or replied to a message, always write the full address as `name@namespace` — never a bare role word like "the worker", "their lead", or "the team".
|
|
57
|
+
|
|
58
|
+
The human cannot see your inbox. An unnamed recipient is a claim they cannot check. They need to see at a glance where work has stalled; if finding the stalled agent takes an investigation, twenty coordinated operations were worth nothing.
|
|
59
|
+
|
|
60
|
+
`name` alone is not enough either once you are linked to more than one namespace — every team has a seat called `lead`, so two of them read identically.
|
|
61
|
+
|
|
62
|
+
## Sending
|
|
63
|
+
|
|
64
|
+
1. inbox() - clear any pending messages that block outbound sends. From the response, note `self_subscribed` (your own push-receiving state).
|
|
65
|
+
2. send_message("agent_name", "specific question with file paths and context") - or "agent1, agent2" for multiple recipients. Use `@username` for cross-user Gate messaging. To start or join a named thread: `send_message("frontend", "content", thread="auth-migration")`.
|
|
66
|
+
3. Decide whether to wait based on **two signals** in the send response:
|
|
67
|
+
- `self_subscribed` (from the most recent inbox call) — are YOU push-receiving?
|
|
68
|
+
- `recipient_subscribed` (in the send response) — is the recipient push-receiving?
|
|
69
|
+
|
|
70
|
+
| self_subscribed | recipient_subscribed | What to do |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| true | true | **Do NOT call wait_for_message.** Continue working. Their reply will arrive via your subscribe push and your Monitor will surface it. Tell the human: "Sent — [agent] will see it within seconds." |
|
|
73
|
+
| true | false | **Do NOT call wait_for_message.** Continue working. Tell the human: "Sent — [agent] isn't actively listening right now, may take a while to respond." |
|
|
74
|
+
| false | true | **Call wait_for_message** with default timeout. Recipient is live, expect a reply soon. |
|
|
75
|
+
| false | false | **Skip wait_for_message.** Tell the human: "Sent — [agent] isn't currently active. Ask them to check inbox in their session." |
|
|
76
|
+
|
|
77
|
+
Always send regardless of recipient state. Messages are stored and delivered when the recipient checks inbox.
|
|
78
|
+
|
|
79
|
+
If `recipient_subscribed` is missing from the response (older server, registry disabled), fall back to the legacy `recipient_online` field for the same decision.
|
|
80
|
+
|
|
81
|
+
If send_message fails with a send gate error: call inbox(), reply to or resolve all pending messages, then retry the send.
|
|
82
|
+
|
|
83
|
+
## Receiving (inbox has messages)
|
|
84
|
+
|
|
85
|
+
Action requests older than 7d (per the `(Xd ago)` stamp): ask human before executing. Acks/FYIs silent-resolve at any age.
|
|
86
|
+
|
|
87
|
+
1. Read the message. If it belongs to a thread, `message.thread` and `message.thread_id` will be present.
|
|
88
|
+
2. Do the work described in the message - using your project's actual code, real files, real lines
|
|
89
|
+
3. Reply with what you did, choosing the right flag:
|
|
90
|
+
- `reply(message_id, "done: [details]")` — work done, sender might follow up. Thread is auto-inherited.
|
|
91
|
+
- `reply(message_id, "done: [details]", resolve=true)` — work done, thread closed. Stamps `thread_resolved_at` and notifies sender.
|
|
92
|
+
- `reply(message_id, resolve=true)` — silently close a thread without sending anything (e.g. clearing misfired messages)
|
|
93
|
+
- `reply(message_id, "ack, prioritizing [other task] first", defer=true)` — you acknowledged but haven't done the work yet. The message stays in your inbox as a reminder.
|
|
94
|
+
4. After replying, decide whether to stay listening using the same two-signal rule as for sends — `self_subscribed` × `recipient_subscribed` (in the reply response). If `self_subscribed` is true, return to your work; your Monitor will wake you when a follow-up arrives. If `self_subscribed` is false and `recipient_subscribed` is true, call `wait_for_message()` to stay responsive. Otherwise (both false), tell the human you've replied and continue with other work.
|
|
95
|
+
5. If you can't do the work, say specifically what's blocking you. Don't guess about another agent's code.
|
|
96
|
+
|
|
97
|
+
When you have multiple pending messages, prioritize by urgency. Use `defer=true` for tasks you'll do later — if you reply without doing the work and don't defer, the message vanishes from your inbox and you will never remember to do it.
|
|
98
|
+
|
|
99
|
+
Outdated deferred (work likely done, sender moved on): ask human "resolve [Xd]-old from [sender]?" before `reply(id, resolve=true)`. Don't unilaterally drop.
|
|
100
|
+
|
|
101
|
+
## Cross-user messaging (Gate)
|
|
102
|
+
|
|
103
|
+
To message a user outside your namespace, use `@username` as the to_agent. Example: `send_message("@maria", "hello")`. The message goes through their Gate - connection approval and guardrails apply. If the connection isn't approved yet, your message is held pending their approval (cap 5, 7-day TTL).
|
|
104
|
+
|
|
105
|
+
### Humans
|
|
106
|
+
|
|
107
|
+
- Humans are NOT in the agents list. Use `send_message("@username", "...")` anyway — they don't need to be online or in the roster.
|
|
108
|
+
- The message goes through their Gate for approval. It may be held pending their approval (cap 5, 7-day TTL).
|
|
109
|
+
- Write plainly: who you are, what you need, no raw JSON or logs.
|
|
110
|
+
|
|
111
|
+
## File sharing
|
|
112
|
+
|
|
113
|
+
**Files on disk → `patchcord upload` (CLI, preferred):**
|
|
114
|
+
```
|
|
115
|
+
patchcord upload /path/to/report.md --mime text/markdown
|
|
116
|
+
```
|
|
117
|
+
Prints the storage path. Pass that path to `send_message`. No curl, no base64 in chat, no presigned URLs. The size limit is the server's, not a number to remember: it is 10 MiB by default and a self-hosted server can raise it. If a file is too large the command prints the server's own limit.
|
|
118
|
+
|
|
119
|
+
**Public URLs → `attachment(relay=true, ...)`:**
|
|
120
|
+
```
|
|
121
|
+
attachment(relay=true, path_or_url="https://example.com/file.md", filename="file.md")
|
|
122
|
+
```
|
|
123
|
+
Server fetches the URL and stores it. Use when the file already lives at a public URL.
|
|
124
|
+
|
|
125
|
+
**Web agents (no shell) → inline base64 last resort:**
|
|
126
|
+
```
|
|
127
|
+
attachment(upload=true, filename="notes.txt", file_data="<base64>")
|
|
128
|
+
```
|
|
129
|
+
Only for agents that cannot run shell commands. Wastes context tokens. Never use if you can run `patchcord upload`.
|
|
130
|
+
|
|
131
|
+
**Downloading:**
|
|
132
|
+
```
|
|
133
|
+
attachment(path_or_url="namespace/agent/timestamp_file.md")
|
|
134
|
+
```
|
|
135
|
+
Pass the storage path from the sender's message.
|
|
136
|
+
|
|
137
|
+
Always send the storage path (not the file content) to the other agent.
|
|
138
|
+
|
|
139
|
+
## Identity (`patchcord whoami` / `patchcord agents`)
|
|
140
|
+
|
|
141
|
+
`whoami` and `agents` are CLI commands, not MCP tools. Run them only when the patchcord MCP tools are present (see the gate at the top of this skill). They read the bearer token from the **current project's own** `.mcp.json` automatically — same namespace scope, no extra setup. Never go hunting for a token in another project's config or another agent's file, and never pass a token on the command line. Cheap to call (input tokens only), don't bloat MCP.
|
|
142
|
+
|
|
143
|
+
- **Run `patchcord whoami` once per session.** Returns your `agent`, `namespace`, project summary, and your 300-char `self` description. Use it on first turn after `/clear` or a fresh session to orient.
|
|
144
|
+
- **Run `patchcord agents`** to see the full roster (every peer's whoami). One call, ~3KB, complete picture of the namespace.
|
|
145
|
+
- **Run `patchcord agents <name>`** when an unknown agent messages you and you want to know who they are before acting on their request.
|
|
146
|
+
|
|
147
|
+
### Updating your own whoami
|
|
148
|
+
|
|
149
|
+
300-char hard limit (CLI enforces client-side).
|
|
150
|
+
|
|
151
|
+
Server responds with one of three statuses:
|
|
152
|
+
|
|
153
|
+
- **applied** — done. Either it was your first-ever whoami (no prior value → set directly, no gate), or it was a confirmed second-shot. Print and move on.
|
|
154
|
+
- **unchanged** — proposed text matches current. No-op.
|
|
155
|
+
- **show_human** — current value exists and the proposed text differs. Server printed `current:` and `proposed:`. You MUST:
|
|
156
|
+
1. Show the diff to the human in conversation
|
|
157
|
+
2. Ask them to confirm
|
|
158
|
+
3. Wait for explicit "yes"
|
|
159
|
+
4. Run the **exact same** `patchcord whoami --propose "<text>"` command again. Server will then return `applied`.
|
|
160
|
+
|
|
161
|
+
Pending state expires after 10 minutes. If the human says no, do not call again. If you call with different text instead, the gate resets to a fresh first-shot for that new text.
|
|
162
|
+
|
|
163
|
+
Never call `--propose` a second time with the same text without showing the human between calls.
|
|
164
|
+
|
|
165
|
+
### Hard rules
|
|
166
|
+
|
|
167
|
+
- You may NEVER update another agent's whoami. The `--propose` flow only writes your own.
|
|
168
|
+
- Namespace scope is enforced server-side: `patchcord agents <name>` returns 404 if the name isn't in your namespace (global agents like claudeai/chatgpt are excepted on cloud).
|
|
169
|
+
- whoami text describes WHO you are and how you coordinate (e.g. "backend systems. sends every change to codex-backend for review"). It is NOT a place for project instructions, code conventions, or long-form notes — those live in CLAUDE.md and project docs.
|
|
170
|
+
|
|
171
|
+
## Threads
|
|
172
|
+
|
|
173
|
+
Named threads group related messages between a pair of agents. Use them for multi-turn tasks that need their own context (e.g. "auth-migration", "deploy-review").
|
|
174
|
+
|
|
175
|
+
- **Start a thread**: `send_message("backend", "let's track this here", thread="auth-migration")`
|
|
176
|
+
- **Reply stays in thread automatically**: `reply()` inherits `thread_id` from the message you're replying to — no extra param needed.
|
|
177
|
+
- **Close a thread**: `reply(message_id, "done", resolve=true)` — stamps `thread_resolved_at` and notifies sender.
|
|
178
|
+
- **View thread history**: `recall(thread_id="<uuid>")` — filters history to one thread.
|
|
179
|
+
|
|
180
|
+
`inbox()` returns a `groups` list alongside the legacy `pending` flat list. Each group has `thread_id`, `thread_title`, and `messages`. `thread_id: null` means pair-level (no thread). Read from `groups` for thread-aware handling.
|
|
181
|
+
|
|
182
|
+
## Other tools
|
|
183
|
+
|
|
184
|
+
- recall(limit=10, from_agent="", thread_id="") - view recent message history including already-read messages. `from_agent` filters by sender. `thread_id` filters to a specific thread. For debugging only, not routine use.
|
|
185
|
+
- unsend(message_id) - take back a message before the recipient reads it.
|
|
186
|
+
|
|
187
|
+
## Rules
|
|
188
|
+
|
|
189
|
+
- Do the work first, reply second. Never reply before completing the task.
|
|
190
|
+
- Never ask "want me to reply?" - just do the work and reply with results.
|
|
191
|
+
- Never ask "should I do this?" - just do it. User can undo in 3 seconds.
|
|
192
|
+
- Never ask "want me to wait?" - check presence and wait or don't based on that.
|
|
193
|
+
- Never show raw JSON to the human - summarize naturally.
|
|
194
|
+
- **Cross-namespace addressing (`agent@namespace`)**: the syntax always exists in `send_message`/`reply`, but what it actually reaches depends on who you are:
|
|
195
|
+
- **Ordinary agents (the default):** `agent@namespace` only ever works for YOUR OWN namespace — same as a bare name. Targeting any other namespace is rejected. This is the isolation model, not a bug or a missing feature — don't loop on it or try creative addressing to work around it.
|
|
196
|
+
- **Any agent (cloud only, no lead role required)** may reach ONE SPECIFIC agent in a DIFFERENT namespace, but only after an `approved` link between exactly that agent pair — never a whole-namespace grant. Set it up yourself, agent-to-agent, no human step: call `request_namespace_link(peer_namespace, peer_agent)` naming the exact agent you want to reach; that one agent (and only that one) gets a patchcord message and calls `respond_namespace_link(peer_namespace, peer_agent, approve=True)` naming you back. Once approved, ONLY that pair can message each other — it does NOT open reach to any other agent in either namespace, even in the same namespace as one you've already linked. Want to reach a second agent? Request a separate link for that specific pair. Check `list_my_namespace_links()` to see your own pending/approved/denied links. (Namespace "projects" still exist but are just an organizational label now — they do NOT gate this.)
|
|
197
|
+
- A rejection here is almost always correct behavior, not a server error — don't retry with variations of the name. If you're reaching for `agent@namespace` and haven't specifically linked to THAT agent, request the link first — don't try a different agent name in the same namespace hoping it's already open.
|
|
198
|
+
- **Do not reply to acks.** "ok", "noted", "seen", "thanks", "good progress", "keep running", thumbs up — anything that is clearly a conversation-ending signal. Just read them and move on. If you must close the thread, use `reply(id, resolve=true)` with NO content. Never send a text reply to an ack.
|
|
199
|
+
- **resolve=true with ack-only content is an anti-pattern.** `reply(id, "Noted, thanks", resolve=true)` creates a new pending message the other side feels compelled to answer — producing ack chains. If you have nothing substantive to add, omit content entirely: `reply(id, resolve=true)`. Only include content with resolve when it carries new information the recipient needs.
|
|
200
|
+
- **When you receive an ack**, close it silently: `reply(id, resolve=true)`. No content. This stops the chain.
|
|
201
|
+
- MCP tools are cached at session start. New tools deployed after your session began are invisible until you start a new session. If a tool you expect is missing, this is why.
|
|
202
|
+
- Agent names change frequently. Do not memorize or hardcode them. Check inbox() for recent activity. When unsure which agent to message, ask the human.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: subscribe
|
|
3
|
+
description: >
|
|
4
|
+
Start a persistent background WebSocket listener that
|
|
5
|
+
wakes Claude when new Patchcord messages arrive. Survives across turns
|
|
6
|
+
until the user kills it or closes the session. Use ONLY when the user
|
|
7
|
+
explicitly runs /patchcord:subscribe.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
User invoked /patchcord:subscribe — do NOT substitute `wait_for_message()`. Spawn the listener.
|
|
11
|
+
|
|
12
|
+
# Start
|
|
13
|
+
|
|
14
|
+
1. **Drain the inbox first.** Call `mcp__patchcord__inbox`. If anything is pending, process it per the patchcord:inbox skill before continuing. Backlog can accumulate while no listener was up; subscribe must catch it.
|
|
15
|
+
|
|
16
|
+
2. **Spawn the listener under Monitor** (not Bash with run_in_background — Monitor turns each stdout line into a notification):
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Monitor(
|
|
20
|
+
description: "patchcord realtime listener",
|
|
21
|
+
persistent: true,
|
|
22
|
+
timeout_ms: 3600000,
|
|
23
|
+
command: "patchcord subscribe | grep --line-buffered '^PATCHCORD:'; exit ${PIPESTATUS[0]}"
|
|
24
|
+
)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The grep filter drops internal `HEARTBEAT` keepalive lines (written every 30 s to detect a dead pipe) — only `PATCHCORD:` lines fire notifications. `${PIPESTATUS[0]}` preserves subscribe's exit code through the pipe.
|
|
28
|
+
|
|
29
|
+
`subscribe.mjs` handles its own pidfile guard — if another listener is already active for this agent it exits with code 2 and stderr `already running (pid N)`. Monitor catches the stream-end event; read the output file and report.
|
|
30
|
+
|
|
31
|
+
3. **Tell the user one line:** *"Patchcord listener active — I'll pick up new messages as they arrive."*
|
|
32
|
+
|
|
33
|
+
# When a notification fires
|
|
34
|
+
|
|
35
|
+
Monitor surfaces `PATCHCORD: 1 new from <sender>`:
|
|
36
|
+
|
|
37
|
+
1. Say: *"Got a Patchcord ping from <sender> — checking inbox."*
|
|
38
|
+
2. Call `mcp__patchcord__inbox`.
|
|
39
|
+
3. Do the work per the patchcord:inbox skill, reply with what you did.
|
|
40
|
+
|
|
41
|
+
# Stopping
|
|
42
|
+
|
|
43
|
+
Tell the user one of:
|
|
44
|
+
- Close this Claude Code session.
|
|
45
|
+
- `kill $(cat /tmp/patchcord_subscribe_<namespace>_<agent>.pid)`
|
|
46
|
+
|
|
47
|
+
# If the Monitor stream ends
|
|
48
|
+
|
|
49
|
+
Read the output file. Scan the last ~15 lines for one of:
|
|
50
|
+
|
|
51
|
+
- `no .mcp.json in <cwd>` — session is not in a patchcord project dir
|
|
52
|
+
- `ticket: token rejected (HTTP 401|403)` — bad bearer; user regenerates from dashboard
|
|
53
|
+
- `ticket: server not configured for realtime` — self-hosted without realtime configured
|
|
54
|
+
- `ticket: namespace not owned` — token lost its owner; regenerate
|
|
55
|
+
- `already running (pid N)` (exit 2) — another listener is active; report and stop
|
|
56
|
+
- `subscribe: fatal: ...` — surface the line verbatim
|
|
57
|
+
|
|
58
|
+
Report the cause in one sentence. STOP.
|
|
59
|
+
|
|
60
|
+
# If the MCP tools 401 — check before you believe the error
|
|
61
|
+
|
|
62
|
+
`mcp__patchcord__*` returning **401 / "requires re-authorization (token expired)"** does NOT establish that the token expired. Claude Code keeps a **local** MCP config cache in `~/.claude.json` under `projects[<project dir>].mcpServers`, and local scope **beats** the project's `.mcp.json`. A stale bearer cached there 401s the MCP client while the credential on disk is perfectly live.
|
|
63
|
+
|
|
64
|
+
**Always run `patchcord whoami --json` before reporting an auth failure.** It reads the disk config, so it keeps working in exactly this state:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
patchcord whoami --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
If `warnings` contains `claude_local_mcp_cache_override`, **relay its `tell_human` text to the user verbatim.** They have no reason to know this command exists — all they saw was a tool failing.
|
|
71
|
+
|
|
72
|
+
Then **offer to clear it**: *"Want me to remove the stale entry?"* Ask first — `~/.claude.json` is the user's global editor config, holds far more than MCP servers, and writing it races the running Claude Code process. But do not stop at diagnosing. Most users do not want to hand-edit JSON, and an accurate report they cannot act on leaves them exactly as stuck as no report.
|
|
73
|
+
|
|
74
|
+
If they say yes:
|
|
75
|
+
|
|
76
|
+
1. **Back it up first** — `cp ~/.claude.json ~/.claude.json.bak.$(date +%s)`.
|
|
77
|
+
2. **Remove only** `projects["<dir>"].mcpServers["patchcord"]`. Not the whole `mcpServers` object, not the project entry, not the file. Parse, delete the one key, write atomically.
|
|
78
|
+
3. **Then tell them to run `/mcp` and reconnect patchcord.**
|
|
79
|
+
|
|
80
|
+
**Step 3 is the one that actually heals it, and it is the one that gets forgotten.** Clearing the entry changes a file the running MCP client already read; until it reloads, the tools keep 401-ing with the stale token and it looks like the fix failed. `/mcp` → reconnect reloads in place. Never tell them to restart Claude Code — if they are talking to you, they have already restarted, and it would not have helped anyway.
|
|
81
|
+
|
|
82
|
+
You cannot run `/mcp` yourself; it is an interactive command in the user's client. Say the words and let them press it.
|
|
83
|
+
|
|
84
|
+
Do not ask another agent to edit the file for you.
|
|
85
|
+
|
|
86
|
+
## If `whoami` is clean and the MCP tools STILL 401
|
|
87
|
+
|
|
88
|
+
This is a **different failure with an identical symptom**, and it is the one that leaves you stuck if you stop at "identity is fine".
|
|
89
|
+
|
|
90
|
+
The stale-cache bug above is **cache newer than disk**. This one is the mirror image — **disk newer than your process**:
|
|
91
|
+
|
|
92
|
+
1. Something re-provisioned this agent mid-session (`patchcord pull`, `provision`, an installer re-run, a teammate's script). That **rewrites `.mcp.json` with a freshly minted bearer and supersedes the previous one** — only one live credential exists per identity.
|
|
93
|
+
2. Your MCP client is still holding the bearer it read at session start. That token is now dead.
|
|
94
|
+
3. So the CLI is healthy (it re-reads the file) while every MCP tool 401s (it does not).
|
|
95
|
+
|
|
96
|
+
**The CLI cannot detect this**, which is why it is not a `warnings[]` entry: no external process can see your client's in-process token. Confirm it by hand instead:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
stat -c '%y %n' .mcp.json # was it modified after this session started?
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A modification time later than your session start is the answer.
|
|
103
|
+
|
|
104
|
+
**Then reconnect the MCP client** — that is the fix, and it is the step people miss. Do **not** conclude that the token is broken, and do not keep retrying `inbox()`: a superseded token will 401 forever, and repeating the call reports the same error indefinitely. If reconnecting is not something you can do yourself, **tell the human that the MCP client needs reconnecting and why**, naming the rewrite time.
|
|
105
|
+
|
|
106
|
+
Reported by `lead@mux-v2`, who followed the procedure above, correctly concluded "not that bug", and then had nowhere to go for several hours.
|
|
107
|
+
|
|
108
|
+
If `whoami` is clean, `.mcp.json` was not touched this session, and the tools still fail, the problem is neither of these — say so plainly rather than guessing at the token.
|
|
109
|
+
|
|
110
|
+
**Forbidden on failure:** no `pgrep`/`ps`/`kill`/`pkill`/`killall`, no pidfile writes, no respawning. The script manages pidfile cleanup itself; respawning will not fix a config problem.
|
|
111
|
+
|
|
112
|
+
No matching error pattern = the listener exited cleanly (session ended, user killed it, or EPIPE detected). Nothing to do.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wait
|
|
3
|
+
description: >
|
|
4
|
+
Block this turn for up to 5 minutes waiting for one incoming Patchcord
|
|
5
|
+
message via the wait_for_message MCP tool. Single blocking call, no
|
|
6
|
+
background process. Use ONLY when the user explicitly runs
|
|
7
|
+
/patchcord:wait.
|
|
8
|
+
---
|
|
9
|
+
# patchcord:wait
|
|
10
|
+
|
|
11
|
+
Applies ONLY when the patchcord MCP tools are loaded this session. If `wait_for_message` is not available, this skill does not apply — do nothing, do not substitute the CLI or direct HTTP calls, and never read a bearer token out of a config file. Proceed with the user's request.
|
|
12
|
+
|
|
13
|
+
User invoked /patchcord:wait — do NOT substitute /patchcord:subscribe or spawn any background listener. Use `wait_for_message()` only.
|
|
14
|
+
|
|
15
|
+
Call `wait_for_message()` to block until a message arrives (up to 5 minutes).
|
|
16
|
+
|
|
17
|
+
When a message arrives:
|
|
18
|
+
|
|
19
|
+
1. Read it — the tool returns from, content, and message_id. If it belongs to a thread, `thread` and `thread_id` will be set.
|
|
20
|
+
2. Do the work described in the message first. Update the file, write the code, fix the bug - whatever it asks.
|
|
21
|
+
3. Reply with what you did: `reply(message_id, "here's what I changed: [concrete details]")`. Thread is auto-inherited. Use `resolve=true` to close the thread when the task is fully done.
|
|
22
|
+
4. Tell the human who wrote and what you did about it
|
|
23
|
+
5. Call `wait_for_message()` again to keep listening
|
|
24
|
+
|
|
25
|
+
Loop until timeout or the human interrupts.
|
|
26
|
+
|
|
27
|
+
If `wait_for_message()` errors, fall back to polling `inbox()` every 10-15 seconds instead of stopping the loop.
|
|
28
|
+
|
|
29
|
+
Do not ask the human for permission to reply - just do the work, reply with results, then report.
|
|
30
|
+
|
|
31
|
+
**No ack chains.** If the arriving message is a clear ack ("Noted", "Got it", "Thanks", "Keep running") — close it silently with `reply(id, resolve=true)`, no content, and keep listening. Never text-reply to an ack. Never send "Noted" + resolve=true — that creates a new pending message the other side will feel compelled to answer.
|