patchcord 0.6.39 → 0.6.41
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 +184 -24
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -789,8 +789,23 @@ if (cmd === "whoami") {
|
|
|
789
789
|
// provisioned". Say explicitly that identity is fine.
|
|
790
790
|
console.log(`self-description: (not set — optional; your identity ${json.agent}@${json.namespace} is fully provisioned. Set one with: patchcord whoami --propose "<text>")`);
|
|
791
791
|
}
|
|
792
|
-
|
|
793
|
-
|
|
792
|
+
// TTY-ONLY, AND THAT IS THE WHOLE POINT.
|
|
793
|
+
//
|
|
794
|
+
// This tip is addressed to a human reading a terminal. It was printed
|
|
795
|
+
// unconditionally, which meant it also landed in the stdout an AGENT captures
|
|
796
|
+
// from a tool call — and to a model, a suggestion in a tool result is
|
|
797
|
+
// indistinguishable from an instruction. An agy session's own record shows the
|
|
798
|
+
// consequence exactly: 6 `patchcord whoami` calls, 6 `patchcord agents` calls,
|
|
799
|
+
// paired one-to-one, each `agents` immediately following the tip in the
|
|
800
|
+
// previous command's output. Nothing in any skill or config asked for it. We
|
|
801
|
+
// did, in our own stdout, every single time.
|
|
802
|
+
//
|
|
803
|
+
// A tip is help. An unrequested command is work. The difference is entirely in
|
|
804
|
+
// who is reading, and `isTTY` is the one place that difference is knowable.
|
|
805
|
+
if (process.stdout.isTTY) {
|
|
806
|
+
console.log();
|
|
807
|
+
console.log(`tip: \`patchcord agents\` returns whoami for all peers.`);
|
|
808
|
+
}
|
|
794
809
|
for (const w of warnings) {
|
|
795
810
|
// stderr, matching the global-scope warning above. The line is addressed
|
|
796
811
|
// to the AGENT and says to relay it, because the human has no reason to
|
|
@@ -978,7 +993,27 @@ if (cmd === "subscribe") {
|
|
|
978
993
|
spawnEnv.PATCHCORD_BEARER_TOKEN = bearerInfo.token;
|
|
979
994
|
}
|
|
980
995
|
const { spawnSync } = await import("child_process");
|
|
981
|
-
|
|
996
|
+
// FORWARD argv. This used to spawn [subscribeScript] with no arguments, so
|
|
997
|
+
// every flag a user typed was silently thrown away between the wrapper and
|
|
998
|
+
// the script that reads it.
|
|
999
|
+
//
|
|
1000
|
+
// What that broke: subscribe.mjs gates its Hermes webhook bridge on
|
|
1001
|
+
// `process.argv.includes("--hermes")` — its OWN argv. Our Hermes skill tells
|
|
1002
|
+
// the human to run `PATCHCORD_HERMES_WEBHOOK=<url> patchcord subscribe
|
|
1003
|
+
// --hermes`. The env var arrived, the flag did not, and the flag is the gate.
|
|
1004
|
+
// notify() then took the `!HERMES_MODE` branch and wrote to a stdout no
|
|
1005
|
+
// Hermes gateway reads. The listener ran. Nothing was ever woken. No error.
|
|
1006
|
+
// The `--hermes <url>` inline form was worse: it has no env fallback at all.
|
|
1007
|
+
//
|
|
1008
|
+
// Forward everything rather than allow-listing `--hermes`. An allow-list
|
|
1009
|
+
// fixes today and leaves the trap armed: the next person adds a child-side
|
|
1010
|
+
// flag, reads subscribe.mjs, sees it handled, and never looks at these eight
|
|
1011
|
+
// lines. Same silent failure, new flag name. Reported by lead@mux-v2, who
|
|
1012
|
+
// asked for the fix in this shape rather than the narrow one.
|
|
1013
|
+
//
|
|
1014
|
+
// `--kimi` never hit this because the kimi branch above parses it here in the
|
|
1015
|
+
// parent and re-passes the interval explicitly. That was luck, not design.
|
|
1016
|
+
const result = spawnSync(process.execPath, [subscribeScript, ...args], {
|
|
982
1017
|
stdio: "inherit",
|
|
983
1018
|
env: spawnEnv,
|
|
984
1019
|
});
|
|
@@ -1432,10 +1467,38 @@ if (cmd === "login" || cmd === "orchestrator" || cmd === "teamlead" || cmd === "
|
|
|
1432
1467
|
// Antigravity CLI: PROJECT-scoped config at .agents/mcp_config.json (one
|
|
1433
1468
|
// project = one namespace = one agent). Remote HTTP uses `serverUrl`.
|
|
1434
1469
|
const adir = join(dir, ".agents"); mkdirSync(adir, { recursive: true });
|
|
1435
|
-
|
|
1470
|
+
const agWritten = writeJson(join(adir, "mcp_config.json"), (o) => {
|
|
1436
1471
|
o.mcpServers = o.mcpServers || {};
|
|
1437
1472
|
o.mcpServers.patchcord = { serverUrl: `${baseUrl}/mcp`, headers: hdr };
|
|
1438
1473
|
});
|
|
1474
|
+
// THE SKILL IS A GUARD, NOT DOCUMENTATION, AND IT WAS MISSING HERE.
|
|
1475
|
+
//
|
|
1476
|
+
// The interactive installer copies these; this path did not, so every seat
|
|
1477
|
+
// provisioned by `patchcord provision --tool agy` — which is how a
|
|
1478
|
+
// mux-deployed Team is built — got the credential and none of the rules.
|
|
1479
|
+
// Three live agy seats were checked: one had only patchcord-wait, two had
|
|
1480
|
+
// no skills at all.
|
|
1481
|
+
//
|
|
1482
|
+
// What goes missing is specifically the line "Do not run the `patchcord`
|
|
1483
|
+
// CLI (whoami, inbox, recall, HTTP calls)". Without it, a seat whose MCP
|
|
1484
|
+
// has not loaded reaches for the CLI instead — and one that did wrote its
|
|
1485
|
+
// own send_patchcord.js and ran it eight times. The skill exists, is
|
|
1486
|
+
// correctly worded, and was installed by one provisioning path out of two,
|
|
1487
|
+
// which is the same as not having it for anyone who arrives by the other.
|
|
1488
|
+
//
|
|
1489
|
+
// Copied the same way hermes is above, which is why the pattern was
|
|
1490
|
+
// already here to notice.
|
|
1491
|
+
try {
|
|
1492
|
+
const agSkills = join(pluginRoot, "per-project-skills", "antigravity");
|
|
1493
|
+
for (const [src, dest] of [["inbox", "patchcord"], ["wait", "patchcord-wait"]]) {
|
|
1494
|
+
const from = join(agSkills, src, "SKILL.md");
|
|
1495
|
+
if (!existsSync(from)) continue;
|
|
1496
|
+
const to = join(adir, "skills", dest);
|
|
1497
|
+
mkdirSync(to, { recursive: true });
|
|
1498
|
+
cpSync(from, join(to, "SKILL.md"));
|
|
1499
|
+
}
|
|
1500
|
+
} catch {}
|
|
1501
|
+
return agWritten;
|
|
1439
1502
|
}
|
|
1440
1503
|
if (tool === "hermes") {
|
|
1441
1504
|
// Hermes reads MCP servers ONLY from its GLOBAL ~/.hermes/config.yaml
|
|
@@ -3994,21 +4057,71 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
3994
4057
|
console.log(` ${green}✓${r} Cleaned PATCHCORD_TOKEN from .env`);
|
|
3995
4058
|
}
|
|
3996
4059
|
}
|
|
3997
|
-
//
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4060
|
+
// We do NOT touch .codex/prompts/ — not the project one, not the home one.
|
|
4061
|
+
//
|
|
4062
|
+
// We used to write /patchcord and /patchcord-wait there, then a later commit
|
|
4063
|
+
// deleted them on every install. Both halves were wrong to keep.
|
|
4064
|
+
//
|
|
4065
|
+
// Writing them is pointless: Codex stopped loading $CODEX_HOME/prompts
|
|
4066
|
+
// entirely. Verified on 0.145 — a probe file at ~/.codex/prompts/pctest.md
|
|
4067
|
+
// and at ./.codex/prompts/pcproj.md both give "no matches" in the slash
|
|
4068
|
+
// menu, while /us still filters to /usage, so the menu itself works.
|
|
4069
|
+
// OpenAI marks custom prompts deprecated and says "use skills instead";
|
|
4070
|
+
// openai/codex#15941 reports the 0.117 break and was closed "not planned".
|
|
4071
|
+
//
|
|
4072
|
+
// Deleting them is worse than pointless. A user still on Codex < 0.117 has
|
|
4073
|
+
// WORKING slash commands, and the delete loop took them away on the next
|
|
4074
|
+
// install. It gave that user nothing back. `@patchcord` is the replacement
|
|
4075
|
+
// and it is installed below via the skills path.
|
|
4076
|
+
//
|
|
4077
|
+
// If you are tempted to re-add either half, run the probe first. Do not
|
|
4078
|
+
// trust this comment and do not trust the docs.
|
|
4079
|
+
|
|
4004
4080
|
// Clean up old per-project skill (now installed as global plugin)
|
|
4005
4081
|
const oldSkillPath = join(cwd, ".agents", "skills", "patchcord", "SKILL.md");
|
|
4006
4082
|
if (existsSync(oldSkillPath)) { unlinkSync(oldSkillPath); }
|
|
4007
4083
|
|
|
4008
|
-
// Install Codex plugin
|
|
4009
|
-
|
|
4084
|
+
// Install the Codex plugin.
|
|
4085
|
+
//
|
|
4086
|
+
// TWO DIFFERENT DIRECTORIES, and putting them in one place is the bug this
|
|
4087
|
+
// replaces. The marketplace FILE lives at ~/.agents/plugins/marketplace.json.
|
|
4088
|
+
// The plugin BODY lives at ~/plugins/patchcord. A `source.path` in that file
|
|
4089
|
+
// resolves against HOME — NOT against the directory holding the file.
|
|
4090
|
+
//
|
|
4091
|
+
// We previously wrote the body to ~/.agents/plugins/patchcord and pointed at
|
|
4092
|
+
// "./patchcord". Codex read that as ~/patchcord, which never existed, so:
|
|
4093
|
+
//
|
|
4094
|
+
// $ codex plugin list
|
|
4095
|
+
// patchcord@patchcord not installed /home/now/patchcord
|
|
4096
|
+
//
|
|
4097
|
+
// The plugin has never once installed. Only the ~/.agents/skills/ copy
|
|
4098
|
+
// written further down kept @patchcord alive, which is why nobody noticed.
|
|
4099
|
+
//
|
|
4100
|
+
// The layout below is OpenAI's own, from the bundled plugin-creator skill
|
|
4101
|
+
// (~/.codex/skills/.system/plugin-creator/): the scaffold writes bodies to
|
|
4102
|
+
// ~/plugins/<name>, and its SKILL.md states "In either location, the
|
|
4103
|
+
// generated source path remains ./plugins/<plugin-name>".
|
|
4104
|
+
//
|
|
4105
|
+
// Verify with `codex plugin list` after changing this. STATUS must not say
|
|
4106
|
+
// "not installed", and PATH must be a directory that exists.
|
|
4107
|
+
// Codex caches an installed plugin BY VERSION under ~/.codex/plugins/cache/.
|
|
4108
|
+
// Re-running the installer on the same release therefore reinstalls nothing,
|
|
4109
|
+
// and re-running it is the normal way people pick up a fix. So append the
|
|
4110
|
+
// cachebuster suffix Codex defines for exactly this case — the format is
|
|
4111
|
+
// `+codex.<token>`, per the bundled plugin-creator helper
|
|
4112
|
+
// (~/.codex/skills/.system/plugin-creator/scripts/update_plugin_cachebuster.py).
|
|
4113
|
+
const basePluginVersion = JSON.parse(readFileSync(join(pluginRoot, "package.json"), "utf-8")).version;
|
|
4114
|
+
const pluginVersion = `${basePluginVersion}+codex.${new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14)}`;
|
|
4010
4115
|
const marketplaceDir = join(homedir(), ".agents", "plugins");
|
|
4011
|
-
const pluginDir = join(
|
|
4116
|
+
const pluginDir = join(homedir(), "plugins", "patchcord");
|
|
4117
|
+
|
|
4118
|
+
// Remove the body from the old, never-loadable location. Leaving it behind
|
|
4119
|
+
// makes `plugin list` ambiguous and keeps a stale copy of the skills on disk.
|
|
4120
|
+
const deadPluginDir = join(marketplaceDir, "patchcord");
|
|
4121
|
+
if (existsSync(join(deadPluginDir, ".codex-plugin", "plugin.json"))) {
|
|
4122
|
+
try { rmSync(deadPluginDir, { recursive: true, force: true }); } catch {}
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4012
4125
|
mkdirSync(join(pluginDir, ".codex-plugin"), { recursive: true });
|
|
4013
4126
|
mkdirSync(join(pluginDir, "skills", "patchcord"), { recursive: true });
|
|
4014
4127
|
mkdirSync(join(pluginDir, "skills", "patchcord-wait"), { recursive: true });
|
|
@@ -4038,7 +4151,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
4038
4151
|
writeFileSync(join(pluginDir, "skills", "patchcord-wait", "SKILL.md"),
|
|
4039
4152
|
readFileSync(join(pluginRoot, "skills", "wait", "SKILL.md"), "utf-8"));
|
|
4040
4153
|
|
|
4041
|
-
// Personal marketplace entry
|
|
4154
|
+
// Personal marketplace entry. `path` is relative to HOME, not to this file.
|
|
4042
4155
|
const marketplacePath = join(marketplaceDir, "marketplace.json");
|
|
4043
4156
|
updateJsonConfig(marketplacePath, (obj) => {
|
|
4044
4157
|
if (!obj.name) obj.name = "patchcord";
|
|
@@ -4046,24 +4159,71 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
|
|
|
4046
4159
|
obj.plugins = (obj.plugins || []).filter(p => p.name !== "patchcord");
|
|
4047
4160
|
obj.plugins.push({
|
|
4048
4161
|
name: "patchcord",
|
|
4049
|
-
source: { source: "local", path: "./patchcord" },
|
|
4162
|
+
source: { source: "local", path: "./plugins/patchcord" },
|
|
4050
4163
|
policy: { installation: "INSTALLED_BY_DEFAULT", authentication: "ON_INSTALL" },
|
|
4051
4164
|
category: "Productivity",
|
|
4052
4165
|
});
|
|
4053
4166
|
});
|
|
4054
4167
|
|
|
4055
|
-
//
|
|
4168
|
+
// Writing the marketplace entry does NOT install the plugin. Codex leaves it
|
|
4169
|
+
// at "not installed" until this runs, `INSTALLED_BY_DEFAULT` notwithstanding.
|
|
4170
|
+
// We never ran it, which is the second half of why the plugin was dead.
|
|
4171
|
+
// The command is `plugin add`, not `plugin install` — see
|
|
4172
|
+
// ~/.codex/skills/.system/plugin-creator/references/installing-and-updating.md.
|
|
4173
|
+
//
|
|
4174
|
+
// Not run() — run() has no timeout, and this is the one call here that talks
|
|
4175
|
+
// to another program's install path. The marketplace entry sets
|
|
4176
|
+
// authentication ON_INSTALL, so a future Codex could decide to ask. An
|
|
4177
|
+
// installer that hangs forever is worse than one that skips the plugin, and
|
|
4178
|
+
// skipping is survivable: the ~/.agents/skills/ fallback below covers it.
|
|
4179
|
+
let pluginAdded = false;
|
|
4180
|
+
try {
|
|
4181
|
+
execSync(`codex plugin add patchcord@patchcord`,
|
|
4182
|
+
{ stdio: "pipe", encoding: "utf-8", timeout: 30_000 });
|
|
4183
|
+
pluginAdded = true;
|
|
4184
|
+
} catch { pluginAdded = false; }
|
|
4185
|
+
|
|
4186
|
+
// ~/.agents/skills/ holds the SAME two skills as a fallback. Codex reads that
|
|
4187
|
+
// tree directly, with no marketplace and no install step, so @patchcord works
|
|
4188
|
+
// there even when the plugin does not load.
|
|
4189
|
+
//
|
|
4190
|
+
// EXACTLY ONE of the two may exist at a time. Codex reads both and shows both.
|
|
4191
|
+
// With the plugin installed AND the fallback present, `@patchcord` lists:
|
|
4192
|
+
//
|
|
4193
|
+
// Patchcord Cross-machine agent messaging Plugin
|
|
4194
|
+
// patchcord Cross-agent messaging for Codex … Skill
|
|
4195
|
+
// patchcord (patchcord) Cross-agent messaging for Codex … Skill
|
|
4196
|
+
// patchcord-wait Block this turn for up to 5 minutes … Skill
|
|
4197
|
+
// patchcord:wait (patchcord) Block this turn for up to 5 minutes … Skill
|
|
4198
|
+
//
|
|
4199
|
+
// Five entries for two skills. Observed, not predicted — an earlier version
|
|
4200
|
+
// of this comment asserted they would not collide, and that was wrong.
|
|
4056
4201
|
const globalSkillDir = join(homedir(), ".agents", "skills", "patchcord");
|
|
4057
|
-
mkdirSync(globalSkillDir, { recursive: true });
|
|
4058
|
-
writeFileSync(join(globalSkillDir, "SKILL.md"),
|
|
4059
|
-
readFileSync(join(pluginRoot, "per-project-skills", "codex", "SKILL.md"), "utf-8"));
|
|
4060
4202
|
const globalWaitDir = join(homedir(), ".agents", "skills", "patchcord-wait");
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4203
|
+
if (pluginAdded) {
|
|
4204
|
+
for (const d of [globalSkillDir, globalWaitDir]) {
|
|
4205
|
+
if (existsSync(d)) { try { rmSync(d, { recursive: true, force: true }); } catch {} }
|
|
4206
|
+
}
|
|
4207
|
+
} else {
|
|
4208
|
+
mkdirSync(globalSkillDir, { recursive: true });
|
|
4209
|
+
writeFileSync(join(globalSkillDir, "SKILL.md"),
|
|
4210
|
+
readFileSync(join(pluginRoot, "per-project-skills", "codex", "SKILL.md"), "utf-8"));
|
|
4211
|
+
mkdirSync(globalWaitDir, { recursive: true });
|
|
4212
|
+
writeFileSync(join(globalWaitDir, "SKILL.md"),
|
|
4213
|
+
readFileSync(join(pluginRoot, "skills", "wait", "SKILL.md"), "utf-8"));
|
|
4214
|
+
}
|
|
4064
4215
|
|
|
4065
4216
|
console.log(`\n ${green}✓${r} Codex configured: ${dim}${configPath}${r}`);
|
|
4066
|
-
|
|
4217
|
+
if (pluginAdded) {
|
|
4218
|
+
console.log(` ${green}✓${r} Plugin installed: ${dim}${pluginDir}${r}`);
|
|
4219
|
+
} else {
|
|
4220
|
+
// Say it plainly. A silent failure here still leaves @patchcord working
|
|
4221
|
+
// through ~/.agents/skills/, so this is a downgrade, not a breakage.
|
|
4222
|
+
console.log(` ${yellow}!${r} Codex plugin did not install (${dim}codex plugin add patchcord@patchcord${r})`);
|
|
4223
|
+
console.log(` ${dim}Skills still work. Check with: codex plugin list${r}`);
|
|
4224
|
+
}
|
|
4225
|
+
console.log(` ${green}✓${r} Type ${dim}@patchcord${r} or ${dim}@patchcord-wait${r} in Codex`);
|
|
4226
|
+
console.log(` ${dim}Codex dropped custom slash commands — @ is the picker now.${r}`);
|
|
4067
4227
|
} else {
|
|
4068
4228
|
// Claude Code: write .mcp.json
|
|
4069
4229
|
const mcpPath = join(cwd, ".mcp.json");
|
package/package.json
CHANGED