@usecortex_ai/openclaw-cortex-ai 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config.ts CHANGED
@@ -7,6 +7,7 @@ export type CortexPluginConfig = {
7
7
  maxRecallResults: number
8
8
  recallMode: "fast" | "thinking"
9
9
  graphContext: boolean
10
+ ignoreTerm: string
10
11
  debug: boolean
11
12
  }
12
13
 
@@ -19,10 +20,12 @@ const KNOWN_KEYS = new Set([
19
20
  "maxRecallResults",
20
21
  "recallMode",
21
22
  "graphContext",
23
+ "ignoreTerm",
22
24
  "debug",
23
25
  ])
24
26
 
25
- const DEFAULT_SUB_TENANT = "cortex-openclaw"
27
+ const DEFAULT_SUB_TENANT = "cortex-openclaw-plugin"
28
+ const DEFAULT_IGNORE_TERM = "cortex-ignore"
26
29
 
27
30
  function envOrNull(name: string): string | undefined {
28
31
  return typeof process !== "undefined" ? process.env[name] : undefined
@@ -86,6 +89,10 @@ export function parseConfig(raw: unknown): CortexPluginConfig {
86
89
  ? ("thinking" as const)
87
90
  : ("fast" as const),
88
91
  graphContext: (cfg.graphContext as boolean) ?? true,
92
+ ignoreTerm:
93
+ typeof cfg.ignoreTerm === "string" && cfg.ignoreTerm.length > 0
94
+ ? cfg.ignoreTerm
95
+ : DEFAULT_IGNORE_TERM,
89
96
  debug: (cfg.debug as boolean) ?? false,
90
97
  }
91
98
  }
package/hooks/capture.ts CHANGED
@@ -1,49 +1,11 @@
1
1
  import type { CortexClient } from "../client.ts"
2
2
  import type { CortexPluginConfig } from "../config.ts"
3
3
  import { log } from "../log.ts"
4
- import { toSourceId } from "../session.ts"
4
+ import { extractAllTurns, filterIgnoredTurns } from "../messages.ts"
5
+ import { toHookSourceId } from "../session.ts"
5
6
  import type { ConversationTurn } from "../types/cortex.ts"
6
7
 
7
- function textFromMessage(msg: Record<string, unknown>): string {
8
- const content = msg.content
9
- if (typeof content === "string") return content
10
- if (Array.isArray(content)) {
11
- return content
12
- .filter(
13
- (b) =>
14
- b &&
15
- typeof b === "object" &&
16
- (b as Record<string, unknown>).type === "text",
17
- )
18
- .map((b) => (b as Record<string, unknown>).text as string)
19
- .join("\n")
20
- }
21
- return ""
22
- }
23
-
24
- function getLatestTurn(messages: unknown[]): ConversationTurn | null {
25
- let userIdx = -1
26
- for (let i = messages.length - 1; i >= 0; i--) {
27
- const m = messages[i]
28
- if (m && typeof m === "object" && (m as Record<string, unknown>).role === "user") {
29
- userIdx = i
30
- break
31
- }
32
- }
33
- if (userIdx < 0) return null
34
-
35
- const userText = textFromMessage(messages[userIdx] as Record<string, unknown>)
36
- if (!userText) return null
37
-
38
- for (let i = userIdx + 1; i < messages.length; i++) {
39
- const m = messages[i]
40
- if (m && typeof m === "object" && (m as Record<string, unknown>).role === "assistant") {
41
- const aText = textFromMessage(m as Record<string, unknown>)
42
- if (aText) return { user: userText, assistant: aText }
43
- }
44
- }
45
- return null
46
- }
8
+ const MAX_HOOK_TURNS = -1
47
9
 
48
10
  function removeInjectedBlocks(text: string): string {
49
11
  return text.replace(/<cortex-context>[\s\S]*?<\/cortex-context>\s*/g, "").trim()
@@ -51,35 +13,89 @@ function removeInjectedBlocks(text: string): string {
51
13
 
52
14
  export function createIngestionHook(
53
15
  client: CortexClient,
54
- _cfg: CortexPluginConfig,
55
- getSessionKey: () => string | undefined,
16
+ cfg: CortexPluginConfig,
56
17
  ) {
57
- return async (event: Record<string, unknown>) => {
58
- if (!event.success || !Array.isArray(event.messages) || event.messages.length === 0) return
18
+ return async (event: Record<string, unknown>, sessionId: string | undefined) => {
19
+ try {
20
+ log.debug(`[capture] hook fired — success=${event.success} msgs=${Array.isArray(event.messages) ? event.messages.length : "N/A"} sid=${sessionId ?? "none"}`)
59
21
 
60
- const turn = getLatestTurn(event.messages)
61
- if (!turn) return
22
+ if (!event.success) {
23
+ log.debug("[capture] skipped — event.success is falsy")
24
+ return
25
+ }
26
+ if (!Array.isArray(event.messages) || event.messages.length === 0) {
27
+ log.debug("[capture] skipped — no messages in event")
28
+ return
29
+ }
62
30
 
63
- const userClean = removeInjectedBlocks(turn.user)
64
- const assistantClean = removeInjectedBlocks(turn.assistant)
65
- if (userClean.length < 5 || assistantClean.length < 5) return
31
+ if (!sessionId) {
32
+ log.debug("[capture] skipped no session id available")
33
+ return
34
+ }
66
35
 
67
- const sk = getSessionKey()
68
- const sourceId = sk ? toSourceId(sk) : undefined
69
- if (!sourceId) {
70
- log.debug("ingestion skipped — no session key")
71
- return
72
- }
36
+ const rawTurns = extractAllTurns(event.messages)
37
+ const allTurns = filterIgnoredTurns(rawTurns, cfg.ignoreTerm)
73
38
 
74
- log.debug(`ingesting turn (u=${userClean.length}c, a=${assistantClean.length}c) → ${sourceId}`)
39
+ if (rawTurns.length > 0 && allTurns.length < rawTurns.length) {
40
+ log.debug(`[capture] filtered ${rawTurns.length - allTurns.length} turns containing ignore term "${cfg.ignoreTerm}"`)
41
+ }
42
+
43
+ if (allTurns.length === 0) {
44
+ log.debug(`[capture] skipped — no user-assistant turns found in ${event.messages.length} messages`)
45
+ const roles = event.messages
46
+ .slice(-5)
47
+ .map((m) => (m && typeof m === "object" ? (m as Record<string, unknown>).role : "?"))
48
+ log.debug(`[capture] last 5 message roles: ${JSON.stringify(roles)}`)
49
+ return
50
+ }
51
+
52
+ const recentTurns = MAX_HOOK_TURNS === -1 ? allTurns : allTurns.slice(-MAX_HOOK_TURNS)
53
+ const turns: ConversationTurn[] = recentTurns.map((t) => ({
54
+ user: removeInjectedBlocks(t.user),
55
+ assistant: removeInjectedBlocks(t.assistant),
56
+ })).filter((t) => t.user.length >= 5 && t.assistant.length >= 5)
57
+
58
+ if (turns.length === 0) {
59
+ log.debug("[capture] skipped — all turns too short after cleaning")
60
+ return
61
+ }
62
+
63
+ const sourceId = toHookSourceId(sessionId)
64
+
65
+ const now = new Date()
66
+ const timestamp = now.toISOString()
67
+ const readableTime = now.toLocaleString("en-US", {
68
+ weekday: "short",
69
+ year: "numeric",
70
+ month: "short",
71
+ day: "numeric",
72
+ hour: "2-digit",
73
+ minute: "2-digit",
74
+ timeZoneName: "short",
75
+ })
76
+
77
+ const annotatedTurns = turns.map((t, i) => ({
78
+ user: i === 0 ? `[Temporal details: ${readableTime}]\n\n${t.user}` : t.user,
79
+ assistant: t.assistant,
80
+ }))
81
+
82
+ log.debug(`[capture] ingesting ${annotatedTurns.length} turns (of ${allTurns.length} total) @ ${timestamp} -> ${sourceId}`)
75
83
 
76
- try {
77
84
  await client.ingestConversation(
78
- [{ user: userClean, assistant: assistantClean }],
85
+ annotatedTurns,
79
86
  sourceId,
87
+ {
88
+ metadata: {
89
+ captured_at: timestamp,
90
+ source: "openclaw_hook",
91
+ turn_count: annotatedTurns.length,
92
+ },
93
+ },
80
94
  )
95
+
96
+ log.debug("[capture] ingestion succeeded")
81
97
  } catch (err) {
82
- log.error("ingestion failed", err)
98
+ log.error("[capture] hook error", err)
83
99
  }
84
100
  }
85
101
  }
package/hooks/recall.ts CHANGED
@@ -2,6 +2,7 @@ import type { CortexClient } from "../client.ts"
2
2
  import type { CortexPluginConfig } from "../config.ts"
3
3
  import { buildRecalledContext, envelopeForInjection } from "../context.ts"
4
4
  import { log } from "../log.ts"
5
+ import { containsIgnoreTerm } from "../messages.ts"
5
6
 
6
7
  export function createRecallHook(
7
8
  client: CortexClient,
@@ -11,6 +12,11 @@ export function createRecallHook(
11
12
  const prompt = event.prompt as string | undefined
12
13
  if (!prompt || prompt.length < 5) return
13
14
 
15
+ if (containsIgnoreTerm(prompt, cfg.ignoreTerm)) {
16
+ log.debug(`recall skipped — prompt contains ignore term "${cfg.ignoreTerm}"`)
17
+ return
18
+ }
19
+
14
20
  log.debug(`recall query (${prompt.length} chars)`)
15
21
 
16
22
  try {
package/index.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
2
2
  import { CortexClient } from "./client.ts"
3
3
  import { registerCliCommands } from "./commands/cli.ts"
4
+ import { registerOnboardingCli, registerOnboardingSlashCommands } from "./commands/onboarding.ts"
4
5
  import { registerSlashCommands } from "./commands/slash.ts"
5
6
  import { cortexConfigSchema, parseConfig } from "./config.ts"
6
7
  import { createIngestionHook } from "./hooks/capture.ts"
7
8
  import { createRecallHook } from "./hooks/recall.ts"
8
9
  import { log } from "./log.ts"
10
+ import { registerDeleteTool } from "./tools/delete.ts"
11
+ import { registerGetTool } from "./tools/get.ts"
12
+ import { registerListTool } from "./tools/list.ts"
9
13
  import { registerSearchTool } from "./tools/search.ts"
10
14
  import { registerStoreTool } from "./tools/store.ts"
11
15
 
@@ -13,40 +17,57 @@ export default {
13
17
  id: "openclaw-cortex-ai",
14
18
  name: "Cortex AI",
15
19
  description:
16
- "Long-term memory for OpenClaw powered by Cortex AI — auto-capture, recall, and graph-enriched context",
20
+ "State-of-the-art agentic memory for OpenClaw powered by Cortex AI — auto-capture, recall, and graph-enriched context",
17
21
  kind: "memory" as const,
18
22
  configSchema: cortexConfigSchema,
19
23
 
20
24
  register(api: OpenClawPluginApi) {
21
25
  const cfg = parseConfig(api.pluginConfig)
22
26
 
23
- log.setDebug(cfg.debug)
27
+ log.init(api.logger, cfg.debug)
24
28
 
25
29
  const client = new CortexClient(cfg.apiKey, cfg.tenantId, cfg.subTenantId)
26
30
 
27
- let activeSessionKey: string | undefined
28
- const getSessionKey = () => activeSessionKey
31
+ let activeSessionId: string | undefined
32
+ let conversationMessages: unknown[] = []
33
+ const getSessionId = () => activeSessionId
34
+ const getMessages = () => conversationMessages
29
35
 
30
36
  registerSearchTool(api, client, cfg)
31
- registerStoreTool(api, client, cfg, getSessionKey)
37
+ registerStoreTool(api, client, cfg, getSessionId, getMessages)
38
+ registerListTool(api, client, cfg)
39
+ registerDeleteTool(api, client, cfg)
40
+ registerGetTool(api, client, cfg)
32
41
 
33
42
  if (cfg.autoRecall) {
34
43
  const onRecall = createRecallHook(client, cfg)
35
44
  api.on(
36
45
  "before_agent_start",
37
46
  (event: Record<string, unknown>, ctx: Record<string, unknown>) => {
38
- if (ctx.sessionKey) activeSessionKey = ctx.sessionKey as string
47
+ if (ctx.sessionId) activeSessionId = ctx.sessionId as string
48
+ if (Array.isArray(event.messages)) conversationMessages = event.messages
49
+ log.debug(`[session] before_agent_start — sid=${activeSessionId ?? "none"} msgs=${conversationMessages.length}`)
39
50
  return onRecall(event)
40
51
  },
41
52
  )
42
53
  }
43
54
 
44
55
  if (cfg.autoCapture) {
45
- api.on("agent_end", createIngestionHook(client, cfg, getSessionKey))
56
+ const captureHandler = createIngestionHook(client, cfg)
57
+ api.on(
58
+ "agent_end",
59
+ (event: Record<string, unknown>, ctx: Record<string, unknown>) => {
60
+ if (ctx.sessionId) activeSessionId = ctx.sessionId as string
61
+ if (Array.isArray(event.messages)) conversationMessages = event.messages
62
+ log.debug(`[session] agent_end — sid=${activeSessionId ?? "none"} msgs=${conversationMessages.length} ctxKeys=${Object.keys(ctx).join(",")}`)
63
+ return captureHandler(event, activeSessionId)
64
+ },
65
+ )
46
66
  }
47
67
 
48
- registerSlashCommands(api, client, cfg, getSessionKey)
49
- registerCliCommands(api, client, cfg)
68
+ registerSlashCommands(api, client, cfg, getSessionId)
69
+ registerOnboardingSlashCommands(api, client, cfg)
70
+ registerCliCommands(api, client, cfg, registerOnboardingCli(cfg))
50
71
 
51
72
  api.registerService({
52
73
  id: "openclaw-cortex-ai",
package/log.ts CHANGED
@@ -1,25 +1,48 @@
1
+ export type LoggerBackend = {
2
+ info(msg: string): void
3
+ warn(msg: string): void
4
+ error(msg: string): void
5
+ debug?(msg: string): void
6
+ }
7
+
1
8
  const TAG = "[cortex-ai]"
2
9
 
10
+ let _backend: LoggerBackend | null = null
3
11
  let _debug = false
4
12
 
5
13
  export const log = {
14
+ init(backend: LoggerBackend, debug: boolean) {
15
+ _backend = backend
16
+ _debug = debug
17
+ },
18
+
6
19
  setDebug(enabled: boolean) {
7
20
  _debug = enabled
8
21
  },
9
22
 
10
23
  info(...args: unknown[]) {
11
- console.log(TAG, ...args)
24
+ const msg = `${TAG} ${args.map(String).join(" ")}`
25
+ if (_backend) _backend.info(msg)
26
+ else console.log(msg)
12
27
  },
13
28
 
14
29
  warn(...args: unknown[]) {
15
- console.warn(TAG, ...args)
30
+ const msg = `${TAG} ${args.map(String).join(" ")}`
31
+ if (_backend) _backend.warn(msg)
32
+ else console.warn(msg)
16
33
  },
17
34
 
18
35
  error(...args: unknown[]) {
19
- console.error(TAG, ...args)
36
+ const msg = `${TAG} ${args.map(String).join(" ")}`
37
+ if (_backend) _backend.error(msg)
38
+ else console.error(msg)
20
39
  },
21
40
 
22
41
  debug(...args: unknown[]) {
23
- if (_debug) console.debug(TAG, ...args)
42
+ if (!_debug) return
43
+ const msg = `${TAG} ${args.map(String).join(" ")}`
44
+ if (_backend?.debug) _backend.debug(msg)
45
+ else if (_backend) _backend.info(msg)
46
+ else console.debug(msg)
24
47
  },
25
48
  }
package/messages.ts ADDED
@@ -0,0 +1,88 @@
1
+ import type { ConversationTurn } from "./types/cortex.ts"
2
+
3
+ export function containsIgnoreTerm(text: string, ignoreTerm: string): boolean {
4
+ return text.toLowerCase().includes(ignoreTerm.toLowerCase())
5
+ }
6
+
7
+ export function filterIgnoredTurns(
8
+ turns: ConversationTurn[],
9
+ ignoreTerm: string,
10
+ ): ConversationTurn[] {
11
+ return turns.filter(
12
+ (t) =>
13
+ !containsIgnoreTerm(t.user, ignoreTerm) &&
14
+ !containsIgnoreTerm(t.assistant, ignoreTerm),
15
+ )
16
+ }
17
+
18
+ export function textFromMessage(msg: Record<string, unknown>): string {
19
+ const content = msg.content
20
+ if (typeof content === "string") return content
21
+ if (Array.isArray(content)) {
22
+ return content
23
+ .filter(
24
+ (b) =>
25
+ b &&
26
+ typeof b === "object" &&
27
+ (b as Record<string, unknown>).type === "text",
28
+ )
29
+ .map((b) => (b as Record<string, unknown>).text as string)
30
+ .filter(Boolean)
31
+ .join("\n")
32
+ }
33
+ return ""
34
+ }
35
+
36
+ export function extractAllTurns(messages: unknown[]): ConversationTurn[] {
37
+ const turns: ConversationTurn[] = []
38
+ let currentUserText: string | null = null
39
+ let currentAssistantText: string | null = null
40
+
41
+ for (const msg of messages) {
42
+ if (!msg || typeof msg !== "object") continue
43
+ const m = msg as Record<string, unknown>
44
+ const text = textFromMessage(m)
45
+
46
+ if (m.role === "user") {
47
+ if (!text) continue
48
+ if (currentUserText && currentAssistantText) {
49
+ turns.push({ user: currentUserText, assistant: currentAssistantText })
50
+ }
51
+ currentUserText = text
52
+ currentAssistantText = "no-message"
53
+ } else if (m.role === "assistant") {
54
+ if (!text) continue
55
+ currentAssistantText = text
56
+ }
57
+ }
58
+
59
+ if (currentUserText && currentAssistantText) {
60
+ turns.push({ user: currentUserText, assistant: currentAssistantText })
61
+ }
62
+
63
+ return turns
64
+ }
65
+
66
+ export function getLatestTurn(messages: unknown[]): ConversationTurn | null {
67
+ let userIdx = -1
68
+ for (let i = messages.length - 1; i >= 0; i--) {
69
+ const m = messages[i]
70
+ if (m && typeof m === "object" && (m as Record<string, unknown>).role === "user") {
71
+ userIdx = i
72
+ break
73
+ }
74
+ }
75
+ if (userIdx < 0) return null
76
+
77
+ const userText = textFromMessage(messages[userIdx] as Record<string, unknown>)
78
+ if (!userText) return null
79
+
80
+ for (let i = userIdx + 1; i < messages.length; i++) {
81
+ const m = messages[i]
82
+ if (m && typeof m === "object" && (m as Record<string, unknown>).role === "assistant") {
83
+ const aText = textFromMessage(m as Record<string, unknown>)
84
+ if (aText) return { user: userText, assistant: aText }
85
+ }
86
+ }
87
+ return null
88
+ }
@@ -43,6 +43,11 @@
43
43
  "help": "Include knowledge graph relations in recalled context (default: true)",
44
44
  "advanced": true
45
45
  },
46
+ "ignoreTerm": {
47
+ "label": "Ignore Term",
48
+ "placeholder": "cortex-ignore",
49
+ "help": "Messages containing this term will be excluded from recall and capture (default: cortex-ignore)"
50
+ },
46
51
  "debug": {
47
52
  "label": "Debug Logging",
48
53
  "help": "Enable verbose debug logs for API calls and responses",
@@ -61,6 +66,7 @@
61
66
  "maxRecallResults": { "type": "number", "minimum": 1, "maximum": 50 },
62
67
  "recallMode": { "type": "string", "enum": ["fast", "thinking"] },
63
68
  "graphContext": { "type": "boolean" },
69
+ "ignoreTerm": { "type": "string" },
64
70
  "debug": { "type": "boolean" }
65
71
  },
66
72
  "required": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usecortex_ai/openclaw-cortex-ai",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Cortex AI — the State-of-the-art agentic memory system with auto-capture, recall, and knowledge graph context for open-claw",
6
6
  "license": "MIT",
package/session.ts CHANGED
@@ -1,3 +1,11 @@
1
- export function toSourceId(sessionKey: string): string {
2
- return `openclaw_cortex_sess_${sessionKey.replace(/\W+/g, "_")}`
1
+ export function toSourceId(sessionId: string): string {
2
+ return `sess_${sessionId}`
3
+ }
4
+
5
+ export function toHookSourceId(sessionId: string): string {
6
+ return `hook_${sessionId}`
7
+ }
8
+
9
+ export function toToolSourceId(sessionId: string): string {
10
+ return `tool_${sessionId}`
3
11
  }
@@ -0,0 +1,54 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CortexClient } from "../client.ts"
4
+ import type { CortexPluginConfig } from "../config.ts"
5
+ import { log } from "../log.ts"
6
+
7
+ export function registerDeleteTool(
8
+ api: OpenClawPluginApi,
9
+ client: CortexClient,
10
+ _cfg: CortexPluginConfig,
11
+ ): void {
12
+ api.registerTool(
13
+ {
14
+ name: "cortex_delete_memory",
15
+ label: "Cortex Delete Memory",
16
+ description:
17
+ "Delete a specific memory from Cortex by its memory ID. Use this when the user explicitly asks you to forget something or remove a specific piece of stored information. Always confirm the memory ID before deleting.",
18
+ parameters: Type.Object({
19
+ memory_id: Type.String({
20
+ description: "The unique ID of the memory to delete",
21
+ }),
22
+ }),
23
+ async execute(
24
+ _toolCallId: string,
25
+ params: { memory_id: string },
26
+ ) {
27
+ log.debug(`delete tool: memory_id=${params.memory_id}`)
28
+
29
+ const res = await client.deleteMemory(params.memory_id)
30
+
31
+ if (res.user_memory_deleted) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text" as const,
36
+ text: `Successfully deleted memory: ${params.memory_id}`,
37
+ },
38
+ ],
39
+ }
40
+ }
41
+
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text" as const,
46
+ text: `Memory ${params.memory_id} was not found or has already been deleted.`,
47
+ },
48
+ ],
49
+ }
50
+ },
51
+ },
52
+ { name: "cortex_delete_memory" },
53
+ )
54
+ }
package/tools/get.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CortexClient } from "../client.ts"
4
+ import type { CortexPluginConfig } from "../config.ts"
5
+ import { log } from "../log.ts"
6
+
7
+ export function registerGetTool(
8
+ api: OpenClawPluginApi,
9
+ client: CortexClient,
10
+ _cfg: CortexPluginConfig,
11
+ ): void {
12
+ api.registerTool(
13
+ {
14
+ name: "cortex_get_content",
15
+ label: "Cortex Get Content",
16
+ description:
17
+ "Fetch the full content of a specific source from Cortex by its source ID. Use this to retrieve the complete text of a memory source when you need more details than what's shown in search results.",
18
+ parameters: Type.Object({
19
+ source_id: Type.String({
20
+ description: "The unique source ID to fetch content for",
21
+ }),
22
+ }),
23
+ async execute(
24
+ _toolCallId: string,
25
+ params: { source_id: string },
26
+ ) {
27
+ log.debug(`get tool: source_id=${params.source_id}`)
28
+
29
+ const res = await client.fetchContent(params.source_id)
30
+
31
+ if (!res.success || res.error) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text" as const,
36
+ text: `Failed to fetch source ${params.source_id}: ${res.error ?? "unknown error"}`,
37
+ },
38
+ ],
39
+ }
40
+ }
41
+
42
+ const content = res.content ?? res.content_base64 ?? "(no text content available)"
43
+ const preview = content.length > 3000 ? `${content.slice(0, 3000)}…\n\n[Content truncated, showing first 3000 characters]` : content
44
+
45
+ return {
46
+ content: [
47
+ {
48
+ type: "text" as const,
49
+ text: `Source: ${params.source_id}\n\n${preview}`,
50
+ },
51
+ ],
52
+ }
53
+ },
54
+ },
55
+ { name: "cortex_get_content" },
56
+ )
57
+ }
package/tools/list.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CortexClient } from "../client.ts"
4
+ import type { CortexPluginConfig } from "../config.ts"
5
+ import { log } from "../log.ts"
6
+
7
+ export function registerListTool(
8
+ api: OpenClawPluginApi,
9
+ client: CortexClient,
10
+ _cfg: CortexPluginConfig,
11
+ ): void {
12
+ api.registerTool(
13
+ {
14
+ name: "cortex_list_memories",
15
+ label: "Cortex List Memories",
16
+ description:
17
+ "List all user memories stored in Cortex. Returns memory IDs and content summaries. Use this when the user asks what you remember about them or wants to see their stored information.",
18
+ parameters: Type.Object({}),
19
+ async execute(_toolCallId: string, _params: Record<string, never>) {
20
+ log.debug("list tool: fetching all memories")
21
+
22
+ const res = await client.listMemories()
23
+ const memories = res.user_memories ?? []
24
+
25
+ if (memories.length === 0) {
26
+ return {
27
+ content: [
28
+ {
29
+ type: "text" as const,
30
+ text: "No memories stored yet.",
31
+ },
32
+ ],
33
+ }
34
+ }
35
+
36
+ const lines = memories.map((m, i) => {
37
+ const preview =
38
+ m.memory_content.length > 100
39
+ ? `${m.memory_content.slice(0, 100)}…`
40
+ : m.memory_content
41
+ return `${i + 1}. [ID: ${m.memory_id}]\n ${preview}`
42
+ })
43
+
44
+ return {
45
+ content: [
46
+ {
47
+ type: "text" as const,
48
+ text: `Found ${memories.length} memories:\n\n${lines.join("\n\n")}`,
49
+ },
50
+ ],
51
+ }
52
+ },
53
+ },
54
+ { name: "cortex_list_memories" },
55
+ )
56
+ }
package/tools/search.ts CHANGED
@@ -17,7 +17,7 @@ export function registerSearchTool(
17
17
  name: "cortex_search",
18
18
  label: "Cortex Search",
19
19
  description:
20
- "Search through Cortex long-term memories. Returns relevant chunks with graph-enriched context.",
20
+ "Search through Cortex AI memories. Returns relevant chunks with graph-enriched context.",
21
21
  parameters: Type.Object({
22
22
  query: Type.String({ description: "Search query" }),
23
23
  limit: Type.Optional(