patchcord 0.6.41 → 0.6.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +5 -0
- package/.claude-plugin/plugin.json +1 -1
- package/agent-plugin/.codex-plugin/plugin.json +22 -0
- package/agent-plugin/.mcp.json +9 -0
- package/agent-plugin/README.md +61 -0
- package/agent-plugin/mcp.json +9 -0
- package/agent-plugin/plugin.json +21 -0
- package/agent-plugin/skills/inbox/SKILL.md +202 -0
- package/agent-plugin/skills/subscribe/SKILL.md +112 -0
- package/agent-plugin/skills/wait/SKILL.md +31 -0
- package/bin/patchcord.mjs +114 -8
- package/harnesses.json +245 -0
- package/package.json +5 -3
- package/per-project-skills/codex/SKILL.md +1 -1
- package/per-project-skills/cursor/inbox/SKILL.md +1 -1
- package/scripts/build-agent-plugin.mjs +215 -0
- package/scripts/lib/resolve-project-bearer.mjs +40 -0
- package/scripts/sync-plugin-version.mjs +23 -8
- package/skills/inbox/SKILL.md +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -683,6 +683,7 @@ if (cmd === "whoami") {
|
|
|
683
683
|
codex: "codex", kimi: "kimi", "kimi-code": "kimi",
|
|
684
684
|
opencode: "opencode", agy: "antigravity", antigravity: "antigravity",
|
|
685
685
|
cursor: "cursor", grok: "grok", hermes: "hermes",
|
|
686
|
+
jcode: "jcode",
|
|
686
687
|
};
|
|
687
688
|
const resolveOpts = {};
|
|
688
689
|
if (toolFlag) {
|
|
@@ -907,8 +908,28 @@ if (cmd === "upload") {
|
|
|
907
908
|
console.error(`file is empty: ${filePath}`);
|
|
908
909
|
process.exit(1);
|
|
909
910
|
}
|
|
910
|
-
|
|
911
|
-
|
|
911
|
+
// THIS IS NOT THE UPLOAD LIMIT AND MUST NOT BE DESCRIBED AS ONE.
|
|
912
|
+
//
|
|
913
|
+
// It used to say "max is 25MB", which was wrong in both directions. The
|
|
914
|
+
// server's limit is PATCHCORD_ATTACHMENT_MAX_BYTES, 10 MiB by default and
|
|
915
|
+
// raisable on a self-hosted install. So this client refused files a
|
|
916
|
+
// configured server would have accepted, and accepted files every default
|
|
917
|
+
// server rejects: a 15 MB file passed here, got base64'd to 20 MB, was
|
|
918
|
+
// uploaded in full, and came back 413. A guard set above the real limit does
|
|
919
|
+
// not guard; it only pays the upload first.
|
|
920
|
+
//
|
|
921
|
+
// The client must not hold a copy of a number the server owns. What is left
|
|
922
|
+
// is a memory guard for THIS process: the file, its base64 (4/3 the size),
|
|
923
|
+
// and the JSON body are all resident at once. The real limit comes back from
|
|
924
|
+
// the server, in max_bytes, and is printed below.
|
|
925
|
+
const MEMORY_GUARD_BYTES = 256 * 1024 * 1024;
|
|
926
|
+
if (stats.size > MEMORY_GUARD_BYTES) {
|
|
927
|
+
console.error(
|
|
928
|
+
`file is ${(stats.size / 1024 / 1024).toFixed(1)}MB. This client buffers the ` +
|
|
929
|
+
`file and its base64 copy in memory and refuses above ` +
|
|
930
|
+
`${MEMORY_GUARD_BYTES / 1024 / 1024}MB. This is a client memory guard, not ` +
|
|
931
|
+
`the server's size limit — the server's limit is lower.`
|
|
932
|
+
);
|
|
912
933
|
process.exit(1);
|
|
913
934
|
}
|
|
914
935
|
|
|
@@ -923,7 +944,18 @@ if (cmd === "upload") {
|
|
|
923
944
|
"POST", `${baseUrl}/api/agent/attachment/upload`, token, body
|
|
924
945
|
);
|
|
925
946
|
if (status !== "200") {
|
|
926
|
-
|
|
947
|
+
// Print max_bytes/allowed when the server sends them. Without this the user
|
|
948
|
+
// reads "attachment exceeds maximum size" and is not told the maximum, so
|
|
949
|
+
// their next move is to guess — or to ask an agent, which will guess.
|
|
950
|
+
let detail = "";
|
|
951
|
+
if (json && typeof json.max_bytes === "number") {
|
|
952
|
+
detail = ` (server limit ${(json.max_bytes / 1024 / 1024).toFixed(1)}MiB` +
|
|
953
|
+
(typeof json.actual_bytes === "number"
|
|
954
|
+
? `, this file ${(json.actual_bytes / 1024 / 1024).toFixed(1)}MiB)` : ")");
|
|
955
|
+
} else if (json && Array.isArray(json.allowed)) {
|
|
956
|
+
detail = ` (allowed: ${json.allowed.join(", ")})`;
|
|
957
|
+
}
|
|
958
|
+
console.error(`✗ HTTP ${status}: ${(json && json.error) || respBody}${detail}`);
|
|
927
959
|
process.exit(1);
|
|
928
960
|
}
|
|
929
961
|
console.log(json.path);
|
|
@@ -1500,6 +1532,48 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1500
1532
|
} catch {}
|
|
1501
1533
|
return agWritten;
|
|
1502
1534
|
}
|
|
1535
|
+
if (tool === "jcode") {
|
|
1536
|
+
// jcode is the ONLY harness here that gets a STDIO entry, and the reason
|
|
1537
|
+
// is not preference: jcode has no HTTP transport. Its loader keeps an
|
|
1538
|
+
// entry only when `is_stdio()` holds and drops the rest at load time --
|
|
1539
|
+
// "MCP: Skipping non-stdio server '<name>' (http); HTTP/SSE transports
|
|
1540
|
+
// are not yet supported" (crates/jcode-base/src/mcp/protocol.rs). So the
|
|
1541
|
+
// `type: "http"` entry every other harness receives is READ, RECOGNISED
|
|
1542
|
+
// AND DISCARDED by jcode, with a log line and no error. A bridge process
|
|
1543
|
+
// is the only shape it can run today.
|
|
1544
|
+
//
|
|
1545
|
+
// NAMED patchcord-jcode, NOT patchcord, AND THAT IS LOAD-BEARING.
|
|
1546
|
+
// jcode merges .jcode/mcp.json, then .mcp.json, then .claude/mcp.json,
|
|
1547
|
+
// with later files overriding same-named servers. A guard currently saves
|
|
1548
|
+
// us -- a non-stdio entry never displaces a working stdio one (their
|
|
1549
|
+
// issue #653) -- but that guard holds only while jcode CANNOT run http.
|
|
1550
|
+
// The day it gains that transport, a shared name means .mcp.json wins,
|
|
1551
|
+
// and jcode silently starts authenticating as claude_code's agent: two
|
|
1552
|
+
// harnesses, one credential, no error anywhere. A distinct name cannot
|
|
1553
|
+
// collide in the first place. Codex already does this (patchcord-codex).
|
|
1554
|
+
//
|
|
1555
|
+
// The bridge command mirrors the OpenClaw fallback this file already
|
|
1556
|
+
// prints. NOT VERIFIED END TO END from here: no jcode on this machine,
|
|
1557
|
+
// and proving it needs a live token against the real endpoint.
|
|
1558
|
+
const jdir = join(dir, ".jcode"); mkdirSync(jdir, { recursive: true });
|
|
1559
|
+
return writeJson(join(jdir, "mcp.json"), (o) => {
|
|
1560
|
+
o.mcpServers = o.mcpServers || {};
|
|
1561
|
+
// No `type` key at all. jcode infers stdio from the presence of
|
|
1562
|
+
// `command`, and writing "type": "stdio" is one more string to get
|
|
1563
|
+
// wrong for a default that is already correct.
|
|
1564
|
+
o.mcpServers["patchcord-jcode"] = {
|
|
1565
|
+
command: "npx",
|
|
1566
|
+
args: [
|
|
1567
|
+
"mcp-remote",
|
|
1568
|
+
`${baseUrl}/mcp`,
|
|
1569
|
+
"--header",
|
|
1570
|
+
`Authorization: Bearer ${token}`,
|
|
1571
|
+
"--header",
|
|
1572
|
+
`X-Patchcord-Machine: ${hostname}`,
|
|
1573
|
+
],
|
|
1574
|
+
};
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1503
1577
|
if (tool === "hermes") {
|
|
1504
1578
|
// Hermes reads MCP servers ONLY from its GLOBAL ~/.hermes/config.yaml
|
|
1505
1579
|
// (mcp_servers key) — it ignores a project-local .mcp.json. So unlike the
|
|
@@ -1555,7 +1629,7 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1555
1629
|
console.error(` .mcp.json → claude_code`);
|
|
1556
1630
|
console.error(` .cursor/mcp.json → cursor`);
|
|
1557
1631
|
console.error(` Defaulting would overwrite an agent you did not name, so there is no default.`);
|
|
1558
|
-
console.error(` One of: claude_code, codex, cursor, kimi, opencode, antigravity, grok, hermes`);
|
|
1632
|
+
console.error(` One of: claude_code, codex, cursor, kimi, opencode, antigravity, grok, hermes, jcode`);
|
|
1559
1633
|
console.error(` ${usage}`);
|
|
1560
1634
|
process.exit(1);
|
|
1561
1635
|
};
|
|
@@ -3194,7 +3268,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3194
3268
|
// falls through to no choice instead of silently configuring a DIFFERENT
|
|
3195
3269
|
// harness and writing someone's token to the wrong file.
|
|
3196
3270
|
"vscode": "6", "zed": "7", "opencode": "8", "openclaw": "9", "antigravity": "10",
|
|
3197
|
-
"cline": "11", "kimi": "12", "hermes": "13",
|
|
3271
|
+
"cline": "11", "kimi": "12", "hermes": "13", "jcode": "15",
|
|
3198
3272
|
};
|
|
3199
3273
|
|
|
3200
3274
|
|
|
@@ -3583,6 +3657,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3583
3657
|
const isCline = choice === "11";
|
|
3584
3658
|
const isKimi = choice === "12";
|
|
3585
3659
|
const isHermes = choice === "13";
|
|
3660
|
+
const isJcode = choice === "15";
|
|
3586
3661
|
|
|
3587
3662
|
// MoonshotAI ships TWO CLIs that both use the `kimi` command:
|
|
3588
3663
|
// • kimi-cli (Python): supports `--mcp-config-file`, config in .kimi/mcp.json
|
|
@@ -3657,6 +3732,36 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3657
3732
|
} catch (e) {
|
|
3658
3733
|
console.log(`\n ${yellow}⚠ Failed to write ${grokPath}: ${e.message}${r}`);
|
|
3659
3734
|
}
|
|
3735
|
+
} else if (isJcode) {
|
|
3736
|
+
// jcode: project-local .jcode/mcp.json, and a STDIO entry rather than the
|
|
3737
|
+
// http one every other harness gets. jcode has no HTTP transport -- it
|
|
3738
|
+
// recognises `type: "http"` and drops the server at load time with a log
|
|
3739
|
+
// line. See the writeWorkerConfig branch above for why the entry is named
|
|
3740
|
+
// `patchcord-jcode` and not `patchcord`; the short version is that jcode
|
|
3741
|
+
// merges .jcode/mcp.json, .mcp.json and .claude/mcp.json by server NAME,
|
|
3742
|
+
// so a shared name lets claude_code's credential silently become jcode's
|
|
3743
|
+
// the day jcode gains http support.
|
|
3744
|
+
const jcodePath = join(cwd, ".jcode", "mcp.json");
|
|
3745
|
+
try {
|
|
3746
|
+
mkdirSync(dirname(jcodePath), { recursive: true });
|
|
3747
|
+
let jobj = {};
|
|
3748
|
+
try { jobj = JSON.parse(readFileSync(jcodePath, "utf-8")); } catch {}
|
|
3749
|
+
jobj.mcpServers = jobj.mcpServers || {};
|
|
3750
|
+
jobj.mcpServers["patchcord-jcode"] = {
|
|
3751
|
+
command: "npx",
|
|
3752
|
+
args: [
|
|
3753
|
+
"mcp-remote",
|
|
3754
|
+
`${serverUrl}/mcp`,
|
|
3755
|
+
"--header",
|
|
3756
|
+
`Authorization: Bearer ${token}`,
|
|
3757
|
+
],
|
|
3758
|
+
};
|
|
3759
|
+
writeSecureFile(jcodePath, JSON.stringify(jobj, null, 2) + "\n");
|
|
3760
|
+
console.log(`\n ${green}✓${r} jcode configured: ${dim}${jcodePath}${r}`);
|
|
3761
|
+
console.log(` ${dim}Bridged over stdio — jcode does not speak HTTP MCP yet.${r}`);
|
|
3762
|
+
} catch (e) {
|
|
3763
|
+
console.log(`\n ${yellow}⚠ Failed to write ${jcodePath}: ${e.message}${r}`);
|
|
3764
|
+
}
|
|
3660
3765
|
} else if (isHermes) {
|
|
3661
3766
|
// Hermes: global only (~/.hermes/config.yaml, YAML, mcp_servers key)
|
|
3662
3767
|
const hermesPath = join(HOME, ".hermes", "config.yaml");
|
|
@@ -4324,10 +4429,11 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
4324
4429
|
// Hermes is global config (~/.hermes/config.yaml) — no per-project file to ignore.
|
|
4325
4430
|
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isCline && !isHermes) {
|
|
4326
4431
|
const gitignorePath = join(cwd, ".gitignore");
|
|
4327
|
-
const configFile = isKimiCode ? ".kimi-code/mcp.json" : isKimi ? ".kimi/mcp.json" : isCodex ? ".codex/config.toml" : isCursor ? ".cursor/mcp.json" : isGrok ? ".grok/config.toml" : isVSCode ? ".vscode/mcp.json" : isOpenCode ? "opencode.json" : isAntigravity ? ".agents/mcp_config.json" : ".mcp.json";
|
|
4432
|
+
const configFile = isJcode ? ".jcode/mcp.json" : isKimiCode ? ".kimi-code/mcp.json" : isKimi ? ".kimi/mcp.json" : isCodex ? ".codex/config.toml" : isCursor ? ".cursor/mcp.json" : isGrok ? ".grok/config.toml" : isVSCode ? ".vscode/mcp.json" : isOpenCode ? "opencode.json" : isAntigravity ? ".agents/mcp_config.json" : ".mcp.json";
|
|
4328
4433
|
// Forms that already cover this config (its file or its dir)
|
|
4329
4434
|
const patterns = [configFile];
|
|
4330
|
-
if (
|
|
4435
|
+
if (isJcode) patterns.push(".jcode/");
|
|
4436
|
+
else if (isKimiCode) patterns.push(".kimi-code/");
|
|
4331
4437
|
else if (isKimi) patterns.push(".kimi/");
|
|
4332
4438
|
else if (isCodex) patterns.push(".codex/");
|
|
4333
4439
|
else if (isCursor) patterns.push(".cursor/");
|
|
@@ -4352,7 +4458,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
4352
4458
|
}
|
|
4353
4459
|
}
|
|
4354
4460
|
|
|
4355
|
-
const toolName = isHermes ? "Hermes" : isKimiCode ? "Kimi Code" : isKimi ? "Kimi Code" : isAntigravity ? "Antigravity CLI" : isCline ? "Cline" : isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isGrok ? "Grok CLI" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
4461
|
+
const toolName = isJcode ? "jcode" : isHermes ? "Hermes" : isKimiCode ? "Kimi Code" : isKimi ? "Kimi Code" : isAntigravity ? "Antigravity CLI" : isCline ? "Cline" : isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isGrok ? "Grok CLI" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
4356
4462
|
|
|
4357
4463
|
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw && !isCline && !isKimi && !isHermes) {
|
|
4358
4464
|
console.log(`\n ${dim}To connect a second agent:${r}`);
|
package/harnesses.json
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": [
|
|
3
|
+
"Machine-readable harness capability registry. Consumed by mux (lead@mux-v2) to decide",
|
|
4
|
+
"whether a seat can hold a Patchcord listener and whether two seats may share a host.",
|
|
5
|
+
"",
|
|
6
|
+
"TWO SCOPE FIELDS ON PURPOSE. `installer_scope` is where OUR installer writes.",
|
|
7
|
+
"`harness_scope` is what the HARNESS itself supports. They are not the same question and",
|
|
8
|
+
"conflating them is what this file exists to prevent: in one thread I answered three",
|
|
9
|
+
"harness-capability questions with installer behaviour and was wrong all three times.",
|
|
10
|
+
"hermes is the worked example — our installer writes one global file, and hermes supports",
|
|
11
|
+
"many isolated profiles. 'Our installer writes X' never implies 'the harness only does X'.",
|
|
12
|
+
"",
|
|
13
|
+
"EVIDENCE IS EARNED, NOT TYPED. This file may only ever say \"declared\" or \"absent\".",
|
|
14
|
+
"\"tested\" is not a legal value here — it lives in tests/test-harness-registry.mjs, in a",
|
|
15
|
+
"list that names what was observed and by whom. A writer can type a confident guess into",
|
|
16
|
+
"a data file; a writer cannot type an observation into a test they did not run. If you",
|
|
17
|
+
"believe a row deserves \"tested\", add it there with the observation, not here.",
|
|
18
|
+
"",
|
|
19
|
+
"Any row whose listener.wake is not \"none\" MUST have a real subscribe path on disk, or",
|
|
20
|
+
"carry blocked_reason saying why not. hermes is why: it HAS a subscribe skill directory",
|
|
21
|
+
"and, until 0.6.41, could not actually be armed. Directory presence is necessary, never",
|
|
22
|
+
"sufficient."
|
|
23
|
+
],
|
|
24
|
+
"version": 1,
|
|
25
|
+
"harnesses": [
|
|
26
|
+
{
|
|
27
|
+
"id": "claude_code",
|
|
28
|
+
"aliases": ["claude"],
|
|
29
|
+
"cli": "claude",
|
|
30
|
+
"kind": "terminal",
|
|
31
|
+
"installer_scope": "project",
|
|
32
|
+
"installer_config": ".mcp.json",
|
|
33
|
+
"harness_scope": "project",
|
|
34
|
+
"listener": {
|
|
35
|
+
"wake": "realtime",
|
|
36
|
+
"mechanism": "subscribe-skill-under-monitor",
|
|
37
|
+
"self_arm": true,
|
|
38
|
+
"survives_wake": true,
|
|
39
|
+
"evidence": "declared"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "codex",
|
|
44
|
+
"aliases": [],
|
|
45
|
+
"cli": "codex",
|
|
46
|
+
"kind": "terminal",
|
|
47
|
+
"installer_scope": "project",
|
|
48
|
+
"installer_config": ".codex/config.toml",
|
|
49
|
+
"harness_scope": "project",
|
|
50
|
+
"listener": {
|
|
51
|
+
"wake": "none",
|
|
52
|
+
"mechanism": "stop-hook-nudge-only",
|
|
53
|
+
"self_arm": false,
|
|
54
|
+
"survives_wake": null,
|
|
55
|
+
"evidence": "declared",
|
|
56
|
+
"note": "A stop hook fires at the END of a turn. An idle codex seat hears nothing. No subscribe skill exists for codex in this package."
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "cursor",
|
|
61
|
+
"aliases": [],
|
|
62
|
+
"cli": "cursor-agent",
|
|
63
|
+
"kind": "terminal",
|
|
64
|
+
"installer_scope": "project",
|
|
65
|
+
"installer_config": ".cursor/mcp.json",
|
|
66
|
+
"harness_scope": "unknown",
|
|
67
|
+
"listener": {
|
|
68
|
+
"wake": "realtime",
|
|
69
|
+
"mechanism": "subscribe-skill-background-shell",
|
|
70
|
+
"self_arm": true,
|
|
71
|
+
"survives_wake": null,
|
|
72
|
+
"evidence": "declared",
|
|
73
|
+
"note": "The skill claims it wakes the agent. Nobody in this repo has watched it happen."
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "grok",
|
|
78
|
+
"aliases": ["grok_cli", "grok_build"],
|
|
79
|
+
"cli": "grok",
|
|
80
|
+
"kind": "terminal",
|
|
81
|
+
"installer_scope": "project",
|
|
82
|
+
"installer_config": ".grok/config.toml",
|
|
83
|
+
"harness_scope": "project",
|
|
84
|
+
"listener": {
|
|
85
|
+
"wake": "realtime",
|
|
86
|
+
"mechanism": "subscribe-skill",
|
|
87
|
+
"self_arm": true,
|
|
88
|
+
"survives_wake": null,
|
|
89
|
+
"evidence": "declared",
|
|
90
|
+
"note": "Grok resolves configs from cwd up to the git root, so per-seat identity works. Wake is claimed by the skill, not observed."
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "opencode",
|
|
95
|
+
"aliases": [],
|
|
96
|
+
"cli": "opencode",
|
|
97
|
+
"kind": "terminal",
|
|
98
|
+
"installer_scope": "project",
|
|
99
|
+
"installer_config": "opencode.json",
|
|
100
|
+
"harness_scope": "unknown",
|
|
101
|
+
"listener": {
|
|
102
|
+
"wake": "realtime",
|
|
103
|
+
"mechanism": "installed-plugin",
|
|
104
|
+
"self_arm": true,
|
|
105
|
+
"survives_wake": true,
|
|
106
|
+
"evidence": "declared",
|
|
107
|
+
"note": "The installer writes .opencode/plugins/patchcord.js, which spawns `patchcord subscribe` and calls client.session.prompt() on each message. It prompts on the realtime event rather than session.idle deliberately: session.idle re-prompting has a teardown race in headless `opencode run`."
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "kimi",
|
|
112
|
+
"aliases": [],
|
|
113
|
+
"cli": "kimi",
|
|
114
|
+
"kind": "terminal",
|
|
115
|
+
"installer_scope": "project",
|
|
116
|
+
"installer_config": ".kimi-code/mcp.json",
|
|
117
|
+
"harness_scope": "unknown",
|
|
118
|
+
"listener": {
|
|
119
|
+
"wake": "poll",
|
|
120
|
+
"mechanism": "poll-script",
|
|
121
|
+
"self_arm": true,
|
|
122
|
+
"survives_wake": false,
|
|
123
|
+
"evidence": "declared",
|
|
124
|
+
"note": "NOT realtime. patchcord-subscribe.sh polls /api/inbox and EXITS 0 on a pending count; Kimi starts a turn when a background task reaches a terminal state. So the listener dies on every delivery and the agent must re-arm it. Re-arm is the failure point: an agent that skips it is silently deaf from then on."
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "hermes",
|
|
129
|
+
"aliases": [],
|
|
130
|
+
"cli": "hermes",
|
|
131
|
+
"kind": "terminal",
|
|
132
|
+
"installer_scope": "global",
|
|
133
|
+
"installer_config": "~/.hermes/config.yaml",
|
|
134
|
+
"harness_scope": "per-profile",
|
|
135
|
+
"installer_defect": "The path is hardcoded to ~/.hermes/config.yaml. The installer never calls `hermes config path` and does not know profiles exist, so installing while a non-default profile is active writes the DEFAULT profile's config — the wrong file, silently. Not fixed as of 0.6.41.",
|
|
136
|
+
"listener": {
|
|
137
|
+
"wake": "realtime",
|
|
138
|
+
"mechanism": "webhook-bridge",
|
|
139
|
+
"self_arm": false,
|
|
140
|
+
"survives_wake": true,
|
|
141
|
+
"evidence": "declared",
|
|
142
|
+
"note": "`patchcord subscribe --hermes` POSTs to a Hermes gateway webhook per message; the gateway injects a prompt. No re-arm needed. self_arm is false: a human creates the route and starts the bridge once. A deploy that waits N seconds for a SEAT-started listener will fail hermes forever while hermes is fully push-capable.",
|
|
143
|
+
"blocked_until": "0.6.41",
|
|
144
|
+
"blocked_reason": "Before 0.6.41 the `patchcord subscribe` wrapper spawned subscribe.mjs with no arguments, so --hermes never reached the child that gates on it. The env var arrived, the flag did not, the flag is the gate. The bridge was unreachable by its own documented command and failed silently. Fixed; not yet observed end to end."
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": "antigravity",
|
|
149
|
+
"aliases": ["agy"],
|
|
150
|
+
"cli": "antigravity",
|
|
151
|
+
"kind": "terminal",
|
|
152
|
+
"installer_scope": "project",
|
|
153
|
+
"installer_config": ".agents/mcp_config.json",
|
|
154
|
+
"harness_scope": "unknown",
|
|
155
|
+
"listener": {
|
|
156
|
+
"wake": "none",
|
|
157
|
+
"mechanism": null,
|
|
158
|
+
"self_arm": false,
|
|
159
|
+
"survives_wake": null,
|
|
160
|
+
"evidence": "declared",
|
|
161
|
+
"note": "No subscribe skill of any kind in this package."
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"id": "openclaw",
|
|
166
|
+
"aliases": [],
|
|
167
|
+
"cli": "openclaw",
|
|
168
|
+
"kind": "terminal",
|
|
169
|
+
"installer_scope": "unknown",
|
|
170
|
+
"installer_config": "~/.openclaw/openclaw.json",
|
|
171
|
+
"harness_scope": "unknown",
|
|
172
|
+
"listener": {
|
|
173
|
+
"wake": "none",
|
|
174
|
+
"mechanism": null,
|
|
175
|
+
"self_arm": false,
|
|
176
|
+
"survives_wake": null,
|
|
177
|
+
"evidence": "absent",
|
|
178
|
+
"note": "installer_scope is unknown, not global. We PREFER `openclaw mcp set patchcord`, so openclaw decides where the entry lands; the ~/.openclaw path is only our fallback when that CLI is missing. Nobody here has openclaw installed. Do not refuse a second openclaw seat on the strength of this row — refuse on a test or not at all."
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"id": "windsurf",
|
|
183
|
+
"aliases": [],
|
|
184
|
+
"cli": null,
|
|
185
|
+
"kind": "editor",
|
|
186
|
+
"installer_scope": "global",
|
|
187
|
+
"installer_config": "~/.codeium/windsurf/mcp_config.json",
|
|
188
|
+
"harness_scope": "unknown",
|
|
189
|
+
"listener": { "wake": "none", "mechanism": null, "self_arm": false, "survives_wake": null, "evidence": "declared" }
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"id": "vscode",
|
|
193
|
+
"aliases": [],
|
|
194
|
+
"cli": null,
|
|
195
|
+
"kind": "editor",
|
|
196
|
+
"installer_scope": "project",
|
|
197
|
+
"installer_config": ".vscode/mcp.json",
|
|
198
|
+
"harness_scope": "unknown",
|
|
199
|
+
"listener": { "wake": "none", "mechanism": null, "self_arm": false, "survives_wake": null, "evidence": "declared" }
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"id": "zed",
|
|
203
|
+
"aliases": [],
|
|
204
|
+
"cli": null,
|
|
205
|
+
"kind": "editor",
|
|
206
|
+
"installer_scope": "global",
|
|
207
|
+
"installer_config": "~/.config/zed/settings.json",
|
|
208
|
+
"harness_scope": "unknown",
|
|
209
|
+
"listener": { "wake": "none", "mechanism": null, "self_arm": false, "survives_wake": null, "evidence": "declared" }
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"id": "cline",
|
|
213
|
+
"aliases": [],
|
|
214
|
+
"cli": null,
|
|
215
|
+
"kind": "editor",
|
|
216
|
+
"installer_scope": "global",
|
|
217
|
+
"installer_config": "cline_mcp_settings.json (VS Code globalStorage)",
|
|
218
|
+
"harness_scope": "unknown",
|
|
219
|
+
"listener": { "wake": "none", "mechanism": null, "self_arm": false, "survives_wake": null, "evidence": "declared" }
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"id": "jcode",
|
|
223
|
+
"aliases": [],
|
|
224
|
+
"cli": "jcode",
|
|
225
|
+
"kind": "terminal",
|
|
226
|
+
"installer_scope": "project",
|
|
227
|
+
"installer_config": ".jcode/mcp.json",
|
|
228
|
+
"harness_scope": "project",
|
|
229
|
+
"listener": {
|
|
230
|
+
"wake": "none",
|
|
231
|
+
"mechanism": null,
|
|
232
|
+
"self_arm": false,
|
|
233
|
+
"survives_wake": null,
|
|
234
|
+
"evidence": "declared",
|
|
235
|
+
"note": "STDIO ONLY, WHICH IS WHY THIS ROW IS NOT LIKE THE OTHERS. jcode reads .jcode/mcp.json, .mcp.json and .claude/mcp.json, so it ALREADY finds the Claude Code entry we write - and drops it, because jcode supports stdio servers only and skips type http/sse at load time with a log line (crates/jcode-base/src/mcp/protocol.rs, retain on is_stdio). So our installer writes a stdio bridge entry instead of the http entry every other harness gets. Two consequences a reader must not undo: the server is named patchcord-jcode, NOT patchcord, because jcode merges those three files with later ones overriding by NAME - a shared name means the day jcode gains http transport, the .mcp.json entry becomes runnable and silently overrides, and jcode starts authenticating as claude_code's agent. And no wake: no subscribe path exists for jcode in this package."
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
],
|
|
239
|
+
"retired": [
|
|
240
|
+
{
|
|
241
|
+
"id": "gemini",
|
|
242
|
+
"reason": "Retired as an install target; Antigravity covers Google's agent. CLIENT_TYPE_MAP slot \"5\" is left UNASSIGNED rather than reused, so a stale connect page sending client_type=gemini falls through to no choice instead of silently configuring a different harness and writing someone's token to the wrong file. Do not mirror the gap as a bug and do not fill it."
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchcord",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.43",
|
|
4
4
|
"description": "Cross-machine agent messaging for Claude Code and Codex",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json"
|
|
6
|
+
"version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json agent-plugin/plugin.json"
|
|
7
7
|
},
|
|
8
8
|
"author": "ppravdin",
|
|
9
9
|
"license": "MIT",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"per-project-skills/",
|
|
33
33
|
"commands/",
|
|
34
34
|
"README.md",
|
|
35
|
-
"plugins/"
|
|
35
|
+
"plugins/",
|
|
36
|
+
"agent-plugin/",
|
|
37
|
+
"harnesses.json"
|
|
36
38
|
]
|
|
37
39
|
}
|
|
@@ -99,7 +99,7 @@ To message a user outside your namespace, use `@username` as the to_agent. Examp
|
|
|
99
99
|
```
|
|
100
100
|
patchcord upload /path/to/report.md --mime text/markdown
|
|
101
101
|
```
|
|
102
|
-
Prints the storage path. Pass it to `send_message`. No curl, no base64 in chat.
|
|
102
|
+
Prints the storage path. Pass it to `send_message`. No curl, no base64 in chat. The size limit belongs to the server: 10 MiB by default, raisable on a self-hosted server. Too large prints the server's own limit.
|
|
103
103
|
|
|
104
104
|
**Public URLs → `attachment(relay=true, ...)`:**
|
|
105
105
|
```
|
|
@@ -105,7 +105,7 @@ To message a user outside your namespace, use `@username` as the to_agent. Examp
|
|
|
105
105
|
```
|
|
106
106
|
patchcord upload /path/to/report.md --mime text/markdown
|
|
107
107
|
```
|
|
108
|
-
Prints the storage path. Pass that path to `send_message`. No curl, no base64 in chat, no presigned URLs.
|
|
108
|
+
Prints the storage path. Pass that path to `send_message`. No curl, no base64 in chat, no presigned URLs. The size limit is the server's, not a number to remember: it is 10 MiB by default and a self-hosted server can raise it. If a file is too large the command prints the server's own limit.
|
|
109
109
|
|
|
110
110
|
**Public URLs → `attachment(relay=true, ...)`:**
|
|
111
111
|
```
|