From 6125802aa5a24cd0ef9e6df6bed73cbeb32eae86 Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Thu, 13 Aug 2026 19:47:47 +0800 Subject: [PATCH] fix(opencode): expose per-call space routing --- .claude-plugin/marketplace.json | 2 +- integrations.json | 8 +- nowledge-mem-opencode-plugin/AGENTS.md | 2 + nowledge-mem-opencode-plugin/CHANGELOG.md | 7 ++ nowledge-mem-opencode-plugin/README.md | 6 +- nowledge-mem-opencode-plugin/dist/index.js | 75 ++++++++++++----- nowledge-mem-opencode-plugin/package.json | 2 +- nowledge-mem-opencode-plugin/src/index.ts | 80 +++++++++++++++---- nowledge-mem-pi-package/CHANGELOG.md | 6 ++ nowledge-mem-pi-package/README.md | 1 + .../extensions/nowledge-mem.ts | 2 +- nowledge-mem-pi-package/package.json | 2 +- tests/plugin_e2e/test_key_plugins_e2e.py | 25 ++++-- 13 files changed, 167 insertions(+), 51 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 0fba97c0..db27d557 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ { "name": "nowledge-mem", "description": "Personal knowledge graph for Claude Code and Grok — remembers decisions, searches past work, captures sessions", - "version": "0.7.22", + "version": "0.7.23", "author": { "name": "Nowledge Labs", "url": "https://www.nowledge-labs.ai/", diff --git a/integrations.json b/integrations.json index f7ce9e3f..d2fbf6df 100644 --- a/integrations.json +++ b/integrations.json @@ -21,7 +21,7 @@ "name": "Claude Code", "category": "coding", "type": "plugin", - "version": "0.7.22", + "version": "0.7.23", "directory": "nowledge-mem-claude-code-plugin", "transport": "cli", "capabilities": { @@ -74,7 +74,7 @@ "name": "Grok Build", "category": "coding", "type": "plugin", - "version": "0.7.22", + "version": "0.7.23", "directory": "nowledge-mem-claude-code-plugin", "transport": "cli", "capabilities": { @@ -868,7 +868,7 @@ "name": "Pi", "category": "coding", "type": "plugin", - "version": "0.8.5", + "version": "0.8.6", "directory": "nowledge-mem-pi-package", "transport": "plugin+cli", "capabilities": { @@ -1091,7 +1091,7 @@ "name": "OpenCode", "category": "coding", "type": "plugin", - "version": "0.3.6", + "version": "0.3.7", "directory": "nowledge-mem-opencode-plugin", "transport": "cli+http", "capabilities": { diff --git a/nowledge-mem-opencode-plugin/AGENTS.md b/nowledge-mem-opencode-plugin/AGENTS.md index 3358aad6..07398f11 100644 --- a/nowledge-mem-opencode-plugin/AGENTS.md +++ b/nowledge-mem-opencode-plugin/AGENTS.md @@ -10,6 +10,8 @@ Use `nowledge_mem_working_memory` only for a lightweight daily briefing or fallb If the shared Mem client config sets `space`, that lane is the ambient default for Context Bundle, Working Memory, search, save, and session-save flows. `NMEM_SPACE=""` can override it for a single OpenCode process. Legacy `NMEM_SPACE_ID` still works for older setups. +When the user names a different Space for a single action, pass it directly on the tool call. Use `space_id` when you have already checked `nmem --json spaces list`; otherwise pass the exact Space name as `space`. Explicit tool arguments override the ambient default for that call. + Reference relevant parts naturally as the conversation progresses. Do not re-read unless the user asks or the session context changes materially. ## Proactive Search diff --git a/nowledge-mem-opencode-plugin/CHANGELOG.md b/nowledge-mem-opencode-plugin/CHANGELOG.md index 9ee7a3e7..efaf06e9 100644 --- a/nowledge-mem-opencode-plugin/CHANGELOG.md +++ b/nowledge-mem-opencode-plugin/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## [0.3.7] - 2026-08-13 + +### Fixed + +- Added per-call `space` and `space_id` arguments to the OpenCode tools that read, search, save, update, or capture scoped knowledge. When a user asks for a named Space, OpenCode can now route that single tool call there instead of falling back to the ambient default. +- Treat explicit `--space-id` the same as explicit `--space` when deciding whether to apply the ambient Space. + ## [0.3.6] - 2026-08-10 ### Fixed diff --git a/nowledge-mem-opencode-plugin/README.md b/nowledge-mem-opencode-plugin/README.md index 55761021..e82a2109 100644 --- a/nowledge-mem-opencode-plugin/README.md +++ b/nowledge-mem-opencode-plugin/README.md @@ -57,7 +57,7 @@ The plugin follows OpenCode's standard plugin update mechanism. To pin a specifi ```json { - "plugin": ["opencode-nowledge-mem@0.3.6"] + "plugin": ["opencode-nowledge-mem@0.3.7"] } ``` @@ -75,6 +75,8 @@ The plugin follows OpenCode's standard plugin update mechanism. To pin a specifi | `nowledge_mem_save_handoff` | Save a curated handoff summary (lighter, agent-composed). | | `nowledge_mem_status` | Check Nowledge Mem server connectivity and diagnostics. | +Tools that read, write, or save scoped knowledge accept optional `space` and `space_id` arguments for one-call routing. Use `space_id` when you have checked `nmem --json spaces list`; otherwise pass the exact Space name as `space`. Explicit tool arguments override `NMEM_SPACE` and the shared client config for that call. + ## How session capture works Nowledge Mem captures OpenCode sessions in four complementary ways: @@ -168,6 +170,8 @@ NMEM_SPACE="Research Agent" opencode The plugin's Context Bundle, Working Memory, search, save, and full-session thread save paths will follow that lane. Environment variables take priority over the shared config. If you do not have a real ambient lane, stay on `Default`. +When a single request names another Space, OpenCode can pass `space` or `space_id` directly to the relevant `nowledge_mem_*` tool. The ambient setting remains the default only for calls that do not provide an explicit Space. + For multi-agent orchestrators, set `NMEM_AGENT_ID=` per spawned OpenCode worker. Add `NMEM_SPACE` only when that run should override the AI Identity's default space. `NMEM_HOST_AGENT_ID` is for advanced external aliases. Context Bundle will use the stable identity while keeping `source_app=opencode` for provenance. Shared spaces, default retrieval, and agent guidance still come from Mem's own space profile. OpenCode should pick the lane once, not invent a second plugin-local memory partition. diff --git a/nowledge-mem-opencode-plugin/dist/index.js b/nowledge-mem-opencode-plugin/dist/index.js index 5339da7f..0e722b88 100644 --- a/nowledge-mem-opencode-plugin/dist/index.js +++ b/nowledge-mem-opencode-plugin/dist/index.js @@ -11,6 +11,8 @@ You have Nowledge Mem tools for cross-tool knowledge management. Use them proact **At session start:** Call \`nowledge_mem_context_bundle\` when identity, scope, or rules may matter. It includes Working Memory, owner identity, AI Identity, active space, and the active rules. Use \`nowledge_mem_working_memory\` only for a lightweight daily briefing or fallback. Reference relevant parts naturally as the conversation progresses. +**Space routing:** If the user names a Nowledge Space, pass \`space\` or \`space_id\` on the relevant tool call. Prefer \`space_id\` after checking \`nmem --json spaces list\`; otherwise pass the exact Space name as \`space\`. Do not rely on the ambient default when the user explicitly asks for a different Space. + **When to search (\`nowledge_mem_search\`):** - The user references previous work, a prior fix, or an earlier decision - The task resumes a named feature, bug, refactor, or subsystem @@ -73,7 +75,7 @@ var index_default = { } function withAmbientSpaceArg(args) { let next = args; - if (ambientSpaceId && !next.includes("--space")) { + if (ambientSpaceId && !next.includes("--space") && !next.includes("--space-id")) { const scopedCommands = /* @__PURE__ */ new Set(["context", "ctx", "wm", "m", "memories", "t", "threads"]); if (scopedCommands.has(next[0] ?? "")) { next = [...next, "--space", ambientSpaceId]; @@ -114,6 +116,27 @@ var index_default = { } return { ...body, space_id: ambientSpaceId }; } + function stringToolValue(value) { + return typeof value === "string" ? value.trim() || void 0 : void 0; + } + function explicitSpaceForCli(args) { + const spaceId = stringToolValue(args.space_id); + if (spaceId) return ["--space-id", spaceId]; + const space = stringToolValue(args.space); + return space ? ["--space", space] : []; + } + function explicitSpaceForHttp(args) { + return stringToolValue(args.space_id) || stringToolValue(args.space); + } + function withExplicitSpaceArg(cmd, args) { + return [...cmd, ...explicitSpaceForCli(args)]; + } + function spaceToolArgs() { + return { + space: tool.schema.string().optional().describe("Optional Nowledge Space name or alias for this one call"), + space_id: tool.schema.string().optional().describe("Optional Nowledge Space id/key for this one call; takes priority over space") + }; + } async function nmemApi(path, body, timeoutMs = 3e4) { const headers = { "Content-Type": "application/json" }; if (apiKey) { @@ -303,6 +326,7 @@ ${reasoning} source: "opencode", project: projectPath, workspace: projectPath, + ...options.spaceId ? { space_id: options.spaceId } : {}, metadata }, timeoutMs @@ -316,7 +340,7 @@ ${reasoning} messages: threadMessages, deduplicate: true, idempotency_key: `opencode:live:${ctx.sessionID}`, - ...ambientSpaceId ? { space_id: ambientSpaceId } : {} + ...options.spaceId ? { space_id: options.spaceId } : ambientSpaceId ? { space_id: ambientSpaceId } : {} }, timeoutMs ); @@ -387,20 +411,24 @@ ${reasoning} tool: { nowledge_mem_context_bundle: tool({ description: "Read Nowledge Mem's startup Context Bundle: owner identity, resolved AI Identity, active scope, active rules, Working Memory, and KFS paths. Call this near session start when behavior, identity, or scope matters.", - args: {}, - async execute(_args, _ctx) { - const bundle = await nmem(["context", "--source-app", "opencode"]); + args: { + ...spaceToolArgs() + }, + async execute(args, _ctx) { + const bundle = await nmem(withExplicitSpaceArg(["context", "--source-app", "opencode"], args)); if (isNmemErrorPayload(bundle)) { - return await nmem(["wm", "read"]); + return await nmem(withExplicitSpaceArg(["wm", "read"], args)); } return bundle; } }), nowledge_mem_working_memory: tool({ description: "Read today's lightweight Working Memory briefing from Nowledge Mem: current focus areas, priorities, recent decisions, and open questions across all your AI tools. Use nowledge_mem_context_bundle for full startup identity/scope/rules context.", - args: {}, - async execute(_args, _ctx) { - return await nmem(["wm", "read"]); + args: { + ...spaceToolArgs() + }, + async execute(args, _ctx) { + return await nmem(withExplicitSpaceArg(["wm", "read"], args)); } }), nowledge_mem_search: tool({ @@ -411,14 +439,15 @@ ${reasoning} label: tool.schema.string().optional().describe("Filter by label name"), mode: tool.schema.enum(["default", "deep"]).optional().describe( "Search mode: 'default' for fast hybrid, 'deep' for broader conceptual matching" - ) + ), + ...spaceToolArgs() }, async execute(args, _ctx) { const cmd = ["m", "search", args.query]; if (args.limit) cmd.push("-n", String(Math.min(20, Math.max(1, args.limit)))); if (args.label) cmd.push("-l", args.label); if (args.mode === "deep") cmd.push("--mode", "deep"); - return await nmem(cmd); + return await nmem(withExplicitSpaceArg(cmd, args)); } }), nowledge_mem_save: tool({ @@ -439,7 +468,8 @@ ${reasoning} labels: tool.schema.string().optional().describe("Comma-separated labels for categorization"), importance: tool.schema.number().optional().describe( "0.0-1.0 importance score. 0.8-1.0: major decisions. 0.5-0.7: useful patterns. 0.3-0.4: minor notes." - ) + ), + ...spaceToolArgs() }, async execute(args, _ctx) { const cmd = ["m", "add", args.content, "-t", args.title, "--source", "opencode"]; @@ -450,7 +480,7 @@ ${reasoning} } } if (args.importance != null) cmd.push("-i", String(args.importance)); - return await nmem(cmd); + return await nmem(withExplicitSpaceArg(cmd, args)); } }), nowledge_mem_update: tool({ @@ -459,38 +489,42 @@ ${reasoning} memory_id: tool.schema.string().describe("ID of the memory to update"), content: tool.schema.string().optional().describe("Updated content"), title: tool.schema.string().optional().describe("Updated title"), - importance: tool.schema.number().optional().describe("Updated importance score") + importance: tool.schema.number().optional().describe("Updated importance score"), + ...spaceToolArgs() }, async execute(args, _ctx) { const cmd = ["m", "update", args.memory_id]; if (args.content) cmd.push("-c", args.content); if (args.title) cmd.push("-t", args.title); if (args.importance != null) cmd.push("-i", String(args.importance)); - return await nmem(cmd); + return await nmem(withExplicitSpaceArg(cmd, args)); } }), nowledge_mem_thread_search: tool({ description: "Search past conversations from any tool (Claude Code, ChatGPT, Cursor, etc.). Use when the user asks about a prior discussion or exact conversation history.", args: { query: tool.schema.string().describe("Search query for past conversations"), - limit: tool.schema.number().optional().describe("Max results (default 5)") + limit: tool.schema.number().optional().describe("Max results (default 5)"), + ...spaceToolArgs() }, async execute(args, _ctx) { const cmd = ["t", "search", args.query]; if (args.limit) cmd.push("--limit", String(Math.min(20, Math.max(1, args.limit)))); - return await nmem(cmd); + return await nmem(withExplicitSpaceArg(cmd, args)); } }), nowledge_mem_save_thread: tool({ description: "Save the current OpenCode session as a full conversation thread in Nowledge Mem. Extracts the complete message history so any tool can find and read this conversation later. Idempotent: safe to call multiple times. Use at natural stopping points or when the user asks to save the session.", args: { - summary: tool.schema.string().optional().describe("Brief description of what was discussed (used as thread title)") + summary: tool.schema.string().optional().describe("Brief description of what was discussed (used as thread title)"), + ...spaceToolArgs() }, async execute(args, ctx) { try { return JSON.stringify(await syncSessionThread(ctx, { reason: "manual_tool", summary: args.summary, + spaceId: explicitSpaceForHttp(args), force: true, timeoutMs: 3e4 })); @@ -507,11 +541,12 @@ ${reasoning} topic: tool.schema.string().describe("Brief topic or title for this session"), summary: tool.schema.string().describe( "Structured handoff: Goal, Decisions made, Key files touched, Risks/open questions, Suggested next steps" - ) + ), + ...spaceToolArgs() }, async execute(args, _ctx) { const title = `Session Handoff - ${args.topic}`; - return await nmem(["t", "create", "-t", title, "-c", args.summary, "-s", "opencode"]); + return await nmem(withExplicitSpaceArg(["t", "create", "-t", title, "-c", args.summary, "-s", "opencode"], args)); } }), nowledge_mem_status: tool({ diff --git a/nowledge-mem-opencode-plugin/package.json b/nowledge-mem-opencode-plugin/package.json index 8e543a55..5678076e 100644 --- a/nowledge-mem-opencode-plugin/package.json +++ b/nowledge-mem-opencode-plugin/package.json @@ -1,6 +1,6 @@ { "name": "opencode-nowledge-mem", - "version": "0.3.6", + "version": "0.3.7", "description": "Nowledge Mem plugin for OpenCode. Cross-tool knowledge with idle-event thread capture, Context Bundle, search, save, and handoffs.", "type": "module", "main": "dist/index.js", diff --git a/nowledge-mem-opencode-plugin/src/index.ts b/nowledge-mem-opencode-plugin/src/index.ts index 87b9ce98..b265555f 100644 --- a/nowledge-mem-opencode-plugin/src/index.ts +++ b/nowledge-mem-opencode-plugin/src/index.ts @@ -10,6 +10,8 @@ You have Nowledge Mem tools for cross-tool knowledge management. Use them proact **At session start:** Call \`nowledge_mem_context_bundle\` when identity, scope, or rules may matter. It includes Working Memory, owner identity, AI Identity, active space, and the active rules. Use \`nowledge_mem_working_memory\` only for a lightweight daily briefing or fallback. Reference relevant parts naturally as the conversation progresses. +**Space routing:** If the user names a Nowledge Space, pass \`space\` or \`space_id\` on the relevant tool call. Prefer \`space_id\` after checking \`nmem --json spaces list\`; otherwise pass the exact Space name as \`space\`. Do not rely on the ambient default when the user explicitly asks for a different Space. + **When to search (\`nowledge_mem_search\`):** - The user references previous work, a prior fix, or an earlier decision - The task resumes a named feature, bug, refactor, or subsystem @@ -84,7 +86,7 @@ export default { function withAmbientSpaceArg(args: string[]): string[] { let next = args - if (ambientSpaceId && !next.includes("--space")) { + if (ambientSpaceId && !next.includes("--space") && !next.includes("--space-id")) { const scopedCommands = new Set(["context", "ctx", "wm", "m", "memories", "t", "threads"]) if (scopedCommands.has(next[0] ?? "")) { next = [...next, "--space", ambientSpaceId] @@ -138,6 +140,43 @@ export default { return { ...body, space_id: ambientSpaceId } } + type ExplicitSpaceArgs = { + space?: unknown + space_id?: unknown + } + + function stringToolValue(value: unknown): string | undefined { + return typeof value === "string" ? value.trim() || undefined : undefined + } + + function explicitSpaceForCli(args: ExplicitSpaceArgs): string[] { + const spaceId = stringToolValue(args.space_id) + if (spaceId) return ["--space-id", spaceId] + const space = stringToolValue(args.space) + return space ? ["--space", space] : [] + } + + function explicitSpaceForHttp(args: ExplicitSpaceArgs): string | undefined { + return stringToolValue(args.space_id) || stringToolValue(args.space) + } + + function withExplicitSpaceArg(cmd: string[], args: ExplicitSpaceArgs): string[] { + return [...cmd, ...explicitSpaceForCli(args)] + } + + function spaceToolArgs() { + return { + space: tool.schema + .string() + .optional() + .describe("Optional Nowledge Space name or alias for this one call"), + space_id: tool.schema + .string() + .optional() + .describe("Optional Nowledge Space id/key for this one call; takes priority over space"), + } + } + async function nmemApi( path: string, body: unknown, @@ -332,6 +371,7 @@ export default { options: { reason: SyncReason summary?: string + spaceId?: string force?: boolean timeoutMs?: number }, @@ -385,6 +425,7 @@ export default { source: "opencode", project: projectPath, workspace: projectPath, + ...(options.spaceId ? { space_id: options.spaceId } : {}), metadata, }, timeoutMs, @@ -399,7 +440,7 @@ export default { messages: threadMessages, deduplicate: true, idempotency_key: `opencode:live:${ctx.sessionID}`, - ...(ambientSpaceId ? { space_id: ambientSpaceId } : {}), + ...(options.spaceId ? { space_id: options.spaceId } : ambientSpaceId ? { space_id: ambientSpaceId } : {}), }, timeoutMs, ) @@ -481,11 +522,13 @@ export default { nowledge_mem_context_bundle: tool({ description: "Read Nowledge Mem's startup Context Bundle: owner identity, resolved AI Identity, active scope, active rules, Working Memory, and KFS paths. Call this near session start when behavior, identity, or scope matters.", - args: {}, - async execute(_args, _ctx) { - const bundle = await nmem(["context", "--source-app", "opencode"]) + args: { + ...spaceToolArgs(), + }, + async execute(args, _ctx) { + const bundle = await nmem(withExplicitSpaceArg(["context", "--source-app", "opencode"], args)) if (isNmemErrorPayload(bundle)) { - return await nmem(["wm", "read"]) + return await nmem(withExplicitSpaceArg(["wm", "read"], args)) } return bundle }, @@ -494,9 +537,11 @@ export default { nowledge_mem_working_memory: tool({ description: "Read today's lightweight Working Memory briefing from Nowledge Mem: current focus areas, priorities, recent decisions, and open questions across all your AI tools. Use nowledge_mem_context_bundle for full startup identity/scope/rules context.", - args: {}, - async execute(_args, _ctx) { - return await nmem(["wm", "read"]) + args: { + ...spaceToolArgs(), + }, + async execute(args, _ctx) { + return await nmem(withExplicitSpaceArg(["wm", "read"], args)) }, }), @@ -521,13 +566,14 @@ export default { .describe( "Search mode: 'default' for fast hybrid, 'deep' for broader conceptual matching", ), + ...spaceToolArgs(), }, async execute(args, _ctx) { const cmd = ["m", "search", args.query] if (args.limit) cmd.push("-n", String(Math.min(20, Math.max(1, args.limit)))) if (args.label) cmd.push("-l", args.label) if (args.mode === "deep") cmd.push("--mode", "deep") - return await nmem(cmd) + return await nmem(withExplicitSpaceArg(cmd, args)) }, }), @@ -564,6 +610,7 @@ export default { .describe( "0.0-1.0 importance score. 0.8-1.0: major decisions. 0.5-0.7: useful patterns. 0.3-0.4: minor notes.", ), + ...spaceToolArgs(), }, async execute(args, _ctx) { const cmd = ["m", "add", args.content, "-t", args.title, "--source", "opencode"] @@ -574,7 +621,7 @@ export default { } } if (args.importance != null) cmd.push("-i", String(args.importance)) - return await nmem(cmd) + return await nmem(withExplicitSpaceArg(cmd, args)) }, }), @@ -597,13 +644,14 @@ export default { .number() .optional() .describe("Updated importance score"), + ...spaceToolArgs(), }, async execute(args, _ctx) { const cmd = ["m", "update", args.memory_id] if (args.content) cmd.push("-c", args.content) if (args.title) cmd.push("-t", args.title) if (args.importance != null) cmd.push("-i", String(args.importance)) - return await nmem(cmd) + return await nmem(withExplicitSpaceArg(cmd, args)) }, }), @@ -618,11 +666,12 @@ export default { .number() .optional() .describe("Max results (default 5)"), + ...spaceToolArgs(), }, async execute(args, _ctx) { const cmd = ["t", "search", args.query] if (args.limit) cmd.push("--limit", String(Math.min(20, Math.max(1, args.limit)))) - return await nmem(cmd) + return await nmem(withExplicitSpaceArg(cmd, args)) }, }), @@ -634,12 +683,14 @@ export default { .string() .optional() .describe("Brief description of what was discussed (used as thread title)"), + ...spaceToolArgs(), }, async execute(args, ctx) { try { return JSON.stringify(await syncSessionThread(ctx, { reason: "manual_tool", summary: args.summary, + spaceId: explicitSpaceForHttp(args), force: true, timeoutMs: 30_000, })) @@ -663,10 +714,11 @@ export default { .describe( "Structured handoff: Goal, Decisions made, Key files touched, Risks/open questions, Suggested next steps", ), + ...spaceToolArgs(), }, async execute(args, _ctx) { const title = `Session Handoff - ${args.topic}` - return await nmem(["t", "create", "-t", title, "-c", args.summary, "-s", "opencode"]) + return await nmem(withExplicitSpaceArg(["t", "create", "-t", title, "-c", args.summary, "-s", "opencode"], args)) }, }), diff --git a/nowledge-mem-pi-package/CHANGELOG.md b/nowledge-mem-pi-package/CHANGELOG.md index 97468bd8..b3ab2020 100644 --- a/nowledge-mem-pi-package/CHANGELOG.md +++ b/nowledge-mem-pi-package/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to the Nowledge Mem Pi package will be documented in this fi The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.6] - 2026-08-13 + +### Fixed + +- Respect explicit `--space-id` arguments on `nmem` calls instead of adding the ambient `--space` on top of them. + ## [0.8.5] - 2026-07-23 ### Fixed diff --git a/nowledge-mem-pi-package/README.md b/nowledge-mem-pi-package/README.md index 1708971f..b924b822 100644 --- a/nowledge-mem-pi-package/README.md +++ b/nowledge-mem-pi-package/README.md @@ -97,6 +97,7 @@ Useful options: ```bash nmem t sync --from pi --session-dir ~/.pi/agent/sessions --limit 20 nmem t sync --from pi --space work --apply +nmem t sync --from pi --space-id sp_work_ab12cd34 --apply ``` When you pass `--session-dir`, only that directory is scanned. Without it, the command uses Pi's standard session locations. diff --git a/nowledge-mem-pi-package/extensions/nowledge-mem.ts b/nowledge-mem-pi-package/extensions/nowledge-mem.ts index 4d188436..129ae54a 100644 --- a/nowledge-mem-pi-package/extensions/nowledge-mem.ts +++ b/nowledge-mem-pi-package/extensions/nowledge-mem.ts @@ -136,7 +136,7 @@ function resolveConfig() { function withAmbientNmemArgs(args: string[], config = resolveConfig()): string[] { let next = [...args]; - if (config.space && !next.includes("--space")) { + if (config.space && !next.includes("--space") && !next.includes("--space-id")) { const scopedCommands = new Set(["context", "ctx", "wm", "m", "memories", "t", "threads"]); if (scopedCommands.has(next[0] || "")) { next = [...next, "--space", config.space]; diff --git a/nowledge-mem-pi-package/package.json b/nowledge-mem-pi-package/package.json index 02c57dec..7a78f44d 100644 --- a/nowledge-mem-pi-package/package.json +++ b/nowledge-mem-pi-package/package.json @@ -1,6 +1,6 @@ { "name": "nowledge-mem-pi", - "version": "0.8.5", + "version": "0.8.6", "description": "Cross-tool memory for Pi. Recall past decisions, search knowledge from every AI tool, and save what matters.", "keywords": ["pi-package"], "author": "Nowledge Labs", diff --git a/tests/plugin_e2e/test_key_plugins_e2e.py b/tests/plugin_e2e/test_key_plugins_e2e.py index cbb96926..bdf9d879 100644 --- a/tests/plugin_e2e/test_key_plugins_e2e.py +++ b/tests/plugin_e2e/test_key_plugins_e2e.py @@ -334,7 +334,7 @@ def test_key_plugin_static_contracts_are_declared(): claude_read_skill = (CLAUDE_PLUGIN / "skills" / "read-working-memory" / "SKILL.md").read_text(encoding="utf-8") claude_search_skill = (CLAUDE_PLUGIN / "skills" / "search-memory" / "SKILL.md").read_text(encoding="utf-8") assert claude_manifest["name"] == "nowledge-mem" - assert claude_manifest["version"] == "0.7.22" + assert claude_manifest["version"] == "0.7.23" assert claude_marketplace_plugin["version"] == claude_manifest["version"] assert registry_by_id["claude-code"]["version"] == claude_manifest["version"] assert registry_by_id["grok"]["version"] == claude_manifest["version"] @@ -519,16 +519,19 @@ def test_key_plugin_static_contracts_are_declared(): opencode_pkg = _read_json(OPENCODE_PLUGIN / "package.json") opencode_source = (OPENCODE_PLUGIN / "src" / "index.ts").read_text(encoding="utf-8") assert opencode_pkg["name"] == "opencode-nowledge-mem" - assert opencode_pkg["version"] == "0.3.6" + assert opencode_pkg["version"] == "0.3.7" assert registry_by_id["opencode"]["version"] == opencode_pkg["version"] assert registry_by_id["opencode"]["capabilities"]["autoCapture"] is True assert registry_by_id["opencode"]["autonomy"]["threads"] == "automatic-capture" assert "fetchSessionMessages" in opencode_source assert "path: { id: ctx.sessionID }" in opencode_source assert "nowledge_mem_context_bundle" in opencode_source - assert 'nmem(["context", "--source-app", "opencode"])' in opencode_source + assert 'nmem(withExplicitSpaceArg(["context", "--source-app", "opencode"], args))' in opencode_source assert '"--source", "opencode"' in opencode_source assert "withAmbientSpaceArg(args)" in opencode_source + assert "withExplicitSpaceArg" in opencode_source + assert "spaceToolArgs" in opencode_source + assert '!next.includes("--space-id")' in opencode_source assert "ambientAgentId" in opencode_source assert "ambientHostAgentId" in opencode_source assert "NMEM_AGENT_ID" in opencode_source @@ -700,7 +703,7 @@ def test_key_plugin_static_contracts_are_declared(): pi_pkg = _read_json(PI_PLUGIN / "package.json") pi_extension = (PI_PLUGIN / "extensions" / "nowledge-mem.ts").read_text(encoding="utf-8") pi_history_sync = (PI_PLUGIN / "scripts" / "sync-history.mjs").read_text(encoding="utf-8") - assert pi_pkg["version"] == "0.8.5" + assert pi_pkg["version"] == "0.8.6" assert "./extensions/nowledge-mem.ts" in pi_pkg["pi"]["extensions"] assert "./skills" in pi_pkg["pi"]["skills"] assert pi_pkg["bin"]["nowledge-mem-pi-sync"] == "./scripts/sync-history.mjs" @@ -730,6 +733,7 @@ def test_key_plugin_static_contracts_are_declared(): assert "sawReadFailure = true;" in pi_extension assert "readFileSync(LOCAL_WORKING_MEMORY_PATH" in pi_extension assert "withAmbientNmemArgs" in pi_extension + assert '!next.includes("--space-id")' in pi_extension assert 'pi.on("session_start"' in pi_extension assert 'pi.on("before_agent_start"' in pi_extension assert 'pi.on("session_compact"' in pi_extension @@ -1451,8 +1455,8 @@ def test_registry_connect_contract_points_agent_prompts_to_universal_skill(): assert by_id["droid"]["version"] == "0.1.1" assert by_id["openclaw"]["version"] == "0.8.31" assert by_id["proma"]["version"] == "0.1.5" - assert by_id["opencode"]["version"] == "0.3.6" - assert by_id["pi"]["version"] == "0.8.5" + assert by_id["opencode"]["version"] == "0.3.7" + assert by_id["pi"]["version"] == "0.8.6" assert by_id["pi"]["capabilities"]["autoRecall"] is True assert by_id["pi"]["autonomy"]["recall"] == "startup-context-injection" assert by_id["kimi-code"]["version"] == "0.2.4" @@ -1571,7 +1575,7 @@ def test_opencode_plugin_static_contract_is_self_contained(): assert pkg["name"] == "opencode-nowledge-mem" assert pkg["type"] == "module" - assert pkg["main"] == "src/index.ts" + assert pkg["main"] == "dist/index.js" assert "@opencode-ai/plugin" in pkg["peerDependencies"] assert opencode_registry["directory"] == "nowledge-mem-opencode-plugin" assert opencode_registry["version"] == pkg["version"] @@ -1591,9 +1595,13 @@ def test_opencode_plugin_static_contract_is_self_contained(): assert f"{tool_name}: tool(" in source assert f"`{tool_name}`" in readme or f"| `{tool_name}` |" in readme - assert 'nmem(["context", "--source-app", "opencode"])' in source + assert 'nmem(withExplicitSpaceArg(["context", "--source-app", "opencode"], args))' in source assert '"--source", "opencode"' in source assert "withAmbientSpaceArg(args)" in source + assert "withExplicitSpaceArg" in source + assert "spaceToolArgs" in source + assert '!next.includes("--space-id")' in source + assert 'space_id: tool.schema' in source assert "NMEM_AGENT_ID" in source assert "NMEM_HOST_AGENT_ID" in source assert "fetchSessionMessages" in source @@ -1611,6 +1619,7 @@ def test_opencode_plugin_static_contract_is_self_contained(): assert '"plugin": ["opencode-nowledge-mem"]' in readme assert "nmem t sync --from opencode --all-projects --apply" in readme assert "OpenCode integration guide" in readme + assert "Explicit tool arguments override" in readme assert "compaction hook now injects" in changelog