patchcord 0.3.95 → 0.3.97
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/bin/patchcord.mjs +83 -20
- package/package.json +1 -1
- package/per-project-skills/codex/SKILL.md +20 -8
- package/per-project-skills/web/SKILL.md +19 -8
- package/skills/{inbox → patchcord}/SKILL.md +16 -5
- package/skills/{wait → patchcord-wait}/SKILL.md +3 -3
package/bin/patchcord.mjs
CHANGED
|
@@ -173,12 +173,12 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
173
173
|
let cursorChanged = false;
|
|
174
174
|
if (!existsSync(cursorSkillDir)) {
|
|
175
175
|
mkdirSync(cursorSkillDir, { recursive: true });
|
|
176
|
-
cpSync(join(pluginRoot, "skills", "
|
|
176
|
+
cpSync(join(pluginRoot, "skills", "patchcord", "SKILL.md"), join(cursorSkillDir, "SKILL.md"));
|
|
177
177
|
cursorChanged = true;
|
|
178
178
|
}
|
|
179
179
|
if (!existsSync(cursorWaitDir)) {
|
|
180
180
|
mkdirSync(cursorWaitDir, { recursive: true });
|
|
181
|
-
cpSync(join(pluginRoot, "skills", "wait", "SKILL.md"), join(cursorWaitDir, "SKILL.md"));
|
|
181
|
+
cpSync(join(pluginRoot, "skills", "patchcord-wait", "SKILL.md"), join(cursorWaitDir, "SKILL.md"));
|
|
182
182
|
cursorChanged = true;
|
|
183
183
|
}
|
|
184
184
|
if (cursorChanged) globalChanges.push("Cursor skills installed");
|
|
@@ -191,12 +191,12 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
191
191
|
let windsurfChanged = false;
|
|
192
192
|
if (!existsSync(windsurfSkillDir)) {
|
|
193
193
|
mkdirSync(windsurfSkillDir, { recursive: true });
|
|
194
|
-
cpSync(join(pluginRoot, "skills", "
|
|
194
|
+
cpSync(join(pluginRoot, "skills", "patchcord", "SKILL.md"), join(windsurfSkillDir, "SKILL.md"));
|
|
195
195
|
windsurfChanged = true;
|
|
196
196
|
}
|
|
197
197
|
if (!existsSync(windsurfWaitDir)) {
|
|
198
198
|
mkdirSync(windsurfWaitDir, { recursive: true });
|
|
199
|
-
cpSync(join(pluginRoot, "skills", "wait", "SKILL.md"), join(windsurfWaitDir, "SKILL.md"));
|
|
199
|
+
cpSync(join(pluginRoot, "skills", "patchcord-wait", "SKILL.md"), join(windsurfWaitDir, "SKILL.md"));
|
|
200
200
|
windsurfChanged = true;
|
|
201
201
|
}
|
|
202
202
|
if (windsurfChanged) globalChanges.push("Windsurf skills installed");
|
|
@@ -210,12 +210,12 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
210
210
|
let geminiChanged = false;
|
|
211
211
|
if (!existsSync(geminiSkillDir)) {
|
|
212
212
|
mkdirSync(geminiSkillDir, { recursive: true });
|
|
213
|
-
cpSync(join(pluginRoot, "skills", "
|
|
213
|
+
cpSync(join(pluginRoot, "skills", "patchcord", "SKILL.md"), join(geminiSkillDir, "SKILL.md"));
|
|
214
214
|
geminiChanged = true;
|
|
215
215
|
}
|
|
216
216
|
if (!existsSync(geminiWaitDir)) {
|
|
217
217
|
mkdirSync(geminiWaitDir, { recursive: true });
|
|
218
|
-
cpSync(join(pluginRoot, "skills", "wait", "SKILL.md"), join(geminiWaitDir, "SKILL.md"));
|
|
218
|
+
cpSync(join(pluginRoot, "skills", "patchcord-wait", "SKILL.md"), join(geminiWaitDir, "SKILL.md"));
|
|
219
219
|
geminiChanged = true;
|
|
220
220
|
}
|
|
221
221
|
if (!existsSync(join(geminiCmdDir, "patchcord.toml"))) {
|
|
@@ -264,6 +264,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
264
264
|
const CLIENT_TYPE_MAP = {
|
|
265
265
|
"claude_code": "1", "codex": "2", "cursor": "3", "windsurf": "4",
|
|
266
266
|
"gemini": "5", "vscode": "6", "zed": "7", "opencode": "8", "openclaw": "9", "antigravity": "10",
|
|
267
|
+
"cline": "11",
|
|
267
268
|
};
|
|
268
269
|
|
|
269
270
|
|
|
@@ -298,9 +299,9 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
298
299
|
console.log(` ${cyan}2.${r} Codex CLI ${cyan}6.${r} VS Code`);
|
|
299
300
|
console.log(` ${cyan}3.${r} Cursor ${cyan}7.${r} Zed`);
|
|
300
301
|
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode`);
|
|
301
|
-
console.log(`
|
|
302
|
-
choice = (await ask(`${dim}Choose (1-9):${r} `)).trim();
|
|
303
|
-
if (!["1","2","3","4","5","6","7","8","9"].includes(choice)) {
|
|
302
|
+
console.log(` ${cyan}11.${r} Cline ${cyan}9.${r} OpenClaw\n`);
|
|
303
|
+
choice = (await ask(`${dim}Choose (1-9, 11):${r} `)).trim();
|
|
304
|
+
if (!["1","2","3","4","5","6","7","8","9","11"].includes(choice)) {
|
|
304
305
|
console.error("Invalid choice.");
|
|
305
306
|
rl.close();
|
|
306
307
|
process.exit(1);
|
|
@@ -566,6 +567,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
566
567
|
const isOpenCode = choice === "8";
|
|
567
568
|
const isOpenClaw = choice === "9";
|
|
568
569
|
const isAntigravity = choice === "10";
|
|
570
|
+
const isCline = choice === "11";
|
|
569
571
|
|
|
570
572
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
571
573
|
|
|
@@ -765,10 +767,68 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
765
767
|
const agWaitDir = join(agDir, "skills", "patchcord-wait");
|
|
766
768
|
mkdirSync(agSkillDir, { recursive: true });
|
|
767
769
|
mkdirSync(agWaitDir, { recursive: true });
|
|
768
|
-
cpSync(join(pluginRoot, "skills", "
|
|
769
|
-
cpSync(join(pluginRoot, "skills", "wait", "SKILL.md"), join(agWaitDir, "SKILL.md"));
|
|
770
|
+
cpSync(join(pluginRoot, "skills", "patchcord", "SKILL.md"), join(agSkillDir, "SKILL.md"));
|
|
771
|
+
cpSync(join(pluginRoot, "skills", "patchcord-wait", "SKILL.md"), join(agWaitDir, "SKILL.md"));
|
|
770
772
|
console.log(` ${green}✓${r} Skills installed: ${dim}patchcord${r}, ${dim}patchcord-wait${r}`);
|
|
771
773
|
console.log(` ${yellow}Global config — all Antigravity projects share this agent.${r}`);
|
|
774
|
+
} else if (isCline) {
|
|
775
|
+
// Cline VS Code extension: global cline_mcp_settings.json
|
|
776
|
+
// Config lives in VS Code's globalStorage for saoudrizwan.claude-dev
|
|
777
|
+
// Try stable VS Code first, then Insiders, then Cursor
|
|
778
|
+
const vsCodeVariants = process.platform === "darwin"
|
|
779
|
+
? [
|
|
780
|
+
join(HOME, "Library", "Application Support", "Code", "User", "globalStorage"),
|
|
781
|
+
join(HOME, "Library", "Application Support", "Code - Insiders", "User", "globalStorage"),
|
|
782
|
+
join(HOME, "Library", "Application Support", "Cursor", "User", "globalStorage"),
|
|
783
|
+
]
|
|
784
|
+
: process.platform === "win32"
|
|
785
|
+
? [
|
|
786
|
+
join(process.env.APPDATA || join(HOME, "AppData", "Roaming"), "Code", "User", "globalStorage"),
|
|
787
|
+
join(process.env.APPDATA || join(HOME, "AppData", "Roaming"), "Code - Insiders", "User", "globalStorage"),
|
|
788
|
+
join(process.env.APPDATA || join(HOME, "AppData", "Roaming"), "Cursor", "User", "globalStorage"),
|
|
789
|
+
]
|
|
790
|
+
: [
|
|
791
|
+
join(HOME, ".config", "Code", "User", "globalStorage"),
|
|
792
|
+
join(HOME, ".config", "Code - Insiders", "User", "globalStorage"),
|
|
793
|
+
join(HOME, ".config", "Cursor", "User", "globalStorage"),
|
|
794
|
+
];
|
|
795
|
+
|
|
796
|
+
// Find the first variant that has Cline's globalStorage directory (or exists at all)
|
|
797
|
+
const clineExtDir = "saoudrizwan.claude-dev";
|
|
798
|
+
let clineSettingsDir = null;
|
|
799
|
+
for (const base of vsCodeVariants) {
|
|
800
|
+
const candidate = join(base, clineExtDir, "settings");
|
|
801
|
+
const extDir = join(base, clineExtDir);
|
|
802
|
+
if (existsSync(extDir)) {
|
|
803
|
+
clineSettingsDir = candidate;
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// Fall back to stable VS Code path even if it doesn't exist yet
|
|
808
|
+
if (!clineSettingsDir) {
|
|
809
|
+
const fallbackBase = vsCodeVariants[0];
|
|
810
|
+
clineSettingsDir = join(fallbackBase, clineExtDir, "settings");
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const clinePath = join(clineSettingsDir, "cline_mcp_settings.json");
|
|
814
|
+
let clineConfig = {};
|
|
815
|
+
if (existsSync(clinePath)) {
|
|
816
|
+
try { clineConfig = JSON.parse(readFileSync(clinePath, "utf-8")); } catch {}
|
|
817
|
+
}
|
|
818
|
+
if (!clineConfig.mcpServers) clineConfig.mcpServers = {};
|
|
819
|
+
clineConfig.mcpServers.patchcord = {
|
|
820
|
+
url: `${serverUrl}/mcp`,
|
|
821
|
+
headers: {
|
|
822
|
+
Authorization: `Bearer ${token}`,
|
|
823
|
+
"X-Patchcord-Machine": hostname,
|
|
824
|
+
},
|
|
825
|
+
disabled: false,
|
|
826
|
+
alwaysAllow: [],
|
|
827
|
+
};
|
|
828
|
+
mkdirSync(clineSettingsDir, { recursive: true });
|
|
829
|
+
writeFileSync(clinePath, JSON.stringify(clineConfig, null, 2) + "\n");
|
|
830
|
+
console.log(`\n ${green}✓${r} Cline configured: ${dim}${clinePath}${r}`);
|
|
831
|
+
console.log(` ${yellow}Global config — all Cline projects share this agent.${r}`);
|
|
772
832
|
} else if (isVSCode) {
|
|
773
833
|
// VS Code: write .vscode/mcp.json (per-project)
|
|
774
834
|
const vscodeDir = join(cwd, ".vscode");
|
|
@@ -811,7 +871,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
811
871
|
const waitDest = join(cwd, ".agents", "skills", "patchcord-wait");
|
|
812
872
|
mkdirSync(waitDest, { recursive: true });
|
|
813
873
|
writeFileSync(join(waitDest, "SKILL.md"),
|
|
814
|
-
readFileSync(join(pluginRoot, "skills", "wait", "SKILL.md"), "utf-8"));
|
|
874
|
+
readFileSync(join(pluginRoot, "skills", "patchcord-wait", "SKILL.md"), "utf-8"));
|
|
815
875
|
|
|
816
876
|
const codexDir = join(cwd, ".codex");
|
|
817
877
|
mkdirSync(codexDir, { recursive: true });
|
|
@@ -873,7 +933,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
873
933
|
writeFileSync(join(pluginDir, "skills", "patchcord", "SKILL.md"),
|
|
874
934
|
readFileSync(join(pluginRoot, "per-project-skills", "codex", "SKILL.md"), "utf-8"));
|
|
875
935
|
writeFileSync(join(pluginDir, "skills", "patchcord-wait", "SKILL.md"),
|
|
876
|
-
readFileSync(join(pluginRoot, "skills", "wait", "SKILL.md"), "utf-8"));
|
|
936
|
+
readFileSync(join(pluginRoot, "skills", "patchcord-wait", "SKILL.md"), "utf-8"));
|
|
877
937
|
|
|
878
938
|
// Personal marketplace entry (relative path from marketplace root)
|
|
879
939
|
const marketplacePath = join(marketplaceDir, "marketplace.json");
|
|
@@ -899,7 +959,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
899
959
|
const globalWaitDir = join(homedir(), ".agents", "skills", "patchcord-wait");
|
|
900
960
|
mkdirSync(globalWaitDir, { recursive: true });
|
|
901
961
|
writeFileSync(join(globalWaitDir, "SKILL.md"),
|
|
902
|
-
readFileSync(join(pluginRoot, "skills", "wait", "SKILL.md"), "utf-8"));
|
|
962
|
+
readFileSync(join(pluginRoot, "skills", "patchcord-wait", "SKILL.md"), "utf-8"));
|
|
903
963
|
|
|
904
964
|
console.log(`\n ${green}✓${r} Codex configured: ${dim}${configPath}${r}`);
|
|
905
965
|
console.log(` ${green}✓${r} Plugin installed: ${dim}@patchcord${r}, ${dim}@patchcord-wait${r}`);
|
|
@@ -944,24 +1004,27 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
944
1004
|
}
|
|
945
1005
|
|
|
946
1006
|
// Warn about gitignore for per-project configs with tokens
|
|
947
|
-
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isAntigravity) {
|
|
1007
|
+
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isAntigravity && !isCline) {
|
|
948
1008
|
const gitignorePath = join(cwd, ".gitignore");
|
|
949
1009
|
const configFile = isCodex ? ".codex/config.toml" : isCursor ? ".cursor/mcp.json" : isVSCode ? ".vscode/mcp.json" : isOpenCode ? "opencode.json" : ".mcp.json";
|
|
950
1010
|
let needsWarning = true;
|
|
951
1011
|
if (existsSync(gitignorePath)) {
|
|
952
1012
|
const gi = readFileSync(gitignorePath, "utf-8");
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1013
|
+
// Only check patterns relevant to this agent's config file
|
|
1014
|
+
const patterns = [configFile];
|
|
1015
|
+
if (isCodex) patterns.push(".codex/");
|
|
1016
|
+
else if (isCursor) patterns.push(".cursor/");
|
|
1017
|
+
else if (isVSCode) patterns.push(".vscode/");
|
|
1018
|
+
if (patterns.some(p => gi.includes(p))) needsWarning = false;
|
|
956
1019
|
}
|
|
957
1020
|
if (needsWarning) {
|
|
958
1021
|
console.log(`\n ${yellow}⚠ Add ${configFile} to .gitignore — it contains your token${r}`);
|
|
959
1022
|
}
|
|
960
1023
|
}
|
|
961
1024
|
|
|
962
|
-
const toolName = isAntigravity ? "Antigravity" : isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
1025
|
+
const toolName = isAntigravity ? "Antigravity" : isCline ? "Cline" : isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
963
1026
|
|
|
964
|
-
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isAntigravity) {
|
|
1027
|
+
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isAntigravity && !isCline) {
|
|
965
1028
|
console.log(`\n ${dim}To connect a second agent:${r}`);
|
|
966
1029
|
console.log(` ${dim}cd into another project and run${r} ${bold}npx patchcord@latest${r} ${dim}there.${r}`);
|
|
967
1030
|
}
|
package/package.json
CHANGED
|
@@ -15,12 +15,12 @@ project's MCP config.
|
|
|
15
15
|
|
|
16
16
|
## Tools available
|
|
17
17
|
|
|
18
|
-
- `inbox(all_agents?)` - read pending messages, current identity, and recently active agents. `all_agents=true` includes inactive agents. Presence tells you whether to wait for a reply after sending, not whether to send.
|
|
19
|
-
- `send_message(to_agent, content)` - send a message. Comma-separated for multiple: `send_message("backend, frontend", "hello")`. Use `@username` for cross-user Gate messaging. Messages support up to 50,000 characters - send full content, specs, and code as-is. Never summarize or truncate.
|
|
20
|
-
- `reply(message_id, content?, defer?, resolve?)` - reply to a received message. `defer=true` keeps the original visible in inbox for later (survives context compaction). `resolve=true`
|
|
18
|
+
- `inbox(all_agents?)` - read pending messages, current identity, and recently active agents. `all_agents=true` includes inactive agents. Returns a `groups` list (messages grouped by thread) alongside the legacy `pending` flat list. Presence tells you whether to wait for a reply after sending, not whether to send.
|
|
19
|
+
- `send_message(to_agent, content, thread?)` - send a message. Comma-separated for multiple: `send_message("backend, frontend", "hello")`. Use `@username` for cross-user Gate messaging. `thread` is an optional slug to start or join a named thread: `send_message("backend", "...", thread="auth-migration")`. Messages support up to 50,000 characters - send full content, specs, and code as-is. Never summarize or truncate.
|
|
20
|
+
- `reply(message_id, content?, defer?, resolve?)` - reply to a received message. Auto-inherits the thread of the original. `defer=true` keeps the original visible in inbox for later (survives context compaction). `resolve=true` closes the thread — stamps `thread_resolved_at` and notifies sender. Content is optional: use `reply(message_id, resolve=true)` to silently close without sending.
|
|
21
21
|
- `wait_for_message(timeout_seconds?)` - block until incoming message arrives. Default 5 minutes. Known to error intermittently - if it fails, poll inbox() every 10-15 seconds as fallback.
|
|
22
22
|
- `attachment(...)` - upload, download, or relay files between agents (see File sharing below)
|
|
23
|
-
- `recall(limit?, from_agent?)` - view recent message history including already-read messages. `from_agent` filters by sender. For debugging only, not routine use.
|
|
23
|
+
- `recall(limit?, 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.
|
|
24
24
|
- `unsend(message_id)` - take back a message before the recipient reads it
|
|
25
25
|
|
|
26
26
|
## Do the work, never just acknowledge
|
|
@@ -49,10 +49,21 @@ If there are pending actionable messages:
|
|
|
49
49
|
|
|
50
50
|
Do not ask the user for permission to reply unless the requested action is destructive or requires secrets you do not have.
|
|
51
51
|
|
|
52
|
+
## Threads
|
|
53
|
+
|
|
54
|
+
Named threads group related messages between a pair of agents. Use them for multi-turn tasks that need their own context.
|
|
55
|
+
|
|
56
|
+
- **Start**: `send_message("backend", "track this here", thread="deploy-review")`
|
|
57
|
+
- **Reply stays in thread automatically** — `reply()` inherits `thread_id` from the message you're replying to.
|
|
58
|
+
- **Close**: `reply(message_id, "done", resolve=true)` — closes the thread and notifies sender.
|
|
59
|
+
- **Filter history**: `recall(thread_id="<uuid>")` — only messages in that thread.
|
|
60
|
+
|
|
61
|
+
`inbox()` `groups` field clusters pending messages by thread. Each group: `{ thread_id, thread_title, messages }`. `thread_id: null` = pair-level.
|
|
62
|
+
|
|
52
63
|
## Sending workflow
|
|
53
64
|
|
|
54
65
|
1. `inbox()` - clear pending messages that block outbound sends. Note who's online (determines whether to wait after sending).
|
|
55
|
-
2. `send_message("agent", "specific question with paths and context")` - or `"agent1, agent2"` for multiple, or `"@username"` for cross-user Gate messaging.
|
|
66
|
+
2. `send_message("agent", "specific question with paths and context")` - or `"agent1, agent2"` for multiple, or `"@username"` for cross-user Gate messaging. Add `thread="slug"` to group messages in a named thread.
|
|
56
67
|
3. If recipient is online: `wait_for_message()` - stay responsive for the response. If offline: skip the wait, tell the human the message is queued.
|
|
57
68
|
|
|
58
69
|
Always send regardless of online/offline status. Messages are stored and delivered when the recipient checks inbox. Never refuse to send because an agent appears offline.
|
|
@@ -63,11 +74,12 @@ If send_message fails with a send gate error: call inbox(), reply to or resolve
|
|
|
63
74
|
|
|
64
75
|
## Receiving workflow
|
|
65
76
|
|
|
66
|
-
1. Read the message from `inbox()` or `wait_for_message()`
|
|
77
|
+
1. Read the message from `inbox()` or `wait_for_message()`. Check `message.thread` / `message.thread_id` if present.
|
|
67
78
|
2. Do the work - use real code, real files, real results from your project
|
|
68
79
|
3. Reply with the right flag:
|
|
69
|
-
- `reply(message_id, "done: [details]")` — work done, sender might follow up
|
|
70
|
-
- `reply(message_id, "done: [details]", resolve=true)` — work done,
|
|
80
|
+
- `reply(message_id, "done: [details]")` — work done, sender might follow up. Thread auto-inherited.
|
|
81
|
+
- `reply(message_id, "done: [details]", resolve=true)` — work done, thread closed.
|
|
82
|
+
- `reply(message_id, resolve=true)` — silently close without sending anything.
|
|
71
83
|
- `reply(message_id, "ack, prioritizing [other task] first", defer=true)` — acknowledged but work not done yet. Message stays in your inbox as a reminder.
|
|
72
84
|
4. If sender is online: `wait_for_message()` for follow-ups
|
|
73
85
|
|
|
@@ -11,12 +11,12 @@ You are connected to Patchcord, a message bus that lets you talk to AI agents on
|
|
|
11
11
|
|
|
12
12
|
## Tools
|
|
13
13
|
|
|
14
|
-
- **inbox(all_agents?)** - read pending messages + recent activity. `all_agents=true` includes inactive agents. Presence tells you whether to wait for a reply, not whether to send.
|
|
15
|
-
- **send_message(to_agent, content)** - send a message. Comma-separated for multiple: `send_message("backend, frontend", "hello")`. Supports `@username` for cross-user Gate messaging. Up to 50,000 characters
|
|
16
|
-
- **reply(message_id, content?, defer?, resolve?)** - reply to a received message. `defer=true` keeps message visible in inbox (survives context compaction). `resolve=true`
|
|
14
|
+
- **inbox(all_agents?)** - read pending messages + recent activity. Returns a `groups` list (messages grouped by thread) alongside the legacy `pending` flat list. `all_agents=true` includes inactive agents. Presence tells you whether to wait for a reply, not whether to send.
|
|
15
|
+
- **send_message(to_agent, content, thread?)** - send a message. Comma-separated for multiple: `send_message("backend, frontend", "hello")`. Supports `@username` for cross-user Gate messaging. `thread` slug starts or joins a named thread: `send_message("backend", "...", thread="deploy-review")`. Up to 50,000 characters — never summarize.
|
|
16
|
+
- **reply(message_id, content?, defer?, resolve?)** - reply to a received message. Auto-inherits thread from the original. `defer=true` keeps message visible in inbox (survives context compaction). `resolve=true` closes the thread — stamps `thread_resolved_at` and notifies sender. Content optional: `reply(message_id, resolve=true)` to silently close.
|
|
17
17
|
- **wait_for_message(timeout_seconds?)** - block until incoming message arrives. Default 300s. Known to error intermittently - if it fails, poll inbox() in a loop as fallback.
|
|
18
18
|
- **attachment(...)** - file operations (see File sharing section below)
|
|
19
|
-
- **recall(limit?, from_agent?)** - view recent message history including already-read messages.
|
|
19
|
+
- **recall(limit?, from_agent?, thread_id?)** - view recent message history including already-read messages. `from_agent` filters by sender. `thread_id` filters to a specific thread. Debugging only.
|
|
20
20
|
- **unsend(message_id)** - take back a message before recipient reads it.
|
|
21
21
|
|
|
22
22
|
## Chat identification
|
|
@@ -61,10 +61,21 @@ Use the dominant topic of your current conversation as the tag. Keep it short (1
|
|
|
61
61
|
|
|
62
62
|
10. **MCP tools are cached at session start.** New tools deployed after your session began are invisible until you open a new chat. If a tool you expect is missing, this is why.
|
|
63
63
|
|
|
64
|
+
## Threads
|
|
65
|
+
|
|
66
|
+
Named threads group related messages. Use them for multi-turn tasks that need their own context.
|
|
67
|
+
|
|
68
|
+
- **Start**: `send_message("backend", "...", thread="auth-migration")`
|
|
69
|
+
- **Reply stays in thread automatically** — `reply()` inherits the thread from the original.
|
|
70
|
+
- **Close**: `reply(message_id, "done", resolve=true)` — closes thread, notifies sender.
|
|
71
|
+
- **Filter history**: `recall(thread_id="<uuid>")` — only that thread's messages.
|
|
72
|
+
|
|
73
|
+
`inbox()` `groups` field clusters pending messages by thread: `{ thread_id, thread_title, messages }`. `thread_id: null` = pair-level (no thread).
|
|
74
|
+
|
|
64
75
|
## Sending workflow
|
|
65
76
|
|
|
66
77
|
1. inbox() - clear pending messages that block outbound sends. Note who's online (determines whether to wait after sending).
|
|
67
|
-
2. send_message("agent_name", "[your-chat-tag] your question with context") - or "agent1, agent2" for multiple, or "@username" for cross-user
|
|
78
|
+
2. send_message("agent_name", "[your-chat-tag] your question with context") - or "agent1, agent2" for multiple, or "@username" for cross-user. Add `thread="slug"` to group in a named thread.
|
|
68
79
|
3. If recipient is online: wait_for_message() - block until response arrives. If offline: skip wait, tell the human the message is queued.
|
|
69
80
|
|
|
70
81
|
ALWAYS send regardless of online/offline status. Messages are stored and delivered when the recipient checks inbox. Never refuse to send because an agent appears offline.
|
|
@@ -73,11 +84,11 @@ After sending to an offline agent, tell the human: "Message sent. [agent] is not
|
|
|
73
84
|
|
|
74
85
|
## Receiving workflow
|
|
75
86
|
|
|
76
|
-
1. Read messages from inbox()
|
|
87
|
+
1. Read messages from inbox(). Check `message.thread` / `message.thread_id` if present.
|
|
77
88
|
2. Check the context tag - is this for your chat?
|
|
78
89
|
3. If yes: do the work, then reply with the right flag:
|
|
79
|
-
- `reply(message_id, "[tag] done: [details]")` — work done,
|
|
80
|
-
- `reply(message_id, "[tag] done", resolve=true)` — work done,
|
|
90
|
+
- `reply(message_id, "[tag] done: [details]")` — work done, thread auto-inherited
|
|
91
|
+
- `reply(message_id, "[tag] done", resolve=true)` — work done, thread closed
|
|
81
92
|
- `reply(message_id, "[tag] ack, will do after [other task]", defer=true)` — acknowledged but work not done yet. Message stays in inbox.
|
|
82
93
|
4. If no: reply(message_id, "For [other-tag] chat", defer=true)
|
|
83
94
|
5. wait_for_message() - stay responsive for follow-ups
|
|
@@ -39,7 +39,7 @@ If there are pending messages, reply to all of them immediately. Do not ask the
|
|
|
39
39
|
## Sending
|
|
40
40
|
|
|
41
41
|
1. inbox() - clear any pending messages that block outbound sends. Note who's online (determines whether to wait after sending, not whether to send).
|
|
42
|
-
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.
|
|
42
|
+
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")`.
|
|
43
43
|
3. If recipient is online: wait_for_message() - block until response arrives. Use the default timeout (300s) - you get the message instantly when it arrives, not after the timeout. The other agent needs time to do the work and reply. Never shorten the timeout. If offline: skip the wait, tell the human the message is queued.
|
|
44
44
|
|
|
45
45
|
Always send regardless of whether the recipient appears online or offline. Messages are stored and delivered when the recipient checks inbox. "Offline" means not recently active - not that they can't receive messages.
|
|
@@ -50,11 +50,11 @@ If send_message fails with a send gate error: call inbox(), reply to or resolve
|
|
|
50
50
|
|
|
51
51
|
## Receiving (inbox has messages)
|
|
52
52
|
|
|
53
|
-
1. Read the message
|
|
53
|
+
1. Read the message. If it belongs to a thread, `message.thread` and `message.thread_id` will be present.
|
|
54
54
|
2. Do the work described in the message - using your project's actual code, real files, real lines
|
|
55
55
|
3. Reply with what you did, choosing the right flag:
|
|
56
|
-
- `reply(message_id, "done: [details]")` — work done, sender might follow up
|
|
57
|
-
- `reply(message_id, "done: [details]", resolve=true)` — work done,
|
|
56
|
+
- `reply(message_id, "done: [details]")` — work done, sender might follow up. Thread is auto-inherited.
|
|
57
|
+
- `reply(message_id, "done: [details]", resolve=true)` — work done, thread closed. Stamps `thread_resolved_at` and notifies sender.
|
|
58
58
|
- `reply(message_id, resolve=true)` — silently close a thread without sending anything (e.g. clearing misfired messages)
|
|
59
59
|
- `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.
|
|
60
60
|
4. wait_for_message() if the sender is online - stay responsive for follow-ups
|
|
@@ -97,9 +97,20 @@ Use the path from the sender's message.
|
|
|
97
97
|
|
|
98
98
|
Send the returned `path` to the other agent in your message so they can download it.
|
|
99
99
|
|
|
100
|
+
## Threads
|
|
101
|
+
|
|
102
|
+
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").
|
|
103
|
+
|
|
104
|
+
- **Start a thread**: `send_message("backend", "let's track this here", thread="auth-migration")`
|
|
105
|
+
- **Reply stays in thread automatically**: `reply()` inherits `thread_id` from the message you're replying to — no extra param needed.
|
|
106
|
+
- **Close a thread**: `reply(message_id, "done", resolve=true)` — stamps `thread_resolved_at` and notifies sender.
|
|
107
|
+
- **View thread history**: `recall(thread_id="<uuid>")` — filters history to one thread.
|
|
108
|
+
|
|
109
|
+
`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.
|
|
110
|
+
|
|
100
111
|
## Other tools
|
|
101
112
|
|
|
102
|
-
- recall(limit=10, from_agent="") - view recent message history including already-read messages.
|
|
113
|
+
- 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.
|
|
103
114
|
- unsend(message_id) - take back a message before the recipient reads it.
|
|
104
115
|
|
|
105
116
|
## Rules
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: patchcord-wait
|
|
3
3
|
description: >
|
|
4
4
|
Enter listening mode - wait for incoming patchcord messages. Use when user
|
|
5
5
|
says "wait", "listen", "stand by", or wants the agent to stay responsive
|
|
@@ -11,9 +11,9 @@ Enter listening mode. Call `wait_for_message()` to block until a message arrives
|
|
|
11
11
|
|
|
12
12
|
When a message arrives:
|
|
13
13
|
|
|
14
|
-
1. Read it
|
|
14
|
+
1. Read it — the tool returns from, content, and message_id. If it belongs to a thread, `thread` and `thread_id` will be set.
|
|
15
15
|
2. Do the work described in the message first. Update the file, write the code, fix the bug - whatever it asks.
|
|
16
|
-
3. Reply with what you did: `reply(message_id, "here's what I changed: [concrete details]")
|
|
16
|
+
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.
|
|
17
17
|
4. Tell the human who wrote and what you did about it
|
|
18
18
|
5. Call `wait_for_message()` again to keep listening
|
|
19
19
|
|