patchcord 0.3.59 → 0.3.61

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.
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "description": "Cross-machine agent messaging with auto-inbox checking. Agents automatically respond to messages from other agents without human intervention.",
4
- "version": "0.3.28",
3
+ "description": "Cross-machine agent messaging with push delivery. Messages from other agents arrive as native channel notifications.",
4
+ "version": "0.4.0",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
8
8
  "repository": "https://github.com/ppravdin/patchcord",
9
- "skills": "./skills/"
9
+ "skills": "./skills/",
10
+ "channel": {
11
+ "server": "./channel/server.ts"
12
+ }
10
13
  }
package/README.md CHANGED
@@ -1,103 +1,38 @@
1
- # Patchcord Plugin for Claude Code
1
+ # Patchcord Plugin
2
2
 
3
- Cross-machine messaging between Claude Code agents.
3
+ Cross-machine messaging between AI coding agents.
4
4
 
5
- This plugin is not the connection itself.
6
-
7
- The plugin provides:
8
-
9
- - Patchcord skills
10
- - statusline integration
11
- - turn-end inbox checks
12
-
13
- The actual Patchcord connection must still come from the current project configuration.
14
-
15
- ## Safe model
16
-
17
- Use this plugin with project-local Patchcord config.
18
-
19
- Good:
20
-
21
- - install the plugin once
22
- - keep `.mcp.json` inside each Patchcord-enabled project
23
- - let the plugin no-op in projects that do not have Patchcord configured
24
-
25
- Bad:
26
-
27
- - exporting `PATCHCORD_TOKEN` / `PATCHCORD_URL` globally in `~/.bashrc`, `~/.profile`, or similar
28
- - keeping Patchcord config in an ancestor directory like `~/.mcp.json`
29
- - assuming the plugin should make every project a Patchcord project
30
-
31
- ## Setup
32
-
33
- ### 1. Install the plugin
5
+ ## Install
34
6
 
35
7
  ```bash
36
- claude plugin marketplace add /path/to/patchcord-internal
37
- claude plugin install patchcord@patchcord-marketplace
8
+ npx patchcord@latest
38
9
  ```
39
10
 
40
- ### 2. Configure the project
11
+ One command. Opens browser, configures everything. Works with Claude Code, Codex CLI, Cursor, Windsurf, Gemini CLI, VS Code, Zed, OpenCode.
41
12
 
42
- Create a project-local `.mcp.json` in the project that should act as a Patchcord agent.
13
+ Self-hosted:
43
14
 
44
- Example:
45
-
46
- ```json
47
- {
48
- "mcpServers": {
49
- "patchcord": {
50
- "type": "http",
51
- "url": "https://patchcord.yourdomain.com/mcp",
52
- "headers": {
53
- "Authorization": "Bearer <project-token>"
54
- }
55
- }
56
- }
57
- }
15
+ ```bash
16
+ npx patchcord@latest --token <token> --server https://patchcord.yourdomain.com
58
17
  ```
59
18
 
60
- ### 3. Restart Claude Code in that project
61
-
62
- The plugin and statusline scripts read the current project configuration when the session starts.
63
-
64
- ## What happens in non-Patchcord projects
65
-
66
- Nothing Patchcord-specific should appear.
19
+ ## What it provides
67
20
 
68
- - no Patchcord identity in the statusline
69
- - no inbox checks
70
- - no hook-driven Patchcord prompts
21
+ - **Skills** patchcord inbox and wait skills for all supported tools
22
+ - **Statusline** shows agent identity in Claude Code statusbar
23
+ - **Stop hook** checks inbox between turns, notifies of pending messages
24
+ - **Slash commands** — `/patchcord` and `/patchcord-wait` for Codex and Gemini CLI
25
+ - **MCP config** — per-project or global config depending on tool
71
26
 
72
- The plugin is allowed to stay installed globally, but it must no-op unless the current project is configured.
27
+ ## How it works
73
28
 
74
- ## Self-hosted server
29
+ The installer:
30
+ 1. Detects installed tools and installs global components (skills, permissions, statusline)
31
+ 2. Opens browser for project + agent setup (or uses `--token` for self-hosted)
32
+ 3. Writes the correct MCP config for the chosen tool
75
33
 
76
- The project `.mcp.json` should point to your own server URL:
77
-
78
- ```json
79
- {
80
- "mcpServers": {
81
- "patchcord": {
82
- "type": "http",
83
- "url": "https://patchcord.yourdomain.com/mcp",
84
- "headers": {
85
- "Authorization": "Bearer <project-token>"
86
- }
87
- }
88
- }
89
- }
90
- ```
34
+ The plugin no-ops in projects without patchcord configured.
91
35
 
92
36
  ## Verify
93
37
 
94
- In a Patchcord-enabled project:
95
-
96
- - statusline should show the Patchcord identity
97
- - `inbox()` should return the expected `namespace_id` and `agent_id`
98
-
99
- In an unrelated project:
100
-
101
- - statusline should not show Patchcord identity
102
- - no Patchcord hooks should fire
103
- - no Patchcord tools should be present unless that project is configured
38
+ After setup, restart your tool session and say `check inbox`. Verify `agent_id` and `namespace_id` are correct.
package/bin/patchcord.mjs CHANGED
@@ -63,11 +63,11 @@ if (cmd === "help" || cmd === "--help" || cmd === "-h") {
63
63
  console.log(`patchcord — agent messaging for AI coding agents
64
64
 
65
65
  Usage:
66
- npx patchcord@latest Full setup (global + project) — run in your project folder
67
- npx patchcord@latest --full Same + full statusline (model, context%, git)
68
- npx patchcord@latest skill apply Fetch custom skill from web console
69
-
70
- That's it. One command does everything.`);
66
+ npx patchcord@latest Setup via browser (patchcord.dev)
67
+ npx patchcord@latest --token <token> Self-hosted / CI setup
68
+ npx patchcord@latest --token <token> --server <url> Self-hosted with custom server
69
+ npx patchcord@latest --full Same + full statusline
70
+ npx patchcord@latest skill apply Fetch custom skill from web console`);
71
71
  process.exit(0);
72
72
  }
73
73
 
@@ -77,7 +77,7 @@ if (cmd === "plugin-path") {
77
77
  }
78
78
 
79
79
  // ── main flow: global setup + project setup (or just install/agent for back-compat) ──
80
- if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd === "--no-browser") {
80
+ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd === "--no-browser" || cmd === "--server") {
81
81
  const flags = cmd?.startsWith("--") ? process.argv.slice(2) : process.argv.slice(3);
82
82
  const fullStatusline = flags.includes("--full");
83
83
  const { readFileSync, writeFileSync } = await import("fs");
@@ -268,7 +268,20 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
268
268
  let apiUrl = "https://api.patchcord.dev";
269
269
  let clientType = "";
270
270
 
271
- // --token bypass for power users / CI
271
+ // --server flag for self-hosters
272
+ const serverFlag = flags.find(f => f.startsWith("--server="))?.split("=")[1]
273
+ || (flags.includes("--server") ? flags[flags.indexOf("--server") + 1] : "");
274
+ if (serverFlag) {
275
+ if (!isSafeUrl(serverFlag)) {
276
+ console.error("Invalid server URL. Must start with https:// or http://");
277
+ rl.close();
278
+ process.exit(1);
279
+ }
280
+ serverUrl = serverFlag.replace(/\/+$/, "");
281
+ apiUrl = serverUrl;
282
+ }
283
+
284
+ // --token bypass for power users / CI / self-hosters
272
285
  const tokenFlag = flags.find(f => f.startsWith("--token="))?.split("=")[1]
273
286
  || (flags.includes("--token") ? flags[flags.indexOf("--token") + 1] : "");
274
287
 
@@ -663,7 +676,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
663
676
  console.log(`\n ${green}✓${r} Codex configured: ${dim}${configPath}${r}`);
664
677
  console.log(` ${green}✓${r} Slash commands: ${dim}/patchcord${r}, ${dim}/patchcord-wait${r}`);
665
678
  } else {
666
- // Claude Code: write .mcp.json
679
+ // Claude Code: write .mcp.json (MCP server + channel plugin)
667
680
  const mcpPath = join(cwd, ".mcp.json");
668
681
  const mcpConfig = {
669
682
  mcpServers: {
@@ -675,6 +688,14 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
675
688
  "X-Patchcord-Machine": hostname,
676
689
  },
677
690
  },
691
+ "patchcord-channel": {
692
+ command: "npx",
693
+ args: ["patchcord@latest", "channel"],
694
+ env: {
695
+ PATCHCORD_TOKEN: token,
696
+ PATCHCORD_SERVER: serverUrl,
697
+ },
698
+ },
678
699
  },
679
700
  };
680
701
 
@@ -683,6 +704,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
683
704
  const existing = JSON.parse(readFileSync(mcpPath, "utf-8"));
684
705
  existing.mcpServers = existing.mcpServers || {};
685
706
  existing.mcpServers.patchcord = mcpConfig.mcpServers.patchcord;
707
+ existing.mcpServers["patchcord-channel"] = mcpConfig.mcpServers["patchcord-channel"];
686
708
  writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + "\n");
687
709
  } catch {
688
710
  writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
@@ -691,6 +713,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
691
713
  writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
692
714
  }
693
715
  console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
716
+ console.log(` ${dim}Channel plugin: launch with${r} ${bold}claude --dangerously-load-development-channels server:patchcord-channel${r}`);
694
717
  }
695
718
 
696
719
  // Warn about gitignore for per-project configs with tokens
@@ -720,6 +743,30 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
720
743
  process.exit(0);
721
744
  }
722
745
 
746
+ // ── channel: spawn the channel MCP server (used by .mcp.json) ──
747
+ if (cmd === "channel") {
748
+ const channelScript = join(pluginRoot, "channel", "server.ts");
749
+ if (!existsSync(channelScript)) {
750
+ console.error("Channel server not found. Reinstall patchcord.");
751
+ process.exit(1);
752
+ }
753
+ // Prefer bun, fall back to node (tsx)
754
+ const hasBun = run("which bun");
755
+ if (hasBun) {
756
+ const { spawnSync } = await import("child_process");
757
+ // Install deps if needed
758
+ const channelDir = join(pluginRoot, "channel");
759
+ if (!existsSync(join(channelDir, "node_modules"))) {
760
+ spawnSync("bun", ["install", "--no-summary"], { cwd: channelDir, stdio: "inherit" });
761
+ }
762
+ const result = spawnSync("bun", ["run", channelScript], { stdio: "inherit", env: process.env });
763
+ process.exit(result.status ?? 1);
764
+ } else {
765
+ console.error("Channel plugin requires bun. Install from https://bun.sh");
766
+ process.exit(1);
767
+ }
768
+ }
769
+
723
770
  // ── back-compat: init → install + agent ───────────────────────
724
771
  if (cmd === "init") {
725
772
  console.log(`"patchcord init" is now two commands:
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "patchcord-channel",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "bun install --no-summary && bun server.ts"
7
+ },
8
+ "dependencies": {
9
+ "@modelcontextprotocol/sdk": "^1.0.0"
10
+ }
11
+ }
@@ -0,0 +1,331 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Patchcord channel for Claude Code.
4
+ *
5
+ * Polls the Patchcord server for new messages and pushes them as native
6
+ * <channel> notifications. Exposes reply and send_message tools for
7
+ * two-way communication.
8
+ *
9
+ * Config: PATCHCORD_TOKEN and PATCHCORD_SERVER env vars (set by .mcp.json).
10
+ */
11
+
12
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
13
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
14
+ import {
15
+ ListToolsRequestSchema,
16
+ CallToolRequestSchema,
17
+ } from '@modelcontextprotocol/sdk/types.js'
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Config
21
+ // ---------------------------------------------------------------------------
22
+
23
+ const TOKEN = process.env.PATCHCORD_TOKEN ?? ''
24
+ const SERVER = (process.env.PATCHCORD_SERVER ?? 'https://mcp.patchcord.dev').replace(/\/+$/, '')
25
+ const POLL_INTERVAL_MS = 3000
26
+
27
+ if (!TOKEN) {
28
+ process.stderr.write(
29
+ `patchcord channel: PATCHCORD_TOKEN required\n` +
30
+ ` Set it in .mcp.json env block or as a shell environment variable.\n`,
31
+ )
32
+ process.exit(1)
33
+ }
34
+
35
+ // Safety nets
36
+ process.on('unhandledRejection', err => {
37
+ process.stderr.write(`patchcord channel: unhandled rejection: ${err}\n`)
38
+ })
39
+ process.on('uncaughtException', err => {
40
+ process.stderr.write(`patchcord channel: uncaught exception: ${err}\n`)
41
+ })
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Identity (resolved on startup)
45
+ // ---------------------------------------------------------------------------
46
+
47
+ let agentId = ''
48
+ let namespaceId = ''
49
+
50
+ async function resolveIdentity(): Promise<void> {
51
+ const res = await fetch(`${SERVER}/api/inbox?limit=0&count_only=true`, {
52
+ headers: { Authorization: `Bearer ${TOKEN}` },
53
+ })
54
+ if (!res.ok) {
55
+ const text = await res.text()
56
+ throw new Error(`identity check failed: ${res.status} ${text.slice(0, 200)}`)
57
+ }
58
+ const data = await res.json() as { agent_id: string; namespace_id: string }
59
+ agentId = data.agent_id
60
+ namespaceId = data.namespace_id
61
+ process.stderr.write(`patchcord channel: connected as ${agentId}@${namespaceId}\n`)
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // HTTP helpers
66
+ // ---------------------------------------------------------------------------
67
+
68
+ const headers = {
69
+ Authorization: `Bearer ${TOKEN}`,
70
+ 'Content-Type': 'application/json',
71
+ }
72
+
73
+ async function channelPoll(): Promise<PollMessage[]> {
74
+ const res = await fetch(`${SERVER}/api/channel/poll`, {
75
+ method: 'POST',
76
+ headers,
77
+ body: JSON.stringify({}),
78
+ })
79
+ if (!res.ok) {
80
+ throw new Error(`poll failed: ${res.status}`)
81
+ }
82
+ return await res.json() as PollMessage[]
83
+ }
84
+
85
+ async function channelSend(to_agent: string, content: string): Promise<any> {
86
+ const res = await fetch(`${SERVER}/api/channel/send`, {
87
+ method: 'POST',
88
+ headers,
89
+ body: JSON.stringify({ to_agent, content }),
90
+ })
91
+ if (!res.ok) {
92
+ const text = await res.text()
93
+ throw new Error(`send failed: ${res.status} ${text.slice(0, 200)}`)
94
+ }
95
+ return await res.json()
96
+ }
97
+
98
+ async function channelReply(message_id: string, content: string): Promise<any> {
99
+ const res = await fetch(`${SERVER}/api/channel/reply`, {
100
+ method: 'POST',
101
+ headers,
102
+ body: JSON.stringify({ message_id, content }),
103
+ })
104
+ if (!res.ok) {
105
+ const text = await res.text()
106
+ throw new Error(`reply failed: ${res.status} ${text.slice(0, 200)}`)
107
+ }
108
+ return await res.json()
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Types
113
+ // ---------------------------------------------------------------------------
114
+
115
+ type PollMessage = {
116
+ id: string
117
+ from_agent: string
118
+ content: string
119
+ created_at: string
120
+ namespace_id: string
121
+ reply_to: string | null
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // MCP Server
126
+ // ---------------------------------------------------------------------------
127
+
128
+ const mcp = new Server(
129
+ { name: 'patchcord', version: '0.1.0' },
130
+ {
131
+ capabilities: {
132
+ experimental: { 'claude/channel': {} },
133
+ tools: {},
134
+ },
135
+ instructions: [
136
+ 'Messages from Patchcord agents arrive as <channel source="patchcord" from="..." message_id="..." namespace="...">.',
137
+ 'Reply with the reply tool, passing message_id from the notification.',
138
+ 'Use send_message to start new conversations with other agents.',
139
+ 'Messages arrive automatically - no need to call inbox or wait_for_message.',
140
+ ].join(' '),
141
+ },
142
+ )
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // Tools
146
+ // ---------------------------------------------------------------------------
147
+
148
+ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
149
+ tools: [
150
+ {
151
+ name: 'reply',
152
+ description: 'Reply to a Patchcord message. Pass message_id from the <channel> notification.',
153
+ inputSchema: {
154
+ type: 'object' as const,
155
+ properties: {
156
+ message_id: { type: 'string', description: 'ID from the <channel> notification' },
157
+ content: { type: 'string', description: 'Reply text (up to 50,000 characters)' },
158
+ },
159
+ required: ['message_id', 'content'],
160
+ },
161
+ },
162
+ {
163
+ name: 'send_message',
164
+ description: 'Send a new message to a Patchcord agent. Use commas for multiple recipients.',
165
+ inputSchema: {
166
+ type: 'object' as const,
167
+ properties: {
168
+ to_agent: { type: 'string', description: 'Target agent name, optionally with @namespace' },
169
+ content: { type: 'string', description: 'Message text (up to 50,000 characters)' },
170
+ },
171
+ required: ['to_agent', 'content'],
172
+ },
173
+ },
174
+ {
175
+ name: 'inbox',
176
+ description: 'Check current Patchcord inbox. Shows pending messages. Normally not needed - messages arrive as push notifications.',
177
+ inputSchema: {
178
+ type: 'object' as const,
179
+ properties: {},
180
+ },
181
+ },
182
+ ],
183
+ }))
184
+
185
+ mcp.setRequestHandler(CallToolRequestSchema, async req => {
186
+ const { name } = req.params
187
+ const args = req.params.arguments as Record<string, string>
188
+
189
+ if (name === 'reply') {
190
+ if (!args.message_id || !args.content) {
191
+ return { content: [{ type: 'text', text: 'Error: message_id and content are required' }] }
192
+ }
193
+ try {
194
+ const result = await channelReply(args.message_id, args.content)
195
+ return { content: [{ type: 'text', text: `Replied to ${result.to_agent} [${result.id}]` }] }
196
+ } catch (err) {
197
+ return { content: [{ type: 'text', text: `Error: ${err}` }] }
198
+ }
199
+ }
200
+
201
+ if (name === 'send_message') {
202
+ if (!args.to_agent || !args.content) {
203
+ return { content: [{ type: 'text', text: 'Error: to_agent and content are required' }] }
204
+ }
205
+ try {
206
+ const result = await channelSend(args.to_agent, args.content)
207
+ return { content: [{ type: 'text', text: `Sent to ${result.to_agent} [${result.id}]` }] }
208
+ } catch (err) {
209
+ return { content: [{ type: 'text', text: `Error: ${err}` }] }
210
+ }
211
+ }
212
+
213
+ if (name === 'inbox') {
214
+ try {
215
+ const messages = await channelPoll()
216
+ if (messages.length === 0) {
217
+ return { content: [{ type: 'text', text: `${agentId}@${namespaceId} | 0 pending` }] }
218
+ }
219
+ const lines = [`${agentId}@${namespaceId} | ${messages.length} pending`]
220
+ for (const msg of messages) {
221
+ lines.push('')
222
+ lines.push(`From ${msg.from_agent} [${msg.id}]`)
223
+ lines.push(` ${msg.content}`)
224
+ // Push as notification too
225
+ await pushMessage(msg)
226
+ }
227
+ return { content: [{ type: 'text', text: lines.join('\n') }] }
228
+ } catch (err) {
229
+ return { content: [{ type: 'text', text: `Error: ${err}` }] }
230
+ }
231
+ }
232
+
233
+ throw new Error(`unknown tool: ${name}`)
234
+ })
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Poll loop
238
+ // ---------------------------------------------------------------------------
239
+
240
+ let consecutiveFailures = 0
241
+ let connectionLostNotified = false
242
+
243
+ async function pushMessage(msg: PollMessage): Promise<void> {
244
+ const meta: Record<string, string> = {
245
+ from: msg.from_agent,
246
+ message_id: msg.id,
247
+ namespace: msg.namespace_id,
248
+ sent_at: msg.created_at,
249
+ }
250
+ if (msg.reply_to) {
251
+ meta.in_reply_to = msg.reply_to
252
+ }
253
+ await mcp.notification({
254
+ method: 'notifications/claude/channel',
255
+ params: { content: msg.content, meta },
256
+ })
257
+ }
258
+
259
+ async function poll(): Promise<void> {
260
+ try {
261
+ const messages = await channelPoll()
262
+ consecutiveFailures = 0
263
+ if (connectionLostNotified) {
264
+ connectionLostNotified = false
265
+ await mcp.notification({
266
+ method: 'notifications/claude/channel',
267
+ params: {
268
+ content: 'Patchcord connection restored.',
269
+ meta: { from: 'system', message_id: 'system' },
270
+ },
271
+ })
272
+ }
273
+ for (const msg of messages) {
274
+ await pushMessage(msg)
275
+ }
276
+ } catch (err) {
277
+ consecutiveFailures++
278
+ process.stderr.write(`patchcord channel: poll error (${consecutiveFailures}): ${err}\n`)
279
+
280
+ if (consecutiveFailures === 1) {
281
+ // Check if it's an auth error
282
+ const errStr = String(err)
283
+ if (errStr.includes('401')) {
284
+ process.stderr.write('patchcord channel: auth failed, stopping poll\n')
285
+ await mcp.notification({
286
+ method: 'notifications/claude/channel',
287
+ params: {
288
+ content: 'Patchcord auth failed. Check your token configuration.',
289
+ meta: { from: 'system', message_id: 'system' },
290
+ },
291
+ })
292
+ clearInterval(pollTimer)
293
+ return
294
+ }
295
+ }
296
+
297
+ if (consecutiveFailures >= 10 && !connectionLostNotified) {
298
+ connectionLostNotified = true
299
+ try {
300
+ await mcp.notification({
301
+ method: 'notifications/claude/channel',
302
+ params: {
303
+ content: 'Patchcord connection lost. Retrying...',
304
+ meta: { from: 'system', message_id: 'system' },
305
+ },
306
+ })
307
+ } catch {
308
+ // notification itself failed, give up
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ // ---------------------------------------------------------------------------
315
+ // Startup
316
+ // ---------------------------------------------------------------------------
317
+
318
+ // Resolve identity first
319
+ try {
320
+ await resolveIdentity()
321
+ } catch (err) {
322
+ process.stderr.write(`patchcord channel: failed to connect: ${err}\n`)
323
+ process.stderr.write('patchcord channel: will retry on first poll\n')
324
+ }
325
+
326
+ // Connect MCP over stdio
327
+ await mcp.connect(new StdioServerTransport())
328
+
329
+ // Drain existing messages, then start polling
330
+ await poll()
331
+ const pollTimer = setInterval(poll, POLL_INTERVAL_MS)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.59",
3
+ "version": "0.3.61",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "files": [
24
24
  "bin/",
25
+ "channel/",
25
26
  ".claude-plugin/",
26
27
  "hooks/",
27
28
  "scripts/",
@@ -87,5 +87,6 @@ Deferred messages survive context compaction — the agent won't forget them.
87
87
  - If user says "check" or "check patchcord" — call inbox().
88
88
  - Presence is not a send or delivery gate. Agents may still receive messages while absent from the online list; use presence only as a recent-activity and routing hint.
89
89
  - send_message() is blocked by unread inbox items, not by offline status. If sending is blocked, clear actionable inbox items first.
90
- - Resolve machine names to agent_ids from inbox() results.
90
+ - Resolve machine names to agent_ids from inbox() results. The human operator is always `human` - never use their real name.
91
+ - Only send to agents that exist in your inbox online list. Don't guess agent names.
91
92
  - Do NOT reply to messages that don't need a response: acks, "ok", "noted", "seen", "👍", confirmations, thumbs up, "thanks", or anything that is clearly a conversation-ending signal. Just read them and move on. Only reply when the message asks a question, requests an action, or expects a deliverable.