Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .test1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import jieba.analyse
res = jieba.analyse.extract_tags("我爱旅游和烧烤", topK=12)
print(res)
87 changes: 74 additions & 13 deletions apps/memos-local-plugin/adapters/openclaw/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,28 @@ function clip(s: string | undefined, n: number): string {
return s.length > n ? s.slice(0, n) + "…" : s;
}

type TextToolContent = Array<{ type: "text"; text: string }>;

function textToolResult<T extends Record<string, unknown>>(
details: T,
text: string,
): T & { content: TextToolContent; details: T } {
return {
...details,
content: [{ type: "text", text }],
details,
};
}

function formatHitList(hits: Array<{ refKind: string; refId: string; score: number; snippet: string }>): string {
if (hits.length === 0) return "No relevant memories found.";
const lines = hits.map((h, i) => {
const snippet = h.snippet.trim() || "(empty snippet)";
return `${i + 1}. [${h.refKind}:${h.refId}] ${snippet} (score=${h.score.toFixed(3)})`;
});
return `Found ${hits.length} memories:\n\n${lines.join("\n")}`;
}

function sessionFromCtx(ctx: OpenClawPluginToolContext | undefined): string | undefined {
const sessionKey = ctx?.sessionKey;
if (!sessionKey) return undefined;
Expand Down Expand Up @@ -178,7 +200,7 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
query: params.query,
topK: topKParams(params, maxResults),
});
return {
const details = {
hits: result.hits.map((h) => ({
tier: h.tier,
refKind: h.refKind,
Expand All @@ -188,6 +210,7 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
})),
totalMs: Date.now() - started,
};
return textToolResult(details, formatHitList(details.hits));
},
}),
{ name: "memory_search" },
Expand All @@ -207,8 +230,11 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
const kind = params.kind ?? "trace";
if (kind === "trace") {
const trace = await core.getTrace(params.id as TraceId, namespaceFromCtx(ctx));
if (!trace) return { found: false, kind, id: params.id, body: "", meta: {} };
return {
if (!trace) {
const details = { found: false, kind, id: params.id, body: "", meta: {} };
return textToolResult(details, `No ${kind} memory found for id "${params.id}".`);
}
const details = {
found: true,
kind,
id: trace.id,
Expand All @@ -226,11 +252,15 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
})),
},
};
return textToolResult(details, details.body || trace.summary || trace.userText || `Found trace ${trace.id}.`);
}
if (kind === "policy") {
const policy = await core.getPolicy(params.id, namespaceFromCtx(ctx));
if (!policy) return { found: false, kind, id: params.id, body: "", meta: {} };
return {
if (!policy) {
const details = { found: false, kind, id: params.id, body: "", meta: {} };
return textToolResult(details, `No ${kind} memory found for id "${params.id}".`);
}
const details = {
found: true,
kind,
id: policy.id,
Expand All @@ -244,16 +274,21 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
status: policy.status,
},
};
return textToolResult(details, details.body);
}
const wm = await core.getWorldModel(params.id, namespaceFromCtx(ctx));
if (!wm) return { found: false, kind, id: params.id, body: "", meta: {} };
return {
if (!wm) {
const details = { found: false, kind, id: params.id, body: "", meta: {} };
return textToolResult(details, `No ${kind} memory found for id "${params.id}".`);
}
const details = {
found: true,
kind,
id: wm.id,
body: clip(wm.body, bodyCap),
meta: { title: wm.title, policyIds: wm.policyIds },
};
return textToolResult(details, `${wm.title}\n\n${details.body}`.trim());
},
}),
{ name: "memory_get" },
Expand All @@ -272,7 +307,7 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
const core = await resolveCore(opts);
const traces = await core.timeline({ episodeId: params.episodeId as never, namespace: namespaceFromCtx(ctx) });
const limited = traces.slice(0, params.limit ?? 20);
return {
const details = {
episodeId: params.episodeId,
traces: limited.map((t) => ({
id: t.id,
Expand All @@ -283,6 +318,13 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
value: t.value,
})),
};
const text = details.traces.length === 0
? `No traces found for episode "${params.episodeId}".`
: `Episode ${params.episodeId} timeline:\n\n` +
details.traces
.map((t, i) => `${i + 1}. ${t.userText || t.agentText || t.id}`)
.join("\n");
return textToolResult(details, text);
},
}),
{ name: "memory_timeline" },
Expand All @@ -303,7 +345,7 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
limit: params.limit,
namespace: namespaceFromCtx(ctx),
});
return {
const details = {
skills: skills.map((s) => ({
id: s.id,
name: s.name,
Expand All @@ -314,6 +356,11 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
invocationGuide: clip(s.invocationGuide, bodyCap),
})),
};
const text = details.skills.length === 0
? "No skills found."
: `Found ${details.skills.length} skills:\n\n` +
details.skills.map((s, i) => `${i + 1}. ${s.name} (${s.id}, ${s.status})`).join("\n");
return textToolResult(details, text);
},
}),
{ name: "skill_list" },
Expand Down Expand Up @@ -348,7 +395,7 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
const core = await resolveCore(opts);
if (!query) {
const rows = await core.listWorldModels({ limit: cap, offset: 0, namespace: namespaceFromCtx(ctx) });
return {
const details = {
worldModels: rows.map((w) => ({
id: w.id,
title: w.title,
Expand All @@ -358,6 +405,11 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
})),
queried: false,
};
const text = details.worldModels.length === 0
? "No environment knowledge found."
: `Environment knowledge:\n\n` +
details.worldModels.map((w, i) => `${i + 1}. ${w.title}\n${w.body}`).join("\n\n");
return textToolResult(details, text);
}
// With a query, go through `searchMemory` so tag filters +
// cosine ranking apply, then keep only the tier-3 hits.
Expand All @@ -368,7 +420,7 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
topK: { tier1: 0, tier2: 0, tier3: cap },
});
const tier3 = res.hits.filter((h) => h.tier === 3);
return {
const details = {
worldModels: tier3.map((h) => ({
id: h.refId,
title: (h.snippet ?? "").split("\n")[0]?.replace(/^World model:\s*/, "") ?? "",
Expand All @@ -378,6 +430,11 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
})),
queried: true,
};
const text = details.worldModels.length === 0
? "No matching environment knowledge found."
: `Environment knowledge for "${query}":\n\n` +
details.worldModels.map((w, i) => `${i + 1}. ${w.title}\n${w.body}`).join("\n\n");
return textToolResult(details, text);
},
}),
{ name: "memory_environment" },
Expand All @@ -399,8 +456,11 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
namespace: namespaceFromCtx(ctx),
toolCallId,
});
if (!skill) return { found: false, skill: null };
return {
if (!skill) {
const details = { found: false, skill: null };
return textToolResult(details, `No skill found for id "${params.id}".`);
}
const details = {
found: true,
skill: {
id: skill.id,
Expand All @@ -418,6 +478,7 @@ export function registerOpenClawTools(api: OpenClawPluginApi, opts: ToolsOptions
lastUsedAt: skill.lastUsedAt,
},
};
return textToolResult(details, `${skill.name}\n\n${skill.invocationGuide}`.trim());
},
}),
{ name: "skill_get" },
Expand Down
1 change: 1 addition & 0 deletions apps/memos-local-plugin/agent-contract/memory-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ export interface MemoryCore {
*/
listApiLogs(input?: {
toolName?: string;
toolNames?: readonly string[];
limit?: number;
offset?: number;
}): Promise<{ logs: ApiLogDTO[]; total: number }>;
Expand Down
1 change: 1 addition & 0 deletions apps/memos-local-plugin/core/config/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
telemetry: { enabled: true },
logging: {
level: "info",
detailedView: false,
console: { enabled: true, pretty: true, channels: ["*"] },
file: {
enabled: true,
Expand Down
2 changes: 2 additions & 0 deletions apps/memos-local-plugin/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ const LoggingSchema = Type.Object({
Type.Literal("error"),
Type.Literal("fatal"),
], { default: "info" }),
/** Viewer-only switch: expose detailed logs, lifecycle tags and chain view. */
detailedView: Bool(false),
console: Type.Object({
enabled: Bool(true),
pretty: Bool(true),
Expand Down
7 changes: 6 additions & 1 deletion apps/memos-local-plugin/core/pipeline/memory-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2451,6 +2451,7 @@ export function createMemoryCore(

async function listApiLogs(input?: {
toolName?: string;
toolNames?: readonly string[];
limit?: number;
offset?: number;
}): Promise<{ logs: ApiLogDTO[]; total: number }> {
Expand All @@ -2459,10 +2460,14 @@ export function createMemoryCore(
const offset = Math.max(0, input?.offset ?? 0);
const rows = handle.repos.apiLogs.list({
toolName: input?.toolName,
toolNames: input?.toolNames,
limit,
offset,
});
const total = handle.repos.apiLogs.count({ toolName: input?.toolName });
const total = handle.repos.apiLogs.count({
toolName: input?.toolName,
toolNames: input?.toolNames,
});
return {
logs: rows.map((r) => ({
id: r.id,
Expand Down
18 changes: 18 additions & 0 deletions apps/memos-local-plugin/core/pipeline/retrieval-repos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export function wrapRetrievalRepos(repos: Repos, namespace: RuntimeNamespace): R
searchByVector(query, k, opts) {
return repos.skills.searchByVector(query, k, opts ?? {});
},
searchByText(ftsMatch, k, opts) {
return repos.skills.searchByText(ftsMatch, k, opts ?? {});
},
searchByPattern(terms, k, opts) {
return repos.skills.searchByPattern(terms, k, opts ?? {});
},
getById(id) {
const row = repos.skills.getById(id);
if (!row || !isVisibleTo(row, namespace)) return null;
Expand All @@ -35,6 +41,12 @@ export function wrapRetrievalRepos(repos: Repos, namespace: RuntimeNamespace): R
searchByVector(query, k, opts) {
return repos.traces.searchByVector(query, k, opts ?? {});
},
searchByText(ftsMatch, k, opts) {
return repos.traces.searchByText(ftsMatch, k, opts ?? {});
},
searchByPattern(terms, k, opts) {
return repos.traces.searchByPattern(terms, k, opts ?? {});
},
getManyByIds(ids) {
const rows = repos.traces.getManyByIds(ids as readonly TraceId[]);
return rows.filter((r) => isVisibleTo(r, namespace)).map((r) => ({
Expand Down Expand Up @@ -74,6 +86,12 @@ export function wrapRetrievalRepos(repos: Repos, namespace: RuntimeNamespace): R
searchByVector(query, k, opts) {
return repos.worldModel.searchByVector(query, k, opts ?? {});
},
searchByText(ftsMatch, k) {
return repos.worldModel.searchByText(ftsMatch, k);
},
searchByPattern(terms, k) {
return repos.worldModel.searchByPattern(terms, k);
},
getById(id) {
const row = repos.worldModel.getById(id);
if (!row || !isVisibleTo(row, namespace)) return null;
Expand Down
60 changes: 57 additions & 3 deletions apps/memos-local-plugin/core/storage/repos/api_logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ export interface ApiLogInsert {
}

export interface ApiLogFilter {
/** Filter by tool name — `memory_search` or `memory_add`. */
/** Filter by a single tool name. */
toolName?: string;
/** Filter by several tool names while preserving newest-first pagination. */
toolNames?: readonly string[];
/** Default 50; max 500 to keep viewer paint times sane. */
limit?: number;
offset?: number;
Expand Down Expand Up @@ -85,6 +87,45 @@ export function makeApiLogsRepo(db: StorageDb) {
LIMIT @limit OFFSET @offset`,
);

const countByToolNames = (toolNames: readonly string[]): number => {
const names = normalizeToolNames(toolNames);
if (names.length === 0) return countAll.get({})?.n ?? 0;
if (names.length === 1) {
return countByTool.get({ tool_name: names[0]! })?.n ?? 0;
}
const params = namedToolParams(names);
const placeholders = Object.keys(params).map((key) => `@${key}`).join(", ");
const row = db
.prepare<Record<string, string>, { n: number }>(
`SELECT COUNT(*) AS n FROM api_logs WHERE tool_name IN (${placeholders})`,
)
.get(params);
return row?.n ?? 0;
};

const selectByToolNames = (
toolNames: readonly string[],
limit: number,
offset: number,
): RawRow[] => {
const names = normalizeToolNames(toolNames);
if (names.length === 0) return selectAll.all({ limit, offset });
if (names.length === 1) {
return selectByTool.all({ tool_name: names[0]!, limit, offset });
}
const toolParams = namedToolParams(names);
const placeholders = Object.keys(toolParams).map((key) => `@${key}`).join(", ");
return db
.prepare<Record<string, string | number>, RawRow>(
`SELECT id, tool_name, input_json, output_json, duration_ms, success, called_at
FROM api_logs
WHERE tool_name IN (${placeholders})
ORDER BY called_at DESC, id DESC
LIMIT @limit OFFSET @offset`,
)
.all({ ...toolParams, limit, offset });
};

return {
insert(row: ApiLogInsert): void {
insert.run({
Expand All @@ -97,7 +138,10 @@ export function makeApiLogsRepo(db: StorageDb) {
});
},

count(filter: Pick<ApiLogFilter, "toolName"> = {}): number {
count(filter: Pick<ApiLogFilter, "toolName" | "toolNames"> = {}): number {
if (filter.toolNames?.length) {
return countByToolNames(filter.toolNames);
}
if (filter.toolName) {
return countByTool.get({ tool_name: filter.toolName })?.n ?? 0;
}
Expand All @@ -107,14 +151,24 @@ export function makeApiLogsRepo(db: StorageDb) {
list(filter: ApiLogFilter = {}): ApiLogRow[] {
const limit = Math.max(1, Math.min(500, filter.limit ?? 50));
const offset = Math.max(0, filter.offset ?? 0);
const rows = filter.toolName
const rows = filter.toolNames?.length
? selectByToolNames(filter.toolNames, limit, offset)
: filter.toolName
? selectByTool.all({ tool_name: filter.toolName, limit, offset })
: selectAll.all({ limit, offset });
return rows.map(mapRow);
},
};
}

function normalizeToolNames(toolNames: readonly string[]): string[] {
return [...new Set(toolNames.map((name) => name.trim()).filter(Boolean))];
}

function namedToolParams(toolNames: readonly string[]): Record<string, string> {
return Object.fromEntries(toolNames.map((name, index) => [`tool_${index}`, name]));
}

interface RawRow {
id: number;
tool_name: string;
Expand Down
Loading