From f91890aef2ca44b63bf968ebbeb93bce5e2c5fdd Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Sat, 31 Oct 2026 02:00:26 +0000 Subject: [PATCH] fix: write the recall allow as a V1 permission rule so OpenCode V1 1.19+ starts Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- README.md | 11 ++++--- src/services/opencode-config.test.ts | 27 +++++++++++++++-- src/services/opencode-config.ts | 43 ++++++++++++++++++---------- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 19da222..41516ea 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,10 @@ is safe to re-run; if anything is missing, add it manually: "opencode-supermemory", // ... other OpenCode 2 plugins ], - "permissions": [ - { "action": "supermemory_recall", "resource": "*", "effect": "allow" }, - ], + // OpenCode V1 rejects V2 "permissions"; OpenCode 2 reads this as the same rule + "permission": { + "supermemory_recall": "allow", + }, } ``` @@ -220,9 +221,7 @@ OpenCode 2 side by side: { "plugin": ["opencode-supermemory@latest"], "plugins": ["opencode-supermemory"], - "permissions": [ - { "action": "supermemory_recall", "resource": "*", "effect": "allow" }, - ], + "permission": { "supermemory_recall": "allow" }, } ``` diff --git a/src/services/opencode-config.test.ts b/src/services/opencode-config.test.ts index 79b1a48..d27fc47 100644 --- a/src/services/opencode-config.test.ts +++ b/src/services/opencode-config.test.ts @@ -18,7 +18,8 @@ describe("OpenCode config editor", () => { expect(result.changed).toBe(true); expect(config.plugin).toEqual([V1_PLUGIN_ENTRY]); expect(config.plugins).toEqual([V2_PLUGIN_ENTRY]); - expect(config.permissions).toEqual([RECALL_PERMISSION]); + expect(config.permission).toEqual({ supermemory_recall: "allow" }); + expect(config.permissions).toBeUndefined(); expect(result.warnings).toEqual([]); }); @@ -36,7 +37,7 @@ describe("OpenCode config editor", () => { expect(first.content).not.toContain(V1_PLUGIN_ENTRY); const config = JSON.parse(first.content.replace(/^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1")); expect(config.plugins).toEqual([V2_PLUGIN_ENTRY]); - expect(config.permissions).toEqual([RECALL_PERMISSION]); + expect(config.permission).toEqual({ supermemory_recall: "allow" }); const second = editOpenCodeConfig(first.content); expect(second.changed).toBe(false); @@ -55,19 +56,39 @@ describe("OpenCode config editor", () => { const content = JSON.stringify({ plugin: ["opencode-supermemory@2.0.13"], plugins: [{ package: "opencode-supermemory@latest", options: {} }], - permissions: [RECALL_PERMISSION], + permission: { supermemory_recall: "allow" }, }); expect(editOpenCodeConfig(content).changed).toBe(false); }); + test("moves the V2 recall rule that OpenCode V1 rejects into V1 permission rules", () => { + const other = { action: "bash", resource: "*", effect: "ask" }; + const onlyRecall = JSON.parse( + editOpenCodeConfig(JSON.stringify({ permissions: [RECALL_PERMISSION] })).content, + ); + expect(onlyRecall.permissions).toBeUndefined(); + expect(onlyRecall.permission).toEqual({ supermemory_recall: "allow" }); + + const withOther = JSON.parse( + editOpenCodeConfig(JSON.stringify({ permissions: [RECALL_PERMISSION, other] })).content, + ); + expect(withOther.permissions).toEqual([other]); + expect(withOther.permission).toEqual({ supermemory_recall: "allow" }); + }); + test("keeps an explicit recall deny and warns instead of overriding it", () => { const deny = { ...RECALL_PERMISSION, effect: "deny" }; const result = editOpenCodeConfig(JSON.stringify({ permissions: [deny] })); const config = JSON.parse(result.content); expect(config.permissions).toEqual([deny]); + expect(config.permission).toBeUndefined(); expect(result.warnings).toHaveLength(1); expect(readOpenCodeRegistration(result.content).recallDenied).toBe(true); + + const v1Deny = editOpenCodeConfig(JSON.stringify({ permission: { supermemory_recall: "deny" } })); + expect(JSON.parse(v1Deny.content).permission).toEqual({ supermemory_recall: "deny" }); + expect(v1Deny.warnings).toHaveLength(1); }); test("rejects configs whose arrays are not arrays", () => { diff --git a/src/services/opencode-config.ts b/src/services/opencode-config.ts index c486259..4a6838d 100644 --- a/src/services/opencode-config.ts +++ b/src/services/opencode-config.ts @@ -151,16 +151,23 @@ function isRecallPermission(value: unknown, effect: "allow" | "deny"): boolean { ); } +function getV1RecallRule(config: JsonObject): unknown { + return isObject(config.permission) ? config.permission[RECALL_PERMISSION.action] : undefined; +} + export function readOpenCodeRegistration(content: string): OpenCodeRegistration { const config = parseOpenCodeConfig(content); const plugin = Array.isArray(config.plugin) ? config.plugin : []; const plugins = Array.isArray(config.plugins) ? config.plugins : []; const permissions = Array.isArray(config.permissions) ? config.permissions : []; + const v1Rule = getV1RecallRule(config); return { v1: plugin.some(isSupermemoryPluginEntry), v2: plugins.some(isSupermemoryPluginEntry), - recallAllowed: permissions.some((entry) => isRecallPermission(entry, "allow")), - recallDenied: permissions.some((entry) => isRecallPermission(entry, "deny")), + recallAllowed: + v1Rule === "allow" || permissions.some((entry) => isRecallPermission(entry, "allow")), + recallDenied: + v1Rule === "deny" || permissions.some((entry) => isRecallPermission(entry, "deny")), }; } @@ -178,26 +185,32 @@ export function editOpenCodeConfig(rawContent: string): OpenCodeConfigEditResult content = addArrayEntry(content, "plugin", V1_PLUGIN_ENTRY, isSupermemoryPluginEntry); content = addArrayEntry(content, "plugins", V2_PLUGIN_ENTRY, isSupermemoryPluginEntry); - const config = parseOpenCodeConfig(content); + let config = parseOpenCodeConfig(content); const permissions = config.permissions; if (permissions !== undefined && !Array.isArray(permissions)) { throw new Error('OpenCode config property "permissions" must be an array'); } - const permissionEntries = permissions ?? []; - if (permissionEntries.some((entry) => isRecallPermission(entry, "deny"))) { - warnings.push( - 'OpenCode 2 permission "supermemory_recall" is explicitly denied; preserving the deny instead of adding an allow.', - ); - } else if ( - !permissionEntries.some((entry) => isRecallPermission(entry, "allow")) - ) { - content = addArrayEntry( + const legacyAllow = (permissions ?? []).findIndex((entry) => isRecallPermission(entry, "allow")); + if (legacyAllow !== -1) { + content = applyModification( content, - "permissions", - RECALL_PERMISSION, - (entry) => isRecallPermission(entry, "allow"), + permissions!.length === 1 ? ["permissions"] : ["permissions", legacyAllow], + undefined, + ); + config = parseOpenCodeConfig(content); + } + + const v1Rule = getV1RecallRule(config); + const recallDenied = + v1Rule === "deny" || + (permissions ?? []).some((entry) => isRecallPermission(entry, "deny")); + if (recallDenied) { + warnings.push( + 'OpenCode permission "supermemory_recall" is explicitly denied; preserving the deny instead of adding an allow.', ); + } else if (v1Rule === undefined && (config.permission === undefined || isObject(config.permission))) { + content = applyModification(content, ["permission", RECALL_PERMISSION.action], "allow"); } return {