diff --git a/extensions/memory-core/api.ts b/extensions/memory-core/api.ts index 3e265a5f8848..3b396d78555d 100644 --- a/extensions/memory-core/api.ts +++ b/extensions/memory-core/api.ts @@ -15,3 +15,4 @@ export { filterRecallEntriesWithinLookback } from "./src/dreaming-phases.js"; export { previewRemHarness } from "./src/rem-harness.js"; export type { PreviewRemHarnessOptions, PreviewRemHarnessResult } from "./src/rem-harness.js"; export { configureMemoryCoreDreamingState } from "./src/dreaming-state.js"; +export { filterMemorySearchHitsBySessionVisibility } from "./src/session-search-visibility.js"; diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index 526d2f3902a5..4d929be28793 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -4,6 +4,9 @@ "private": true, "description": "OpenClaw core memory search plugin", "type": "module", + "exports": { + "./api.js": "./api.ts" + }, "dependencies": { "chokidar": "5.0.0", "json5": "2.2.3", diff --git a/extensions/memory-core/src/session-search-visibility.qmd.test.ts b/extensions/memory-core/src/session-search-visibility.qmd.test.ts index 3bfbbf1f1904..39784cf64782 100644 --- a/extensions/memory-core/src/session-search-visibility.qmd.test.ts +++ b/extensions/memory-core/src/session-search-visibility.qmd.test.ts @@ -253,6 +253,12 @@ describe("filterMemorySearchHitsBySessionVisibility for QMD", () => { sessionFile: "/tmp/sessions/current.jsonl", chatType: "direct", }, + "agent:main:visible-export": { + sessionId: "visible-export", + updatedAt: 1, + sessionFile: "/tmp/sessions/visible-export.jsonl", + chatType: "direct", + }, "agent:main:explicit:laptop": { sessionId: "actual-session-id", updatedAt: 1, @@ -267,10 +273,12 @@ describe("filterMemorySearchHitsBySessionVisibility for QMD", () => { chatType: "group", }, }; - const searchPath = "qmd/sessions-main/shared-transcript-export.md"; + // The filename resolves to an allowed decoy session; only the attached QMD + // identity reveals that the actual transcript also has a shared alias. + const searchPath = "qmd/sessions-main/visible-export.md"; const indexPath = await createQmdArtifactIndex({ agentId: "main", - artifactPath: "shared-transcript-export.md", + artifactPath: "visible-export.md", collection: "sessions-main", searchPath, sessionId: "actual-session-id", @@ -285,17 +293,17 @@ describe("filterMemorySearchHitsBySessionVisibility for QMD", () => { endLine: 2, }, { - artifactPath: "shared-transcript-export.md", + artifactPath: "visible-export.md", collection: "sessions-main", indexPath, searchPath, }, ); - const cfg = asOpenClawConfig({ tools: { sessions: { visibility: "self" } } }); + const cfg = asOpenClawConfig({ tools: { sessions: { visibility: "all" } } }); const filtered = await filterMemorySearchHitsBySessionVisibility({ cfg, - requesterSessionKey: "agent:main:telegram:direct:owner", + requesterSessionKey: "agent:main:telegram:direct:owner:active-memory:abcdef123456", sandboxed: false, hits: [hit], conversationRecall: { diff --git a/extensions/memory-core/src/session-search-visibility.ts b/extensions/memory-core/src/session-search-visibility.ts index 5a5977e50992..b110795ebed5 100644 --- a/extensions/memory-core/src/session-search-visibility.ts +++ b/extensions/memory-core/src/session-search-visibility.ts @@ -164,6 +164,8 @@ export async function filterMemorySearchHitsBySessionVisibility(params: { sandboxed: boolean; hits: MemorySearchResult[]; conversationRecall?: ConversationRecallContext; + /** Trusted control-plane calls may authorize only hits already scoped to this agent. */ + trustedAgentScope?: boolean; }): Promise { const visibility = resolveEffectiveSessionToolsVisibility({ cfg: params.cfg, @@ -192,6 +194,9 @@ export async function filterMemorySearchHitsBySessionVisibility(params: { ); const conversationRecall = params.conversationRecall; + const trustedAgentScope = Boolean( + params.trustedAgentScope && scopedAgentId && !params.requesterSessionKey && !conversationRecall, + ); const anchorSessionKey = conversationRecall?.anchorSessionKey.trim(); const recallAgentId = anchorSessionKey ? resolveSessionAgentId({ sessionKey: anchorSessionKey, config: params.cfg }) @@ -235,7 +240,7 @@ export async function filterMemorySearchHitsBySessionVisibility(params: { scopedAgentId && isGlobalSessionKeyForSharedScope(params.cfg, key) ? `agent:${scopedAgentId}:global` : key; - return guard?.check(visibilityKey).allowed === true; + return trustedAgentScope || guard?.check(visibilityKey).allowed === true; } const candidateEntry = combinedSessionStore[key]; // Canonical and legacy alias keys can identify one transcript. Exclude the @@ -290,7 +295,7 @@ export async function filterMemorySearchHitsBySessionVisibility(params: { } continue; } - if (!params.requesterSessionKey || (!guard && !conversationRecall)) { + if (!trustedAgentScope && (!params.requesterSessionKey || (!guard && !conversationRecall))) { continue; } const artifactIdentity = readQmdSessionArtifactIdentity(hit); diff --git a/extensions/memory-wiki/index.test.ts b/extensions/memory-wiki/index.test.ts index 3706ef21f8e4..3ccb7011e939 100644 --- a/extensions/memory-wiki/index.test.ts +++ b/extensions/memory-wiki/index.test.ts @@ -154,6 +154,34 @@ describe("memory-wiki plugin", () => { } }); + it("forwards protected recall authorization to wiki search", () => { + const { api, registerTool } = createPluginApi(); + plugin.register(api); + const registration = registerTool.mock.calls.find((call) => call[1]?.name === "wiki_search"); + const factory = registration?.[0]; + const conversationRecall = { + anchorSessionKey: "agent:main:telegram:direct:owner", + scope: "same-agent-private", + corpus: "sessions", + } as const; + + expect( + factory?.({ + agentId: "main", + sessionKey: "agent:main:telegram:direct:owner:active-memory:abcdef123456", + sandboxed: false, + conversationRecall, + }), + ).toMatchObject({ + testMemoryContext: { + agentId: "main", + agentSessionKey: "agent:main:telegram:direct:owner:active-memory:abcdef123456", + sandboxed: false, + conversationRecall, + }, + }); + }); + it("activates an initialized legacy vault before an external compile", async () => { const rootDir = await createTempDir("memory-wiki-index-legacy-vault-"); await fs.mkdir(path.join(rootDir, ".openclaw-wiki"), { recursive: true }); diff --git a/extensions/memory-wiki/index.ts b/extensions/memory-wiki/index.ts index 894e711ac905..be5684da2a92 100644 --- a/extensions/memory-wiki/index.ts +++ b/extensions/memory-wiki/index.ts @@ -202,6 +202,7 @@ export default definePluginEntry({ agentId: resolved.config.agentId ?? ctx.agentId, agentSessionKey: ctx.sessionKey, sandboxed: ctx.sandboxed, + conversationRecall: ctx.conversationRecall, }); }, { name: "wiki_search" }, diff --git a/extensions/memory-wiki/package.json b/extensions/memory-wiki/package.json index 1fa1b1b2b5ab..92cd598b5aa1 100644 --- a/extensions/memory-wiki/package.json +++ b/extensions/memory-wiki/package.json @@ -12,6 +12,7 @@ "zod": "4.4.3" }, "devDependencies": { + "@openclaw/memory-core": "workspace:*", "@openclaw/plugin-sdk": "workspace:*", "openclaw": "workspace:*" }, diff --git a/extensions/memory-wiki/src/query.test.ts b/extensions/memory-wiki/src/query.test.ts index b4b9660f4d68..72ffe6fca64b 100644 --- a/extensions/memory-wiki/src/query.test.ts +++ b/extensions/memory-wiki/src/query.test.ts @@ -2,6 +2,8 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { filterMemorySearchHitsBySessionVisibility } from "@openclaw/memory-core/api.js"; +import type { MemorySearchResult } from "openclaw/plugin-sdk/memory-core-host-runtime-files"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../api.js"; import { compileMemoryWikiVault } from "./compile.js"; @@ -29,6 +31,8 @@ vi.mock("openclaw/plugin-sdk/memory-host-search", () => ({ getActiveMemorySearchManager: getActiveMemorySearchManagerMock, })); +vi.mock("@openclaw/memory-core/api.js", { spy: true }); + vi.mock("openclaw/plugin-sdk/memory-host-core", () => ({ resolveDefaultAgentId: resolveDefaultAgentIdMock, resolveSessionAgentId: resolveSessionAgentIdMock, @@ -75,6 +79,7 @@ beforeEach(() => { loadCombinedSessionStoreForGatewayMock.mockReturnValue({ storePath: "(test)", store: {} }); resolveDefaultAgentIdMock.mockClear(); resolveSessionAgentIdMock.mockClear(); + vi.mocked(filterMemorySearchHitsBySessionVisibility).mockClear(); }); beforeAll(async () => { @@ -932,6 +937,60 @@ describe("searchMemoryWiki", () => { ]); }); + it("delegates protected QMD recall with the raw hit identity intact", async () => { + const { config } = await createQueryVault({ + initialize: true, + config: { search: { backend: "shared", corpus: "memory" } }, + }); + const appConfig = createSessionVisibilityAppConfig(); + const conversationRecall = { + anchorSessionKey: "agent:main:telegram:direct:owner", + scope: "same-agent-private", + corpus: "sessions", + } as const; + const requesterSessionKey = "agent:main:telegram:direct:owner:active-memory:abcdef123456"; + const qmdIdentity = Symbol("qmd-identity"); + const qmdHit: MemorySearchResult = { + path: "qmd/sessions-main/visible-export.md", + startLine: 1, + endLine: 2, + score: 30, + snippet: "protected transcript", + source: "sessions", + }; + Object.defineProperty(qmdHit, qmdIdentity, { + value: { agentId: "peer", sessionId: "peer-session" }, + }); + const manager = createMemoryManager({ searchResults: [qmdHit] }); + getActiveMemorySearchManagerMock.mockResolvedValue({ manager }); + vi.mocked(filterMemorySearchHitsBySessionVisibility).mockResolvedValueOnce([]); + + const results = await searchMemoryWiki({ + config, + appConfig, + agentId: "main", + agentSessionKey: requesterSessionKey, + sandboxed: false, + conversationRecall, + query: "protected", + }); + + expect(results).toStrictEqual([]); + expect(filterMemorySearchHitsBySessionVisibility).toHaveBeenCalledWith({ + cfg: appConfig, + agentId: "main", + requesterSessionKey, + sandboxed: false, + hits: [qmdHit], + conversationRecall, + trustedAgentScope: false, + }); + expect(Reflect.get(qmdHit, qmdIdentity)).toEqual({ + agentId: "peer", + sessionId: "peer-session", + }); + }); + it("keeps QMD archived session search hits inside visibility policy", async () => { const { config } = await createQueryVault({ initialize: true, diff --git a/extensions/memory-wiki/src/query.ts b/extensions/memory-wiki/src/query.ts index f6627d619bff..a81c7aac160f 100644 --- a/extensions/memory-wiki/src/query.ts +++ b/extensions/memory-wiki/src/query.ts @@ -1,19 +1,11 @@ // Memory Wiki plugin module implements query behavior. import fs from "node:fs/promises"; import path from "node:path"; +import { filterMemorySearchHitsBySessionVisibility } from "@openclaw/memory-core/api.js"; import type { MemorySearchResult } from "openclaw/plugin-sdk/memory-core-host-runtime-files"; import { resolveDefaultAgentId, resolveSessionAgentId } from "openclaw/plugin-sdk/memory-host-core"; import { getActiveMemorySearchManager } from "openclaw/plugin-sdk/memory-host-search"; -import { - extractTranscriptIdentityFromSessionsMemoryHit, - loadCombinedSessionStoreForGateway, - resolveTranscriptStemToSessionKeys, -} from "openclaw/plugin-sdk/session-transcript-hit"; -import { - createAgentToAgentPolicy, - createSessionVisibilityGuard, - resolveEffectiveSessionToolsVisibility, -} from "openclaw/plugin-sdk/session-visibility"; +import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry"; import { normalizeLowercaseStringOrEmpty, uniqueStrings, @@ -171,6 +163,8 @@ type QuerySearchOverrides = { searchCorpus?: WikiSearchCorpus; }; +type ConversationRecallContext = NonNullable; + function sortWikiSearchResults(results: WikiSearchResult[]): WikiSearchResult[] { return results.toSorted((left, right) => { if (left.score !== right.score) { @@ -1194,184 +1188,6 @@ function toMemoryWikiSearchResult( }; } -async function filterMemoryWikiSearchHitsBySessionVisibility(params: { - cfg: OpenClawConfig; - agentId: string | undefined; - requesterSessionKey: string | undefined; - sandboxed: boolean; - hits: MemorySearchResult[]; -}): Promise { - if (!params.hits.some((hit) => hit.source === "sessions")) { - return params.hits; - } - - const canReadSessionPath = await createSessionMemoryPathVisibilityChecker({ - cfg: params.cfg, - agentId: params.agentId, - requesterSessionKey: params.requesterSessionKey, - sandboxed: params.sandboxed, - }); - return filterMemoryWikiSearchHitsWithSessionVisibility({ - canReadSessionPath, - hits: params.hits, - }); -} - -type SessionMemoryPathVisibilityChecker = (relPath: string) => boolean; - -function filterSessionKeysByScopedAgent(params: { - cfg: OpenClawConfig; - keys: string[]; - scopedAgentId: string | undefined; -}): string[] { - const scopedAgentId = normalizeLowercaseStringOrEmpty(params.scopedAgentId); - if (!scopedAgentId) { - return params.keys; - } - return params.keys.filter((key) => { - if (params.cfg.session?.scope === "global" && key.trim().toLowerCase() === "global") { - return true; - } - const ownerAgentId = resolveSessionAgentId({ - sessionKey: key, - config: params.cfg, - }); - return normalizeLowercaseStringOrEmpty(ownerAgentId) === scopedAgentId; - }); -} - -async function createSessionMemoryPathVisibilityChecker(params: { - cfg: OpenClawConfig; - agentId: string | undefined; - requesterSessionKey: string | undefined; - sandboxed: boolean; -}): Promise { - const visibility = resolveEffectiveSessionToolsVisibility({ - cfg: params.cfg, - sandboxed: params.sandboxed, - }); - const a2aPolicy = createAgentToAgentPolicy(params.cfg); - const requesterAgentId = params.requesterSessionKey - ? resolveSessionAgentId({ - sessionKey: params.requesterSessionKey, - config: params.cfg, - }) - : undefined; - const scopedAgentId = params.agentId?.trim() || requesterAgentId; - const guard = params.requesterSessionKey - ? await createSessionVisibilityGuard({ - action: "history", - requesterSessionKey: params.requesterSessionKey, - visibility, - a2aPolicy, - }) - : null; - - const { store: combinedSessionStore } = loadCombinedSessionStoreForGateway( - params.cfg, - scopedAgentId ? { agentId: scopedAgentId } : {}, - ); - return (relPath) => { - const identity = extractTranscriptIdentityFromSessionsMemoryHit(relPath); - if (!identity) { - return false; - } - const isQmdSessionPath = relPath.replace(/\\/g, "/").startsWith("qmd/"); - const normalizedScopedAgentId = normalizeLowercaseStringOrEmpty(scopedAgentId); - const normalizedOwnerAgentId = normalizeLowercaseStringOrEmpty(identity.ownerAgentId); - if ( - normalizedScopedAgentId && - normalizedOwnerAgentId && - normalizedOwnerAgentId !== normalizedScopedAgentId - ) { - return false; - } - const sameAgentLiveOwnerId = - !identity.archived && - normalizedScopedAgentId && - normalizedOwnerAgentId === normalizedScopedAgentId - ? normalizedOwnerAgentId - : undefined; - const archivedOwnerMatchesScope = Boolean( - identity.archived && - ((identity.ownerAgentId && - (!normalizedScopedAgentId || normalizedOwnerAgentId === normalizedScopedAgentId)) || - (isQmdSessionPath && scopedAgentId)), - ); - const archivedOwnerAgentId = archivedOwnerMatchesScope - ? (identity.ownerAgentId ?? scopedAgentId) - : undefined; - const liveKeys = identity.liveStem - ? resolveTranscriptStemToSessionKeys({ - store: combinedSessionStore, - stem: identity.liveStem, - allowQmdSlugFallback: false, - }) - : []; - const resolvedKeys = - liveKeys.length > 0 - ? liveKeys - : resolveTranscriptStemToSessionKeys({ - store: combinedSessionStore, - stem: identity.stem, - allowQmdSlugFallback: isQmdSessionPath && !identity.archived, - ...(archivedOwnerAgentId ? { archivedOwnerAgentId } : {}), - }); - const keys = filterSessionKeysByScopedAgent({ - cfg: params.cfg, - scopedAgentId, - keys: resolvedKeys, - }); - if (keys.length === 0) { - const agentWideVisibility = visibility === "agent" || visibility === "all"; - return Boolean(sameAgentLiveOwnerId && agentWideVisibility); - } - if (!guard) { - return Boolean(scopedAgentId); - } - return keys.some((key) => guard.check(key).allowed); - }; -} - -function filterMemoryWikiSearchHitsWithSessionVisibility(params: { - canReadSessionPath: SessionMemoryPathVisibilityChecker; - hits: MemorySearchResult[]; -}): MemorySearchResult[] { - const next: MemorySearchResult[] = []; - for (const hit of params.hits) { - if (hit.source !== "sessions") { - next.push(hit); - continue; - } - - if (params.canReadSessionPath(hit.path)) { - next.push(hit); - } - } - return next; -} - -function canReadSessionMemoryPath(params: { - canReadSessionPath: SessionMemoryPathVisibilityChecker; - relPath: string; -}): boolean { - // Reuses the search filter with a synthetic hit; update this if the filter needs more than path/source. - const filtered = filterMemoryWikiSearchHitsWithSessionVisibility({ - canReadSessionPath: params.canReadSessionPath, - hits: [ - { - path: params.relPath, - startLine: 1, - endLine: 1, - score: 0, - snippet: "", - source: "sessions", - }, - ], - }); - return filtered.length > 0; -} - async function searchWikiCorpus(params: { config: ResolvedMemoryWikiConfig; query: string; @@ -1449,6 +1265,7 @@ export async function searchMemoryWiki(params: { agentId?: string; agentSessionKey?: string; sandboxed?: boolean; + conversationRecall?: ConversationRecallContext; query: string; maxResults?: number; searchBackend?: WikiSearchBackend; @@ -1496,12 +1313,14 @@ export async function searchMemoryWiki(params: { shouldEnforceSessionVisibility(params) && rawMemoryResults.some((hit) => hit.source === "sessions") ) { - rawMemoryResults = await filterMemoryWikiSearchHitsBySessionVisibility({ + rawMemoryResults = await filterMemorySearchHitsBySessionVisibility({ cfg: params.appConfig, agentId: params.agentId, requesterSessionKey: params.agentSessionKey, sandboxed: params.sandboxed === true, hits: rawMemoryResults, + conversationRecall: params.conversationRecall, + trustedAgentScope: !params.agentSessionKey && Boolean(params.agentId?.trim()), }); } const memoryResults = rawMemoryResults.map((result) => toMemoryWikiSearchResult(result, mode)); @@ -1591,27 +1410,35 @@ export async function getMemoryWikiPage(params: { } const lookupCandidates = buildLookupCandidates(params.lookup); - const canReadSessionPath = + const visibleSessionPaths = params.appConfig && shouldEnforceSessionVisibility(params) && lookupCandidates.some((relPath) => isSessionMemoryPath(relPath)) - ? await createSessionMemoryPathVisibilityChecker({ - cfg: params.appConfig, - agentId: params.agentId, - requesterSessionKey: params.agentSessionKey, - sandboxed: params.sandboxed === true, - }) + ? new Set( + ( + await filterMemorySearchHitsBySessionVisibility({ + cfg: params.appConfig, + agentId: params.agentId, + requesterSessionKey: params.agentSessionKey, + sandboxed: params.sandboxed === true, + trustedAgentScope: !params.agentSessionKey && Boolean(params.agentId?.trim()), + hits: lookupCandidates + .filter((relPath) => isSessionMemoryPath(relPath)) + .map((relPath) => ({ + path: relPath, + startLine: 1, + endLine: 1, + score: 0, + snippet: "", + source: "sessions" as const, + })), + }) + ).map((hit) => hit.path), + ) : null; for (const relPath of lookupCandidates) { - if ( - canReadSessionPath && - isSessionMemoryPath(relPath) && - !canReadSessionMemoryPath({ - canReadSessionPath, - relPath, - }) - ) { + if (visibleSessionPaths && isSessionMemoryPath(relPath) && !visibleSessionPaths.has(relPath)) { continue; } diff --git a/extensions/memory-wiki/src/tool.ts b/extensions/memory-wiki/src/tool.ts index 164280ed24a4..2e0259015c33 100644 --- a/extensions/memory-wiki/src/tool.ts +++ b/extensions/memory-wiki/src/tool.ts @@ -1,6 +1,7 @@ // Memory Wiki plugin module implements tool behavior. import path from "node:path"; import { optionalFiniteNumberSchema } from "openclaw/plugin-sdk/channel-actions"; +import type { OpenClawPluginToolContext } from "openclaw/plugin-sdk/plugin-entry"; import { Type } from "typebox"; import type { AnyAgentTool, OpenClawConfig } from "../api.js"; import { applyMemoryWikiMutation, normalizeMemoryWikiMutationInput } from "./apply.js"; @@ -112,6 +113,7 @@ type WikiToolMemoryContext = { agentId?: string; agentSessionKey?: string; sandboxed?: boolean; + conversationRecall?: OpenClawPluginToolContext["conversationRecall"]; }; export function createWikiStatusTool( @@ -165,6 +167,7 @@ export function createWikiSearchTool( agentId: memoryContext.agentId, agentSessionKey: memoryContext.agentSessionKey, sandboxed: memoryContext.sandboxed, + conversationRecall: memoryContext.conversationRecall, query: params.query, maxResults: params.maxResults, ...(params.backend ? { searchBackend: params.backend } : {}), diff --git a/extensions/tsconfig.package-boundary.paths.json b/extensions/tsconfig.package-boundary.paths.json index 63a9b63b3378..bc644fb725c3 100644 --- a/extensions/tsconfig.package-boundary.paths.json +++ b/extensions/tsconfig.package-boundary.paths.json @@ -554,6 +554,9 @@ "@openclaw/qa-channel/api.js": [ "../dist/plugin-sdk/extensions/qa-channel/api.d.ts" ], + "@openclaw/memory-core/api.js": [ + "../dist/plugin-sdk/extensions/memory-core/api.d.ts" + ], "@openclaw/matrix/test-api.js": [ "../dist/plugin-sdk/extensions/matrix/test-api.d.ts" ], diff --git a/extensions/xai/tsconfig.json b/extensions/xai/tsconfig.json index 6914837ac54b..994c2c99ade7 100644 --- a/extensions/xai/tsconfig.json +++ b/extensions/xai/tsconfig.json @@ -548,6 +548,9 @@ "@openclaw/qa-channel/api.js": [ "../../dist/plugin-sdk/extensions/qa-channel/api.d.ts" ], + "@openclaw/memory-core/api.js": [ + "../../dist/plugin-sdk/extensions/memory-core/api.d.ts" + ], "@openclaw/ai": [ "../../dist/plugin-sdk/packages/ai/src/index.d.ts" ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f0a7c5d53ff..43b0ed6ff41c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1282,6 +1282,9 @@ importers: specifier: 4.4.3 version: 4.4.3 devDependencies: + '@openclaw/memory-core': + specifier: workspace:* + version: link:../memory-core '@openclaw/plugin-sdk': specifier: workspace:* version: link:../../packages/plugin-sdk diff --git a/scripts/lib/extension-package-boundary.ts b/scripts/lib/extension-package-boundary.ts index 925c3386fa69..5c677d3b6608 100644 --- a/scripts/lib/extension-package-boundary.ts +++ b/scripts/lib/extension-package-boundary.ts @@ -70,6 +70,7 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = { "openclaw/plugin-sdk/secret-ref-runtime": ["../dist/plugin-sdk/secret-ref-runtime.d.ts"], "openclaw/plugin-sdk/ssrf-runtime": ["../dist/plugin-sdk/ssrf-runtime.d.ts"], "@openclaw/qa-channel/api.js": ["../dist/plugin-sdk/extensions/qa-channel/api.d.ts"], + "@openclaw/memory-core/api.js": ["../dist/plugin-sdk/extensions/memory-core/api.d.ts"], "@openclaw/matrix/test-api.js": ["../dist/plugin-sdk/extensions/matrix/test-api.d.ts"], "@openclaw/discord/api.js": ["../dist/plugin-sdk/extensions/discord/api.d.ts"], "@openclaw/slack/api.js": ["../dist/plugin-sdk/extensions/slack/api.d.ts"], diff --git a/scripts/prepare-extension-package-boundary-artifacts.mjs b/scripts/prepare-extension-package-boundary-artifacts.mjs index a4167a6880bb..573770f57b08 100644 --- a/scripts/prepare-extension-package-boundary-artifacts.mjs +++ b/scripts/prepare-extension-package-boundary-artifacts.mjs @@ -274,6 +274,13 @@ const QA_CHANNEL_DTS_INPUTS = [ ]; const QA_CHANNEL_DTS_STAMP = "dist/plugin-sdk/extensions/qa-channel/.boundary-dts.stamp"; const QA_CHANNEL_DTS_REQUIRED_OUTPUTS = ["dist/plugin-sdk/extensions/qa-channel/api.d.ts"]; +const MEMORY_CORE_DTS_INPUTS = [ + "extensions/memory-core/api.ts", + "extensions/memory-core/src", + "extensions/memory-core/tsconfig.json", +]; +const MEMORY_CORE_DTS_STAMP = "dist/plugin-sdk/extensions/memory-core/.boundary-dts.stamp"; +const MEMORY_CORE_DTS_REQUIRED_OUTPUTS = ["dist/plugin-sdk/extensions/memory-core/api.d.ts"]; const MATRIX_DTS_INPUTS = [ "extensions/matrix/test-api.ts", "extensions/matrix/src", @@ -768,6 +775,12 @@ async function main(argv = process.argv.slice(2)) { outputPaths: [QA_CHANNEL_DTS_STAMP, ...QA_CHANNEL_DTS_REQUIRED_OUTPUTS], includeFile: isRelevantTypeInput, }) && !hasMissingOutput(QA_CHANNEL_DTS_REQUIRED_OUTPUTS); + const memoryCoreDtsFresh = + isArtifactSetFresh({ + inputPaths: MEMORY_CORE_DTS_INPUTS, + outputPaths: [MEMORY_CORE_DTS_STAMP, ...MEMORY_CORE_DTS_REQUIRED_OUTPUTS], + includeFile: isRelevantTypeInput, + }) && !hasMissingOutput(MEMORY_CORE_DTS_REQUIRED_OUTPUTS); const matrixDtsFresh = isArtifactSetFresh({ inputPaths: MATRIX_DTS_INPUTS, @@ -862,6 +875,36 @@ async function main(argv = process.argv.slice(2)) { } else { process.stdout.write("[qa-channel boundary dts] fresh; skipping\n"); } + if (!memoryCoreDtsFresh) { + removeStaleIncrementalState({ + tsBuildInfoPath: "dist/plugin-sdk/extensions/memory-core/.tsbuildinfo", + }); + dependentSteps.push({ + label: "memory-core boundary dts", + args: [ + runTsgoScript, + "-p", + "extensions/memory-core/tsconfig.json", + "--declaration", + "true", + "--emitDeclarationOnly", + "true", + "--noEmit", + "false", + "--outDir", + "dist/plugin-sdk/extensions/memory-core", + "--rootDir", + "extensions/memory-core", + "--tsBuildInfoFile", + "dist/plugin-sdk/extensions/memory-core/.tsbuildinfo", + ], + env: { OPENCLAW_TSGO_HEAVY_CHECK_LOCK_HELD: "1" }, + timeoutMs: 300_000, + stampPath: MEMORY_CORE_DTS_STAMP, + }); + } else { + process.stdout.write("[memory-core boundary dts] fresh; skipping\n"); + } if (!matrixDtsFresh) { removeStaleIncrementalState({ tsBuildInfoPath: "dist/plugin-sdk/extensions/matrix/.tsbuildinfo", diff --git a/test/vitest/vitest.shared.config.ts b/test/vitest/vitest.shared.config.ts index f4131a5909b1..9c5342e34986 100644 --- a/test/vitest/vitest.shared.config.ts +++ b/test/vitest/vitest.shared.config.ts @@ -203,6 +203,10 @@ export const sharedVitestConfig = { find: "@openclaw/matrix/test-api.js", replacement: path.join(repoRoot, "extensions", "matrix", "test-api.ts"), }, + { + find: "@openclaw/memory-core/api.js", + replacement: path.join(repoRoot, "extensions", "memory-core", "api.ts"), + }, { find: "@openclaw/slack/api.js", replacement: path.join(repoRoot, "extensions", "slack", "api.ts"),