patchcord 0.6.2 → 0.6.4
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
|
@@ -93,7 +93,7 @@ agent:
|
|
|
93
93
|
patchcord whoami this agent's identity + project
|
|
94
94
|
patchcord whoami --propose "<text>" set self-description (two-shot confirm)
|
|
95
95
|
patchcord agents [name] list agents in your namespace, or show one
|
|
96
|
-
patchcord subscribe [interval]
|
|
96
|
+
patchcord subscribe [interval] [--kimi] realtime listener (Kimi: poll + exit)
|
|
97
97
|
patchcord upload <file> [--mime <t>] [--as <name>] share a file as an attachment
|
|
98
98
|
|
|
99
99
|
account:
|
|
@@ -122,9 +122,10 @@ projects (cloud-only — a free-text label grouping your own namespaces):
|
|
|
122
122
|
patchcord namespace set-project <namespace> <project> assign a project label
|
|
123
123
|
patchcord namespace set-project <namespace> clear it
|
|
124
124
|
|
|
125
|
-
cross-namespace links (cloud-only, no CLI —
|
|
126
|
-
request_namespace_link / respond_namespace_link
|
|
127
|
-
|
|
125
|
+
cross-namespace links (cloud-only, no CLI — agent-to-agent via MCP:
|
|
126
|
+
request_namespace_link(ns, agent) / respond_namespace_link(ns, agent, approve)
|
|
127
|
+
/ list_my_namespace_links. Any agent, no lead role — but each link is to ONE
|
|
128
|
+
named agent, never the whole namespace; opt-in, not project-based.)
|
|
128
129
|
|
|
129
130
|
schedules:
|
|
130
131
|
patchcord schedule create <name> --namespace <ns> --to <agent> --content "..."
|
|
@@ -166,6 +167,45 @@ function _kimiContextRequested(options = {}) {
|
|
|
166
167
|
|| Boolean(process.env.KIMI_MCP_CONFIG_FILE);
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
// Strip legacy Python kimi-cli per-project artifacts. Kimi Code (Node) uses
|
|
171
|
+
// .kimi-code/mcp.json only — a leftover .kimi/mcp.json makes subscribe and
|
|
172
|
+
// docs lie about which identity is active.
|
|
173
|
+
function _purgeLegacyKimiProjectConfig(dir) {
|
|
174
|
+
const legacyMcp = join(dir, ".kimi", "mcp.json");
|
|
175
|
+
if (existsSync(legacyMcp)) {
|
|
176
|
+
try {
|
|
177
|
+
const obj = JSON.parse(readFileSync(legacyMcp, "utf-8"));
|
|
178
|
+
if (obj?.mcpServers?.patchcord) {
|
|
179
|
+
delete obj.mcpServers.patchcord;
|
|
180
|
+
if (Object.keys(obj.mcpServers).length === 0) delete obj.mcpServers;
|
|
181
|
+
}
|
|
182
|
+
if (!obj.mcpServers || Object.keys(obj).length === 0) {
|
|
183
|
+
rmSync(legacyMcp, { force: true });
|
|
184
|
+
} else {
|
|
185
|
+
writeFileSync(legacyMcp, JSON.stringify(obj, null, 2) + "\n");
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
try { rmSync(legacyMcp, { force: true }); } catch {}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const legacySkills = join(dir, ".kimi", "skills");
|
|
192
|
+
if (existsSync(legacySkills)) {
|
|
193
|
+
for (const stale of ["patchcord", "patchcord-inbox", "patchcord-wait", "patchcord-subscribe", "patchcord:inbox", "patchcord:wait", "patchcord:subscribe"]) {
|
|
194
|
+
const d = join(legacySkills, stale);
|
|
195
|
+
if (existsSync(d)) { try { rmSync(d, { recursive: true, force: true }); } catch {} }
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
if (readdirSync(legacySkills).length === 0) rmSync(legacySkills, { recursive: true, force: true });
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const legacyKimi = join(dir, ".kimi");
|
|
203
|
+
if (existsSync(legacyKimi) && readdirSync(legacyKimi).length === 0) {
|
|
204
|
+
rmSync(legacyKimi, { recursive: true, force: true });
|
|
205
|
+
}
|
|
206
|
+
} catch {}
|
|
207
|
+
}
|
|
208
|
+
|
|
169
209
|
// Insert-or-update mcp_servers.patchcord in a Hermes ~/.hermes/config.yaml,
|
|
170
210
|
// preserving the rest of the user's YAML. Block-style only (what Hermes writes).
|
|
171
211
|
function upsertHermesConfig(existing, url, token) {
|
|
@@ -667,9 +707,14 @@ if (cmd === "subscribe") {
|
|
|
667
707
|
process.exit(1);
|
|
668
708
|
}
|
|
669
709
|
const { spawnSync } = await import("child_process");
|
|
710
|
+
const spawnEnv = {
|
|
711
|
+
...process.env,
|
|
712
|
+
PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
|
|
713
|
+
};
|
|
714
|
+
if (forceKimi) spawnEnv.PATCHCORD_KIMI_REQUIRED = "1";
|
|
670
715
|
const result = spawnSync("bash", [kimiSubScript, intervalArg], {
|
|
671
716
|
stdio: "inherit",
|
|
672
|
-
env:
|
|
717
|
+
env: spawnEnv,
|
|
673
718
|
});
|
|
674
719
|
process.exit(result.status ?? (result.signal ? 1 : 0));
|
|
675
720
|
}
|
|
@@ -1034,6 +1079,7 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1034
1079
|
}
|
|
1035
1080
|
if (tool === "kimi" || tool === "kimi-code") {
|
|
1036
1081
|
const kdir = join(dir, ".kimi-code"); mkdirSync(kdir, { recursive: true });
|
|
1082
|
+
_purgeLegacyKimiProjectConfig(dir);
|
|
1037
1083
|
return writeJson(join(kdir, "mcp.json"), (o) => {
|
|
1038
1084
|
o.mcpServers = o.mcpServers || {};
|
|
1039
1085
|
o.mcpServers.patchcord = { url: `${baseUrl}/mcp/bearer`, headers: hdr };
|
|
@@ -1856,20 +1902,11 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1856
1902
|
if (windsurfChanged) globalChanges.push("Windsurf skills installed");
|
|
1857
1903
|
}
|
|
1858
1904
|
|
|
1859
|
-
// Gemini CLI
|
|
1860
|
-
//
|
|
1861
|
-
//
|
|
1862
|
-
//
|
|
1863
|
-
//
|
|
1864
|
-
// A patchcord entry in the global ~/.gemini/antigravity(-cli)/mcp_config.json or
|
|
1865
|
-
// ~/.gemini/config/mcp_config.json (Antigravity's post-migration global path —
|
|
1866
|
-
// confirmed live via the sibling ~/.gemini/config/.migrated marker; the older
|
|
1867
|
-
// two paths are its predecessors) is a leaked per-project token: it makes agy
|
|
1868
|
-
// load the patchcord MCP (and its "check inbox first" instruction) in EVERY
|
|
1869
|
-
// project, and it kept this skill seeded everywhere. We strip that leak below
|
|
1870
|
-
// and gate the skill on Gemini only. Keep all three paths in sync — a prior
|
|
1871
|
-
// fix (a36809c) covered only the first two and missed the migrated path,
|
|
1872
|
-
// leaving leaks there uncleaned.
|
|
1905
|
+
// Gemini CLI and Antigravity CLI SHARE ~/.gemini/skills/ — Antigravity reads it
|
|
1906
|
+
// globally in EVERY project. NEVER seed patchcord skills there (v0.6.2 still did
|
|
1907
|
+
// when Gemini had MCP configured, poisoning bare agy sessions). Gemini CLI uses
|
|
1908
|
+
// ~/.gemini/commands/ slash commands only. Antigravity gets per-project skills
|
|
1909
|
+
// at <project>/.agents/skills/ during provision (see isAntigravity below).
|
|
1873
1910
|
if (existsSync(join(HOME, ".gemini"))) {
|
|
1874
1911
|
// Remove any leaked patchcord server from Antigravity's GLOBAL configs so a
|
|
1875
1912
|
// per-project token stops hijacking every agy session. Preserve other MCP
|
|
@@ -1889,27 +1926,28 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1889
1926
|
}
|
|
1890
1927
|
} catch {}
|
|
1891
1928
|
}
|
|
1892
|
-
//
|
|
1893
|
-
//
|
|
1894
|
-
//
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
const staleAntigravityPatchcord = [
|
|
1929
|
+
// Unconditionally purge every global Patchcord skill/instruction path Antigravity
|
|
1930
|
+
// reads. Older installers seeded ~/.gemini/skills/patchcord for Gemini; agy-cli
|
|
1931
|
+
// inherits that shared dir and auto-loads it in unrelated projects.
|
|
1932
|
+
const staleGlobalPatchcord = [
|
|
1933
|
+
join(HOME, ".gemini", "skills", "patchcord"),
|
|
1934
|
+
join(HOME, ".gemini", "skills", "patchcord-wait"),
|
|
1899
1935
|
join(HOME, ".gemini", "antigravity", "skills", "patchcord"),
|
|
1900
1936
|
join(HOME, ".gemini", "antigravity", "skills", "patchcord-wait"),
|
|
1901
1937
|
join(HOME, ".gemini", "antigravity-cli", "skills", "patchcord"),
|
|
1902
1938
|
join(HOME, ".gemini", "antigravity-cli", "skills", "patchcord-wait"),
|
|
1939
|
+
join(HOME, ".gemini", "config", "skills", "patchcord"),
|
|
1940
|
+
join(HOME, ".gemini", "config", "skills", "patchcord-wait"),
|
|
1903
1941
|
join(HOME, ".gemini", "antigravity-cli", "mcp", "patchcord"),
|
|
1904
1942
|
];
|
|
1905
|
-
let
|
|
1906
|
-
for (const stale of
|
|
1943
|
+
let removedStaleGlobal = false;
|
|
1944
|
+
for (const stale of staleGlobalPatchcord) {
|
|
1907
1945
|
if (existsSync(stale)) {
|
|
1908
|
-
try { rmSync(stale, { recursive: true, force: true });
|
|
1946
|
+
try { rmSync(stale, { recursive: true, force: true }); removedStaleGlobal = true; } catch {}
|
|
1909
1947
|
}
|
|
1910
1948
|
}
|
|
1911
|
-
if (
|
|
1912
|
-
globalChanges.push("Removed
|
|
1949
|
+
if (removedStaleGlobal) {
|
|
1950
|
+
globalChanges.push("Removed global Patchcord skills/instructions (agy reads ~/.gemini/skills — per-project only)");
|
|
1913
1951
|
}
|
|
1914
1952
|
// true = configured, false = file absent (definitely not), null = unparseable (unknown)
|
|
1915
1953
|
const hasPatchcordMcp = (p, keyPath) => {
|
|
@@ -1922,38 +1960,27 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
1922
1960
|
];
|
|
1923
1961
|
const mcpConfigured = checks.some((c) => c === true);
|
|
1924
1962
|
const mcpUnknown = checks.some((c) => c === null);
|
|
1925
|
-
const geminiSkillDir = join(HOME, ".gemini", "skills", "patchcord");
|
|
1926
|
-
const geminiWaitDir = join(HOME, ".gemini", "skills", "patchcord-wait");
|
|
1927
1963
|
const geminiCmdDir = join(HOME, ".gemini", "commands");
|
|
1928
1964
|
const geminiLegacyCmd = join(geminiCmdDir, "patchcord.toml"); // single-command file from older installs
|
|
1929
1965
|
if (mcpConfigured) {
|
|
1930
1966
|
let geminiChanged = false;
|
|
1931
|
-
// Refresh to the CURRENT plugin version (overwrite, not create-if-absent)
|
|
1932
|
-
// so skill/gate fixes reach machines that already have an older copy.
|
|
1933
1967
|
const seedFile = (dest, src) => {
|
|
1934
1968
|
mkdirSync(dirname(dest), { recursive: true });
|
|
1935
1969
|
const want = readFileSync(src, "utf-8");
|
|
1936
1970
|
let cur = ""; try { cur = readFileSync(dest, "utf-8"); } catch {}
|
|
1937
1971
|
if (cur !== want) { writeFileSync(dest, want); geminiChanged = true; }
|
|
1938
1972
|
};
|
|
1939
|
-
|
|
1940
|
-
seedFile(join(geminiWaitDir, "SKILL.md"), join(pluginRoot, "skills", "wait", "SKILL.md"));
|
|
1973
|
+
// Gemini CLI only — slash commands, NOT ~/.gemini/skills (shared with agy-cli).
|
|
1941
1974
|
seedFile(join(geminiCmdDir, "inbox.toml"), join(pluginRoot, "commands", "inbox.toml"));
|
|
1942
1975
|
seedFile(join(geminiCmdDir, "wait.toml"), join(pluginRoot, "commands", "wait.toml"));
|
|
1943
1976
|
if (existsSync(geminiLegacyCmd)) { try { unlinkSync(geminiLegacyCmd); geminiChanged = true; } catch {} }
|
|
1944
|
-
if (geminiChanged) globalChanges.push("Gemini
|
|
1977
|
+
if (geminiChanged) globalChanges.push("Gemini CLI patchcord commands refreshed");
|
|
1945
1978
|
} else if (!mcpUnknown) {
|
|
1946
|
-
// Definitively no patchcord MCP under ~/.gemini → remove any previously
|
|
1947
|
-
// seeded skill/commands so a bare Gemini/Antigravity session has nothing
|
|
1948
|
-
// to act on (incl. the legacy patchcord.toml command).
|
|
1949
1979
|
let removed = false;
|
|
1950
|
-
for (const d of [geminiSkillDir, geminiWaitDir]) {
|
|
1951
|
-
if (existsSync(d)) { try { rmSync(d, { recursive: true, force: true }); removed = true; } catch {} }
|
|
1952
|
-
}
|
|
1953
1980
|
for (const f of [join(geminiCmdDir, "inbox.toml"), join(geminiCmdDir, "wait.toml"), geminiLegacyCmd]) {
|
|
1954
1981
|
if (existsSync(f)) { try { unlinkSync(f); removed = true; } catch {} }
|
|
1955
1982
|
}
|
|
1956
|
-
if (removed) globalChanges.push("Removed stale Gemini
|
|
1983
|
+
if (removed) globalChanges.push("Removed stale Gemini CLI patchcord commands (no patchcord MCP configured there)");
|
|
1957
1984
|
}
|
|
1958
1985
|
}
|
|
1959
1986
|
|
|
@@ -2100,6 +2127,13 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2100
2127
|
const p = join(legacyKimiHome, f);
|
|
2101
2128
|
if (existsSync(p)) { try { rmSync(p, { force: true }); } catch {} }
|
|
2102
2129
|
}
|
|
2130
|
+
const legacySkillsRoot = join(legacyKimiHome, "skills");
|
|
2131
|
+
if (existsSync(legacySkillsRoot)) {
|
|
2132
|
+
for (const stale of ["patchcord", "patchcord-inbox", "patchcord-wait", "patchcord-subscribe", "patchcord:inbox", "patchcord:wait", "patchcord:subscribe"]) {
|
|
2133
|
+
const d = join(legacySkillsRoot, stale);
|
|
2134
|
+
if (existsSync(d)) { try { rmSync(d, { recursive: true, force: true }); } catch {} }
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2103
2137
|
const legacyConfigPath = join(legacyKimiHome, "config.toml");
|
|
2104
2138
|
if (existsSync(legacyConfigPath)) {
|
|
2105
2139
|
try {
|
|
@@ -2843,9 +2877,8 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2843
2877
|
// Antigravity CLI: PROJECT-scoped config at <project>/.agents/mcp_config.json
|
|
2844
2878
|
// (one project = one namespace = one agent identity — never global, which
|
|
2845
2879
|
// would give one shared agent cross-project visibility). Remote HTTP uses
|
|
2846
|
-
// the `serverUrl` key. Skills are
|
|
2847
|
-
//
|
|
2848
|
-
// server injects its own instructions once connected.
|
|
2880
|
+
// the `serverUrl` key. Skills are per-project at .agents/skills/ only — never
|
|
2881
|
+
// ~/.gemini/skills (agy-cli reads that globally in every folder).
|
|
2849
2882
|
const agPath = join(cwd, ".agents", "mcp_config.json");
|
|
2850
2883
|
const agOk = updateJsonConfig(agPath, (obj) => {
|
|
2851
2884
|
obj.mcpServers = obj.mcpServers || {};
|
|
@@ -2857,8 +2890,15 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2857
2890
|
},
|
|
2858
2891
|
};
|
|
2859
2892
|
});
|
|
2893
|
+
const agInboxDir = join(cwd, ".agents", "skills", "patchcord");
|
|
2894
|
+
const agWaitDir = join(cwd, ".agents", "skills", "patchcord-wait");
|
|
2895
|
+
mkdirSync(agInboxDir, { recursive: true });
|
|
2896
|
+
mkdirSync(agWaitDir, { recursive: true });
|
|
2897
|
+
cpSync(join(pluginRoot, "per-project-skills", "antigravity", "inbox", "SKILL.md"), join(agInboxDir, "SKILL.md"));
|
|
2898
|
+
cpSync(join(pluginRoot, "per-project-skills", "antigravity", "wait", "SKILL.md"), join(agWaitDir, "SKILL.md"));
|
|
2860
2899
|
if (agOk) {
|
|
2861
2900
|
console.log(`\n ${green}✓${r} Antigravity CLI configured: ${dim}${agPath}${r}`);
|
|
2901
|
+
console.log(` ${green}✓${r} Skills installed: ${dim}.agents/skills/patchcord${r}, ${dim}patchcord-wait${r}`);
|
|
2862
2902
|
console.log(` ${yellow}Project config — this agent is scoped to ${cwd}.${r}`);
|
|
2863
2903
|
console.log(` ${dim}Verify in agy with the /mcp slash command.${r}`);
|
|
2864
2904
|
}
|
|
@@ -2937,6 +2977,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
2937
2977
|
};
|
|
2938
2978
|
});
|
|
2939
2979
|
if (kcOk) {
|
|
2980
|
+
_purgeLegacyKimiProjectConfig(cwd);
|
|
2940
2981
|
console.log(`\n ${green}✓${r} Kimi Code configured: ${dim}${kcPath}${r}`);
|
|
2941
2982
|
console.log(` ${dim}Kimi Code auto-loads project-local .kimi-code/mcp.json — just run ${r}${bold}kimi${r}${dim} in this project (no wrapper needed).${r}`);
|
|
2942
2983
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: patchcord-inbox
|
|
3
|
+
description: Patchcord MCP inbox workflow for this project's Antigravity agent
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Applies only when Patchcord MCP tools are loaded
|
|
7
|
+
|
|
8
|
+
If the `inbox` MCP tool is not available in this session, this skill does not
|
|
9
|
+
apply. Do nothing: do not run the Patchcord CLI, read a token from a config
|
|
10
|
+
file, or call the HTTP API. A missing project MCP config is a normal state.
|
|
11
|
+
|
|
12
|
+
Invoke with `/patchcord-inbox` when Patchcord MCP is connected in this project.
|
|
13
|
+
|
|
14
|
+
Call the `inbox` MCP tool now. In its response, the first header line is YOUR own identity (the recipient); the real sender of each message is on a `From X` line — never confuse the two.
|
|
15
|
+
|
|
16
|
+
For each pending message, classify it and act:
|
|
17
|
+
- **ACK** — short signals like thanks, noted, works, great, ok, 👍 with no task → close it silently: `reply(message_id, resolve=true)` with NO content. Never send a text reply to an ack (it creates infinite ack chains).
|
|
18
|
+
- **BLOCKED** — you cannot do the work right now (busy, missing credentials, ambiguous target) → `reply(message_id, "<reason>", defer=true)` so it stays in your inbox as a reminder. Never silently skip a message.
|
|
19
|
+
- **ACTIONABLE** — do the work the message asks for FIRST (edit the file, run the command, write the code), THEN `reply(message_id, "<concrete summary of what you did, with file paths and line numbers>")`. Never reply "will do" / "understood" before doing the work.
|
|
20
|
+
|
|
21
|
+
After handling every message, tell the human who wrote, what they asked, and what you did.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: patchcord-wait
|
|
3
|
+
description: Block until one Patchcord message arrives (this project's Antigravity agent)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Applies only when Patchcord MCP tools are loaded
|
|
7
|
+
|
|
8
|
+
If the `wait_for_message` MCP tool is not available in this session, this skill
|
|
9
|
+
does not apply. Do nothing: do not run the Patchcord CLI, read a token from a
|
|
10
|
+
config file, or call the HTTP API. A missing project MCP config is a normal state.
|
|
11
|
+
|
|
12
|
+
Invoke with `/patchcord-wait` when Patchcord MCP is connected in this project.
|
|
13
|
+
|
|
14
|
+
Call `wait_for_message()` to block until a message arrives (up to 5 minutes).
|
|
15
|
+
|
|
16
|
+
When a message arrives:
|
|
17
|
+
1. Do the work described in the message first.
|
|
18
|
+
2. `reply(message_id, "<concrete summary of what you did>")`.
|
|
19
|
+
3. Tell the human who wrote and what you did.
|
|
20
|
+
4. Call `wait_for_message()` again to keep listening.
|
|
21
|
+
|
|
22
|
+
If `wait_for_message()` errors, poll `inbox()` every 10–15 seconds instead.
|
|
@@ -15,13 +15,11 @@ set -euo pipefail
|
|
|
15
15
|
command -v jq >/dev/null 2>&1 || { echo "jq required" >&2; exit 1; }
|
|
16
16
|
|
|
17
17
|
# Resolve MCP config: PROJECT-scoped ONLY (walk up from cwd for .kimi-code/mcp.json).
|
|
18
|
-
#
|
|
19
|
-
# deprecated and MUST NOT be used; it also mismatched where `patchcord provision`
|
|
20
|
-
# writes the config (.kimi-code/mcp.json), so the listener never found it.
|
|
18
|
+
# Legacy .kimi/mcp.json (Python kimi-cli) is NOT read — reinstall/provision removes it.
|
|
21
19
|
# NO global fallback: auto-launched by the (global) SessionStart hook on EVERY
|
|
22
20
|
# Kimi session, so a global fallback would start a listener for some leftover
|
|
23
|
-
# identity in every folder. No project config in the cwd tree =>
|
|
24
|
-
#
|
|
21
|
+
# identity in every folder. No project config in the cwd tree => exit quietly
|
|
22
|
+
# (0) unless PATCHCORD_KIMI_REQUIRED=1 (explicit `patchcord subscribe --kimi`).
|
|
25
23
|
KIMI_MCP=""
|
|
26
24
|
dir="$PWD"
|
|
27
25
|
while [ "$dir" != "/" ]; do
|
|
@@ -33,6 +31,11 @@ while [ "$dir" != "/" ]; do
|
|
|
33
31
|
done
|
|
34
32
|
|
|
35
33
|
if [ -z "$KIMI_MCP" ] || [ ! -f "$KIMI_MCP" ]; then
|
|
34
|
+
if [ "${PATCHCORD_KIMI_REQUIRED:-}" = "1" ]; then
|
|
35
|
+
echo "No Kimi Code Patchcord config in this project tree (.kimi-code/mcp.json)." >&2
|
|
36
|
+
echo "Run patchcord provision <agent> --tool kimi --namespace <ns> from the project root." >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
36
39
|
exit 0
|
|
37
40
|
fi
|
|
38
41
|
|
|
@@ -59,7 +62,7 @@ if [ -z "$NAMESPACE_ID" ] || [ -z "$AGENT_ID" ]; then
|
|
|
59
62
|
fi
|
|
60
63
|
|
|
61
64
|
PIDFILE="/tmp/patchcord_subscribe_${NAMESPACE_ID}_${AGENT_ID}.pid"
|
|
62
|
-
NOTIFY_FILE="${HOME}/.kimi/patchcord-subscribe-notify.txt"
|
|
65
|
+
NOTIFY_FILE="${KIMI_CODE_HOME:-${HOME}/.kimi-code}/patchcord-subscribe-notify.txt"
|
|
63
66
|
|
|
64
67
|
# Always replace any existing listener for this project. Otherwise the agent
|
|
65
68
|
# sees an instant exit ("already running") and narrates it as "listener failed
|
package/skills/inbox/SKILL.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: patchcord:inbox
|
|
3
3
|
description: >
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
messages.
|
|
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.
|
|
8
7
|
---
|
|
9
8
|
# patchcord
|
|
10
9
|
|
|
@@ -42,7 +41,9 @@ The user can undo any change in 3 seconds with git. A wrong action costs nothing
|
|
|
42
41
|
|
|
43
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.
|
|
44
43
|
|
|
45
|
-
##
|
|
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.
|
|
46
47
|
|
|
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.
|
|
48
49
|
|
|
@@ -184,8 +185,8 @@ Named threads group related messages between a pair of agents. Use them for mult
|
|
|
184
185
|
- Never show raw JSON to the human - summarize naturally.
|
|
185
186
|
- **Cross-namespace addressing (`agent@namespace`)**: the syntax always exists in `send_message`/`reply`, but what it actually reaches depends on who you are:
|
|
186
187
|
- **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.
|
|
187
|
-
- **
|
|
188
|
-
- A rejection here is almost always correct behavior, not a server error — don't retry with variations of the name.
|
|
188
|
+
- **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.)
|
|
189
|
+
- 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.
|
|
189
190
|
- **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.
|
|
190
191
|
- **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.
|
|
191
192
|
- **When you receive an ack**, close it silently: `reply(id, resolve=true)`. No content. This stops the chain.
|