@codemem/opencode-plugin 0.36.1 → 0.37.1

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.
@@ -15,7 +15,7 @@ import {
15
15
 
16
16
  const TRUTHY_VALUES = ["1", "true", "yes"];
17
17
  const DISABLED_VALUES = ["0", "false", "off"];
18
- const PINNED_BACKEND_VERSION = "0.36.1";
18
+ const PINNED_BACKEND_VERSION = "0.37.1";
19
19
  const COMPAT_CHECK_DELAY_MS = 1500;
20
20
  const COMPAT_CHECK_CACHE_TTL_MS = 5 * 60 * 1000;
21
21
 
@@ -623,7 +623,22 @@ const detectRunner = ({ cwd, envRunner }) => {
623
623
  // Prefer the TS codemem if installed globally, fall back to npx
624
624
  try {
625
625
  const versionOutput = execSync("codemem --version", { encoding: "utf-8", timeout: 3000 }).trim();
626
- if (versionOutput === PINNED_BACKEND_VERSION || versionOutput.startsWith("0.2")) {
626
+ // Accept any global codemem at or above the pinned backend version, so a
627
+ // newer global install is used instead of silently downgrading to
628
+ // `npx codemem@<pin>` (the old check only matched the exact pin or the
629
+ // long-obsolete 0.2x series).
630
+ // Accept a global codemem only when it is the EXACT pinned version, or a
631
+ // clean (non-prerelease) release at least as new as the pin. isVersionAtLeast
632
+ // ignores prerelease suffixes, so without this a prerelease like
633
+ // "0.36.1-alpha.0" would wrongly compare >= the "0.36.1" release even though
634
+ // SemVer orders it below. Requiring an exact match for prereleases also
635
+ // handles a prerelease PIN correctly (the release script can set one): only
636
+ // the identical prerelease is trusted, never an older/different one.
637
+ const isPrerelease = versionOutput.includes("-");
638
+ const matchesOrCleanNewer =
639
+ versionOutput === PINNED_BACKEND_VERSION ||
640
+ (!isPrerelease && isVersionAtLeast(versionOutput, PINNED_BACKEND_VERSION));
641
+ if (matchesOrCleanNewer) {
627
642
  return "codemem";
628
643
  }
629
644
  } catch {
@@ -722,10 +737,14 @@ export const OpencodeMemPlugin = async ({
722
737
  const viewerPort = process.env.CODEMEM_VIEWER_PORT || "38888";
723
738
  const viewerDbPath = process.env.CODEMEM_DB || "";
724
739
  const viewerConfigPath = process.env.CODEMEM_CONFIG || "";
725
- const commandTimeout = Number.parseInt(
726
- process.env.CODEMEM_PLUGIN_CMD_TIMEOUT || "20000",
727
- 10
728
- );
740
+ // A malformed value (e.g. "abc", "", "-1") falls back to the 20s default
741
+ // instead of NaN, which would silently disable the timeout. An explicit "0"
742
+ // is preserved as the intentional opt-out (disable the timeout, e.g. for slow
743
+ // first-run npx installs/backend updates) — the `commandTimeout > 0` guard at
744
+ // the call site skips installing the timer when it is 0.
745
+ const rawCommandTimeout = String(process.env.CODEMEM_PLUGIN_CMD_TIMEOUT ?? "").trim();
746
+ const commandTimeout =
747
+ rawCommandTimeout === "0" ? 0 : parsePositiveInt(rawCommandTimeout, 20000);
729
748
  const backendUpdatePolicy = parseBackendUpdatePolicy(
730
749
  process.env.CODEMEM_BACKEND_UPDATE_POLICY || "notify"
731
750
  );
@@ -1272,8 +1291,18 @@ export const OpencodeMemPlugin = async ({
1272
1291
  });
1273
1292
  let stdout = "";
1274
1293
  let stderr = "";
1275
- proc.stdout.on("data", (chunk) => { stdout += chunk; });
1276
- proc.stderr.on("data", (chunk) => { stderr += chunk; });
1294
+ // Guard stdio access: a hard spawn failure (ENOENT) can hand back null
1295
+ // streams, and a synchronous null-deref here would reject the promise
1296
+ // that callers await for a resolved { exitCode, stdout, stderr }.
1297
+ if (proc.stdout) proc.stdout.on("data", (chunk) => { stdout += chunk; });
1298
+ if (proc.stderr) proc.stderr.on("data", (chunk) => { stderr += chunk; });
1299
+ // Absorb async stream errors (e.g. EPIPE when the child exits mid-write,
1300
+ // or a spawn failure surfacing on a pipe). Without these handlers an
1301
+ // unhandled stream "error" event would crash the host process. The
1302
+ // child-level proc.once("error") below still resolves the promise.
1303
+ for (const stream of [proc.stdin, proc.stdout, proc.stderr]) {
1304
+ if (stream && typeof stream.on === "function") stream.on("error", () => {});
1305
+ }
1277
1306
  if (typeof stdinText === "string") {
1278
1307
  try {
1279
1308
  proc.stdin.write(stdinText);
@@ -2078,7 +2107,9 @@ export const OpencodeMemPlugin = async ({
2078
2107
  limit: tool.schema.number().optional(),
2079
2108
  },
2080
2109
  async execute({ limit }) {
2081
- const safeLimit = Number.isFinite(limit) ? String(limit) : "5";
2110
+ // Number.isFinite accepts floats and negatives; coerce to a positive
2111
+ // integer (default 5) before forwarding to the CLI.
2112
+ const safeLimit = String(parsePositiveInt(limit, 5));
2082
2113
  const recent = await runCli(["recent", "--limit", safeLimit]);
2083
2114
  if (recent.exitCode === 0) {
2084
2115
  return recent.stdout.trim() || "No recent memories.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemem/opencode-plugin",
3
- "version": "0.36.1",
3
+ "version": "0.37.1",
4
4
  "description": "CodeMem plugin for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -18,7 +18,7 @@
18
18
  ".opencode/lib/"
19
19
  ],
20
20
  "dependencies": {
21
- "@opencode-ai/plugin": "1.17.8"
21
+ "@opencode-ai/plugin": "1.17.11"
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",