mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 12:15:53 +00:00
fix(memory): keep wiki search inside protected recall visibility (#118265)
* fix(memory): unify wiki session authorization * build: expose memory core boundary declarations
This commit is contained in:
committed by
GitHub
parent
c965909a30
commit
cbd4b8dec8
@@ -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";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<MemorySearchResult[]> {
|
||||
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);
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"zod": "4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openclaw/memory-core": "workspace:*",
|
||||
"@openclaw/plugin-sdk": "workspace:*",
|
||||
"openclaw": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<OpenClawPluginToolContext["conversationRecall"]>;
|
||||
|
||||
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<MemorySearchResult[]> {
|
||||
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<SessionMemoryPathVisibilityChecker> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 } : {}),
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
],
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user