@usecortex_ai/openclaw-cortex-ai 0.1.0 → 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/README.md CHANGED
@@ -47,6 +47,7 @@ Or configure directly in `openclaw.json`:
47
47
  | `maxRecallResults` | `number` | `10` | Max memory chunks injected into context per turn |
48
48
  | `recallMode` | `string` | `"fast"` | `"fast"` or `"thinking"` (deeper personalised recall with graph traversal) |
49
49
  | `graphContext` | `boolean` | `true` | Include knowledge graph relations in recalled context |
50
+ | `ignoreTerm` | `string` | `"cortex-ignore"` | Messages containing this term are excluded from recall & capture |
50
51
  | `debug` | `boolean` | `false` | Verbose debug logs |
51
52
 
52
53
  ## How It Works
@@ -54,10 +55,28 @@ Or configure directly in `openclaw.json`:
54
55
  - **Auto-Recall** — Before every AI turn, queries Cortex (`/recall/recall_preferences`) for relevant memories and injects graph-enriched context (entity paths, chunk relations, extra context).
55
56
  - **Auto-Capture** — After every AI turn, the last user/assistant exchange is sent to Cortex (`/memories/add_memory`) as conversation pairs with `infer: true` and `upsert: true`. The session ID is used as `source_id` so Cortex groups exchanges per session and builds a knowledge graph automatically.
56
57
 
58
+ ## Interactive Onboarding
59
+
60
+ Run the interactive CLI wizard to configure Cortex AI:
61
+
62
+ ```bash
63
+ # Basic onboarding (API key, tenant ID, sub-tenant, ignore term)
64
+ openclaw cortex onboard
65
+
66
+ # Advanced onboarding (all options including recall mode, graph context, etc.)
67
+ openclaw cortex onboard --advanced
68
+ ```
69
+
70
+ The wizard guides you through configuration with colored prompts, validates inputs, and outputs:
71
+ - `.env` file lines for credentials
72
+ - Plugin config JSON for non-default settings
73
+ - A summary table with masked sensitive values
74
+
57
75
  ## Slash Commands
58
76
 
59
77
  | Command | Description |
60
78
  | --------------------------- | ------------------------------------- |
79
+ | `/cortex-onboard` | Show current configuration status |
61
80
  | `/cortex-remember <text>` | Save something to Cortex memory |
62
81
  | `/cortex-recall <query>` | Search memories with relevance scores |
63
82
  | `/cortex-list` | List all stored user memories |
@@ -74,11 +93,13 @@ Or configure directly in `openclaw.json`:
74
93
  ## CLI
75
94
 
76
95
  ```bash
77
- openclaw cortex search <query> # Search memories
78
- # List all user memories
79
- openclaw cortex delete <id> # Delete a memory
80
- openclaw cortex get <source_id> # Fetch source content
81
- openclaw cortex status # Show plugin configuration
96
+ openclaw cortex onboard # Interactive onboarding wizard
97
+ openclaw cortex onboard --advanced # Advanced onboarding wizard
98
+ openclaw cortex search <query> # Search memories
99
+ openclaw cortex list # List all user memories
100
+ openclaw cortex delete <id> # Delete a memory
101
+ openclaw cortex get <source_id> # Fetch source content
102
+ openclaw cortex status # Show plugin configuration
82
103
  ```
83
104
 
84
105
  ## Context Injection
@@ -86,5 +107,4 @@ openclaw cortex status # Show plugin configuration
86
107
  Recalled context is injected inside `<cortex-context>` tags containing:
87
108
 
88
109
  - **Entity Paths** — Knowledge graph paths connecting entities relevant to the query
89
- - **
90
- Context Chunks** — Retrieved memory chunks with source titles, graph relations, and linked extra context
110
+ - **Context Chunks** — Retrieved memory chunks with source titles, graph relations, and linked extra context
package/commands/cli.ts CHANGED
@@ -6,6 +6,7 @@ export function registerCliCommands(
6
6
  api: OpenClawPluginApi,
7
7
  client: CortexClient,
8
8
  cfg: CortexPluginConfig,
9
+ onboardingRegistrar?: (root: any) => void,
9
10
  ): void {
10
11
  api.registerCli(
11
12
  ({ program }: { program: any }) => {
@@ -86,7 +87,10 @@ export function registerCliCommands(
86
87
  console.log(`Recall Mode: ${cfg.recallMode}`)
87
88
  console.log(`Graph: ${cfg.graphContext}`)
88
89
  console.log(`Max Results: ${cfg.maxRecallResults}`)
90
+ console.log(`Ignore Term: ${cfg.ignoreTerm}`)
89
91
  })
92
+
93
+ if (onboardingRegistrar) onboardingRegistrar(root)
90
94
  },
91
95
  { commands: ["cortex"] },
92
96
  )
@@ -0,0 +1,442 @@
1
+ import * as readline from "node:readline"
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
+ // ── ANSI helpers ──
8
+
9
+ const c = {
10
+ reset: "\x1b[0m",
11
+ bold: "\x1b[1m",
12
+ dim: "\x1b[2m",
13
+ cyan: "\x1b[36m",
14
+ green: "\x1b[32m",
15
+ yellow: "\x1b[33m",
16
+ red: "\x1b[31m",
17
+ magenta: "\x1b[35m",
18
+ white: "\x1b[37m",
19
+ bgCyan: "\x1b[46m",
20
+ bgGreen: "\x1b[42m",
21
+ black: "\x1b[30m",
22
+ }
23
+
24
+ function mask(value: string, visible = 4): string {
25
+ if (value.length <= visible) return "****"
26
+ return `${"*".repeat(value.length - visible)}${value.slice(-visible)}`
27
+ }
28
+
29
+ // ── Prompt primitives ──
30
+
31
+ function createRl(): readline.Interface {
32
+ return readline.createInterface({
33
+ input: process.stdin,
34
+ output: process.stdout,
35
+ })
36
+ }
37
+
38
+ function ask(rl: readline.Interface, question: string): Promise<string> {
39
+ return new Promise((resolve) => rl.question(question, resolve))
40
+ }
41
+
42
+ async function promptText(
43
+ rl: readline.Interface,
44
+ label: string,
45
+ opts?: { default?: string; required?: boolean; secret?: boolean },
46
+ ): Promise<string> {
47
+ const def = opts?.default
48
+ const hint = def ? `${c.dim} (${def})${c.reset}` : opts?.required ? `${c.red} *${c.reset}` : ""
49
+ const prefix = ` ${c.cyan}?${c.reset} ${c.bold}${label}${c.reset}${hint}${c.dim} ›${c.reset} `
50
+
51
+ while (true) {
52
+ const raw = await ask(rl, prefix)
53
+ const value = raw.trim()
54
+ if (value) return value
55
+ if (def) return def
56
+ if (opts?.required) {
57
+ console.log(` ${c.red}This field is required.${c.reset}`)
58
+ continue
59
+ }
60
+ return ""
61
+ }
62
+ }
63
+
64
+ async function promptChoice(
65
+ rl: readline.Interface,
66
+ label: string,
67
+ choices: string[],
68
+ defaultChoice: string,
69
+ ): Promise<string> {
70
+ const tags = choices
71
+ .map((ch) => (ch === defaultChoice ? `${c.green}${c.bold}${ch}${c.reset}` : `${c.dim}${ch}${c.reset}`))
72
+ .join(`${c.dim} / ${c.reset}`)
73
+
74
+ const prefix = ` ${c.cyan}?${c.reset} ${c.bold}${label}${c.reset} ${tags}${c.dim} ›${c.reset} `
75
+
76
+ while (true) {
77
+ const raw = await ask(rl, prefix)
78
+ const value = raw.trim().toLowerCase()
79
+ if (!value) return defaultChoice
80
+ const match = choices.find((ch) => ch.toLowerCase() === value)
81
+ if (match) return match
82
+ console.log(` ${c.yellow}Choose one of: ${choices.join(", ")}${c.reset}`)
83
+ }
84
+ }
85
+
86
+ async function promptBool(
87
+ rl: readline.Interface,
88
+ label: string,
89
+ defaultVal: boolean,
90
+ ): Promise<boolean> {
91
+ const hint = defaultVal
92
+ ? `${c.dim} (${c.green}Y${c.reset}${c.dim}/n)${c.reset}`
93
+ : `${c.dim} (y/${c.green}N${c.reset}${c.dim})${c.reset}`
94
+ const prefix = ` ${c.cyan}?${c.reset} ${c.bold}${label}${c.reset}${hint}${c.dim} ›${c.reset} `
95
+
96
+ const raw = await ask(rl, prefix)
97
+ const value = raw.trim().toLowerCase()
98
+ if (!value) return defaultVal
99
+ return value === "y" || value === "yes" || value === "true"
100
+ }
101
+
102
+ async function promptNumber(
103
+ rl: readline.Interface,
104
+ label: string,
105
+ defaultVal: number,
106
+ min: number,
107
+ max: number,
108
+ ): Promise<number> {
109
+ const prefix = ` ${c.cyan}?${c.reset} ${c.bold}${label}${c.reset}${c.dim} (${defaultVal}) [${min}–${max}] ›${c.reset} `
110
+
111
+ while (true) {
112
+ const raw = await ask(rl, prefix)
113
+ const value = raw.trim()
114
+ if (!value) return defaultVal
115
+ const n = Number.parseInt(value, 10)
116
+ if (!Number.isNaN(n) && n >= min && n <= max) return n
117
+ console.log(` ${c.yellow}Enter a number between ${min} and ${max}.${c.reset}`)
118
+ }
119
+ }
120
+
121
+ // ── Banner ──
122
+
123
+ function printBanner(): void {
124
+ console.log()
125
+ console.log(` ${c.bgCyan}${c.black}${c.bold} ${c.reset}`)
126
+ console.log(` ${c.bgCyan}${c.black}${c.bold} ◆ Cortex AI — Onboard ${c.reset}`)
127
+ console.log(` ${c.bgCyan}${c.black}${c.bold} ${c.reset}`)
128
+ console.log()
129
+ }
130
+
131
+ function printSection(title: string): void {
132
+ console.log()
133
+ console.log(` ${c.magenta}${c.bold}── ${title} ${"─".repeat(Math.max(0, 40 - title.length))}${c.reset}`)
134
+ console.log()
135
+ }
136
+
137
+ function printSummaryRow(label: string, value: string, sensitive = false): void {
138
+ const display = sensitive ? mask(value) : value
139
+ console.log(` ${c.dim}│${c.reset} ${c.bold}${label.padEnd(18)}${c.reset} ${c.cyan}${display}${c.reset}`)
140
+ }
141
+
142
+ function printSuccess(msg: string): void {
143
+ console.log()
144
+ console.log(` ${c.bgGreen}${c.black}${c.bold} ✓ ${c.reset} ${c.green}${msg}${c.reset}`)
145
+ console.log()
146
+ }
147
+
148
+ // ── Config output ──
149
+
150
+ type WizardResult = {
151
+ apiKey: string
152
+ tenantId: string
153
+ subTenantId: string
154
+ ignoreTerm: string
155
+ autoRecall?: boolean
156
+ autoCapture?: boolean
157
+ maxRecallResults?: number
158
+ recallMode?: "fast" | "thinking"
159
+ graphContext?: boolean
160
+ debug?: boolean
161
+ }
162
+
163
+ function buildConfigJson(result: WizardResult): string {
164
+ const obj: Record<string, unknown> = {}
165
+
166
+ if (result.apiKey && !result.apiKey.startsWith("$")) {
167
+ obj.apiKey = result.apiKey
168
+ }
169
+ if (result.tenantId && !result.tenantId.startsWith("$")) {
170
+ obj.tenantId = result.tenantId
171
+ }
172
+ if (result.subTenantId !== "cortex-openclaw-plugin") {
173
+ obj.subTenantId = result.subTenantId
174
+ }
175
+ if (result.ignoreTerm !== "cortex-ignore") {
176
+ obj.ignoreTerm = result.ignoreTerm
177
+ }
178
+ if (result.autoRecall !== undefined && result.autoRecall !== true) {
179
+ obj.autoRecall = result.autoRecall
180
+ }
181
+ if (result.autoCapture !== undefined && result.autoCapture !== true) {
182
+ obj.autoCapture = result.autoCapture
183
+ }
184
+ if (result.maxRecallResults !== undefined && result.maxRecallResults !== 10) {
185
+ obj.maxRecallResults = result.maxRecallResults
186
+ }
187
+ if (result.recallMode !== undefined && result.recallMode !== "fast") {
188
+ obj.recallMode = result.recallMode
189
+ }
190
+ if (result.graphContext !== undefined && result.graphContext !== true) {
191
+ obj.graphContext = result.graphContext
192
+ }
193
+ if (result.debug !== undefined && result.debug !== false) {
194
+ obj.debug = result.debug
195
+ }
196
+
197
+ return JSON.stringify(obj, null, 2)
198
+ }
199
+
200
+ function buildEnvLines(result: WizardResult): string[] {
201
+ const lines: string[] = []
202
+ if (result.apiKey && !result.apiKey.startsWith("$")) {
203
+ lines.push(`CORTEX_OPENCLAW_API_KEY=${result.apiKey}`)
204
+ }
205
+ if (result.tenantId && !result.tenantId.startsWith("$")) {
206
+ lines.push(`CORTEX_OPENCLAW_TENANT_ID=${result.tenantId}`)
207
+ }
208
+ return lines
209
+ }
210
+
211
+ // ── Wizards ──
212
+
213
+ async function runBasicWizard(cfg: CortexPluginConfig): Promise<void> {
214
+ const rl = createRl()
215
+
216
+ try {
217
+ printBanner()
218
+ console.log(` ${c.dim}Configure the essential settings for Cortex AI.${c.reset}`)
219
+ console.log(` ${c.dim}Press Enter to accept defaults shown in parentheses.${c.reset}`)
220
+
221
+ printSection("Credentials")
222
+
223
+ const apiKey = await promptText(rl, "API Key", {
224
+ required: true,
225
+ secret: true,
226
+ })
227
+
228
+ const tenantId = await promptText(rl, "Tenant ID", {
229
+ required: true,
230
+ })
231
+
232
+ printSection("Customisation")
233
+
234
+ const subTenantId = await promptText(rl, "Sub-Tenant ID", {
235
+ default: cfg.subTenantId,
236
+ })
237
+
238
+ const ignoreTerm = await promptText(rl, "Ignore Term", {
239
+ default: cfg.ignoreTerm,
240
+ })
241
+
242
+ const result: WizardResult = { apiKey, tenantId, subTenantId, ignoreTerm }
243
+
244
+ // ── Summary ──
245
+
246
+ printSection("Summary")
247
+
248
+ console.log(` ${c.dim}┌${"─".repeat(50)}${c.reset}`)
249
+ printSummaryRow("API Key", apiKey, true)
250
+ printSummaryRow("Tenant ID", tenantId)
251
+ printSummaryRow("Sub-Tenant ID", subTenantId)
252
+ printSummaryRow("Ignore Term", ignoreTerm)
253
+ console.log(` ${c.dim}└${"─".repeat(50)}${c.reset}`)
254
+
255
+ // ── Output config ──
256
+
257
+ const envLines = buildEnvLines(result)
258
+ if (envLines.length > 0) {
259
+ console.log()
260
+ console.log(` ${c.yellow}${c.bold}Add to your .env file:${c.reset}`)
261
+ console.log()
262
+ for (const line of envLines) {
263
+ console.log(` ${c.green}${line}${c.reset}`)
264
+ }
265
+ }
266
+
267
+ const json = buildConfigJson(result)
268
+ if (json !== "{}") {
269
+ console.log()
270
+ console.log(` ${c.yellow}${c.bold}Plugin config (openclaw.plugin.json / settings):${c.reset}`)
271
+ console.log()
272
+ for (const line of json.split("\n")) {
273
+ console.log(` ${c.cyan}${line}${c.reset}`)
274
+ }
275
+ }
276
+
277
+ printSuccess("Onboarding complete! Run `cortex onboard --advanced` to fine-tune all options.")
278
+ } finally {
279
+ rl.close()
280
+ }
281
+ }
282
+
283
+ async function runAdvancedWizard(cfg: CortexPluginConfig): Promise<void> {
284
+ const rl = createRl()
285
+
286
+ try {
287
+ printBanner()
288
+ console.log(` ${c.dim}Full configuration wizard — customise every option.${c.reset}`)
289
+ console.log(` ${c.dim}Press Enter to accept defaults shown in parentheses.${c.reset}`)
290
+
291
+ printSection("Credentials")
292
+
293
+ const apiKey = await promptText(rl, "API Key", {
294
+ required: true,
295
+ secret: true,
296
+ })
297
+
298
+ const tenantId = await promptText(rl, "Tenant ID", {
299
+ required: true,
300
+ })
301
+
302
+ const subTenantId = await promptText(rl, "Sub-Tenant ID", {
303
+ default: cfg.subTenantId,
304
+ })
305
+
306
+ printSection("Behaviour")
307
+
308
+ const autoRecall = await promptBool(rl, "Enable Auto-Recall?", cfg.autoRecall)
309
+ const autoCapture = await promptBool(rl, "Enable Auto-Capture?", cfg.autoCapture)
310
+ const ignoreTerm = await promptText(rl, "Ignore Term", {
311
+ default: cfg.ignoreTerm,
312
+ })
313
+
314
+ printSection("Recall Settings")
315
+
316
+ const maxRecallResults = await promptNumber(
317
+ rl, "Max Recall Results", cfg.maxRecallResults, 1, 50,
318
+ )
319
+ const recallMode = await promptChoice(
320
+ rl, "Recall Mode", ["fast", "thinking"], cfg.recallMode,
321
+ ) as "fast" | "thinking"
322
+ const graphContext = await promptBool(rl, "Enable Graph Context?", cfg.graphContext)
323
+
324
+ printSection("Debug")
325
+
326
+ const debug = await promptBool(rl, "Enable Debug Logging?", cfg.debug)
327
+
328
+ const result: WizardResult = {
329
+ apiKey,
330
+ tenantId,
331
+ subTenantId,
332
+ ignoreTerm,
333
+ autoRecall,
334
+ autoCapture,
335
+ maxRecallResults,
336
+ recallMode,
337
+ graphContext,
338
+ debug,
339
+ }
340
+
341
+ // ── Summary ──
342
+
343
+ printSection("Summary")
344
+
345
+ console.log(` ${c.dim}┌${"─".repeat(50)}${c.reset}`)
346
+ printSummaryRow("API Key", apiKey, true)
347
+ printSummaryRow("Tenant ID", tenantId)
348
+ printSummaryRow("Sub-Tenant ID", subTenantId)
349
+ printSummaryRow("Auto-Recall", String(autoRecall))
350
+ printSummaryRow("Auto-Capture", String(autoCapture))
351
+ printSummaryRow("Ignore Term", ignoreTerm)
352
+ printSummaryRow("Max Results", String(maxRecallResults))
353
+ printSummaryRow("Recall Mode", recallMode)
354
+ printSummaryRow("Graph Context", String(graphContext))
355
+ printSummaryRow("Debug", String(debug))
356
+ console.log(` ${c.dim}└${"─".repeat(50)}${c.reset}`)
357
+
358
+ // ── Output config ──
359
+
360
+ const envLines = buildEnvLines(result)
361
+ if (envLines.length > 0) {
362
+ console.log()
363
+ console.log(` ${c.yellow}${c.bold}Add to your .env file:${c.reset}`)
364
+ console.log()
365
+ for (const line of envLines) {
366
+ console.log(` ${c.green}${line}${c.reset}`)
367
+ }
368
+ }
369
+
370
+ const json = buildConfigJson(result)
371
+ if (json !== "{}") {
372
+ console.log()
373
+ console.log(` ${c.yellow}${c.bold}Plugin config (openclaw.plugin.json / settings):${c.reset}`)
374
+ console.log()
375
+ for (const line of json.split("\n")) {
376
+ console.log(` ${c.cyan}${line}${c.reset}`)
377
+ }
378
+ }
379
+
380
+ printSuccess("Onboarding complete! All options configured.")
381
+ } finally {
382
+ rl.close()
383
+ }
384
+ }
385
+
386
+ // ── Registration (CLI + Slash) ──
387
+
388
+ export function registerOnboardingCli(
389
+ cfg: CortexPluginConfig,
390
+ ): (root: any) => void {
391
+ return (root: any) => {
392
+ root
393
+ .command("onboard")
394
+ .description("Interactive Cortex AI onboarding wizard")
395
+ .option("--advanced", "Configure all options (credentials, behaviour, recall, debug)")
396
+ .action(async (opts: { advanced?: boolean }) => {
397
+ if (opts.advanced) {
398
+ await runAdvancedWizard(cfg)
399
+ } else {
400
+ await runBasicWizard(cfg)
401
+ }
402
+ })
403
+ }
404
+ }
405
+
406
+ export function registerOnboardingSlashCommands(
407
+ api: OpenClawPluginApi,
408
+ client: CortexClient,
409
+ cfg: CortexPluginConfig,
410
+ ): void {
411
+ api.registerCommand({
412
+ name: "cortex-onboard",
413
+ description: "Show Cortex plugin config status (run `cortex onboard` in CLI for interactive wizard)",
414
+ acceptsArgs: false,
415
+ requireAuth: false,
416
+ handler: async () => {
417
+ try {
418
+ const lines: string[] = [
419
+ "=== Cortex AI — Current Config ===",
420
+ "",
421
+ ` API Key: ${cfg.apiKey ? `${mask(cfg.apiKey)} ✓` : "NOT SET ✗"}`,
422
+ ` Tenant ID: ${cfg.tenantId ? `${mask(cfg.tenantId, 8)} ✓` : "NOT SET ✗"}`,
423
+ ` Sub-Tenant: ${client.getSubTenantId()}`,
424
+ ` Ignore Term: ${cfg.ignoreTerm}`,
425
+ ` Auto-Recall: ${cfg.autoRecall}`,
426
+ ` Auto-Capture: ${cfg.autoCapture}`,
427
+ ` Recall Mode: ${cfg.recallMode}`,
428
+ ` Graph Context: ${cfg.graphContext}`,
429
+ ` Max Results: ${cfg.maxRecallResults}`,
430
+ ` Debug: ${cfg.debug}`,
431
+ "",
432
+ "Tip: Run `cortex onboard` in the CLI for an interactive configuration wizard,",
433
+ " or `cortex onboard --advanced` for all options.",
434
+ ]
435
+ return { text: lines.join("\n") }
436
+ } catch (err) {
437
+ log.error("/cortex-onboard", err)
438
+ return { text: "Failed to show status. Check logs." }
439
+ }
440
+ },
441
+ })
442
+ }
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
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,7 +1,7 @@
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 { extractAllTurns } from "../messages.ts"
4
+ import { extractAllTurns, filterIgnoredTurns } from "../messages.ts"
5
5
  import { toHookSourceId } from "../session.ts"
6
6
  import type { ConversationTurn } from "../types/cortex.ts"
7
7
 
@@ -13,7 +13,7 @@ function removeInjectedBlocks(text: string): string {
13
13
 
14
14
  export function createIngestionHook(
15
15
  client: CortexClient,
16
- _cfg: CortexPluginConfig,
16
+ cfg: CortexPluginConfig,
17
17
  ) {
18
18
  return async (event: Record<string, unknown>, sessionId: string | undefined) => {
19
19
  try {
@@ -33,7 +33,12 @@ export function createIngestionHook(
33
33
  return
34
34
  }
35
35
 
36
- const allTurns = extractAllTurns(event.messages)
36
+ const rawTurns = extractAllTurns(event.messages)
37
+ const allTurns = filterIgnoredTurns(rawTurns, cfg.ignoreTerm)
38
+
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
+ }
37
42
 
38
43
  if (allTurns.length === 0) {
39
44
  log.debug(`[capture] skipped — no user-assistant turns found in ${event.messages.length} messages`)
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,6 +1,7 @@
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"
@@ -16,7 +17,7 @@ export default {
16
17
  id: "openclaw-cortex-ai",
17
18
  name: "Cortex AI",
18
19
  description:
19
- "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",
20
21
  kind: "memory" as const,
21
22
  configSchema: cortexConfigSchema,
22
23
 
@@ -65,7 +66,8 @@ export default {
65
66
  }
66
67
 
67
68
  registerSlashCommands(api, client, cfg, getSessionId)
68
- registerCliCommands(api, client, cfg)
69
+ registerOnboardingSlashCommands(api, client, cfg)
70
+ registerCliCommands(api, client, cfg, registerOnboardingCli(cfg))
69
71
 
70
72
  api.registerService({
71
73
  id: "openclaw-cortex-ai",
package/messages.ts CHANGED
@@ -1,5 +1,20 @@
1
1
  import type { ConversationTurn } from "./types/cortex.ts"
2
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
+
3
18
  export function textFromMessage(msg: Record<string, unknown>): string {
4
19
  const content = msg.content
5
20
  if (typeof content === "string") return content
@@ -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.1.0",
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/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(
package/tools/store.ts CHANGED
@@ -3,7 +3,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
3
  import type { CortexClient } from "../client.ts"
4
4
  import type { CortexPluginConfig } from "../config.ts"
5
5
  import { log } from "../log.ts"
6
- import { extractAllTurns } from "../messages.ts"
6
+ import { extractAllTurns, filterIgnoredTurns } from "../messages.ts"
7
7
  import { toToolSourceId } from "../session.ts"
8
8
  import type { ConversationTurn } from "../types/cortex.ts"
9
9
 
@@ -16,7 +16,7 @@ function removeInjectedBlocks(text: string): string {
16
16
  export function registerStoreTool(
17
17
  api: OpenClawPluginApi,
18
18
  client: CortexClient,
19
- _cfg: CortexPluginConfig,
19
+ cfg: CortexPluginConfig,
20
20
  getSessionId: () => string | undefined,
21
21
  getMessages: () => unknown[],
22
22
  ): void {
@@ -25,7 +25,7 @@ export function registerStoreTool(
25
25
  name: "cortex_store",
26
26
  label: "Cortex Store",
27
27
  description:
28
- "Save the full conversation history to Cortex long-term memory. Use this to persist facts, preferences, or decisions the user wants remembered. The complete chat history will be sent for context-rich storage.",
28
+ "Save the full conversation history to Cortex AI memory. Use this to persist facts, preferences, or decisions the user wants remembered. The complete chat history will be sent for context-rich storage.",
29
29
  parameters: Type.Object({
30
30
  text: Type.String({
31
31
  description: "A brief summary or note about what is being saved",
@@ -46,14 +46,15 @@ export function registerStoreTool(
46
46
 
47
47
  log.debug(`[store] tool called — sid=${sid ?? "none"} msgs=${messages.length} text="${params.text.slice(0, 50)}"`)
48
48
 
49
- const allTurns = extractAllTurns(messages)
50
- const recentTurns = allTurns.slice(-MAX_STORE_TURNS)
49
+ const rawTurns = extractAllTurns(messages)
50
+ const filteredTurns = filterIgnoredTurns(rawTurns, cfg.ignoreTerm)
51
+ const recentTurns = filteredTurns.slice(-MAX_STORE_TURNS)
51
52
  const turns: ConversationTurn[] = recentTurns.map((t) => ({
52
53
  user: removeInjectedBlocks(t.user),
53
54
  assistant: removeInjectedBlocks(t.assistant),
54
55
  }))
55
56
 
56
- log.debug(`[store] extracted ${allTurns.length} total turns, using last ${turns.length} (MAX_STORE_TURNS=${MAX_STORE_TURNS})`)
57
+ log.debug(`[store] extracted ${rawTurns.length} total turns, ${rawTurns.length - filteredTurns.length} ignored, using last ${turns.length} (MAX_STORE_TURNS=${MAX_STORE_TURNS})`)
57
58
 
58
59
  if (turns.length > 0 && sourceId) {
59
60
  const now = new Date()