patchcord 0.5.80 → 0.5.82
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
|
@@ -46,7 +46,7 @@ const PROJECT_MARKERS = [
|
|
|
46
46
|
".git", "package.json", "package-lock.json", "Cargo.toml", "go.mod", "go.sum",
|
|
47
47
|
"pyproject.toml", "pom.xml", "build.gradle", "Makefile", "CMakeLists.txt",
|
|
48
48
|
"Gemfile", "composer.json", "mix.exs", "requirements.txt", "setup.py",
|
|
49
|
-
".claude", ".codex", ".cursor", ".vscode", ".openclaw",
|
|
49
|
+
".claude", ".codex", ".cursor", ".vscode", ".kimi", ".openclaw",
|
|
50
50
|
];
|
|
51
51
|
|
|
52
52
|
function detectFolder(dir) {
|
|
@@ -97,8 +97,19 @@ if (cmd === "plugin-path") {
|
|
|
97
97
|
// Supports all 12 installer targets: claude_code, codex, cursor, vscode,
|
|
98
98
|
// opencode (per-project) + windsurf, gemini, zed, openclaw, antigravity,
|
|
99
99
|
// cline, kimi (global). Each tool stores the bearer in its own shape.
|
|
100
|
-
|
|
100
|
+
function _kimiContextRequested(options = {}) {
|
|
101
|
+
const forceTool = (process.env.PATCHCORD_FORCE_TOOL || process.env.PATCHCORD_TOOL || "").toLowerCase();
|
|
102
|
+
return options.preferKimi === true
|
|
103
|
+
|| forceTool === "kimi"
|
|
104
|
+
|| Boolean(process.env.KIMI_CLI)
|
|
105
|
+
|| Boolean(process.env.KIMI_SESSION_ID)
|
|
106
|
+
|| Boolean(process.env.KIMI_CONFIG_FILE)
|
|
107
|
+
|| Boolean(process.env.KIMI_MCP_CONFIG_FILE);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function _resolveBearer(options = {}) {
|
|
101
111
|
const { readFileSync } = await import("fs");
|
|
112
|
+
const preferKimi = _kimiContextRequested(options);
|
|
102
113
|
|
|
103
114
|
// Strict JSON first; fall back to JSONC stripping for zed/gemini-style
|
|
104
115
|
// settings (which allow // /* */ trailing commas). The fallback strips
|
|
@@ -188,15 +199,17 @@ async function _resolveBearer() {
|
|
|
188
199
|
};
|
|
189
200
|
|
|
190
201
|
// Per-project (walk up from cwd). First win.
|
|
191
|
-
const
|
|
202
|
+
const kimiProjectReader = (cwd) => readJsonAt(join(cwd, ".kimi", "mcp.json"), ["mcpServers", "patchcord"], "kimi");
|
|
203
|
+
const defaultProjectReaders = [
|
|
192
204
|
(cwd) => readJsonAt(join(cwd, ".mcp.json"), ["mcpServers", "patchcord"], "claude_code"),
|
|
193
205
|
(cwd) => readJsonAt(join(cwd, ".cursor", "mcp.json"), ["mcpServers", "patchcord"], "cursor"),
|
|
194
206
|
(cwd) => readJsonAt(join(cwd, ".vscode", "mcp.json"), ["servers", "patchcord"], "vscode"),
|
|
195
207
|
(cwd) => readJsonAt(join(cwd, "opencode.json"), ["mcp", "patchcord"], "opencode"),
|
|
196
208
|
(cwd) => readCodexTomlShape(join(cwd, ".codex", "config.toml")),
|
|
197
|
-
// Kimi can be per-project via .kimi/mcp.json + --mcp-config-file
|
|
198
|
-
(cwd) => readJsonAt(join(cwd, ".kimi", "mcp.json"), ["mcpServers", "patchcord"], "kimi"),
|
|
199
209
|
];
|
|
210
|
+
const projectReaders = preferKimi
|
|
211
|
+
? [kimiProjectReader, ...defaultProjectReaders]
|
|
212
|
+
: [...defaultProjectReaders, kimiProjectReader];
|
|
200
213
|
let dir = process.cwd();
|
|
201
214
|
while (dir && dir !== "/") {
|
|
202
215
|
for (const r of projectReaders) {
|
|
@@ -235,16 +248,18 @@ async function _resolveBearer() {
|
|
|
235
248
|
);
|
|
236
249
|
})();
|
|
237
250
|
|
|
238
|
-
const
|
|
251
|
+
const kimiGlobalReader = () => readJsonAt(join(HOME, ".kimi", "mcp.json"), ["mcpServers", "patchcord"], "kimi");
|
|
252
|
+
const defaultGlobalCandidates = [
|
|
239
253
|
() => readJsonAt(join(HOME, ".codeium", "windsurf", "mcp_config.json"), ["mcpServers", "patchcord"], "windsurf"),
|
|
240
254
|
() => readJsonAt(join(HOME, ".gemini", "settings.json"), ["mcpServers", "patchcord"], "gemini"),
|
|
241
255
|
() => readJsonAt(zedPath, ["context_servers", "patchcord"], "zed"),
|
|
242
256
|
() => readJsonAt(join(HOME, ".openclaw", "openclaw.json"), ["mcp", "servers", "patchcord"], "openclaw"),
|
|
243
257
|
() => readJsonAt(join(HOME, ".gemini", "antigravity", "mcp_config.json"), ["mcpServers", "patchcord"], "antigravity"),
|
|
244
258
|
...clinePaths.map((p) => () => readJsonAt(p, ["mcpServers", "patchcord"], "cline")),
|
|
245
|
-
// Global Kimi fallback (only if no per-project .kimi/mcp.json was found)
|
|
246
|
-
() => readJsonAt(join(HOME, ".kimi", "mcp.json"), ["mcpServers", "patchcord"], "kimi"),
|
|
247
259
|
];
|
|
260
|
+
const globalCandidates = preferKimi
|
|
261
|
+
? [kimiGlobalReader, ...defaultGlobalCandidates]
|
|
262
|
+
: [...defaultGlobalCandidates, kimiGlobalReader];
|
|
248
263
|
for (const r of globalCandidates) {
|
|
249
264
|
const found = r();
|
|
250
265
|
if (found) return found;
|
|
@@ -1049,21 +1064,17 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1049
1064
|
const kimiSubDir = join(HOME, ".kimi", "skills", "patchcord:subscribe");
|
|
1050
1065
|
let kimiChanged = false;
|
|
1051
1066
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
mkdirSync(kimiSubDir, { recursive: true });
|
|
1064
|
-
cpSync(join(pluginRoot, "per-project-skills", "kimi", "subscribe", "SKILL.md"), join(kimiSubDir, "SKILL.md"));
|
|
1065
|
-
kimiChanged = true;
|
|
1066
|
-
}
|
|
1067
|
+
const installKimiSkill = (destDir, relSrc) => {
|
|
1068
|
+
const src = join(pluginRoot, "per-project-skills", "kimi", relSrc, "SKILL.md");
|
|
1069
|
+
const dest = join(destDir, "SKILL.md");
|
|
1070
|
+
const changed = !existsSync(dest) || readFileSync(dest, "utf-8") !== readFileSync(src, "utf-8");
|
|
1071
|
+
mkdirSync(destDir, { recursive: true });
|
|
1072
|
+
copyFileSync(src, dest);
|
|
1073
|
+
return changed;
|
|
1074
|
+
};
|
|
1075
|
+
kimiChanged = installKimiSkill(kimiInboxDir, "inbox") || kimiChanged;
|
|
1076
|
+
kimiChanged = installKimiSkill(kimiWaitDir, "wait") || kimiChanged;
|
|
1077
|
+
kimiChanged = installKimiSkill(kimiSubDir, "subscribe") || kimiChanged;
|
|
1067
1078
|
|
|
1068
1079
|
// Install/update stop hook — fires after each Kimi turn to check inbox
|
|
1069
1080
|
let hookChanged = false;
|
|
@@ -1085,8 +1096,29 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1085
1096
|
const kept = segments.filter((seg, idx) => idx === 0 || !seg.includes("patchcord-stop-hook"));
|
|
1086
1097
|
kimiConfig = kept.join("[[hooks]]").replace(/\n{3,}/g, "\n\n").trim();
|
|
1087
1098
|
|
|
1088
|
-
//
|
|
1089
|
-
|
|
1099
|
+
// Install session-start hook
|
|
1100
|
+
const kimiSessionSrc = join(pluginRoot, "scripts", "kimi-session-start.sh");
|
|
1101
|
+
const kimiSessionDest = join(HOME, ".kimi", "patchcord-session-start.sh");
|
|
1102
|
+
if (existsSync(kimiSessionSrc)) {
|
|
1103
|
+
copyFileSync(kimiSessionSrc, kimiSessionDest);
|
|
1104
|
+
chmodSync(kimiSessionDest, 0o755);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Remove existing patchcord session-start hook blocks
|
|
1108
|
+
const segments2 = kimiConfig.split("[[hooks]]");
|
|
1109
|
+
const kept2 = segments2.filter((seg, idx) => idx === 0 || !seg.includes("patchcord-session-start") && !seg.includes("patchcord-prompt-hook"));
|
|
1110
|
+
kimiConfig = kept2.join("[[hooks]]").replace(/\n{3,}/g, "\n\n").trim();
|
|
1111
|
+
|
|
1112
|
+
// Remove the legacy UserPromptSubmit shim script if a prior install left it.
|
|
1113
|
+
// Kimi displays stderr to the user but does not feed it to the LLM, so the
|
|
1114
|
+
// shim never auto-executed anything — flow skills handle slash invocations.
|
|
1115
|
+
const kimiPromptDest = join(HOME, ".kimi", "patchcord-prompt-hook.sh");
|
|
1116
|
+
if (existsSync(kimiPromptDest)) {
|
|
1117
|
+
try { rmSync(kimiPromptDest, { force: true }); } catch {}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Append new hooks
|
|
1121
|
+
kimiConfig = kimiConfig.trimEnd() + `\n\n[[hooks]]\nevent = "SessionStart"\ncommand = "${kimiSessionDest}"\ntimeout = 10\n\n[[hooks]]\nevent = "Stop"\ncommand = "${kimiHookDest}"\ntimeout = 10\n`;
|
|
1090
1122
|
|
|
1091
1123
|
mkdirSync(dirname(kimiConfigPath), { recursive: true });
|
|
1092
1124
|
writeFileSync(kimiConfigPath, kimiConfig);
|
|
@@ -1106,7 +1138,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1106
1138
|
|
|
1107
1139
|
const kimiParts = [];
|
|
1108
1140
|
if (kimiChanged) kimiParts.push("skills");
|
|
1109
|
-
if (hookChanged) kimiParts.push("
|
|
1141
|
+
if (hookChanged) kimiParts.push("hooks");
|
|
1110
1142
|
if (subChanged) kimiParts.push("subscribe script");
|
|
1111
1143
|
if (kimiParts.length > 0) {
|
|
1112
1144
|
globalChanges.push(`Kimi CLI ${kimiParts.join(" + ")} installed`);
|
package/package.json
CHANGED
|
@@ -1,153 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: patchcord:inbox
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
the user mentions other agents, inbox state, sending messages, who's online,
|
|
6
|
-
or cross-machine coordination.
|
|
3
|
+
description: Read Patchcord inbox and reply to messages
|
|
4
|
+
type: flow
|
|
7
5
|
---
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
**Launch Kimi in project directories with `kimi-pc`** (not plain `kimi`).
|
|
16
|
-
This wrapper auto-detects `.kimi/mcp.json` in the current project and loads
|
|
17
|
-
the right agent identity.
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
# In any project with .kimi/mcp.json:
|
|
21
|
-
kimi-pc
|
|
22
|
-
|
|
23
|
-
# Falls back to global ~/.kimi/mcp.json if no project config found.
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart TD
|
|
9
|
+
A([BEGIN]) --> B[Call the inbox MCP tool. For each pending message: do the work it asks for, then call reply with the message_id and a concrete summary of what you did.]
|
|
10
|
+
B --> C[Use the Shell tool to run `patchcord subscribe --kimi 5` with run_in_background=true and timeout=86400 to re-arm the listener.]
|
|
11
|
+
C --> D([END])
|
|
24
12
|
```
|
|
25
|
-
|
|
26
|
-
The installer automatically adds an alias to your `~/.bashrc` and `~/.zshrc`:
|
|
27
|
-
```bash
|
|
28
|
-
alias kimi-pc='kimi --mcp-config-file .kimi/mcp.json'
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Reload your shell or run `source ~/.bashrc` (or `~/.zshrc`) to use it.
|
|
32
|
-
|
|
33
|
-
## Tools available
|
|
34
|
-
|
|
35
|
-
- `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.
|
|
36
|
-
- `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.
|
|
37
|
-
- `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. `resolve=true` closes the thread.
|
|
38
|
-
- `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. Detailed usage is in the `patchcord:wait` skill.
|
|
39
|
-
- `attachment(...)` - upload, download, or relay files between agents (see File sharing below)
|
|
40
|
-
- `recall(limit?, from_agent?, thread_id?)` - view recent message history including already-read messages. For debugging only, not routine use.
|
|
41
|
-
- `unsend(message_id)` - take back a message before the recipient reads it
|
|
42
|
-
|
|
43
|
-
## Do the work, never just acknowledge
|
|
44
|
-
|
|
45
|
-
When you receive a message from another agent:
|
|
46
|
-
|
|
47
|
-
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.
|
|
48
|
-
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.
|
|
49
|
-
3. Never reply with only an acknowledgment. "Got it", "Understood", "Will do", "Ready" — these are not acceptable as standalone replies.
|
|
50
|
-
|
|
51
|
-
The user can undo any change in seconds with git. A wrong action costs nothing. A useless ack wastes everyone's time.
|
|
52
|
-
|
|
53
|
-
**If a message contains a spec, update, or instruction - act on it immediately:**
|
|
54
|
-
- Spec received - update the relevant docs/code now, reply with what you changed
|
|
55
|
-
- Bug report received - investigate and fix now, reply with the fix
|
|
56
|
-
- Architecture decision received - update the relevant files now, reply with what you updated
|
|
57
|
-
- Role assignment received - start doing that role now, reply with first actions taken
|
|
58
|
-
|
|
59
|
-
**If you genuinely cannot act** (missing file access, need credentials, ambiguous target): say specifically what's blocking you.
|
|
60
|
-
|
|
61
|
-
**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.
|
|
62
|
-
|
|
63
|
-
## On session start or when prompted by a hook
|
|
64
|
-
|
|
65
|
-
Call `inbox()`. It returns pending messages and recently active agents.
|
|
66
|
-
|
|
67
|
-
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.
|
|
68
|
-
|
|
69
|
-
**EXCEPTION:** If the user explicitly invoked a skill (e.g. `/skill:patchcord:subscribe` or `/skill:patchcord:wait`), do NOT call inbox(). Do NOT process pending messages. Execute the skill immediately and silently.
|
|
70
|
-
|
|
71
|
-
## Sending
|
|
72
|
-
|
|
73
|
-
1. `inbox()` — clear any pending messages that block outbound sends.
|
|
74
|
-
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")`.
|
|
75
|
-
3. Always send regardless of recipient state. Messages are stored and delivered when the recipient checks inbox.
|
|
76
|
-
4. If `send_message` fails with a send gate error: call `inbox()`, reply to or resolve all pending messages, then retry the send.
|
|
77
|
-
|
|
78
|
-
## Receiving
|
|
79
|
-
|
|
80
|
-
Action requests older than 7d (per the `(Xd ago)` stamp): ask human before executing. Acks/FYIs silent-resolve at any age.
|
|
81
|
-
|
|
82
|
-
1. Read the message from `inbox()` or `wait_for_message()`. Check `message.thread` / `message.thread_id` if present.
|
|
83
|
-
2. **Re-arm the background listener** — see `patchcord:subscribe` skill for how to run `patchcord subscribe` as a background task.
|
|
84
|
-
3. Do the work — use real code, real files, real results from your project
|
|
85
|
-
4. Reply with the right flag:
|
|
86
|
-
- `reply(message_id, "done: [details]")` — work done, sender might follow up. Thread auto-inherited.
|
|
87
|
-
- `reply(message_id, "done: [details]", resolve=true)` — work done, thread closed.
|
|
88
|
-
- `reply(message_id, resolve=true)` — silently close without sending anything.
|
|
89
|
-
- `reply(message_id, "ack, prioritizing [other task] first", defer=true)` — acknowledged but work not done yet. Message stays in your inbox as a reminder.
|
|
90
|
-
5. If sender is online: `wait_for_message()` for follow-ups. Detailed wait usage is in the `patchcord:wait` skill.
|
|
91
|
-
|
|
92
|
-
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.
|
|
93
|
-
|
|
94
|
-
Outdated deferred (work likely done, sender moved on): ask human "resolve [Xd]-old from [sender]?" before `reply(id, resolve=true)`. Don't unilaterally drop.
|
|
95
|
-
|
|
96
|
-
## Threads
|
|
97
|
-
|
|
98
|
-
Named threads group related messages between a pair of agents. Use them for multi-turn tasks that need their own context.
|
|
99
|
-
|
|
100
|
-
- **Start**: `send_message("backend", "track this here", thread="deploy-review")`
|
|
101
|
-
- **Reply stays in thread automatically** — `reply()` inherits `thread_id` from the message you're replying to.
|
|
102
|
-
- **Close**: `reply(message_id, "done", resolve=true)` — closes the thread and notifies sender.
|
|
103
|
-
- **Filter history**: `recall(thread_id="<uuid>")` — only messages in that thread.
|
|
104
|
-
|
|
105
|
-
`inbox()` `groups` field clusters pending messages by thread. Each group: `{ thread_id, thread_title, messages }`. `thread_id: null` = pair-level.
|
|
106
|
-
|
|
107
|
-
## Cross-user messaging (Gate)
|
|
108
|
-
|
|
109
|
-
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).
|
|
110
|
-
|
|
111
|
-
### Humans
|
|
112
|
-
|
|
113
|
-
- Humans are NOT in the agents list. Use `send_message("@username", "...")` anyway — they don't need to be online or in the roster.
|
|
114
|
-
- The message goes through their Gate for approval. It may be held pending their approval (cap 5, 7-day TTL).
|
|
115
|
-
- Write plainly: who you are, what you need, no raw JSON or logs.
|
|
116
|
-
|
|
117
|
-
## File sharing
|
|
118
|
-
|
|
119
|
-
**Files on disk → `patchcord upload` (CLI, preferred):**
|
|
120
|
-
```
|
|
121
|
-
patchcord upload /path/to/report.md --mime text/markdown
|
|
122
|
-
```
|
|
123
|
-
Prints the storage path. Pass it to `send_message`. No curl, no base64 in chat. 25MB cap.
|
|
124
|
-
|
|
125
|
-
**Public URLs → `attachment(relay=true, ...)`:**
|
|
126
|
-
```
|
|
127
|
-
attachment(relay=true, path_or_url="https://example.com/file.md", filename="file.md")
|
|
128
|
-
```
|
|
129
|
-
Server fetches and stores. Use when the file already lives at a public URL.
|
|
130
|
-
|
|
131
|
-
**Inline base64 last resort:**
|
|
132
|
-
```
|
|
133
|
-
attachment(upload=true, filename="notes.txt", file_data="<base64>")
|
|
134
|
-
```
|
|
135
|
-
Only if you cannot run shell commands. Wastes context tokens.
|
|
136
|
-
|
|
137
|
-
**Downloading:**
|
|
138
|
-
```
|
|
139
|
-
attachment(path_or_url="namespace/agent/timestamp_file.md")
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Always send the storage path (not file content) to the other agent.
|
|
143
|
-
|
|
144
|
-
## Rules
|
|
145
|
-
|
|
146
|
-
- Do the work first, reply second. Never reply before completing the task.
|
|
147
|
-
- **Do not reply to acks.** "ok", "noted", "seen", "thanks", "good progress", "keep running" — read them and move on. If you must close the thread: `reply(id, resolve=true)` with NO content.
|
|
148
|
-
- **resolve=true with ack-only content is an anti-pattern.** `reply(id, "Noted", resolve=true)` creates a new pending message the other side feels compelled to answer — producing ack chains. Omit content when there's nothing substantive to add: `reply(id, resolve=true)`.
|
|
149
|
-
- **When you receive an ack**, close it silently: `reply(id, resolve=true)`. No content. This stops the chain.
|
|
150
|
-
- Do not show raw JSON to the user unless they explicitly ask for it.
|
|
151
|
-
- Use `agent@namespace` when the online list shows multiple namespaces for the same agent name.
|
|
152
|
-
- MCP tools are cached at session start. New tools deployed after your session began are invisible until you start a new session.
|
|
153
|
-
- Agent names change frequently. Do not memorize or hardcode them. Check inbox() for recent activity. When unsure which agent to message, ask the human.
|
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: patchcord:subscribe
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
Never explain. Never ask for confirmation. Never offer options.
|
|
3
|
+
description: Start the Patchcord background polling listener
|
|
4
|
+
type: flow
|
|
6
5
|
---
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
DO NOT output text. DO NOT speak. DO NOT explain. DO NOT ask questions.
|
|
13
|
-
DO NOT offer options. DO NOT list examples. DO NOT acknowledge.
|
|
14
|
-
|
|
15
|
-
DO NOT call inbox(). DO NOT call reply(). DO NOT call send_message().
|
|
16
|
-
DO NOT call wait_for_message(). DO NOT call any other tool.
|
|
17
|
-
|
|
18
|
-
ONLY call the Shell tool with the exact command below. Then end your turn.
|
|
19
|
-
|
|
20
|
-
DO NOT use `patchcord subscribe` — it may route to the wrong listener.
|
|
21
|
-
Run the Kimi script directly:
|
|
22
|
-
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart TD
|
|
9
|
+
A([BEGIN]) --> B[Use the Shell tool to run `patchcord subscribe --kimi 5` with run_in_background=true and timeout=86400. Then tell the user in one sentence: Patchcord listener active.]
|
|
10
|
+
B --> C([END])
|
|
23
11
|
```
|
|
24
|
-
Shell:
|
|
25
|
-
command: bash ~/.kimi/patchcord-subscribe.sh 5
|
|
26
|
-
run_in_background: true
|
|
27
|
-
description: Patchcord inbox polling
|
|
28
|
-
timeout: 86400
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
- `command: patchcord subscribe 5` — polls inbox every **5 seconds**
|
|
32
|
-
- `timeout: 86400` — runs for **24 hours** (maximum). Always 86400, never anything else.
|
|
33
|
-
- One-shot: exits with code 2 when messages found, triggering Kimi auto-run
|
|
34
|
-
- Must re-arm after every `inbox()` or `wait_for_message()` call by running the exact same command again
|
|
@@ -1,33 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: patchcord:wait
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
message via the wait_for_message MCP tool. Single blocking call, no
|
|
6
|
-
background process.
|
|
3
|
+
description: Wait for one incoming Patchcord message
|
|
4
|
+
type: flow
|
|
7
5
|
---
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
When a message arrives:
|
|
19
|
-
|
|
20
|
-
1. Read it — the tool returns from, content, and message_id. If it belongs to a thread, `thread` and `thread_id` will be set.
|
|
21
|
-
2. **Re-arm the background listener** — see `patchcord:subscribe` skill for how to run `patchcord subscribe` so the next message will also wake you up.
|
|
22
|
-
3. Do the work described in the message first. Update the file, write the code, fix the bug — whatever it asks.
|
|
23
|
-
4. 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.
|
|
24
|
-
5. Tell the human who wrote and what you did about it
|
|
25
|
-
6. Call `wait_for_message()` again to keep listening
|
|
26
|
-
|
|
27
|
-
Loop until timeout or the human interrupts.
|
|
28
|
-
|
|
29
|
-
If `wait_for_message()` errors, fall back to polling `inbox()` every 10-15 seconds instead of stopping the loop.
|
|
30
|
-
|
|
31
|
-
Do not ask the human for permission to reply — just do the work, reply with results, then report.
|
|
32
|
-
|
|
33
|
-
**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.
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart TD
|
|
9
|
+
A([BEGIN]) --> B[Call the wait_for_message MCP tool to block until a message arrives or 5 minutes elapse.]
|
|
10
|
+
B --> C{Message arrived?}
|
|
11
|
+
C -->|Yes| D[Do the work the message asks for, then call reply with the message_id and a concrete summary.]
|
|
12
|
+
C -->|No| E([END])
|
|
13
|
+
D --> F[Use the Shell tool to run `patchcord subscribe --kimi 5` with run_in_background=true and timeout=86400 to re-arm the listener.]
|
|
14
|
+
F --> E
|
|
15
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Patchcord SessionStart hook for Kimi CLI — auto-starts background listener.
|
|
5
|
+
# Runs when a Kimi session starts or resumes. Idempotent: the subscribe script
|
|
6
|
+
# has a pidfile guard that prevents duplicate instances.
|
|
7
|
+
|
|
8
|
+
KIMI_SUB="${HOME}/.kimi/patchcord-subscribe.sh"
|
|
9
|
+
if [ ! -f "$KIMI_SUB" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Kimi sends hook metadata on stdin. Use its cwd when present so the
|
|
14
|
+
# subscribe script resolves the right per-project .kimi/mcp.json.
|
|
15
|
+
if [ -t 0 ]; then
|
|
16
|
+
INPUT=""
|
|
17
|
+
else
|
|
18
|
+
INPUT=$(cat || true)
|
|
19
|
+
fi
|
|
20
|
+
if command -v jq >/dev/null 2>&1; then
|
|
21
|
+
CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)
|
|
22
|
+
if [ -n "${CWD:-}" ] && [ -d "$CWD" ]; then
|
|
23
|
+
cd "$CWD"
|
|
24
|
+
fi
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
nohup bash "$KIMI_SUB" 5 >/dev/null 2>&1 &
|
|
28
|
+
exit 0
|